Archive

Archive for the ‘iScroll’ Category

Smart Pascal: Information to alpha testers

April 3, 2017 Leave a comment

Note: This is re-posted here since we are experiencing networking problems at http://www.smartmobilestudio.com. The information should show up there shortly.

Our next-gen RTL will shortly be sent to people who have shown interest in testing our new RTL. We will finish the RTL in 3 stages of alpha before we hit beta (including code freeze, just fixes to existing code) and then release. After that we move on to the IDE to bring that up to date as well.

Important changes

You will notice that visual controls now have a ton of new methods, but one very interesting in particular called: ObjectReady. This method holds an important and central role in the new architecture.

You may remember that sometimes you had to use Handle.ReadyExecute() in the previous RTL? Often to synchronize an activity, setting properties or just calling ReSize() when the control was ready and available in the DOM.

To make a long story short, ObjectReady() is called when a control has been constructed in full and the element is ready to be used. This also includes the ready-state of child elements. So if you create 10 child controls, ObjectReady will only be called once those 10 children have finished constructing – and the handle(s) have safely been injected into the DOM.

To better understand why this is required, remember that JavaScript is asynchronous. So even though the constructor finish – child objects can still be busy “building” in the background.

If you look in SmartCL.Components.pas, notice that when the constructor finishes –it does a asynchronous call to a procedure named ReadySync(). This is a very important change from the previous version – because now synchronization can be trusted. I have also added a limit to how long ReadySync() can wait. So if it takes to long the ReadySync() method will simply exit.

ObjectReady

As you probably have guessed, when ReadySync() is done and all child elements have been successfully created – it called the ObjectReady() method.

This makes things so much easier to work with. In many ways it resembles Delphi and Freepascal’s AfterConstruction() method.

To summarize the call-chain (or timeline) all visual controls follow as they are constructed:

  • Constructor Create()
  • InitializeObject()
  • ReadySync
  • ObjectReady()
  • Invalidate
  • Resize

If you are pondering what on earth Invalidate() is doing in a framework based on HTML elements: it calls Resize() via the RequestAnimationFrame API. TW3GraphicControl visual controls that are actually drawn, much like native VCL or LCL components are – naturally invalidate the graphics in this method. But ordinary controls just does a synchronized resize (and re-layout) of the content.

When implementing your own visual controls (inheriting from TW3CustomControl), the basic procedures you would have to override and implement are:

  • InitializeObject;
  • FinalizeObject;
  • ObjectReady;
  • Resize;

Naturally, if you dont create any child controls or data of any type – then you can omit InitializeObject and FinalizeObject; these act as constructor and destructor in our framework. So in the JVL (Javascript Visual component Library) you dont override the constructor and destructor directly unless it is extremely important.

Where to do what

What the JVL does is to ensure a fixed set of behavioral traits in a linear fashion- inside an environment where anything goes. In order to achieve that the call chain (as explained above) must be predictable and rock solid.

Here is a quick cheat sheet over what to do and where:

  • You create child instances and set variables in InitializeObject()
  • You set values and access the child instances for the first time in ObjectReady()
  • You release any child instances and data in FinalizeObject()
  • You enable/disable behavior in CreationFlags()
  • You position and place child controls in Resize()
  • Calling Invalidate() refreshes the layout or graphics, depending on what type of control you are working with.

What does a custom control look like ?

Its actually quite simple. Now I have included a ton of units here in order to describe what they contain, then you can remove those you wont need. The reason we have fragmented the code like this (for example System.Reader, System.Stream.Reader and so on) is because node.js, Arduino, Raspberry PI, Phonegap, NodeWebKit are all platforms that run JavaScript in one form or another – and each have code that is not 1:1 compatible with the next.

Universal code, or code that executes the same on all platforms is isolated in the System namespace. All files prefixed with “System.” are universal and can be used everywhere, regardless of project type, target or platform.

When it comes to the reader / writer classes, it’s not just streams. We also have binary buffers (yes, you actually have allocmem() etc. in our RTL) – but you also have special readers that work with database blobs, Bson attachments .. hence we had no option but to fragment the units. At least it makes for smaller code 🙂

unit MyOwnControlExample;

interface

uses
  // ## The System namespace is platform independent
  System.Widget,           // TW3Component
  System.Types,            // General types
  System.Types.Convert,    // Binary access to types
  System.Types.Graphics,   // Graphic types (TRect, TPoint etc)
  System.Colors,           // TColor constants + tools
  System.Time,             // TW3Dispatch + time methods

  // Binary data and streams
  System.Streams,
  System.Reader, System.Stream.Reader,
  System.Writer, System.Stream.Writer,

  // Binary data and allocmem, freemem, move, copy etc
  System.Memory,
  System.Memory.Allocation,
  System.Memory.Buffer,

  // ## The SmartCL namespace works only with the DOM
  SmartCL.System,         // Fundamental methods and classes
  SmartCL.Time,           // Adds requestAnimationFrame API to TW3Dispatch
  SmartCL.Graphics,       // Offscreen pixmap, canvas etc.
  SmartCL.Components,     // Classes for visual controls
  SmartCL.Effects,        // Adds about 50 fx prefixed CSS3 GPU effect methods
  SmartCL.Fonts,          // Font and typeface control
  SmartCL.Borders,        // Classes that control the border of a control
  SmartCL.CSS.Classes,    // Classes for self.css management
  SmartCL.CSS.StyleSheet, // Create stylesheets or add styles by code

  { Typical child controls
  SmartCL.Controls.Image,
  SmartCL.Controls.Label,
  SmartCL.Controls.Panel,
  SmartCL.Controls.Button,
  SMartCL.Controls.Scrollbar,
  SMartCL.Controls.ToggleSwitch,
  SmartCL.Controls.Toolbar }
  ;

type

  TMyVisualControl = class(TW3CustomControl)
  protected
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure ObjectReady; override;
    procedure Resize; override;
  end;

implementation

procedure TMyVisualControl.InitializeObject;
begin
  inherited;
  // create child instances here
end;

procedure TMyVisualControl.FinalizeObject;
begin
  // Release child instances here
  inherited;
end;

procedure TMyVisualControl.ObjectReady;
begin
  inherited;
  // interact with controls first time here
end;

procedure TMyVisualControl.Resize;
begin
  inherited;
  if not (csDestroying in ComponentState) then
  begin
    // Position child elements here
  end;
end;

CreateFlags? What is that?

Delphi’s VCL and Lazarus’s LCL have had support for CreateFlags for ages. It essentially allows you to set some very important properties when a control is created; properties that enable or disable how the control behaves.

  TW3CreationFlags = set of
    (
    cfIgnoreReadyState,     // Ignore waiting for readystate
    cfSupportAdjustment,    // Controls requires boxing adjustment (default!)
    cfReportChildAddition,  // Dont call ChildAdded() on element insertion
    cfReportChildRemoval,   // Dont call ChildRemoved() on element removal
    cfReportMovement,       // Report movements? Managed call to Moved()
    cfReportResize,         // Report resize? Manages call to Resize()
    cfAllowSelection,       // Allow for text selection
    cfKeyCapture            // flag to issue key and focus events
    );

As you can see these properties are quite fundamental, but there are times when you want to alter the default behavior radically to make something work. Child elements that you position and size directly doesn’t need cfReportReSize for instance. This might not mean much if its 100 or 200 child elements. But if its 4000 rows in a DB grid, then dropping that event check has a huge impact.

ComponentState

Yes, finally all visual (and non visual) have componentstate support. This makes your code much more elegant, and also way more compatible with Delphi and Lazarus. Here are the component-states we support right now:

  TComponentState = set of (
    csCreating,   // Set by the RTL when a control is created
    csLoading,    // Set by the RTL when a control is loading resources
    csReady,      // Set by the RTL when the control is ready for use
    csSized,      // Set this when a resize call is required
    csMoved,      // Set this when a control has moved
    csDestroying  // Set by the RTL when a control is being destroyed
    );

So now you can do things like:

procedure TMyControl.StartAnimation;
begin
  // Can we do this yet?
  if (csReady in ComponentState) then
  begin
    // Ok do magical code here
  end else
  //If not, call back in 100ms - unless the control is being destroyed
  if not (csDestroying in ComponentState) then
    TW3Dispatch.Execute(StartAnimation, 100);
end;

Also notice TW3Dispatch. You will find this in System.Time and SmartCL.Time (it is a partial class and can be expanded in different units); we have isolated timers, repeats and delayed dispatching in one place.

SmartCL.Time adds RequestAnimationFrame() and CancelAnimationFrame() access, which is an absolute must for synchronized graphics and resize.

Other changes

In this post I have tried to give you an overview of immediate changes. Changes that will hit you the moment you fire up Smart with the new RTL. It is very important that you change your existing code to make use of the ObjectReady() method in particular. Try to give the child elements some air between creation and first use – you get a much more consistent result on all browsers.

The total list of changes is almost to big to post on a blog but I did publish a part of it earlier. But not even that list contains the full extent. I have tried to give you an understanding

Click here to look at the full change log

Smart Pascal: Changes

February 6, 2017 3 comments

The changes to Smart Mobile Studio over the past 12 months have been tremendous. At first glance they might not seem huge, but if you do a raw compare on the upcoming RTL and the now more than a year old RTL – I think you will find that there are very few places in the code where I havent done improvements.

In this post I will try to collect some of the changes. A final change log will be released together with the update, but at least this “preview” will tell you something about the hours, days, weeks and months I have put into the RTL.

The Smart Lab, this is where most of my ideas turn into code :)

The Smart Lab, this is where most of my ideas turn into code 🙂

There will also be changes to the IDE; We have 3 members that both have added new features in the past, and members that are adding changes right now. The immediate being an update from Chromium Embedded CEF2 to CEF4, which is a huge change in speed and preview quality. And there are other more important changes being worked on, the most pressing being the designer and “live” rendering of controls.

Ok, let’s just dive into it!

RTL Changes

  • The units that make up the RTL are now organized according to a namespace scheme
    • Visual units are prefixed with SmartCL
    • Universal units are prefixed with System
    • Low level API units for node.js are prefixed with NodeJS
    • High level Node classes are prefixed with SmartNJ
  • Better fragmentation: Code that was previously a part of one very large unit has been divided into smaller files to avoid large binaries. For example:
    •  TRect, TPoint etc. are now in System.Types.Graphics
    • System.Time is extended with SmartCL.Time which gives graphical timing functions like TW3Dispatch.RequestAnimationFrame()
  • The unit System.Objects has been added, isolating classes that work both in browsers, node and headless runtime environments:
    • TW3ErrorObjectOptions
    • TW3ErrorObject
    • TW3OwnedErrorObject
    • TW3HandleBasedObject
  • TW3Label has been completely re-written and is now faster, has synchronized state management, resize on ready-state and much more
  • The unit System.NameValuePairs was added. This implements a traditional name/value list (or dictionary) that is used by various classes and standards throughout the RTL (http headers being an example)
  • A show stopping bug in SmartCL.Effects has been fixed. Effects now works as expected (simply add SmartCL.Effects to your uses clause)
  • TFileStream has been added to SmartNJ.Streams. This is unique for node.js since only node (and some hybrid runtime engines) support direct file access.
  • TBinaryData is extended in SmartNJ.Streams.pas to emit and consume node.js buffers which are different from traditional JS buffers. ToNodeBuffer() and FromNodeBuffer() allows you to consume special node buffer types.
  • A full software tweening engine has been written from scratch in pure Smart Pascal and added to the SmartCL namespace. The following units have been added:
    • SmartCL.Tween
      • TW3TweenElement
      • TW3TweenEngine
      • function TweenEngine: TW3TweenEngine
    • SmartCL.Tween.Effect
      • TW3CustomEffect
      • TW3CustomTweenEffect
      • TW3ControlTweenEffect
      • TW3MoveXEffect
      • TW3MoveYEffect
      • TW3MoveToEffect
      • TW3ColorMorphEffect
      • TW3CustomOpacityEffect
      • TW3FadeInEffect
      • TW3FadeOutEffect
      • TW3OpacityEffect
    • SmartCL.Tween.Ease
      • TW3TweenEase
      • TW3TweenEaseLinear
      • TW3TweenEaseQuadIn
      • TW3TweenEaseQuadOut
      • TW3TweenEaseQuadInOut
      • TW3TweenEaseCubeIn
      • TW3TweenEaseCubeOut
      • TW3TweenEaseCubeInOut
      • TW3TweenEaseQuartIn
      • TW3TweenEaseQuartOut
      • TW3TweenEaseQuartInOut
      • TW3TweenEaseQuintIn
      • TW3TweenEaseQuintOut
      • TW3TweenEaseQuintInOut
      • TW3TweenEaseSineIn
      • TW3TweenEaseSineOut
      • TW3TweenEaseSineInOut
      • TW3TweenEaseExpoIn
      • TW3TweenEaseExpoOut
      • TW3TweenEaseExpoInOut
      • TW3TweenEaseCollection
      • function TweenEaseCollection: TW3TweenEaseCollection
  • Support for require.js (simplified resource management and more) is now a part of the RTL. The unit SmartCL.Require gives you:
    • TRequireError
    • TW3RequireJSConfig
    • TW3RequireJS
    • function Require: TW3RequireJS;
    • procedure Require(Files: TStrArray);
    • procedure Require(Files: TStrArray; const Success: TProcedureRef);
    • procedure Require(Files: TStrArray; const Success: TProcedureRef; const Failure: TW3RequireErrHandler);
  • SmartCL (browser) based communication has been consolidated into more appropriate named units:
    • SmartCL.Net.http
    • SmartCL.Net.http.headers
    • SmartCL.Net.websocket
    • SmartCL.Net.jsonp
    • SmartCL.Net.socketIO
    • SmartCL.Net.rest
  • A callback bug in the REST api has been fixed (called wrong handler on exit)
  • Suppport for mutation events has been added to the RTL. This can be found in the unit SmartCL.Observer. Mutation observer allows you to listen for changes to any HTML element, its properties or attributes. This is a very important unit with regards to data-aware controls:
    • TMutationObserverOptions
    • TMutationObserver
  • A fallback shim for mutation observing has been added to the $RTL\Shims folder. This ensures that mutation events can be observed even on older browsers.
  • A MSIE legacy shim that patches all missing IE functionality from the current back to IE5 has been added. This allows Smart applications to execute without problems on older browsers (which there are surpricingly many of)
  • The unit SmartCL.Styles has been added. This contains classes and methods that create a stylesheet at runtime. The stylesheet is deleted when the object instance is disposed. This allows for easier styling from within your code rather than having to pre-define it in the global css file.
  • Polygon helper classes have been isolated in SmartCL.Polygons. These expose functionality to TPointArray and TPointFArray:
    • TPolygonHelper
    • TPolygonFHelper
  • Support for socket.io has been added for the browser. The unit SmartCL.Net.SocketIO contains the following client class and methods:
    • TW3SocketIOClient
      • procedure Connect(RemoteHost: string)
      • procedure Disconnect
      • procedure Emit(EventName: string; Data: variant)
      • procedure On(EventName: string; Handler: TW3SocketIOHandler)
  • Support for socket.io has been added to the new SmartNJ namespace as well, allowing you to write fast, good performance SocketIO servers.
  • Support for WebSocketIO, an amalgamation of classic WebSocket and SocketIO has been added. This is the default socketio server type for our node.js system
  • Support for user-attributes has been added and isolated in SmartCL.Attributes. This allows you to easily add, read, write and remove custom attributes to any tag. This is a very important feature that is used both by the effects framework – and also by the database framework.
  • SmartCl.Buffers unit has been updated. Now uses the latest methods of the RTL and now works on all browsers, not just webkit and Firefox.
  • $RTL\SmartCL\Controllers\SmartCL.Scroll.Momentum has been removed
  • $RTL\SmartCL\Controllers\SmartCL.Scroll.plain has been removed
  • All visual controls have been updated, rewritten and thoroughly tested
  • The RTL now has support for ACE, the #1 JavaScript code editor. This is more or less a JavaScript implementation of Delphi’s Synedit, but with functionality closer to Sublime. Ace comprises over 100 files. In this first release we have focused on getting a respectable basis in place.
    • The wrapper units is SmartCL.AceEditor, which gives you the following classes:
      • TW3AceMode
      • TW3AceTheme
      • TW3AceEditor
        • Text: string
        • SelectedText: string
        • LineCount: integer
        • WordWrap: boolean {get; set;}
        • ShowPrintMargin: boolean {get; set;}
        • EditorMode: TW3AceMode {get; set;}
        • Theme: TW3AceTheme {get; set;}
    • Modes and syntax we added support for:
      • AceEdit.Mode.Abap
      • AceEdit.Mode.Abc
      • AceEdit.Mode.ActionScript
      • AceEdit.Mode.Ada
      • AceEdit.Mode.Apache
      • AceEdit.Mode.AppleScript
      • AceEdit.Mode.Ascii
      • AceEdit.Mode.AutoHotKey
      • AceEdit.Mode.Base
      • AceEdit.Mode.BatchFile
      • AceEdit.Mode.C9Search
      • AceEdit.Mode.Cirru
      • AceEdit.Mode.Clojure
      • AceEdit.Mode.Cobol
      • AceEdit.Mode.Coffee
      • AceEdit.Mode.ColdFusion
      • AceEdit.Mode.Cpp
      • AceEdit.Mode.CSharp
      • AceEdit.Mode.CSS
      • AceEdit.Mode.Curly
      • AceEdit.Mode.D
      • AceEdit.Mode.Dart
      • AceEdit.Mode.Diff
      • AceEdit.Mode.django
      • AceEdit.Mode.Pascal
      • AceEdit.Mode.VbScript
      • AceEdit.Mode.X86Asm
    • Themes we support:
      • AceEdit.Theme.Ambiance
      • AceEdit.Theme.Base
      • AceEdit.Theme.Chaos
      • AceEdit.Theme.Chrome
      • AceEdit.Theme.CloudMidnight
      • AceEdit.Theme.Clouds
      • AceEdit.Theme.Cobalt
      • AceEdit.Theme.CrimsonEdit
      • AceEdit.Theme.Dawn
      • AceEdit.Theme.Dreamweaver
      • AceEdit.Theme.eclipse
      • AceEdit.Theme.Github
      • AceEdit.Theme.Monokai
      • AceEdit.Theme.Twilight
  • The RTL has a completely re-written ready-state engine. Since JavaScript is ASync by nature, the handle or reference to the TAG a class manages may not be ready during the constructor. The ready-state engine waits in the background for the handle to become valid and the control available in the DOM (document object model). It then sets the appropriate component-state flags and issues a resize to ensure that the control looks as it should.
  • Support for Control-State (which is common in Delphi and Lazarus) has been added to visual controls. The following state flags can be read or set:
    • csCreating
    • csLoading
    • csReady
    • csSized
    • csMoved
    • csDestroying
  • TW3CustomControl now supports creation-flags. This is also a feature common to Delphi’s VCL and Lazarus’s LCL. It allows you to set some flags that disable fundamental behavior when you don’t need it. For instance, a label which will always have a fixed size or be in a fixed place does not need Resize() management. Turning off resize-checking makes you application execute faster. The following creation flags can be defined:
    • cfIgnoreReadyState
    • cfSupportAdjustment
    • cfReportChildAddition
    • cfReportChildRemoval
    • cfReportMovement
    • cfReportResize
    • cfAllowSelection
    • cfKeyCapture
  • To enable or disable component states, use the following methods of TW3CustomControl:
    • procedure AddToComponentState(const Flags: TComponentState)
    • procedure RemoveFromComponentState(Const Flags: TComponentState)
  • Support for keyboard input and charcode capture has been added to the RTL. Simply add the flag cfKeyCapture to the CreationFlags() function of your custom-control and the control’s OnKeyPress event will fire when the control has focus and a key is pressed. By default this is turned off.
  • Support for text-selection and disabling text-selection has been added. Simply add or remove cfAllowSelection from your controls CreationFlags() function. By default text selection is turned off (except for obvious controls like text-edit).
  • TW3CustomControl now has a zIndex property. This represents the z-order of a control (which control is in front of others).
  • The function TW3CustomControl.GetZOrderList(sort: boolean) can be called to get a sorted or un-sorted list of child elements. This provides both the handle for each control and its zindex value.
  • The method TW3CustomControl.Showing() has been updated, it now takes height for IFrame and CSS4 GPU positioning (read: it will understand that its offscreen even if you use CSS3 to move it).
  • TW3CustomControl now has a Cursor property. This allows you to read and set the mouse cursor for a control. The following cursors are supported:
    • crAuto
    • crDefault
    • crInherited
    • crURL
    • crCrossHair
    • crHelp
    • crMove
    • crPointer
    • crProgress
    • crText
    • crWait
    • crNResize
    • crSResize
    • crEResize
    • crWResize
    • crNEResize
    • crNWResize
    • crNSResize
    • crSEResize
    • crSWResize
    • crEWResize
  • To simplify mouse cursor handling, the class TW3MouseCursor, which is a static class, has been added to SmartCL.System.pas. It expose the following methods:
    • function  CursorByName(const CursorName: string): TCursor
    • function  NameByCursor(const Cursor: TCursor): String
    • function  GetCursorFromElement(const Handle: TControlHandle): TCursor
    • procedure SetCursorForElement (const Handle: TControlHandle; const Cursor: TCursor)
  • TW3CustomControl have two new methods to deal with cursor changes. These are virtual and can be overriden:
    •  function GetMouseCursor: TCursor;
    • procedure SetMouseCursor(const NewCursor: TCursor);
  • Basic device capabillity examination support has been added. The class TW3DOMDeviceCapabilities has been added to SmartCL.System. This exposes the following methods and properties:
    • DevicePixelRatio: float
    • DisplayPixelsPerInch: TPixelsPerInch
    • GetMouseSupport: boolean
    • GetTouchSupport: boolean
    • GetGamePadSupport: boolean
    • GetKeyboardSupported: boolean
    • GetDevicePixelRatio: float
    • GetDisplayPixelsPerInch: TPixelsPerInch
  • In order to make the VJL more architectually compatible with Delphi’s VCL and Freepascal’s LCL – TW3Component as name has been pushed back. TW3TagObj used to be the root class for visual components, followed by TW3Component, TW3MovableControl and finally TW3CustomControl. However, since we want to use TW3Component as a common non-visual control, this name was pushed back. So TW3TagObj now inherits from TW3Component, and TW3TagContainer has taken the place TW3Component once had. Here is the new inheritance chain
    • TW3CustomComponent
      • TW3Component
        • TW3TagObj
          • TW3TagContainer
            • TW3MovableControl
              • TW3GraphicControl
              • TW3CustomControl
  • TW3Component is now the basis for non-visual components, which opens up for a whole new set of controls that can be dragged onto a form or datamodule. Sadly Datamodules did not make it into this update, but it is the next logical step – hopefully we will have it in place with the IDE update that will appear after this.
    • Controllers will eventually be re-incarnated as TW3Component’s
    • Rouge classes will be implemented as TW3Components
    • Database classes will become non-visual TW3Components
    • Communication classes will become non-visual TW3Components
  • TW3Timer is moved to the unit System.Time.pas
  • TW3Timer now inherits from TW3Component
  • The following procedures have been deprecated and moved to System.Time.pas. Please use the corresponding methods in TW3Dispatch (Note: The w3_* methods will be deleted in the next update!):
    • w3_Callback = TW3Dispatch.Execute()
    • w3_SetInterval = TW3Dispatch.SetInterval()
    • w3_ClearInterval = TW3Dispatch.ClearInterval()
    • w3_SetTimeout = TW3Dispatch.SetTimeOut()
    • w3_ClearTimeout = TW3Dispatch.ClearTimeOut()
  • TW3Dispatch, which is a class dealing with timing and scheduling, now support the following new methods:
    • class function JsNow: JDate;
    • class function Ticks: integer;
    •  class function TicksOf(const Present: TDateTime): integer;
    • class function TicksBetween(const Past, Future: TDateTime): integer;
    • class procedure RepeatExecute(const Entrypoint: TProcedureRef; const RepeatCount: integer; const IntervalInMs: integer);
  • SQLite has been recompiled from C to JS and is now version 3.8.7.4
  • The SQLite units have been moved from SmartCL.SQLite to System.SQLite
  • The following SQLite classes have been given an overhaul:
    • TSQLiteDatabase
    • TSQLiteDBObject
    • TSQLiteRowValues
    • TSQLiteResult
    • TSQLParamData
    • TSQLitePair
    • TSQLiteParams
    • TSQLiteStatement
  • The SQLiteInitialize() global method is no longer requires and has been removed. When you include System.SQLite.pas in your project, the whole database engine is automatically linked and loaded into memory on execute. Use the SQLiteReady() global function to check if the database engine has loaded before use.
  • TReader, TWriter which was exclusively a re-implementation based on LCL and Delphi has been fragmented. TReader and TWriter are now base classes that takes an interface as parameter in their constructor (IBinaryTransport). Both TStream and TBinaryData, which are the easiest ways of dealing with binary files and memory – implements the IBinaryTransport interface.
  • TStreamReader and TStreamWriter has been added to the RTL, these are implemented in System.Stream.Reader.pas and System.Stream.Writer.pas. When working with streams, make sure you use TStreamReader and TStreamWriter. Using TReader and TWriter directly may cause problems if you are not intimately familiar with the RTL.
  • The unit System.Structure has been added to the RTL. This is to simplify working with structured records, abstracting you from the underlying format. The following classes are exposed in System.Structure:
    • EW3Structure [exception]
    • TW3Structure
      • procedure WriteString(Name: string; Value: string; const Encode: boolean);
      • procedure WriteInt(const Name: string; value: integer);
      • procedure WriteBool(const Name: string; value: boolean);
      • procedure WriteFloat(const Name: string; value: float);
      • procedure WriteDateTime(const Name: string; value: TDateTime);
      • function ReadString(const Name: string): string;
      • function ReadInt(const Name: string): integer;
      • function ReadBool(const Name: string): boolean;
      • function ReadFloat(const Name: string): float;
      • function ReadDateTime(const Name: string): TDateTime;
      • function Read(const Name: string): variant;
      • procedure Write(const Name: string; const Value: variant);
      • procedure Clear;
      • procedure SaveToStream(const Stream: TStream);
      • procedure LoadFromStream(const Stream: TStream);
      • procedure LoadFromFile(Url: string; const callback: TW3StructureLoadedCallback);
  • The unit System.Structure.JSON implements a TW3Structure class that stores data as JSON. The data is stored in memory as a single record, but naturally you can add further records as child properties – and then export the complete data as binary format to a stream, or as a single string through the ToString() method.
  • The unit System.Structure.XML implements a TW3Structure class which emits XML. Since the XML interface in browsers is both unstable and wildly different between vendors, we use our own BTree engine to manage the data. The class emits valid XML and will also deal with sub-records just as TW3JSonStructure.
  • The unit System.JSON has been added, which contains the following classes and methods. TJSON is a highly effective wrapper over the built-in JSON API, making it easier to work with JSON in general. The constructor is highly overloaded so you can wrap existing JS elements directly. This class is used by TW3JSONStructure to simplify its work:
    • TJSONObjectOptions
    • EJSONObject [exception]
    • TJSONObject
    • TJSON
  • The Delphi parser library TextCraft has been added to the RTL. When I write added that is not really correct. TextCraft was first written in Smart and then post converted to Delphi. But the RTL now has the latest update of this library, making advanced, recursive text-parsing possible:
    • System.Text.Parser.pas
    • System.Text.Parser.Words.pas
  • TW3Borders has been re-coded and optimized.
    • TW3Border.EdgeString() now use a lookup table for maximum efficiency
    • TW3Border’s edges now use a lookup table to quickly map types to strings
    • All superfluous reference testing has been removed, resulting in much faster and efficient code
    • Styles are no longer read via the older w3_read/write mechanisms but rather directly from the controlhandle
  • Two new classes has been added to deal with border-radius.
    • All controls can set the radius for all edges using the older TW3MovableControl.BorderRadius property, but this has now been expanded on. The following classes has been added. TW3BorderRadius can be accessed via the new TW3CustomControl.EdgeRadius property, which creates an instance on demand (first access):
      • TW3BorderEdgeRadius
      • TW3BorderEdgeTopRadius
      • TW3BorderEdgeBottomRadius
      • TW3BorderRadius
  • TStringBuilder has been added to System.Type. It deviates some from the Lazarus and Delphi variations because it adds functions that actually makes sense ! 🙂
  • All custom controls have a cool background class that allows you to set color, add images and export pixmaps. A new class called TW3ControlBackgroundSize has been added to this (TW3CustomControl.Background.Size) making things even easier. Especially detecting if a background is a graphic and its true size (!)
    • function  BackgroundIsImage: boolean;
    • property Mode: TW3ControlBackgroundSizeMode
    • property Width: integer;
    • property Height: integer;
  • TW3Constraints (inherits from TW3OwnedObject) has been added to the RTL. This is a class that deals with size constraints for controls. Please note that this is experimental (!) When active it forces size restrictions much like the VCL and LCL — but it can cause havoc in a design that auto-scales (!). It implements the following:
    • property Enabled: boolean
    • property MinWidth: integer
    • property MinHeight: integer
    • property MaxWidth: integer
    • property MaxHeight: integer
    • procedure   ApplyToOwner
    • function GetMaxWidth: integer;
    • function GetMaxHeight: integer;
    • procedure SetMaxWidth(const NewMaxWidth: integer);
    • procedure SetMaxHeight(const NewMaxHeight: integer);
    • function GetMinWidth: integer;
    • function GetMinHeight: integer;
    • procedure SetMinWidth(aValue: integer);
    • procedure SetMinHeight(aValue: integer);
    • procedure   SetEnabled(const NewValue: boolean); virtual;
  • Event classes has finally been added! Need another OnClick event? just Create an event object and attach it to your control. The following event classes are now in SmartCL.Events :
    • TW3DOMEvent
    • TW3StandardDOMEvent
    • TW3MouseEnterEvent
    • TW3MouseLeaveEvent
    • TW3MouseDownEvent
    • TW3MouseMoveEvent
    • TW3MouseUpEvent
    • TW3ElementRemovedEvent
    • TW3ElementAddedEvent
    • TW3ElementContextMenuEvent
    • TW3ElementClickEvent
    • TW3ElementDblClickEvent
    • TW3ElementMouseOverEvent
    • TW3ElementKeyDownEvent
    • TW3ElementKeyPressEvent
    • TW3ElementKeyUpEvent
    • TW3ElementChangeEvent
    • TW3ElementMouseWheelEvent
    • TW3DOMEventAPI
      • class procedure RegisterEvent(Handle: TControlHandle; EventName: string; EventHandler:TW3JSEventHandler; Mode: TW3DOMEventMode); static;
      • class procedure UnRegisterEvent(Handle: TControlHandle; EventName: string; EventHandler:TW3JSEventHandler; Mode: TW3DOMEventMode); static;
  • Since events are not DOM or browser bound, they also occur in node.js and other JavaScript virtual machines (and you can also register your own events just like you do in Delphi or C#). As such I added System.Events.pas which contains the basic functionality for OOP events. The following classes and methods are added:
    • TW3SystemEventObject
      • procedure Attach(NameOfEvent: string);
      • procedure Detach;
      • property  Attached: boolean
      • property  EventName: string
  • TW3CustomBrowserAPI.Styles references “window.styles” which is no longer supported by browsers. This has been updated to “window.document.head.style”
  • BrowserAPI() now expose key browser objects as actual class objects. The following objects are exposed as handles only (as they have been for some time):
    • Document
    • Body
    • Window
    • Styles
    • Console
    • Navigator
    • Self
    • Event
  • Besides the above handles, we now expose direct access to the mapped class objects directly. The W3C units have been updated to reflect modern browsers (so the objects have the latest methods exposed):
    • DocumentObject: JDocument
    • BodyObject: JHTMLElement
    • WindowObject: JWindow
    • StylesObject: JCSSStyleDeclaration
    • NavigatorObject: JNavigator
    • EventObject: JEvent
  • Added missing W3C.localStorage unit file which expose the localstorage API. This can now be accessed via BrowserAPI().WindowObject.localStorage. It supports the following functions:
    • property  key[const keyId: string]: variant
    • property  length: integer
    • procedure setItem(const key: string; const Value: variant)
    • procedure removeItem(const key: string)
    • procedure clear
    • function  valueOf: variant [* prototype]
    • function  hasOwnProperty(const key: string): boolean [* prototype]
  • TW3Image now has a fitstyle property which wraps the “object-fit” css style. This gives you finer control over how the image is presented inside the control (or the background of a control). The following presentation styles are supported:
    •  fsNone
      Image will ignore the height and width of the parent and retain its original size.
    • fsFill
      This is the default value which stretches the image to fit the content box, regardless of its aspect-ratio.
    • fsContain
      Increases or decreases the size of the image to fill the box whilst preserving its aspect-ratio.
    • fsCover
      The image will fill the height and width of its box, once again maintaining its aspect ratio but often cropping the image in the process.
    • fsScaleDown
      The control will compare the difference between fsNone and fsContain in order to find the smallest concrete object size.
  • TW3Image now supports binary IO besides the common HTML src property. The following methods have been added to TW3Image:
    • function  ToBuffer: TBinaryData;
    • function  ToStream: TStream;
    • function  ToDataUrl: string;
    • function  ToImageData: TW3ImageData;
    • procedure LoadFromURL(aURL: String);
    • procedure LoadFromImageData(Const Data:TW3ImageData);
    • procedure LoadFromBinaryData(const Memory:TBinaryData);
    • procedure LoadFromStream(const Stream:TStream);
    • procedure Allocate(aWidth,aHeight:Integer);
  • TW3Image now expose 3 new properties that are very helpful. The PixelWidth and PixelHeight gives you the actual size of an image, regardless of scale or viewport proportions. This is handy when using the Allocate() method to create a pixel-buffer that matches an existing image:
    • property  PixelWidth: integer
    • property  PixelHeight: integer
    • property  Complete: boolean
  • The following procedures and functions have been removed. Faster to assign and deal with events directly on the handle – no need for the extra call:
    • procedure w3_bind
    • procedure w3_bind2
    • procedure w3_unbind
    • procedure w3_unbind2
  • TStreamHelper has been added to SmartCL.System. It adds data objectification. Generating an internal URL to access the data via a link. These functions wraps the createObjectURL API (https://developer.mozilla.org/en-US/docs/Web/API/URL/createObjectURL) The following methods are in the helper:
    • function  GetObjectURL: string;
    • procedure RevokeObjectURL(const ObjectUrl: string);
  • TAllocationHelper has been added to SmartCL.System. It does exactly the same as TStreamHelper but for the memory class TAllocation (system.memory.allocation.pas).
  • TBinaryData which is a very flexible and fast memory buffer class (system.memory.buffer.pas), inherits from TAllocation – as such TAllocationHelper also affects this class.
  • The class TW3URLObject, which is a static class, has been added to SmartCL.System.pas. It wraps the same API as TStreamHelper and TAllocationHelper, but operates independent of medium. This class also adds forced download methods – where you can start a “save as” of binary data (or any data) with a single call. The class interface contains the following:
    • class function  GetObjectURL(const Text, Encoding, ContentType, Charset: string): string;
    • class function  GetObjectURL(const Text: string): string;
    • class function  GetObjectURL(const Stream: TStream): string;
    • class function  GetObjectURL(const Data: TAllocation): string;
    • class procedure RevokeObjectURL(const ObjectUrl: string);
    • class procedure Download(const ObjectURL: string; Filename: string);
    • class procedure Download(const ObjectURL: string; Filename: string;
      const OnStarted: TProcedureRefId);
  • TW3CustomControl now have automatic ZIndex assignment. On creation a ZIndex value is automatically assigned to each control (auto-inc number). The following methods have been added for that value:
    • function  GetZIndex: integer;
    • procedure SetZIndex(const NewZIndex: integer);
  • To better deal with z-order and the creation sequence of child elements, the following methods have been added to TW3CustomControl. This makes it easier to control how child elements stack and overlap:
    • function  GetZOrderList(const Sort: boolean): TW3StackingOrderList;
    • function  GetChildrenSortedByYPos: TW3TagContainerArray;
    • function  GetChildrenSortedByXpos: TW3TagContainerArray;
  • TW3CustomControl’s Invalidate() method has previously done nothing. It has been present for TW3GraphicControl (which inherits from TW3CustomControl) as means of redrawing or refreshing graphics.
    Invalidate for non-graphic controls now calls resize() to update the layout. While coders should use BeginUpdate / EndUpdate, there are times when a call to Invalidate() is all you need.
  • Text-shadow, which is a very effective CSS effect for text, has been isolated in its own class (TW3TextShadow) and is now exposed as TW3CustomControl’s TextShadow property. The class is created on-demand and will not consume memory until used.
  • TW3TagStyle, a class that parses and allows you to add, check and remove CSS style-rules to your control has been updated. The previous parsing routine has been replaced by a much faster string.split() call – removing the need for the for/next loop used to extract styles previously. This class is exposed as TW3CustomControl’s “TagStyle” property. Like Text-Shadow it is created on demand.
    Note: This replaces the older “CssClasses” property which has now been deprecated.
  • The TW3AnimationFrame class has been moved from SmartCL.Components.pas to SmartCL.Animation.pas which seems more fitting.
  • TControlHandleHelper is a strict helper-class for TControlHandle, which is the handle type used for HTML elements in the DOM. Many of the rouge utility functions that were scattered around the RTL have now been isolated here. It now gives you the following functionality:
    • function Defined: boolean;
    • function UnDefined: boolean;
    • function Valid: boolean;
    • function Equals(const Source: TControlHandle): boolean;
    • function Ready: Boolean;
    • function GetChildById(TagId: string): TControlHandle;
    • function GetChildCount: integer;
    • function GetChildByIndex(const Index: integer): TControlHandle;
    • function GetChildOf(TagType: string; var Items: TControlHandleArray): boolean;
    • function GetChildren: TControlHandleArray;
    • function HandleParent: TControlHandle;
    • function HandleRoot: TControlHandle;
    • procedure ReadyExecute(OnReady: TProcedureRef);
    • procedure ReadyExecuteEx(const Tag: TObject; OnReady: TProcedureRefEx);
    • procedure ReadyExecuteAnimFrame(OnReady: TProcedureRef);
  • function w3_GetCursorCSSName, w3_GetCursorFromCSSName has been removed from SmartCL.System.pas – use the methods in the static class TW3MouseCursor instead
  • w3_DOMReady, w3_CSSPrefix and w3_CSSPrefixDef has been removed from SmartCL.System.pas – use the methods in BrowserAPI instead
  • The RTL now has its own stylesheet classes. The unit SmartCL.CSS.StyleSheet.pas and SmartCL.CSS.Classes represents the CSS management units for the system.

    TSuperstyle (see below) generates animation styles on-the-fly and returns the name for it. You can add this name to a control with Control.TagStyle.add().

    TCSS is a helper class for building complex styles via code. TSuperstyle uses this to create the animation effects. Note that these two classes are meant as examples (!) SmartCL.CSS.StyleSheet contains the following classes:

    • TW3StyleSheet
      • property    Handle: TControlHandle
      • property    StyleSheet: JCSSStyleSheet
      • property    StyleRules: JCSSRuleList
      • property    Count: integer
      • property    Items[const Index: integer]: string
      • class function  CreateStyleId: string;
      • class function  CreateStylesheetId: string;
      • class function  CreateStyleElement: TControlHandle;
      • class procedure DisposeStyleElement(const Handle: TControlHandle);
      • class function DefaultStylesheet: TW3StyleSheet;
      • function GetSheetObject: THandle;
      • function GetRuleObject: THandle;
      • function GetCount: integer;
      • function GetItem(const Index: integer): string;
    • TSuperStyle
      • class function EdgeRound(Size: integer): string; overload;
      • class function EdgeRound(TopLeftP, TopRightP, BottomLeftP, BottomRightP: integer):String; overload;
      • class function EdgeTopaz: string;
      • class function EdgeAngaro: string;
      • class function AnimGlow(GlowFrom, GlowTo: TColor): string;
      • class procedure AnimStart(Handle: TControlHandle; animName: String);
    • TCSS
      • function KeyFrames: TCSS;
      • function From: TCSS;
      • function &To: TCSS;
      • function Enter: TCSS;
      • function Leave: TCSS;
      • function PercentOf(Value: integer): TCSS;
      • function IntOf(Value: integer): TCSS;
      • function ColorOf(Value: TColor): TCSS;
      • function &inc(Value: string): TCSS;
      • function CRLF: TCSS;
      • function OpenParam: TCSS;
      • function CloseParam: TCSS;
      • function Background: TCSS;
      • function BoxShadow(Left, Top, Right, Bottom: integer): TCSS;overload;
      • function BoxShadow(Left, Top, Right, Bottom: integer; Color:TColor):TCSS;overload;
      • function LinearGradientV(aFrom, aTo: TColor): TCSS;
      • function LinearGradientH(aFrom, aTo: TColor): TCSS;
      • function LinearGradientTL(aFrom, aTo: TColor): TCSS;
      • function LinearGradientTR(aFrom, aTo: TColor): TCSS;
      • function BeginComplexGradient(Angle: integer): TCSS;
      • function EndComplexGradient: TCSS;
      • function ColorPercent(PercentOf: integer; Color: TColor): TCSS;
      • function LinearGradientAngle(aFrom, aTo: TColor; const Angle: float): TCSS;overload;
      • function LinearGradientAngle(Colors: Array of TColor; const Angle: float): TCSS; overload;
      • function AnimationName(Name: string): TCSS;
      • function AnimationDuration(Secs, MSecs: integer): TCSS;
      • function AnimationInfinite: TCSS;
      • class function Make: TCSS;
      • function Assign(Value: string): TCSS;

Controllers

A new concept has been added to the RTL, namely something called controllers. These are classes that attach to a control and either alter its behavior or exposes functionality that is not available by default.

SmartCL.Controller.EdgeSense

This unit exposes the class TW3EdgeSenseController, which can be attached to any visual control. Once attached the controller will fire events whenever the mouse-pointer or touch is close to the border of the control. This is typically used by controls that is expected to be resized, like a grid header column.

SmartCL.Controller.Swipe

This unit provides the following classes:

  • TW3SwipeControllerOptions
  • TW3SwipeRange
  • TW3SwipeController

The TW3SwipeController attaches itself to any visual control and will trigger an event if a swipe gesture is performed on the target control. Since gesures such as swipe is one of the most common gestures, it made sense to isolate this in particular.

This controller is presently used by one of the new components, TW3Win10Header, which is a html5 implementation of the Windows 10 mobile header. This is a good-looking control that allows you to swipe through categories, much like a page-control for Windows applications, but better suited for mobile designs.

SmartCL.Controller.TagAttributes

This controller implements access to custom data attributes for any visual control. It is essentially the functionality of TW3ElementAttributes (unit: SmartCL.Attributes= but here implemented as a controller.

The class used by the RTL for this behavior today is TW3ElementAttributes, but this will be deprecated in the future in favour of the controller.

SmartCL.Scroll.IScroll

iScroll is now the default scrolling mechanism for visual Smart Mobile Studio applications. The new TW3ScrollWindow and TW3CustomScrollList visual classes all derive their fantastic accurate scrolling and movement capabilities from iScroll – which by many is regarded as the defacto JavaScript scrolling library.

To make iScroll easier to use when writing custom controls that don’t inherit from TW3ScrollWindow or TW3CustomScrollList – you can use the controller instead. Simply create an instance, attach it to the parent container (which contains the elements you want to scroll) and you pretty much have instant iOS / Android scrolling.

Note: $Libraries\IScroll\iScroll.js has been updated to iScroll 5.2.0, which works fine on older Android devices (there were some problems with iScroll before). I have also updated our class implementation to include ALL options, events and functionality.

New Visual Controls

Indeed. While more or more controls is going to be added once the IDE update is in place, we at least have a few new controls in place that can liven up your applications.

First of all, iScroll is now (as explained) the default scroll mechanism in Smart Mobile Studio. When you combine that with the already impressive list of effects (smartcl.effects.pas) writing controls that gives visual feedback when you touch them, that scroll smoothly with momentum, bounce and snap to – is now almost ridiculously simple.

TW3DBGrid

First up is the long-awaited DB grid. This is a fast paced grid that can show thousands of elements without any significant speed penalty. This is because it creates the elements as they come into view – and it uses a very effective cache system to keep track of which elements (rows / cols) have been created, and which needs some attention.

grid

It also allows you to resize and move columns as you would expect from a native grid, so hopefully this first incarnation is just what you expected.

TW3CategoryHeader

Next we have the fancy Windows 10 mobile category header control. This is a touch driven, swipe responsive header that shows both a category and a small ingress text.

categories

This is an excellent control to create with Application.Display as a parent. That way it remains on top regardless of form – and the user can quickly navigate through swipes.

TW3SimpleLabel

We have also added a more lightweight label control. This is best suited for static text that don’t need horizontal alignment. It is a lot more resource friendly than TW3Label which is a composite custom control.

Ace editor control

Ace, the #1 Javascript code editor, akin to Delphi’s SynEdit or the popular Sublime editor, has likewise been added. This should make a lot of things easier, especially if you are creating front-end code for your cloud services (which you can now write in pristine node.js).

ace

TW3LayoutControl

TW3LayoutControl is another nice addition. This is essentially a scrollbox with classical rulers top and left. Perfect for image display programs, paint programs, layout designers and similar tasks. You can override and adapt this heavily and make it the basis for a calendar grid if you like.

We have also completely updated, re-written and quality checked every single control in the RTL. They are now more robust, they update and synchronize with their ready-state more or less like native controls do – and I have spent quite some time making sure they behave as they should.

Oh and yes, there are more controls (this post is getting long).

NodeJS high-level classes

Without a doubt the feature that has received most attention in this update are the Node.JS high level classes and units.

The previous NodeJS namespace and project type gave you more or less the bare-bones of node.js. This could be quite confusing for developers coming straight from Delphi or Lazarus. Especially if you don’t really want to dive to deep into the world of JavaScript – but enjoy some abstraction and ease of use.

The new SmartNJ namespace, where the files also are prefixed with the sane, implements common classes for both clients and servers. Writing an HTTP server is for instance very simple – and the code is in many cases as close to the other server types as possible.

The idea is that if you know how to write one type of server, you should also be able to write other types as well; because they share the same ancestor and commonalities.

As of writing the following server types are supported:

  • UDP
  • HTTP
  • Websocket (WebSocketIO)

The class architecture looks more or less like this:

  • TNJCustomServer
    • TNJUDPService
      • TNJUDPServer
    • TNJHTTPServer
    • TNJWebSocketServer
  • TNJCustomClient
    • TNJUDPService
      • TNJUDPClient
    • TNJHttpClient
    • TNJWebSocketClient

Clients and servers have traditional auxillary objects, such as request and response objects:

  • TNJServerChildClass
    • TNJCustomServerRequest
      • TNJHttpRequest
    • TNJCustomServerResponse
      • TNJHttpResponse

The following units make up the SmartNJ namespace today:

  • SmartNJ.Filesystem
  • SmartNJ.Network.Bindings
  • SmartNJ.Network
  • SmartNJ.Server
  • SmartNJ.Server.Http
  • SmartNJ.Server.Udp
  • SmartNJ.Server.WebSocket
  • SmartNJ.Streams
  • SmartNJ.System
  • SmartNJ.Udp
  • SmartNJ.WebSocket

Unified filesystem

In the previous versions of the RTL we presented several ways to store information. We had thin wrappers over cookies, local storage and filestorage. You could also use one of the 3 database engines to store data (sandboxed).

These unit still ship with the RTL, so should you want to pick one in particular that is of course not a problem. But in order to streamline and make IO as uniform as possible, I have introduced the notion of a standard “filesystem”.

The filesystem base-class can be found (as expected) in the system.filesystem.pas file. This contains the abstract class that makes up the most common filesystem operations.

NodeJS gives you a filesystem class that has direct access to the real files and folders, so be careful when you use that. As expected this filesystem implementation can be found under SmartNJ.Filesystem.pas.

And last but not least you will find a filesystem unit for visual browser applications, under SmartCL.Filesystem.pas.

The idea here is that you can write the exact same code and it will work regardless of which platform you run on.The same code you write for node.js will then save or load data in the browser as well.

This system will be expanded to include Cordova Phonegap, NodeWebkit (a hybrid post-compiler to make desktop applications) and embedded platforms.

Note: NodeJS is special in that it gives you both an asynchronous filesystem and blocking filesystem. If you want to use the blocking version, simply access it via the blocking interface (see unit file). But we strongly suggest that you avoid this since that will have a huge impact on performance. You must never use the blocking IO interface on servers, expect perhaps during startup – it will completely undermine the node.js runtime scheme and yield terrible performance.

Database support

This has been a pressing issue for quite some time, and while we are close, we still need to add more infrastructure before we can wrap the engines we want. Although classes will be available soon – being able to visually hook up data in common RAD style is what we want.

For that to happen I need the following features in the IDE:

  • Datamodules
  • Designer drop-zone for non-visual components
  • Design support for property mapping

Without these features database support under node.js will be flawed at best. But fret not, it is being worked on and we are presently looking at the following engines:

Client side we will provide the same DB API, but here it will wrap the existing engines:

  • WebSQL [deprecated by W3C]
  • SQLite [default]
  • TW3Dataset
  • IndexDB

The reason this is more complicated than it may look is first of all because IndexDB and MongoDB are object based databases, not SQL based. So the architecture really needs property mapping and the IDE to back it up. Naturally it can be done in pure code (which we have started) but the IDE with the built-in database designer is what will make this more sane to work with.

Action support

Yes, finally it is here. TCustomAction and TAction has been added to the RTL, and appropriate controls now expose an Action property that you can assign to.

Again we need the next update before this becomes more easy to work with. You need to be able to drop a TW3ActionManager on your form or datamodule, and then assign that via the property inspector to the control.

The IDE will automatically generate the “glue” code on compile for you, and it will execute as a part of your {$I ‘form: impl’} section and set things up.

Right now you have to create and assign actions manually — but at least it’s finally in place. Which is really good because that allows me to write default-actions for various tasks just like you have in Delphi, C++ builder and Lazarus.

Final words

This has been a preview, not the full load. There are many other changes to the codebase, some of the changes quite radical. Hopefully you find this interesting and will enjoy using it as much as we have enjoyed creating it.

Things that havent been covered yet are:

  • Pixi.js
  • Plumb.js
  • Stacktrace support
  • Codec framework
  • eSpeak
  • Vector graphics display control
  • Support for Scetchpad 3d models
Loading HD 3d models into your smart applications is now possible (and easy). Stills doesnt do this model justice ..

Loading HD 3d models into your smart applications is now possible (and easy). Stills doesnt do this model justice ..

And yes, I have also added support for asset management. This wont be fully visible before the IDE update, but being able to pre-load pictures, sounds and resources before your application starts is now simplicity itself. As a bonus we also added support for Require() which is more or less the de-facto JavaScript way of loading assets (and more) these days. You even get a sweet callback when its done or when something goes wrong.

Stay tuned! I’m working as fast as I can

/Jon

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:

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!

For those about to scroll, we salute you

June 10, 2016 1 comment

Came up with a better solution to the scrolling problem for Smart Mobile Studio. While I hate having to write code for Internet Explorer, it is nice to have a system that works everywhere. But that means more abstraction and “drivers” type classes.

The scrolling model

TW3Scrollbox and it’s variation(s) implement different types of scrolling. TW3Scrollbox has a very fast non-momentum scroll, making it perfect for displaying detailed information. But it would be nice if we could choose right?

The model I have come up with is super simple, especially in the upcoming RTL where we finally have non-visual components. I give you – TW3ScrollController

scroll_model

In the present model, TW3Scrollbox deals with scrolling directly. Actually the scrolling is implemented in the TW3ScrollContent control, mapping touch and mouse events directly. This turned out to be a remarkably fast way of moving things around. I really did not expect IE to keep up, but it’s perfectly pristine!

In the momentum-scroll example I posted, control of scrolling is handled by the container rather than content. This is very fast on webkit, and I also tried it on Microsoft mobile and Windows 10 mobile – and it’s very fast there. But for some reason the desktop Internet Explorer is slow and the content jitters a bit.

The culprit is not my code or approach, it’s actually something else. Internet Explorer and Edge are the only browsers that implements OnReSize() events. No other browser has this. In the SMS RTL we have to manually figure out when to call ReSize (based on the BeginUpdate, AddControlState, EndUpdate methods).

In the momentum scroller I use SetBounds() to keep the content within the horizontal bounds of the control. This causes an extra call to Resize every time the content moves even a pixel (even though it shouldnt, because the size doesnt change). So yeah, fixing that will make all the difference. I’m going to nail this thing once and for all, just like I did with font measurements way back.

TW3ScrollController

But isolating the code that actually deals with scrolling in separate, non visual components that you can attach to the TW3ScrollBox makes sense. Rather than hardcoding everything into a huge, spaghetti monster unit — we can now isolate different scroll methods in their own units (keeping those bytes down).
Normal per-pixel scrolling, momentum scrolling, CSS3 animation based scrolling, tween based (cpu) scrolling. It gives us some options – and allows you to implement your own variations if you find mine lacking.

I’m also giving the browser driver a much deserved overhaul. Getting the browser type and version info should be easy (and humanly readable). And since you may want to pick different scroll methods depending on the browser type — being able to check if your running on Edge for the desktop, or IE on a mobile device… well, it should be there. End of story.

iScroll

I really want iScroll to be the standard scroll library for Smart Mobile Studio, but since people feel it’s hard to use and adapt to — I may end up doing the unthinkable and re-write it in object pascal from scratch. But iScroll5 really is so much better. It has been developed and tested on a plethora of devices for six years now.

It even does things the built-in browser scrolling (for the browsers that allows this, yeah im looking at you Safari!) doesn’t deal with.

But I have enough on my plate right now, so iScroll porting will have to wait.

Momentum Scrolling

May 31, 2016 5 comments

Momentum scrolling is something we havent had as an option in the VJL directly. We excluded it initially because there were excellent JavaScript libraries especially for this (like iScroll), but in retrospect I guess it wouldnt hurt to have it in the VJL written in object pascal.

Here is a little something I slapped together the other day. Im going to make both TListbox and the ordinary content containers have an option for this.

scroller

Oooo.. sexy sexy scroller thingy!

 

Note: This supports both mouse and touch, and if you are confused about the event objects then head over to Github and snag a copy of that there. Just remove the references to units you dont have and include eventobjs.pas in your uses clause!

The call to SetInitialTransformationStyles() should be replaced with (this makes the browser mark the element for GPU, which is very fast):

    FContent.Handle.style[BrowserAPI.Prefix('transformStyle')] := 'preserve-3d';
    FContent.Handle.style[BrowserAPI.Prefix('Perspective')] := 800;
    FContent.Handle.style[BrowserAPI.Prefix('transformOrigin')] := '50% 50%';
    FContent.Handle.style[BrowserAPI.Prefix('Transform')] := 'translateZ(0px)';

Oh and it fades out the indicator after a scroll session, quite nice if I say so myself 🙂

Enjoy!

unit Form1;

interface

uses
  System.types, System.Colors,
  System.Events, System.Time, System.Widget, System.Objects,

  W3C.Date, W3C.DOM,

  SmartCL.Effects,

  SmartCL.Events, SmartCL.MouseCapture, SmartCL.System, SmartCL.Graphics,
  SmartCL.Components, SmartCL.Forms,  SmartCL.Fonts, SmartCL.Borders,
  SmartCL.Application, SmartCL.Controls.Listbox, SmartCL.Controls.Panel,
  SmartCL.Controls.CheckBox, SmartCL.Controls.Button;

type

  TScrollContent = class(TW3CustomControl)
  end;

  TW3ScrollIndicator = class(TW3CustomControl)
  end;

  TW3VScrollControl = class(TW3CustomControl)
  private
    FYOffset: integer;
    FContent: TScrollContent;
    FVRange:  TW3Range;
    FHRange:  TW3Range;
    FPressed: boolean;
    FStartY:  integer;

    FTarget: integer;
    FAmplitude: double;
    FTimestamp: integer;
    FVelocity: double;
    FFrame: double;
    FTicker: TW3DispatchHandle;
    FFader: TW3DispatchHandle;
    FTimeConstant: double;

    FMouseDownEvent: TW3DOMEvent;
    FMouseUpEvent: TW3DOMEvent;
    FMouseMoveEvent: TW3DOMEvent;
    FTouchDownEvent: TW3DOMEvent;
    FTouchMoveEvent: TW3DOMEvent;
    FTouchEndsEvent: TW3DOMEvent;

    FIndicator: TW3ScrollIndicator;
    function  GetYPosition(const E: variant): integer;
    procedure MoveBegins(sender: TObject; EventObj: JEvent);
    procedure MoveEnds(sender: TObject; EventObj: JEvent);
    procedure MoveUpdate(sender: TObject; EventObj: JEvent);
    procedure HandleContentSizeChanged(sender: TObject);
  protected
    procedure Track;virtual;
    procedure AutoScroll;virtual;

    procedure ScrollBegins;virtual;
    procedure ScrollEnds;virtual;

    procedure Resize;override;
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure ObjectReady;override;
    procedure ScrollY(const NewTop: integer);
  public
    Property  Content:TScrollContent read FContent;
  end;

  TForm1 = class(TW3Form)
    procedure W3Button1Click(Sender: TObject);
  private
    {$I "Form1:intf"}
    FBox: TW3VScrollControl;
  protected
    procedure InitializeForm; override;
    procedure InitializeObject; override;
    procedure Resize; override;
  end;

implementation

//###################################################################
// TW3VScrollControl
//###################################################################

procedure TW3VScrollControl.InitializeObject;
begin
  inherited;
  FPressed:=false;
  FYOffset := 0;
  FStartY := 0;

  FTimeConstant := 325;

  Background.fromColor(clWhite);
  FContent := TScrollContent.Create(self);
  FIndicator:=TW3ScrollIndicator.Create(self);
  FIndicator.width:=8;
  FIndicator.height:=32;
  FIndicator.StyleClass:='TW3ScrollContentIndicator';
  FIndicator.Transparent := true;

  FMouseDownEvent := TW3DOMEvent.Create(self);
  FMouseDownEvent.Attach("mousedown");
  FMouseDownEvent.OnEvent := @MoveBegins;

  FMouseMoveEvent := TW3DOMEvent.Create(self);
  FMouseMoveEvent.Attach("mousemove");
  FMouseMoveEvent.OnEvent := @MoveUpdate;

  FMouseUpEvent := TW3DOMEvent.Create(self);
  FMouseUpEvent.Attach("mouseup");
  FMouseUpEvent.OnEvent := @MoveEnds;

  FTouchDownEvent := TW3DOMEvent.Create(self);
  FTouchDownEvent.Attach("touchstart");
  FTouchDownEvent.OnEvent:= @MoveBegins;

  FTouchMoveEvent := TW3DOMEvent.Create(self);
  FTouchMoveEvent.Attach("touchmove");
  FTouchMoveEvent.OnEvent := @MoveUpdate;

  FTouchEndsEvent := TW3DOMEvent.Create(self);
  FTouchEndsEvent.Attach("touchend");
  FTouchEndsEvent.OnEvent := @MoveEnds;

  FContent.Handle.ReadyExecute(
  procedure ()
  begin
    (* Mark content for GPU acceleration *)
    FContent.SetInitialTransformationStyles;
  end);
end;

procedure TW3VScrollControl.ObjectReady;
begin
  inherited;
  FContent.OnReSize := HandleContentSizeChanged;
  FIndicator.left:=ClientWidth-FIndicator.width;
  FIndicator.bringToFront;
  FIndicator.Visible:=false;
  resize;
end;

procedure TW3VScrollControl.FinalizeObject;
begin
  FContent.free;
  inherited;
end;

procedure TW3VScrollControl.HandleContentSizeChanged(sender: TObject);
begin
  if not (csDestroying in ComponentState) then
  begin
    FVRange := TW3Range.Create(0, FContent.Height - ClientHeight);
    FHRange := TW3Range.Create(0, FContent.Width - ClientWidth);
  end;
end;

procedure TW3VScrollControl.Resize;
var
  LClient:  TRect;
begin
  inherited;
  if (csReady in ComponentState) then
  begin
    LClient := ClientRect;
    FVRange := TW3Range.Create(0, FContent.Height - LClient.Height);
    FHRange := TW3Range.Create(0, FContent.Width - LClient.Width);
    FContent.SetBounds(0,FContent.top,LClient.Width,FContent.height);
    FIndicator.MoveTo(ClientWidth-FIndicator.Width,FIndicator.top);
  end;
end;

procedure TW3VScrollControl.ScrollY(const NewTop: integer);
var
  LGPU: string;
  LIndicatorTarget: integer;

  function GetRelativePos:double;
  begin
    result := (ClientHeight - FIndicator.Height) / (FContent.Height - ClientHeight);
  end;

begin
  if not (csDestroying in ComponentState) then
  begin
    if (csReady in ComponentState) then
    begin
      (* Use GPU scrolling to position the content *)
      FYOffset := FVRange.ClipTo(NewTop);
      LGPU := "translate3d(0px,";
      LGPU += FloatToStr(-FYOffset) + "px, 0px)";
      FContent.Handle.style[BrowserAPI.Prefix("Transform")] := LGPU;

      (* Use GPU scrolling to position the indicator *)
      LIndicatorTarget := FYOffset * GetRelativePos;
      FIndicator.left := clientwidth - FIndicator.width;
      LGPU :="translateY(" + TInteger.ToPxStr(LIndicatorTarget) + ")";
      FIndicator.Handle.style[BrowserAPI.Prefix("Transform")]:= LGPU;
    end;
  end;
end;

procedure TW3VScrollControl.Track;
var
  LNow: integer;
  Elapsed: integer;
  Delta: double;
  V: double;
begin
  LNow := TW3Dispatch.JsNow.now();
  Elapsed := LNow - FTimestamp;
  FTimestamp := TW3Dispatch.JsNow.now();
  Delta := FYOffset - FFrame;
  FFrame := FYOffset;
  v := 1000 * Delta / (1 + Elapsed);
  FVelocity := 0.8 * v + 0.2 * FVelocity;
end;

procedure TW3VScrollControl.ScrollBegins;
begin
  TW3Dispatch.ClearInterval(FFader);
  if not (csDestroying in ComponentState) then
  begin
    FIndicator.Visible := true;
    FIndicator.AlphaBlend := true;
    FIndicator.Opacity := 255;
  end;
end;

procedure TW3VScrollControl.ScrollEnds;
begin
  TW3Dispatch.ClearInterval(FFader);
  if not (csDestroying in ComponentState) then
  begin
    FFader:=TW3Dispatch.SetInterval(procedure ()
      begin
        FIndicator.AlphaBlend := true;
        FIndicator.Opacity := FIndicator.Opacity - 10;
        if FIndicator.Opacity=0 then
        begin
          TW3Dispatch.ClearInterval(FFader);
        end;
      end,
      50);
  end;
end;

procedure TW3VScrollControl.AutoScroll;
var
  Elapsed: integer;
  Delta: double;
begin
  if FAmplitude<>0 then
  begin
    Elapsed := TW3Dispatch.JsNow.now() - FTimestamp;
    Delta := -FAmplitude * Exp(-Elapsed / FTimeConstant);
  end;

  (* Scrolled passed end-of-document ? *)
  if (FYOffset >= (FContent.Height - ClientHeight)) then
  begin
    TW3Dispatch.ClearInterval(FTicker);
    FTicker := unassigned;
    ScrollY(FContent.Height-ClientHeight);
    ScrollEnds;
    exit;
  end;

  (* Scrolling breaches beginning of document? *)
  if (FYOffset < 0) then   begin     TW3Dispatch.ClearInterval(FTicker);     FTicker := unassigned;     ScrollY(0);     ScrollEnds;     exit;   end;   if (delta > 5) or (delta < -5) then   begin     ScrollY(FTarget + Delta);     W3_RequestAnimationFrame(AutoScroll);   end else   begin     ScrollY(FTarget);     ScrollEnds;   end; end; function TW3VScrollControl.GetYPosition(const e: variant): integer; begin   if ( (e.targetTouches) and (e.targetTouches.length >0)) then
  result := e.targetTouches[0].clientY else
  result := e.clientY;
end;

procedure TW3VScrollControl.MoveBegins(sender: TObject; EventObj: JEvent);
begin
  FPressed := true;
  FStartY := GetYPosition(EventObj);
  FVelocity := 0;
  FAmplitude := 0;
  FFrame := FYOffset;
  FTimestamp := TW3Dispatch.JsNow.now();
  TW3Dispatch.ClearInterval(FTicker);
  FTicker := TW3Dispatch.SetInterval(Track,100);
  EventObj.preventDefault();
  EventObj.stopPropagation();
end;

procedure TW3VScrollControl.MoveUpdate(sender: TObject; EventObj: JEvent);
var
  y, delta: integer;
begin
  if FPressed then
  begin
    y := GetYPosition(eventObj);
    delta := (FStartY - Y);
    if (Delta>2) or (Delta < -2) then     begin       FStartY := Y;       ScrollY(FYOffset + Delta);     end;   end;   EventObj.preventDefault();   EventObj.stopPropagation(); end; procedure TW3VScrollControl.MoveEnds(sender: TObject; EventObj: JEvent); begin   FPressed := false;   TW3Dispatch.ClearInterval(FTicker);   if (FVelocity > 10) or (FVelocity < -10) then
  begin
    FAmplitude := 0.8 * FVelocity;
    FTarget := round(FYOffset + FAmplitude);
    FTimeStamp := TW3Dispatch.JsNow.Now();

    ScrollBegins;
    w3_requestAnimationFrame(autoscroll);
  end;
  EventObj.preventDefault();
  EventObj.stopPropagation();
end;

{ TForm1 }

procedure TForm1.W3Button1Click(Sender: TObject);
begin
  self.FBox.Content.height:=1000;
end;

procedure TForm1.InitializeForm;
begin
  inherited;

  // this is a good place to initialize components
  FBox := TW3VScrollControl.Create(self);
  FBox.SetBounds(10,10,300,300);

  //

  var LText :="
<table cellpadding=|0px| style=|border-collapse: collapse| width=|100%|>";
  for var x:=1 to 400 do
  begin
    if ((x div 2) * 2) = x then
    LText += "
<tr padding=|0px| style=|border: 0px solid black; background:#ECECEC|>" else
    LText += "
<tr style=|border: 0px solid black; background:#FFFFFF|>";
    LText += "
<td padding=|0px| height=|32px| style=|border-bottom: 1px solid #ddd|>" + x.toString + "</td>
";
    LText += "
<td style=|border-bottom: 1px solid #ddd|>List item #" + x.toString + "</td>
";
    LText += "</tr>
";
  end;
  LText +="</table>
";
  LText := StrReplace(LText,'|','''');

  FBox.Content.innerHTML := LText;
  FBox.Content.width:=1000;
  FBox.Content.height := FBox.Content.ScrollInfo.ScrollHeight;

end;

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

procedure TForm1.Resize;
begin
  inherited;
  if (csReady in ComponentState) then
  begin
    //FBox.setBounds(10,10,clientwidth div 2, clientHeight div 2);
  end;
end;

initialization
begin
  Forms.RegisterForm({$I %FILE%}, TForm1);
end;

end.

CSS Builder Class, Smart Syntax

April 12, 2015 Leave a comment

In my last post I demonstrated just how flexible CSS can be when you start to automate it. Well, if you want to play around with this NOW as opposed to waiting for the next update, I have written down directions to do so here.

First you need the CSS class. This can be found in the QTX library which is hosted on Google Code (Click here to view the repository). Note: You dont need the whole library, just download the unit in the link and rename it and you’re good. Save the files in the libraries folder of Smart Mobile Studio (see the start button registration for Smart, there is a link to the RTL and Library folder).

Generating cool effects is now super easy

Generating cool effects is now super easy

Right. With the CSS class in your possession you now need the builder class. This is essentially a small class with keywords that, when used, generate CSS code according to the functions and when they are used. So if you do something wrong the CSS will come out wrong.

Here is the generator class so far:

unit darth.stylesheet;

//#############################################################################
//
//  DARTH COMPONENTS
//
//  Author:     Jon Lennart Aasenden
//  Copyright:  Jon Lennart Aasenden, all rights reserved
//
//#############################################################################

interface

uses
  System.Types,
  System.Colors,
  SmartCL.System;

type

  TDarthStyleSheet = Class(TObject)
  private
    FHandle:    THandle;
  protected
    function    getSheet:THandle;
    function    getRules:THandle;
    function    getCount:Integer;
    function    getItem(index:Integer):String;
  public
    Property    Sheet:THandle read getSheet;
    Property    Handle:THandle read FHandle;
    function    Add(aName:String;const aRules:String):String;overload;
    Procedure   Add(const aRuleText:String);overload;

    Property    Count:Integer read getCount;
    Property    Items[index:Integer]:String
                read getItem;

    class procedure addClassToElement(const aElement:THandle;const aName:String);
    class procedure removeClassFromElement(const aElement:THandle;const aName:String);
    class function  findClassInElement(const aElement:THandle;const aName:String):Boolean;

    Constructor Create;virtual;
    Destructor  Destroy;Override;
  End;


  TSuperStyle = static class
  public
    class function  EdgeRound(Size:Integer):String;overload;
    class function  EdgeRound(topleftP,toprightP,bottomleftP,
                    bottomrightP:Integer):String;overload;
    class function  EdgeTopaz:String;
    class function  EdgeAngaro:String;

    class function  AnimGlow(aFrom,aTo:TColor):String;

    class procedure AnimStart(Handle:TControlHandle;animName:String);

  end;


implementation

var
_sheet: TDarthStyleSheet;

function getStyleSheet:TDarthStyleSheet;
begin
  if _sheet=NIL then
  _sheet:=TDarthStyleSheet.Create;
  result:=_sheet;
end;

class procedure TSuperStyle.AnimStart(Handle:TControlHandle;animName:String);
begin
  animName:=animName.trim;
  if  (animname.length>0)
  and (handle) then
  begin
    Handle.style['-webkit-animation-name']:=animName;
    Handle.style['-webkit-animation-duration']:='2s';
    Handle.style['-webkit-animation-iteration-count']:='infinite';
  end;
end;

  type
  TCSS = class
  public
    Property  Text:String;
    function  KeyFrames:TCSS;
    function  From:TCSS;
    function  &To:TCSS;
    function  Enter:TCSS;
    function  Leave:TCSS;
    function  PercentOf(Value:Integer):TCSS;
    function  IntOf(Value:Integer):TCSS;
    function  ColorOf(Value:TColor):TCSS;
    function  &inc(Value:String):TCSS;
    function  CRLF:TCSS;
    function  OpenParam:TCSS;
    function  CloseParam:TCSS;
    function  Background:TCSS;
    function  BoxShadow(Left,Top,Right,Bottom:Integer):TCSS;overload;
    function  BoxShadow(Left,Top,Right,Bottom:Integer;
              Color:TColor):TCSS;overload;

    function  LinearGradientV(aFrom,aTo:TColor):TCSS;
    function  LinearGradientH(aFrom,aTo:TColor):TCSS;
    function  LinearGradientTL(aFrom,aTo:TColor):TCSS;
    function  LinearGradientTR(aFrom,aTo:TColor):TCSS;

    Function  BeginComplexGradient(Angle:Integer):TCSS;
    function  EndComplexGradient:TCSS;

    function  ColorPercent(PercentOf:Integer;Color:TColor):TCSS;

    function  LinearGradientAngle(aFrom,aTo:TColor;
              Const Angle:Float):TCSS;overload;

    function  LinearGradientAngle(Colors:Array of TColor;
              Const Angle:Float):TCSS;overload;

    function  AnimationName(name:String):TCSS;
    function  AnimationDuration(Secs,MSecs:Integer):TCSS;
    function  AnimationInfinite:TCSS;
  end;

function TCSS.KeyFrames:TCSS;
begin
  Text:=Text + '@-webkit-keyframes ';
  result:=self;
end;

function TCSS.CRLF:TCSS;
begin
  Text:=Text + #13;
  result:=self;
end;

function  TCSS.OpenParam:TCSS;
begin
  Text:=Text + ' (';
  result:=self;
end;

function  TCSS.CloseParam:TCSS;
begin
  Text:=Text + ') ';
  result:=self;
end;

function TCSS.&inc(Value:String):TCSS;
begin
  Text:=Text + value;
  result:=self;
end;

function TCSS.PercentOf(Value:Integer):TCSS;
begin
  Text:=Text + TInteger.EnsureRange(Value,0,100).toString + '% ';
  result:=self;
end;

function TCSS.IntOf(Value:Integer):TCSS;
begin
  Text:=Text + Value.toString + ' ';
  result:=self;
end;

function TCSS.ColorOf(Value:TColor):TCSS;
begin
  Text:=Text + ColorToStr(Value) + ' ';
  result:=self;
end;

function  TCSS.Enter:TCSS;
begin
  Text:=Text + '{' + #13#10;
  result:=self;
end;

function  TCSS.Leave:TCSS;
begin
  Text:=Text + '}' + #13#10;
  result:=self;
end;

function TCSS.From:TCSS;
begin
  Text:=Text + 'from ';
  result:=self;
end;

function TCSS.&To:TCSS;
begin
  Text:=Text + 'to ';
  result:=self;
end;

function TCSS.BoxShadow(Left,Top,Right,Bottom:Integer):TCSS;
begin
  Text:=Text + '-webkit-box-shadow: '
    + TInteger.ToPxStr(Left) + ' '
    + TInteger.ToPxStr(Top) + ' '
    + TInteger.ToPxStr(Right) + ' '
    + TInteger.ToPxStr(Bottom) + ';';
  result:=self;
end;

function TCSS.BoxShadow(Left,Top,Right,Bottom:Integer;Color:TColor):TCSS;
begin
  Text:=Text + '-webkit-box-shadow: '
    + TInteger.ToPxStr(Left) + ' '
    + TInteger.ToPxStr(Top) + ' '
    + TInteger.ToPxStr(Right) + ' '
    + TInteger.ToPxStr(Bottom) + ' '
    + ColorToWebStr(Color) + '; ';
  result:=self;
end;

function TCSS.Background:TCSS;
begin
  Text:=Text +'background:';
  result:=Self;
end;

function  TCSS.LinearGradientV(aFrom,aTo:TColor):TCSS;
begin
  Text:=Text + '-webkit-linear-gradient('
  + ColorToWebStr(aFrom) + ',' + ColorToWebStr(aTo) + ');';
  result:=Self;
end;

function  TCSS.LinearGradientH(aFrom,aTo:TColor):TCSS;
begin
  Text:=Text + '-webkit-linear-gradient(left,'
  + ColorToWebStr(aFrom) + ',' + ColorToWebStr(aTo) + ');';
  result:=Self;
end;

function TCSS.LinearGradientTL(aFrom,aTo:TColor):TCSS;
begin
  Text:=Text + '-webkit-linear-gradient(left top,'
  + ColorToWebStr(aFrom) + ',' + ColorToWebStr(aTo) + ');';
  result:=Self;
end;

function TCSS.LinearGradientTR(aFrom,aTo:TColor):TCSS;
begin
  Text:=Text + '-webkit-linear-gradient(right top,'
  + ColorToWebStr(aFrom) + ',' + ColorToWebStr(aTo) + ');';
  result:=Self;
end;

function TCSS.LinearGradientAngle(aFrom,aTo:TColor;Const Angle:Float):TCSS;
begin
  Text:=Text + '-webkit-linear-gradient(' + FloatToStr(Angle)
    + 'deg, ' + ColorToWebStr(aFrom) + ',' + ColorToWebStr(aTo) + ');';
  result:=Self;
end;

function TCSS.LinearGradientAngle(Colors:Array of TColor;Const Angle:Float):TCSS;
var
  x:  Integer;
begin
  Text:=Text + '-webkit-linear-gradient('
  + FloatToStr(Angle) + 'deg, ';
  for x:=0 to Colors.length-1 do
  begin
    Text:=Text + ColorToWebStr(Colors[x]);
    if x<Colors.length-1 then
    Text:=Text + ',';
  end;
  Text:=Text + ');';
  result:=Self;
end;

Function TCSS.BeginComplexGradient(Angle:Integer):TCSS;
begin
  Text:=Text + '-webkit-linear-gradient(' + FloatToStr(Angle) + 'deg,';
  result:=self;
end;

function TCSS.EndComplexGradient:TCSS;
begin
  Text:=Text + ');';
  result:=self;
end;

function TCSS.ColorPercent(PercentOf:Integer;Color:TColor):TCSS;
begin
  if Text[length(text)]='%' then
  Text:=Text + ',';

  Text:=Text + ColorToWebStr(Color) +  ' '
  + TInteger.EnsureRange(PercentOf,0,100).toString + '%';
  result:=self;
end;

function TCSS.AnimationName(name:String):TCSS;
begin
  Text:=Text + '-webkit-animation-name: ' + Name + ';' + #13#10;
  result:=self;
end;

function TCSS.AnimationDuration(Secs,MSecs:Integer):TCSS;
begin
  Text:=text + '-webkit-animation-duration: ' + IntToStr(Secs)
    + '.' + IntToStr(mSecs) + 's;' + #13#10;
  result:=self;
end;

function TCSS.AnimationInfinite:TCSS;
begin
  Text:=Text + '-webkit-animation-iteration-count: infinite;' + #13#10;
  result:=Self;
end;

class function  TSuperStyle.AnimGlow(aFrom,aTo:TColor):String;
var
  mStyles:  TDarthStyleSheet;
  mWriter:  TCSS;
begin
  mStyles:=getStyleSheet;
  if mStyles<>NIL then
  Begin
    result:=w3_GetUniqueObjId;
    mWriter:=TCSS.Create;
    try
      mWriter
        .KeyFrames.inc(result + ' ')
        .Enter
          .from
          .enter.BoxShadow(0,0,0,0).leave.CRLF

          .PercentOf(50)
          .Enter.boxShadow(0,0,12,0,aTo).leave.CRLF

          .to
          .enter.BoxShadow(0,0,0,0).leave.CRLF
        .Leave .CRLF;

        writeln(mWriter.text);
        mStyles.Add(mWriter.Text);
        mWriter.text:='';

        mWriter
          .inc('.' + result + '_player ')
          .enter
            .AnimationName(result)
            .AnimationDuration(2,0)
            .AnimationInfinite
          .leave;
        mStyles.add(mWriter.text);
    finally
      mWriter.free;
    end;
    result:=result + '_player';
  end;
end;

class function TSuperStyle.EdgeRound(topleftP,toprightP,bottomleftP,
      bottomrightP:Integer):String;
var
  mStyles:  TDarthStyleSheet;
begin
  mStyles:=getStyleSheet;
  if mStyles<>NIL then
  Begin
    var mText:='border-radius:';
    if topleftP>0 then
    mText := mText + TInteger.ToPxStr(TInteger.ensureRange(topLeftP,0,100));

    if toprightP>0 then
    mText := mText + ' ' + TInteger.ToPxStr(TInteger.ensureRange(toprightP,0,100));

    if bottomRightP>0 then
    mText := mText + ' ' +TInteger.ToPxStr(TInteger.ensureRange(bottomRightP,0,100));

    if bottomLeftP>0 then
    mText := mText + ' ' +TInteger.ToPxStr(TInteger.ensureRange(bottomLeftP,0,100)) + ';';

    result:=mStyles.Add(result,mText);
  end;
end;

class function TSuperStyle.EdgeRound(Size:Integer):String;
var
  mStyles:  TDarthStyleSheet;
begin
  mStyles:=getStyleSheet;
  if mStyles<>NIL then
  result:=mStyles.Add(result,'border-radius: ' + TInteger.ToPxStr(Size));
end;

class function TSuperStyle.EdgeTopaz:String;
begin
  result:=TSuperStyle.EdgeRound(15,50,0,0);
end;

class function TSuperStyle.EdgeAngaro:String;
begin
  result:=TSuperStyle.EdgeRound(15,50,30,0);
end;

//############################################################################
// TDarthStyleSheet
//############################################################################

Constructor TDarthStyleSheet.Create;
var
  mDocument: THandle;
begin
  inherited Create;
  mDocument:=BrowserAPI.document;
  FHandle:=mDocument.createElement('style');
  FHandle.type := 'text/css';
  mDocument.getElementsByTagName('head')[0].appendChild(FHandle);
end;

Destructor TDarthStyleSheet.Destroy;
Begin
  if (FHandle) then
  FHandle.parentNode.removeChild(FHandle);
  FHandle:=null;
  Inherited;
end;

function TDarthStyleSheet.getCount:Integer;
Begin
  if (FHandle) then
  result:=getRules.length else
  result:=0;
end;

function TDarthStyleSheet.getItem(index:Integer):String;
Begin
  if (FHandle) then
  result:=getRules[index].cssText
end;

(* Takes height for differences between webkit, moz and IE *)
function TDarthStyleSheet.getRules:THandle;
var
  xRef: THandle;
Begin
  if (FHandle) then
  begin
    xRef:=getSheet;
    asm
      @result = (@xRef).cssRules || (@xRef).rules;
    end;
  end;
end;

(* Takes height for differences between webkit, moz and IE *)
function TDarthStyleSheet.getSheet:THandle;
var
  xRef: THandle;
Begin
  if (FHandle) then
  begin
    xRef:=FHandle;
    asm
      @result = (@xRef).styleSheet || (@xRef).sheet;
    end;
  end;
end;

class procedure TDarthStyleSheet.addClassToElement(const aElement:THandle;
      const aName:String);
Begin
 w3_AddClass( aElement,aName);
end;

class procedure TDarthStyleSheet.removeClassFromElement(const aElement:THandle;
      const aName:String);
Begin
  w3_RemoveClass(aElement,aName);
end;

class function  TDarthStyleSheet.findClassInElement(const aElement:THandle;
      const aName:String):Boolean;
Begin
  result:=w3_hasClass(aElement,aName);
end;

Procedure TDarthStyleSheet.Add(const aRuleText:String);
var
  mDocument: THandle;
  mSheet: THandle;
Begin
  mDocument:=BrowserAPI.document;
  if (FHandle) then
  Begin
    mSheet:=getSheet;
    if not (mSheet.insertRule) then
    mSheet.addRule(aRuleText) else
    mSheet.insertRule(aRuleText,0);
  end;
end;

function TDarthStyleSheet.Add(aName:String;const aRules:String):String;
var
  mDocument: THandle;
  mSheet: THandle;
Begin
  aName:=trim(aName);
  if length(aName)=0 then
  aName:=w3_GetUniqueObjId;

  mDocument:=BrowserAPI.document;
  if (FHandle) then
  Begin
    mSheet:=getSheet;
    if not (mSheet.insertRule) then
    mSheet.addRule('.' + aName,aRules) else
    mSheet.insertRule('.' + aName + '{' + aRules + '}',0);
  end;

  result:=aName;
end;

end.

Now inside the AnimGlow function in TSuperClass, you will notice that we have pretty much gone overboard with linear-gradients. Just ignore this for now, we will be dealing with each section one by one.

HTML5 valentines card

HTML5 valentines card, pure animated CSS

[coded by me back in 2012 to my GF]

But first, let’s get to know the commands of TCC. We start with the simple 1:1 type commands and leave the more advanced for later.

TCSS functions

  • Enter = {
  • Leave = }
  • To = to
  • From = from
  • PercentOf(Value) = Value%
  • IntOf(Value) = Value
  • ColorOf(Color) = $hex-color-value
  • inc(string) = add plain text to buffer
  • CRLF = carriage return and linefeed (#13#10, or just #13)
  • OpenParam = (
  • CloseParam = )
  • ColorPercent(Value,Color) = $hex-color-value %value,

The above functions map to simple CSS constructs. For instance, to generate a small JSON object you would write:

 .enter .inc(&amp;quot;value = 12;&amp;quot;) .leave 

And if you check the “text” property you find this:

  {
    value = 12;
  }

These types of functions are not so much time helpers as they are space helpers. It’s the other, high level functions which greatly simplify css generation.

For instance, a complex multi-colored, percentage-divided angular linear-gradient (phew!) can now be reduced to this:

.beginComplexGradient(80)
  .ColorPercent(10,clRed)
  .colorPercent(40,clBlue)
  .colorPercent(50,clWhite)
.endComplexGradient

This results in a variation of this (more compact):

background: -webkit-linear-gradient
   (
     80deg,
     #ff0000 10%,
     #0000ff 40%,
     #ffffff 50%
   );

When you start to build ever more complex shapes (this class is not yet done, not by a longshot) the generated CSS will become more and more complex, meaning that you save more and more time and headache; yet your code remain easy to read and maintain. At least once you know the basics of the class and what is going on.

Odd syntax

If you think the syntax is a bit odd then yes, we are using a fairly modern tecnique – where all functions return a reference to its “self”. This means you can call function after function in the same object without line-break.

At the same time we add data to an internal property (in this case cleverly called “text”). When all is done and ready we collect the output from Text and voila — we have our magic CSS style(s) and animations.

I will keep you all updated on this development. It will not be included in the next update of Smart Mobile Studio, because — well, it’s just a little snippet I’ve been playing with for half an hour. But it may make the next update after that, when it’s more mature and advanced.

Until then, play around with it and add more CSS wrappers (!)

Smart Mobile Studio For Business

April 8, 2015 1 comment

This is a topic which surfaces from time to time. I completely understand that people whole are new to Smart Mobile Studio, people who havent had time to get into the whole HTML5 “shift” in technology that has occurred for the past 6 years have to ask. And I am very happy to answer such questions, it’s why we made the product to begin with – to help Delphi and Lazarus developers preserve their hard-earned knowledge and use their skillset on a new and exciting platform: namely HTML5 and the cloud.

So is Smart Mobile Studio up for the challenge of business apps? Let’s have a look.

The application

Wilma, screenshot from application

Wilma, screenshot from application

While we have several customers who use and work exclusively with Smart Mobile Studio as their primary development platform, the first and most obvious example for me has to be StarSoft. StarSoft OY is a company from Finland, one of Norway’s neighbours (The Smart Company is Norwegian). One of its developers is Jarto Tarpio which I have had the good fortune of talking with on several occasions. I must admit that Jarto is not an average programmer, his insight into both Delphi and Smart Mobile Studio and the speed at which he adopted the technology demonstrates that he has a solid grasp of both native and virtual environments. So as far as case studies goes, Jarto cannot be called a complete beginner.

StarSoft has produced their latest application, simply called Wilma, written from scratch in Smart Mobile Studio. It’s an application which targets the Finnish school system, and can be downloaded from Google Store and Apple Appstore. The application has around 100.000 downloads and is a good example of what can be achieved using Smart Mobile Studio alone.

You can view the application here (Google AppStore)

Wilma

In Jarto’s own words “When it comes to feedback, we’ve got a lot of good feedback, but there are also a lot of 1-ratings from kids who hate going to school” which is to be anticipated I guess for an app that empowers teachers and parents with access to the public school system, with real-time messaging if someone has skipped class among the features 🙂

Jarto was nice enough to write a few words in response to a Facebook debate, here is a verbatim copy of his reply about Smart Mobile Studio:

“let me tell my reasons for going with SMS instead of Delphi when it came to mobile apps: I didn’t like Delphi’s slow compiler in mobile development. I also fought a long time to get Delphi to recognize my Android device to be able to debug on it – and failed. I didn’t like the way everything needed to be set up for iOS development. SMS was a lot simpler and faster. I also noticed, that app speeds were about the same, so there was no clear advantage of using Delphi compared to SMS. I also did a test of filling a TListBox with a 1k lines on Delphi and noticed that it took a lot of time on Android, so I figured that I needed to write my own listboxes no matter which tool I used. And the last reason is my goal of using the same codebase for iOS, Android and Windows Phone. We already do use 100% same source code with SMS on iOS and Android, but haven’t started to use it on Windows Phone yet. The app itself has about 100k total downloads. There have been problems with really old Android phones, but otherwise it has worked well. When it comes to feedback, we’ve got a lot of good feedback, but there are also a lot of 1-ratings from kids who hate going to school” -Source: Jarto Tarpio, StarSoft OY

Snapshots

Below are some snapshots from Google Store. As you can see Jarto and the team at StarSoft have used Smart Mobile Studio exactly as it was supposed to be used. With a rich CSS style made for the app, custom controls written and adapted for the solution and focus on code which delivers on all platforms. The application is available for both Android and iOS from the same codebase with a Microsoft Phone version in the pipeline.

Wilma, screenshot from application

Wilma, screenshot from application

Wilma, screenshot from application

Wilma, screenshot from application

Wilma, screenshot from application

Wilma, screenshot from application

QTXLibrary + CaseBook on google code

August 29, 2014 Leave a comment
Casebook, a QTXLibrary demo

Casebook, a QTXLibrary demo

Casebook is now a part of the Demo folder in QTXLibrary on google code. This makes it much easier for people to follow examples, as all they need to do is grab the repo and they pretty much have everything. The QTXLibrary for Smart Mobile Studio is a “must have” for anyone working with Smart Mobile Studio and HTML5. It adds a wealth of effects, classes and helpers that makes HTML5 app development so much easier.

QTXLibrary also wraps iScroll, the number one scrolling content library for javascript – which means you can now knock out super smooth scrolling components like the best of them.

Head on over to google code and download the source-code now:

https://code.google.com/p/qtxlibrary/

Remember to *star* the project!

Also, check out the live casebook version here:

http://lennartaasenden.magix.net/public/

Casebook updated

August 26, 2014 Leave a comment

Took the time to update CaseBook, an example mobile application written completely in Smart Mobile Studio.

It is a skeleton application, meaning that it’s just intended as a “bare bones” mobile app, demoing the basic components that ship with Smart Mobile Studio – as well as a few custom enhancements (QTXLibrary) which is freely available.

CaseBook

CaseBook

Written for IPhone 5

The mobile application was written especially for IPhone 5 (Safari webkit) but has been tested on Android (Galaxy S4) and works fine there as well. But Android is sadly not capable of the same level of animation as IOS. The application has also been tested in Chrome, which it works more or less identical to iOS – and also Safari on IPad 3 without any difficulty.

CaseBook welcome screen

CaseBook welcome screen

Future development

Casebook will be continously updated (code will be available on Google Code shortly). Next in line is in-memory database support, which will later be coupled with a live online webservice (Remobjects SDK or node.JS). A purely file-based version will also be available (where each article is represented by a separate file online).

Casebook "edit article" form

Casebook “edit article” form

Casebook source

August 24, 2014 Leave a comment
Welcome to casebook

Welcome to casebook

Updated

All code moved to the same repo. Casebook is now a part of QTXLibrary!

If you want to have a peek at the “casebook” skeleton app (http://lennartaasenden.magix.net/public/) then you can download the “R&D” source in zip format here.

Please be warned that this source will change drastically over the next weeks and that it’s undocumented. The first to be added will be a datasource so articles are truly dynamic, meaning the source of articles can either be a “live” webservice, a file stored on the server or a JSON service.

Also be sure you grab the QTX library here:  https://code.google.com/p/qtxlibrary/

A few notes

Heavy use of callback’s and delays make for a more fluent and responsive interface. You will find plenty of this, especially in the event-handlers for buttons and form navigation.

Cloud effects turn themselves off when the present-form is not the login-form, but they can complete a cycle before that happens.

Dont be afraid to mess things up