Archive

Archive for August, 2014

SMS fonts, once and for all, part 2

August 31, 2014 5 comments
Typographic measurements

Typographic measurements

In the previous article on this topic, we covered the ported “font detection” JS library which is now a part of QTXLibrary (just update your SVN repository and you have it. The class can be found in the file qtxutils.pas).

As mentioned yesterday, detecting if the browser supports a font doesn’t give us any real tangible information about which font has actually been selected by the browser for an element. A calculated style is, after all, calculated and managed by the browser – not the stylesheet. A stylesheet does nothing more than tell the browser what it would like to have, but it’s the browser that calls all the shots and will override your styles (naturally, if a font is not on your system the browser cant magically invent it) if it has to.

But as luck would have it, being able to detect if a font is installed just what we need to figure out if it’s applied to an element!

In short, here is what we do to figure out what font the browser has selected for an element:

  • Read the font-family string for the element
  • Split the string into an array, separated by “,”
  • Iterate through the array, detecting if each item is installed
  • Select the first valid font, since that’s the rule defined by WC3

We should also try to expand this in the future by adding support for:

  • Px vs. pt calculations (using body font as % basis for points)
  • Percent support (same as above, but percentage of defined document size)
  • Inherited keyword support
  • Initial keyword support

While I dont intend to deal with the latter list right now, we at least have a game plan! But.. we are also going to take this one step further and introduce something very valuable, namely content pre-calculation.

Content pre-calculation and measurement

Imagine you have to code a news-app for Android and iOS. Just like Facebook and twitter you have to display a piece of a message (or indeed, the whole message if it’s reasonable in size) in a scrollable list. Just like I do in the CaseBook demo app. It’s also a common task when working on e-books or anything which requires dynamic display of content.

Font metrics is more complex than you think

Font metrics is more complex than you think

The question of “how many pixels will my text occupy” will arise in your mind rather quickly. And like all problems dealing with content rendering (be it under Delphi or Smart Mobile), what you already know are the only factors you can use to truly pre-calculate the correct end result.

In our case we have only one fixed constant, namely the width of the display (depends only on how the user holds the mobile device), while the height factor is flexible (since we have a scrolling display). So the height is unknown and determined completely by the length of the text, the size of the font and ultimately how the browser breaks the content down within the boundaries of the width.

So once we are able to determine the font used by a HTML element and it’s size, being able to pre-calculate what a text message (or a compound message which may include images and other HTML content) looks like becomes the logical next step. And a very valuable and important one, since we are dealing with HTML and browser based app’s after all.

Well, I’m not going to hold you in suspense any longer, here is the final result (below). Again, this code is stored on Google Code, so just update your local SVN repository copy and that’s it. If you install for the first time, remember to check-out the SVN trunk under your SMS/Libraries folder – otherwise the Smart Mobile Studio IDE wont be able to find the files.

Well, here it is – the evolved version of Font Detector library (JS):

type

  TQTXFontInfo = Record
    fiName: String;
    fiSize: Integer;
    function  toString:String;
  End;

  TQTXFontDetector = Class(TObject)
  private
    FBaseFonts:     array of string;
    FtestString:    String = "mmmmmmmmmmlli";
    FtestSize:      String = '72px';
    Fh:             THandle;
    Fs:             THandle;
    FdefaultWidth:  Variant;
    FdefaultHeight: Variant;
  public
    function    Detect(aFont:String):Boolean;

    function    MeasureText(aFontInfo:TQTXFontInfo;
                aContent:String):TQTXTextMetric;overload;

    function    MeasureText(aFontInfo:TQTXFontInfo;
                aFixedWidth:Integer;
                aContent:String):TQTXTextMetric;overload;

    function    MeasureText(aFontName:String;aFontSize:Integer;
                aContent:String):TQTXTextMetric;overload;

    function    MeasureText(aFontName:String;aFontSize:Integer;
                aFixedWidth:Integer;
                aContent:String):TQTXTextMetric;overload;

    function    getFontInfo(const aHandle:THandle):TQTXFontInfo;

    Constructor Create;virtual;
  End;

//############################################################################
// TQTXFontInfo
//############################################################################

function TQTXFontInfo.toString:String;
begin
  result:=Format('%s %dpx',[fiName,fiSize]);
end;

//############################################################################
// TQTXFontDetector
//############################################################################

Constructor TQTXFontDetector.Create;
var
  x:  Integer;
begin
  inherited Create;
  FBaseFonts.add('monospace');
  FBaseFonts.add('sans-serif');
  FBaseFonts.add('serif');

  Fh:=browserApi.document.body;

  Fs:=browserApi.document.createElement("span");
  Fs.style.fontSize:=FtestSize;
  Fs.innerHTML := FtestString;
  FDefaultWidth:=TVariant.createObject;
  FDefaultHeight:=TVariant.createObject;

  if FBaseFonts.Count>0 then
  for x:=FBaseFonts.low to FBaseFonts.high do
  begin
    Fs.style.fontFamily := FbaseFonts[x];
    Fh.appendChild(Fs);
    FdefaultWidth[FbaseFonts[x]]  :=  Fs.offsetWidth;
    FdefaultHeight[FbaseFonts[x]] :=  Fs.offsetHeight;
    Fh.removeChild(Fs);
  end;
end;

function TQTXFontDetector.getFontInfo(const aHandle:THandle):TQTXFontInfo;
var
  mName:  String;
  mSize:  Integer;
  mData:  Array of string;
  x:  Integer;
Begin
  result.fiSize:=-1;
  if aHandle.valid then
  begin
    mName:=w3_getStyleAsStr(aHandle,'font-family');
    mSize:=w3_getStyleAsInt(aHandle,'font-size');

    if length(mName)>0 then
    begin
      asm
        @mData = (@mName).split(",");
      end;
      if mData.Length>0 then
      Begin
        for x:=mData.low to mData.high do
        begin
          if Detect(mData[x]) then
          begin
            result.fiName:=mData[x];
            result.fiSize:=mSize;
            break;
          end;
        end;
      end;
    end;
  end;
end;

function TQTXFontDetector.MeasureText(aFontInfo:TQTXFontInfo;
         aFixedWidth:Integer;
         aContent:String):TQTXTextMetric;
Begin
  result:=MeasureText(aFontInfo.fiName,aFontInfo.fiSize,aFixedWidth,aContent);
end;

function TQTXFontDetector.MeasureText(aFontInfo:TQTXFontInfo;
         aContent:String):TQTXTextMetric;
Begin
  result:=MeasureText(aFontInfo.fiName,aFontInfo.fiSize,aContent);
end;

function TQTXFontDetector.MeasureText(aFontName:String;aFontSize:Integer;
         aFixedWidth:Integer;
         aContent:String):TQTXTextMetric;
var
  mElement: THandle;
Begin
  if Detect(aFontName) then
  begin
    aContent:=trim(aContent);
    if length(aContent)>0 then
    begin
      mElement:=BrowserAPi.document.createElement("div");
      if (mElement) then
      begin
        mElement.style['font-family']:=aFontName;
        mElement.style['font-size']:=TInteger.toPxStr(aFontSize);
        mElement.style['overflow']:='scroll';

        mElement.style.maxWidth:=TInteger.toPxStr(aFixedWidth);
        mElement.style.width:=TInteger.toPxStr(aFixedWidth);
        mElement.style.height:='10000px';

        mElement.innerHTML := aContent;
        Fh.appendChild(mElement);

        mElement.style.width:="4px";
        mElement.style.height:="4px";

        result.tmWidth:=mElement.scrollWidth;
        result.tmHeight:=mElement.scrollHeight;
        Fh.removeChild(mElement);

      end;
    end;
  end;
end;

function TQTXFontDetector.MeasureText(aFontName:String;aFontSize:Integer;
         aContent:String):TQTXTextMetric;
var
  mElement: THandle;
Begin
  if Detect(aFontName) then
  begin
    aContent:=trim(aContent);
    if length(aContent)>0 then
    begin
      mElement:=BrowserAPi.document.createElement("div");
      if (mElement) then
      begin
        mElement.style['font-family']:=aFontName;
        mElement.style['font-size']:=TInteger.toPxStr(aFontSize);
        mElement.style['overflow']:='scroll';

        mElement.style['display']:='inline-block';
        mElement.style['white-space']:='nowrap';

        mElement.style.width:='10000px';
        mElement.style.height:='10000px';

        mElement.innerHTML := aContent;
        Fh.appendChild(mElement);

        mElement.style.width:="4px";
        mElement.style.height:="4px";

        result.tmWidth:=mElement.scrollWidth;
        result.tmHeight:=mElement.scrollHeight;
        Fh.removeChild(mElement);

      end;
    end;
  end;
end;

function TQTXFontDetector.Detect(aFont:String):Boolean;
var
  x:  Integer;
Begin
  aFont:=trim(aFont);
  if aFont.Length>0 then
  Begin
    if FBaseFonts.Count>0 then
    for x:=FBaseFonts.low to FBaseFonts.high do
    begin
      Fs.style.fontFamily:=aFont + ',' + FbaseFonts[x];
      Fh.appendChild(Fs);
      result:= (Fs.offsetWidth  <> FdefaultWidth[FBaseFonts[x]])
          and  (Fs.offsetHeight <> FdefaultHeight[FBaseFonts[x]]);
      Fh.removeChild(Fs);
      if result then
      break;
    end;
  end;
end;

Now things are much easier to work with! In fact, we can now measure the width and height of a message by simply doing this:

Procedure TForm1.Test;
var
  mObj:TQTXFontDetector;
  mInfo: TQTXFontInfo;
Begin
  mObj:=TQTXFontDetector.Create;
  mInfo:=mObj.MeasureText(mObj.getFontInfo(self.handle),
         'this is cool!<br>And this is even cooler');
  showmessage(mInfo.toString);
End;

Now we can measure any HTML segment no matter what it is, be it a compound string which contains images and text combined – even including fancy css fonts like Font Awesome. This gives us a huge advantage over those poor guys writing JavaScript by hand.

Isolating use

Like all great snippets, automating and isolating it’s use is a must. At the moment this piece of gold is safely tucked away in the QTXLibrary – but I plan to include this after more rigourous testing and, if time permits for another hotfix, support for inherited and initial keywords – which is much more complex than it sounds.

The initial and inherited CSS keywords means that we have to recursively query the parent and keep on going until we find a valid font declaration. This also means that I have to carefully sculpt the code to minimize expensive round-trips (so you dont want to put inherited font use on an element under a time critical segment of your app) and slow performance.

Hopefully I will be able to marry TQTXFontDetector with TW3Fonts (the latter name will be kept) to provide the best, easiest to use and most powerful HTML5 font manager out there. And considering that not even jQuery have functions like I have provided here now, that should not be to hard 🙂

Well, enjoy!

SMS fonts, once and for all, part 1

August 31, 2014 Leave a comment

While Smart Mobile Studio makes HTML5 app development fun and enjoyable, there are aspects of HTML5 and CSS3 which quite frankly cannot be simplified without a lot of research. This has nothing to with the top-level programming language, be it Smart Pascal or Typescript, but rather how browsers are designed, their abhorrent need for legacy support for all manner of whimsical ideas over the past 15 years – and last but not least, shortcomings of CSS and Javascript combined.

One of these topics is font management, which (at the present moment is driving me insane) you could be mistaken for being an issue of the 90’s and not 2014. But you will quickly realize that despite all the advancements of HTML5 there are still huge holes in the .. eh, whole WC3 scheme of things.

Now there used to be a time when you could, anywhere in a HTML segment, simply get away with:

<font name="verdana" size="14px">Some text</font>

But alas those days are gone (and we should be happy about that), and the free-standing font tag has been marked as obsolete for quite some time.

For Smart Pascal, how you style your components is really not the issue, but rather – getting concurrent and reliable information from the browser is. You should think that something as trivial as asking what font is used for an element would be simple and straight-forward, but sadly that is not the case.

For instance, you should imagine that you could do something like this:

var mSize = element.style.fontSize; //works
var mName = element.style.fontName; //nope

The above wont even work, not even the first. In order to read a calculated style you have to access the browser’s internal workings like this:

   mObj := BrowserAPI.Document.defaultView.getComputedStyle(tagRef, Null);
   if (mObj) then
   Result := (mObj).getPropertyValue(aStyleName);

But all this aside. The point of this article is to make more sense of fonts under HTML5. As the designer of SMS up to version 1.0 I know perfectly well how much work has gone into this, and also how many aspects of SMS that still needs a polish. And font management is absolutely one of them. Not because we did anything wrong, but because coding clever font detection and/or analysis requires more work than anyone could be expected to anticipate. Smart gives you a huge advantage over raw HTML5 authoring in plain JavaScript – but it can be done better and more accurate. Which is what I am going to do now once and for all (insert sound of trumpet’s going off in the distance here).

Before we dive into some code, here are a few things you can do to alleviate the problem straight away.

Clean up the default stylesheet

At the very top of the stylesheet for your app, is a strange segment which looks like this:

* {
  //content here
}

Just like under SQL, the “*” (star) character means “all”. So whatever you define within those curly-brackets’s is automatically applied to ALL elements (read: all controls) in your app.
So this is the perfect place to clearly define the font-name you want to use in your app, and it’s size.

So if you do like this:

* {
  font-family: "Segoe UI", "Helvetica neue";
  font-color: Transparent;
}

Then the default font for the document and all the elements created by Smart will be (and this is important) either “Segoe UI” (Windows 7 and 8) or, if that is not available, “Helvetica neue” (which is the font for iPhone and iPad). You may want to add Helvetica to that list, just in case your app is running under android.

The next step is to completely remove all “font-family” declarations besides that one, from the entire CSS file. This will ensure that unless you have hard-coded a font into your app, you will be able to control font handling from your stylesheet completely. Which is very handy!

Getting the name and size of a font

Let me explain the problem clearly. When you define what font should be used, there is no guarantee that this font is indeed installed on the system running you app. This is why the CSS “font-family” accepts more than one font name. And the rule is, that if “Segoe UI” (first in list) is not installed, then the browser tries to use the next one – and so fourth.

The problem? Whenever you try to read-back what font was actually selected, well.. there are no such function (!) The only thing you can do is to read back the whole “font-family” string which contains exactly what you already know. I was hoping the computed style contained just the applied font and not the whole list, but no such luck.

So what is the solution? Well, there are JS libraries for checking if a font is installed on the system – and the only thing you can actually do about this, is to iterate through the font-family string and use the first one which is valid.

One such solution is FontUnStack, which sadly has a dependency on jQuery. Which is not something I want to ship with Smart Mobile Studio to be perfectly frank. A second solution is Font Detector which, since it has no dependencies, is the one I have re-implemented in Smart Pascal and which we will be using.

But we are still not out of the woods, because while detecting if a font is installed is all nice and dandy, we still have to make a little snippet which uses the font detector to compare against the font-family list we know. Then, and only then, will we have fixed the problem of being able to determine what font is used for an element (phew!).

But let us begin with the font detection class, which turned out very small (based on the 0.3 branch):

type

  TQTXFontDetector = Class(TObject)
  private
    FBaseFonts:     array of string;
    FtestString:    String = "mmmmmmmmmmlli";
    FtestSize:      String = '72px';
    Fh:             THandle;
    Fs:             THandle;
    FdefaultWidth:  Variant;
    FdefaultHeight: Variant;
  public
    function    Detect(aFont:String):Boolean;
    Constructor Create;virtual;
  End;

//############################################################################
// TQTXFontDetector
//############################################################################

Constructor TQTXFontDetector.Create;
var
  x:  Integer;
begin
  inherited Create;
  FBaseFonts.add('monospace');
  FBaseFonts.add('sans-serif');
  FBaseFonts.add('serif');

  Fh:=browserApi.Document.getElementsByTagName("body")[0];

  Fs:=browserApi.document.createElement("span");
  Fs.style.fontSize:=FtestSize;
  Fs.innerHTML := FtestString;
  FDefaultWidth:=TVariant.createObject;
  FDefaultHeight:=TVariant.createObject;

  if FBaseFonts.Count>0 then
  for x:=FBaseFonts.low to FBaseFonts.high do
  begin
    Fs.style.fontFamily := FbaseFonts[x];
    Fh.appendChild(Fs);
    FdefaultWidth[FbaseFonts[x]]  :=  Fs.offsetWidth;
    FdefaultHeight[FbaseFonts[x]] :=  Fs.offsetHeight;
    Fh.removeChild(Fs);
  end;
end;

function TQTXFontDetector.Detect(aFont:String):Boolean;
var
  x:  Integer;
Begin
  aFont:=trim(aFont);
  if aFont.Length>0 then
  Begin
    if FBaseFonts.Count>0 then
    for x:=FBaseFonts.low to FBaseFonts.high do
    begin
      Fs.style.fontFamily:=aFont + ',' + FbaseFonts[x];
      Fh.appendChild(Fs);
      result:= (Fs.offsetWidth  <> FdefaultWidth[FBaseFonts[x]])
          and  (Fs.offsetHeight <> FdefaultHeight[FBaseFonts[x]]);
      Fh.removeChild(Fs);
      if result then
      break;
    end;
  end;
end;

You can try this out yourself, and you will find that it absolutely works and easily detects if a font is installed on your system. So great, that’s the first part of the equation finished.

More to follow soon..

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/

Creating a modal dialog in Smart Mobile Studio

August 28, 2014 2 comments

Just a quick tip for all you SMS hackers out there. Have you ever wanted to create a modal dialog for your SMS applications? Well, the bad news is that JavaScript doesnt know a modal form if it’s life depended on it — but the good news is, that you can force the user to take notice, creating the same desired affect.

The way to make the user take notice is, naturally, to make it impossible for him/her to continue doing anything until your dialog is released. Now there are many ways to do this, from the extremely anoying to the almost pointless. In our case we will be simple and to the point (which I think most users favour) and simply create a block-box.

The block-box approach

What is a block-box? Well, it’s actually just something that is semi-transparent and covers the whole display — blocking all user input from ever reaching underlying components. Clever right?

You will find the class TW3BlockBox defined in W3Application.pas and all it does is to fill the display completely with a black, semi-transparent panel. If you have ever user TApplication.ShowDialog then you know what it looks like.
Creating a block box is as simple as:

  FBlocker := TW3BlockBox.Create(Application.Display);
  FBlocker.SetBounds(0,0,Application.Display.Width,Application.Display.Height);
  FBlocker.BringToFront;

And voila, you have effectively blocked the entire display from getting any user-input events (so make sure your panel or dialog release the FBlocker instance, or you will never get out again.

When you create your dialog, remember to use FBlocker as parent (!)

Also, if you are worrying about “oh what if i forget to release it? What about memory”, well that’s not a problem. With the block-box in place the user cannot do anything until you release it (unless you have a timer that kicks in and does something fantastically illegal) – and secondly, releasing your instance is very simple too.

I suggest you do like me, and isolate your dialog in a class. Create the block-box inside your constructor. Then have a “ok” button (or whatever you want the user to do) and in that button you just go:

Procedure TMyDialog.W3Button2Click(Sender:TObject)
Begin
  if assigned(FOnDialogDone) then
  FOnDialogDone(self,fmOk);

  w3_callback( procedure ()
  begin
  self.free;
  end, 100);
end;

This will call back to the main app with your results, and gracefully release the dialog 100ms later. W3_Callback is important because it gives the browser time to update stuff, and since we now write async code – that is a very important factor. How you sculpt the dialog and/or events is up to you. Also, javascript is managed, so there is no such thing as memory loss (in theory) unless you create 1 million nested divs and just keep on going 🙂

Built in modal functions

The RTL contains functionality for dealing with modal dialogs in a more uniform way. If you head over to Primoz’s website you will find a full tutorial on using that functionality: http://www.smartprogrammer.org/2013/04/new-in-smart-11-modal-dialogs.html 

Enjoy!

Getting a handle on things

August 28, 2014 Leave a comment

This is a nice addition to QTXLibrary, and perhaps it will one day find it’s way into the Smart RTL.

Under native languages, a Handle is just a longword number. It is usually a reference number you get when you call a DLL driver (like a parking slip, to remember where you parked), a graphics library or the operative system (e.g “window handle”, “graphics context handle” and various other handles). In some rare cases a handle is the numeric version of a pointer – but hopefully everyone has left that practice back in the late 90’s early 2k’s.

Smart Mobile Studio Handles

Under smart mobile studio a handle is a reference, in most cases it’s a reference handle to either a HTML element (usually a DIV element, which is the basis for 90% of all SMS controls), a timer (returned by setTimeOut() in JavaScript) or a graphics context. The latter two are hardly ever accessed directly – except by advanced programmers who want to link to external libraries.

So, what does all of these handles have in common? Well, they all have to be tested before used, that’s one thing they have in common. And also, even if the handle is valid – there is no guarantee that they are part of the visible DOM. That’s another thing they all have in common.

A graphics context can be created “off screen”, so it’s not connected to a HTML element and thus invisible. A DIV element (which is wrapped by TW3CustomControl) can likewise be created “off screen” by providing NIL as a parameter to the constructor. This means that it’s valid and ready to be used — but no visible output can be measured from it.

Another thing handles have in common, is that we tend to do stuff with them when they become valid. For instance, it’s typical to attach events which executes whenever the document has loaded — and also whenever a HTML elements has been created and attached to the DOM.

When you sum up the chores for handles, what we get is a very small but effective handle-helper which will make tedious tests trivial. And it looks like this:


type

  TQTXHandleHelper = helper for THandle
  public
    function  Valid:Boolean;
    function  Ready:Boolean;
    procedure ReadyExecute(OnReady:TProcedureRef);

    Function  Defined:Boolean;
    function  Equals(const aHandle:THandle):Boolean;
    function  Parent:THandle;
    function  Root:THandle;
  end;

//############################################################################
// TQTXHandleHelper
//############################################################################

function TQTXHandleHelper.Root:THandle;
var
  mAncestor:  THandle;
Begin
  if self.valid then
  Begin
    while (mAncestor.parentNode) do
    result:=mAncestor.parentNode;
  end else
  result:=undefined;
end;

Function TQTXHandleHelper.Defined:Boolean;
Begin
  asm
    @result = !(self == undefined);
  end;
end;

function TQTXHandleHelper.Valid:Boolean;
Begin
  asm
    @Result = !( (@self == undefined) || (@self == null) );
  end;
end;

function TQTXHandleHelper.Parent:THandle;
Begin
  if self.valid then
  result:=self.parentNode else
  result:=undefined;
end;

function TQTXHandleHelper.Ready:Boolean;
Begin
  result:=self.Valid and TQTXTools.getElementInDOM(self);
end;

function TQTXHandleHelper.Equals(const aHandle:THandle):Boolean;
Begin
  asm
    @result = (@self == @aHandle);
  end;
end;

procedure TQTXHandleHelper.ReadyExecute(OnReady:TProcedureRef);
Begin
  if Valid then
  begin
    if assigned(OnReady) then
    Begin
      (* Element already in DOM? Execute now *)
      if Ready then
      OnReady() else

      (* Try again in 100ms *)
      w3_callback(
        procedure ()
        begin
          self.ReadyExecute(OnReady);
        end,100);
    end;
  end;
end;

Valid

The valid function checks if the handle is valid. Under JavaScript that means not just to check for “null” – but also to check for “unassigned”.

Ready

This is a very important check. It tests if the handle has been injected into the DOM and that it can actually be operated on. You must always check the readiness of a handle before you attempt to alter an element.

ReadyExecute

This is typically used in constructors, where you can attach an anonymous procedure to execute whenever the handle becomes ready. Excellent procedure to use for components that needs a “nudge” to trigger an internal resize to layout the child elements.

Defined

Check that the handle is not undefined (unassigned).

Equals

Compares the present handle with another handle, returns true if the handle is identical.

Parent

Returns the parent (owner) of the element a handle represents. For elements living on a form, the form handle is returned since that is the parent of the child elements.

Root

Returns the topmost root of an element. For elements not yet injected into the DOM this will be NIL, for elements injected this will either be document or the host container for the smart app.

Using the helper

The helper is extremely easy to use. Once you include the QTXUtils.pas unit, all handles will support the helper. And now you can write more clearly accessible code as such:

if  ObjectReady //constructor complete? 
and Handle.Ready then //object injected into DOM?
Begin
  self.fxZoomIn(0.3); //Use CSS3 effect
end;

Enjoy!

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

Instant CSS3 smoothness

August 26, 2014 Leave a comment

Here is a fun little one-liner which forces the browser to turn all horizontal movement of a component into a CSS3 transformation. Simply add this one-liner to your component’s InitializeObject() method and enjoy:

procedure TMyControl.InitializeObject;
begin
  inherited;
  w3_setStyle(Handle,w3_CSSPrefix('Transition'), 'left .12s');
end;

What it does is to tell the browser, that all and any changes to the “left” property should be delayed and transitioned to 0.12 seconds. This means that if you do a simple:

FMyControl.left:=100;

Your control (or any control, you can apply this to any TW3MovableObject descendant) will slide from it’s current position – to it’s new position at 100px, within a timeframe of 0.12 seconds. So it will smoothly glide over the screen as opposed to be moved instantly.

Try it out! Make a new visual project, drop a button on the form – and apply the one-liner using the button-handle (W3Button1.handle for instance). Now add an onClick event handler to the button with a self.moveTo(100,100); and see what happens 🙂

QTX updated + casebook

August 26, 2014 Leave a comment

As promised in the previous article, I have replaced the standard TW3HeaderControl with the new one we made yesterday. To make it easier to spot the effects on the title, i have turned it’s background red.

Head over to http://lennartaasenden.magix.net/public/ and have a peek at the new header

A better IPhone Header for Smart Mobile Studio

August 25, 2014 Leave a comment
Welcome to casebook

Welcome to casebook

With the introduction of the QTX effects units, we can start to make some interesting components that feel more responsive and alive (for the lack of a better word). If the world of HTML5 apps is about anything – it’s flamboyant and exciting user interfaces which not only rival, but surpass the classical components of native languages.

Since writing your own components is often regarded as a black art, something you really dont want to do which is time consuming and boring – I have decided to show just how little it takes to make a completely new IOS header component for Smart Mobile Studio. The goals for this new header is simple, namely to mimic some of the native effects introduced in iOS 5.x – which in no particular order are:

  • When either back or next buttons are set to invisible, they slide out of view
  • When a button slides out, the caption follows suit – taking up the newly available space
  • When you alter the title of the header, the text fades out before setting the new text, then fades back in
  • The title font is not longer weight-bold, but plain and shaded

Sounds very complicated right? Well, under Delphi or C++ builder it would have required more than a fair share of black magic, but under Smart Mobile Studio – which were built for this very purpose, it’s a snap.

Isolating the parts

First, let’s look at what an IOS header consists of. It’s actually a very simple piece of engineering, with only two possible buttons visible at once, a gradient background (and buttons styled to match) and a caption. Depending on your IOS version the title is either centered or left oriented. We are going to opt for a centered caption.

So, what we end up with are just 3 custom controls. That’s all it’s going to take to make our new and exciting IOS header, which will look oh-so-clever in combination with sliding forms whizzing about the place. So let’s get to work.

First, we isolate the button classes:

  TQTXBackButton = Class(TW3CustomControl)
  private
    FOnVisible: TQTXButtonVisibleEvent;
  protected
    procedure setVisible(const aValue:Boolean);reintroduce;
  public
    property  OnVisibleChange:TQTXButtonVisibleEvent
              read FOnVisible write FOnVisible;
  published
    Property  Visible:Boolean read getVisible write setVisible;
  End;

  TQTXNextButton = Class(TW3CustomControl)
  protected
    procedure setVisible(const aValue:Boolean);reintroduce;
  End;

Since we want our buttons to be more responsive when they either appear or go-away, we target the visible property on our button components. We also need to inform our toolbar that a button has appeared or disappeared, so we add an event to the mix – simply called OnVisibleChange. This way, we can create a response which deals with re-positioning the title whenever a button is moving.

Next, we need the actual header control. This will be extremely simple since all it’s gonna do is to house 3 child components (two buttons and a label). So that looks like this:

  TQTXHeaderBar = Class(TW3CustomControl)
  private
    FBackButton:  TQTXBackButton;
    FNextButton:  TQTXNextButton;
    FCaption:     TQTXHeaderTitle;
    Procedure HandleBackButtonVisibleChange(sender:TObject;aVisible:Boolean);
  protected
    Procedure Resize;override;
    Procedure InitializeObject;override;
    Procedure FinalizeObject;Override;
  public
    Property  Title:TQTXHeaderTitle read FCaption;
    Property  BackButton:TQTXBackButton read FBackButton;
    property  NextButton:TQTXNextButton read FNextButton;
  End;

Using the force

With the classes clearly defined, we can start to add some meat to our spanking new component. But first, let’s talk a bit about timing.

When you change the title of what is ultimately a label, you want it execute the change in sequence. First, you want the label to quickly fade out using a smooth effect, then you want the actual text to be altered (invisible at this point), until we quickly fade the label back into view.

This probably sounds very complicated, but it’s really the most simple thing to deal with in this task. In fact, the entire label class is no larger than this:

Procedure TQTXHeaderTitle.SetInheritedCaption(const aValue:String);
Begin
  inherited setCaption(aValue);
end;

Procedure TQTXHeaderTitle.setCaption(const aValue:String);
begin
  if  ObjectReady
  and TQTXTools.getElementInDOM(Handle) then
  Begin
    self.fxZoomOut(0.3,
      procedure ()
      Begin
        setInheritedCaption(aValue);
        self.fxZoomIn(0.3);
      end);
  end else
  inherited setCaption(aValue);
end;

You may be wondering why there is a function called “setInheritedCaption” defined. To make a long story short, you can only call inherited methods inside a normal procedure, hence we cant call “inherited setCaption()” to change the caption inside a callback procedure. The only way to solve this is to isolate it outside the setCaption() method.

Now in the setCaption() method we first check if the object is ready to be used, that the handle has become a part of the DOM (document object model). If we omit this, you stand at risk of triggering an effect before the DOM is loaded – and your app wont work. You notice that if things are not ready, we call the ancestor directly, because the text being applied is set from the constructor – so no effect can be applied.

If everything is ready and the component is visible, then we use the QTX effects to zoom the label out in 0.3 seconds. We also provide a callback event (anonymous procedure) which is executed when the effect is done. Here we change the text via setInheritedCaption() and fade the component back in. Easy as apple pie.

Next is the button(s). Like mentioned we wanted an event to let us know whenever a button has it’s visible property changed. The event handler for that presently looks like this:

 

Procedure TQTXHeaderBar.HandleBackButtonVisibleChange
          (sender:TObject;aVisible:Boolean);
Begin
  case aVisible of
  false:
    Begin
      FCaption.fxMoveTo(2, (clientHeight div 2) - (FCaption.height div 2), 0.3);
    end;
  true:
    Begin
      FCaption.fxMoveTo((clientwidth div 2) - (FCaption.width div 2),
        (clientHeight div 2) - (FCaption.height div 2), 0.3);
    end;
  end;
end;

So, whenever you alter the visibility of the back-button (button on the left of the header), we either slide the header-label center, or backwards, to occupy the space BackButton used to have.

And lest we forget, here is the code for the actual header. All it does at the moment is to house the other controls, so it’s not that advanced. But it’s a lot more responsive than the default IOS header which ship with Smart Mobile Studio.

Procedure TQTXHeaderBar.InitializeObject;
Begin
  inherited;
  FBackButton:=TQTXBackButton.Create(self);
  FBackbutton.InnerHTML:='&amp;lt;b&amp;gt;Back&amp;lt;/b&amp;gt;';
  FBackbutton.Background.FromColor(clRed);
  FBackButton.OnVisibleChange:=HandleBackButtonVisibleChange;

  FNextButton:=TQTXNextButton.Create(self);

  FCaption:=TQTXHeaderTitle.Create(self);
  FCaption.Background.FromColor(clRed);
end;

Procedure TQTXHeaderBar.FinalizeObject;
Begin
  FBackbutton.free;
  FNextButton.free;
  FCaption.free;
  inherited;
end;

Procedure TQTXHeaderBar.HandleBackButtonVisibleChange
          (sender:TObject;aVisible:Boolean);
Begin
  case aVisible of
  false:
    Begin
      FCaption.fxMoveTo(2, (clientHeight div 2) - (FCaption.height div 2), 0.3);
    end;
  true:
    Begin
      FCaption.fxMoveTo((clientwidth div 2) - (FCaption.width div 2),
        (clientHeight div 2) - (FCaption.height div 2), 0.3);
    end;
  end;
end;

Procedure TQTXHeaderBar.Resize;
Begin
  inherited;
  FBackbutton.setbounds(2,2,100,24);
  FNextButton.setBounds((clientwidth-2)-100,2,100,24);

  if FBackbutton.Visible then
  FCaption.MoveTo((clientwidth div 2) - (FCaption.width div 2),
    (clientHeight div 2) - (FCaption.height div 2)) else
  Begin
    FCaption.moveto(2,
    (clientHeight div 2) - (FCaption.height div 2) );
  end;
end;

Adding some bling

Before we start working on the NextButton (right on the header), let’s have a peek at what we got so far, and let’s add some dummy events to the mix to make sure the effects are working. First, add the following to InitializeComponent:

  FCaption.OnClick:=Procedure (sender:TObject)
    Begin
      FBackButton.Visible:=not FBackbutton.Visible;
    end;

So whenever you click on the title-label, this code will toggle the visibility of our back-button. Next, let’s create an instance of our header and add another event, just for fun. So in the initializeObject on your form, create the IOS header like this:

  mTemp:=TQTXHeaderBar.Create(display);
  mTemp.height:=40;
  mTemp.BackButton.OnClick:=Procedure (sender:TObject)
    Begin
      mTemp.Title.Caption:='Testing changes';
      w3_callback( procedure ()
        Begin
          mTemp.title.caption:='And this is cool stuff';
        end,
        1000);
    end;

This means that whenever we click the back-button, we alter the caption twice after each other. Now let’s have a peek at what it looks like.

Not much to look at

Not much to look at

Meeeh.. let’s try that again, and this time with the CSS used by TW3HeaderControl

That's better

That’s better

Final touches

As you probably anticipate already, the Next-Button is more or less a clone of what we did with the first, so I wont cover that. The difference is that the label wont move so much – as resize itself to the new available size.

What we must do however, is to change the ancestor for the button classes to TW3ToolButton (as opposed to TW3CustomControl) – and basically, that’s it! Less than 100 lines of code and you have a fully effect driven clone of the IOS form header.

Now when we navigate our forms the GUI feels so much more alive and responsive. It’s just an optical effect really, but it gives that special “feel” to it which we recognize from native, Objective C components under IOS.

After thought

This example of writing components was extremely simple, but still the topics involved, like using GPU powered CSS effects is right up there with the best of code. Under native Delphi or C++ we would have to code even that, so naturally the amount of source-code we have to write under those languages would be much, much larger.

But that is also the point, namely to point out how simple, elegant and even enjoying it is to write your own HTML5 components. I know it sounds like i’m just basking in my own ideas, but Smart Mobile Studio is the only product which made me feel like I was 15 years old again, hacking away on my Amiga. Nothing is nicer than waking up, grabbing a fresh pot of coffey and sit down with SMS to code something you love working on.

As always, remember to download the Quartex library (which is needed for the effects), this can be found here: https://code.google.com/p/qtxlibrary/ (and check it out into the SMS/Libraries folder).

Here is the full source so far (the finished version will be on QTX tomorrow):

unit qtxheader;

//#############################################################################
//
//  Unit:       qtxheader.pas
//  Author:     Jon Lennart Aasenden [Cipher Diaz of Quartex]
//  Company:    Jon Lennart Aasenden LTD
//  Copyright:  Copyright Jon Lennart Aasenden, all rights reserved
//
//  About:      This unit introduces a replacement for TW3HeaderControl.
//              It uses CSS3 animation effects to slide and fade header
//              elements out of view, which makes for a more responsive
//              and living UI experience.
//
//
//  _______           _______  _______ _________ _______
// (  ___  )|\     /|(  ___  )(  ____ )\__   __/(  ____ \|\     /|
// | (   ) || )   ( || (   ) || (    )|   ) (   | (    \/( \   / )
// | |   | || |   | || (___) || (____)|   | |   | (__     \ (_) /
// | |   | || |   | ||  ___  ||     __)   | |   |  __)     ) _ (
// | | /\| || |   | || (   ) || (\ (      | |   | (       / ( ) \
// | (_\ \ || (___) || )   ( || ) \ \__   | |   | (____/\( /   \ )
// (____\/_)(_______)|/     \||/   \__/   )_(   (_______/|/     \|
//
//
//
//#############################################################################


interface

uses 
  W3System, w3components, w3graphics, w3ToolButton, w3borders,
  qtxutils,
  qtxeffects,
  qtxlabel;

{.$DEFINE USE_ANIMFRAME_SYNC}

const
CNT_ANIM_DELAY  = 0.22;

type

  TQTXButtonVisibleEvent = Procedure (sender:TObject;aVisible:Boolean);

  (* Isolate commonalities for Back/Next buttons in ancestor class *)
  TQTXHeaderButton = Class(TW3ToolButton)
  private
    FOnVisible: TQTXButtonVisibleEvent;
  public
    property  OnVisibleChange:TQTXButtonVisibleEvent
              read FOnVisible write FOnVisible;
  Protected
    Procedure setInheritedVisible(const aValue:Boolean);
  End;

  (* Back-button, slides to the left out of view *)
  TQTXBackButton = Class(TQTXHeaderButton)
  protected
    procedure setVisible(const aValue:Boolean);reintroduce;
  published
    Property  Visible:Boolean read getVisible write setVisible;
  End;

  (* Next-button, slides to the right out of view *)
  TQTXNextButton = Class(TQTXHeaderButton)
  protected
    procedure setVisible(const aValue:Boolean);reintroduce;
  published
    Property  Visible:Boolean read getVisible write setVisible;
  End;

  (* Header title label, uses fx to change text *)
  TQTXHeaderTitle = Class(TQTXLabel)
  private
    Procedure SetInheritedCaption(const aValue:String);
  protected
    procedure setCaption(const aValue:String);override;
  End;

  (* Header control, dynamically resizes and positions caption and
     button based on visibility. Otherwise identical to TW3HeaderControl *)
  TQTXHeaderBar = Class(TW3CustomControl)
  private
    FBackButton:  TQTXBackButton;
    FNextButton:  TQTXNextButton;
    FCaption:     TQTXHeaderTitle;
    FMargin:  Integer = 4;
    FFader:   Boolean = false;
    Procedure HandleBackButtonVisibleChange(sender:TObject;aVisible:Boolean);
    Procedure HandleNextButtonVisibleChange(sender:TObject;aVisible:Boolean);
  protected
    Procedure setMargin(const aValue:Integer);
    Procedure Resize;override;
    Procedure InitializeObject;override;
    Procedure FinalizeObject;Override;
  public
    Property  FadeTitle:Boolean read FFader write FFader;
    Property  Margin:Integer read FMargin write setMargin;
    Property  Title:TQTXHeaderTitle read FCaption;
    Property  BackButton:TQTXBackButton read FBackButton;
    property  NextButton:TQTXNextButton read FNextButton;
  End;

implementation

//#############################################################################
// TQTXHeaderButton
//#############################################################################

(* This method simply exposes access to the inherited version of
   setVisible. Since inherited method cannot be called from
   anonymous event-handlers, we expose it here. *)
Procedure TQTXHeaderButton.setInheritedVisible(const aValue:Boolean);
Begin
  inherited setVisible(aValue);
end;

//#############################################################################
// TQTXBackButton
//#############################################################################

procedure TQTXBackButton.setVisible(const aValue:Boolean);
var
  mParent:  TQTXHeaderBar;
  dx: Integer;
Begin
  (* Make sure object is ready and that the
     button is injected into the DOM *)
  if  ObjectReady
  and Handle.Ready
  and TQTXTools.getDocumentReady then
  Begin
    (* Make sure parent is valid *)
    if Parent<>NIL then
    Begin
      (* get parent by ref *)
      mParent:=TQTXHeaderBar(Parent);

      if aValue<>getVisible then
      begin

        case aValue of
        false:
          Begin
            if mParent.ObjectReady
            and mParent.Handle.Ready then
            Begin

              dx:=-Width;

              {$IFDEF USE_ANIMFRAME_SYNC}
              w3_requestAnimationFrame( procedure ()
              begin
              {$ENDIF}
                self.fxMoveTo(dx,top,CNT_ANIM_DELAY,
                procedure ()
                begin
                  setInheritedVisible(false);
                end);
              {$IFDEF USE_ANIMFRAME_SYNC}
              end);
              {$ENDIF}

            end else
            setInheritedVisible(false);
          end;
        True:
          Begin
            setInheritedVisible(true);
            self.MoveTo(-Width,
             (mParent.ClientHeight div 2) - self.height div 2);

            if mParent.ObjectReady
            and mParent.Handle.Ready then
            {$IFDEF USE_ANIMFRAME_SYNC}
            w3_requestAnimationFrame( procedure ()
            {$ENDIF}
            Begin
              self.fxMoveTo(mParent.margin,
              (mParent.ClientHeight div 2) - self.height div 2,CNT_ANIM_DELAY);
            {$IFDEF USE_ANIMFRAME_SYNC}
            end);
            {$ELSE}
            end;
            {$ENDIF}
          end;
        end;

        if assigned(OnVisibleChange)
        and mParent.Handle.Ready then
        OnVisibleChange(self,aValue);
      end;
    end;
  end else
  inherited setVisible(aValue);
end;

//#############################################################################
// TQTXNextButton
//#############################################################################

procedure TQTXNextButton.setVisible(const aValue:Boolean);
var
  dy: Integer;
  dx: Integer;
  mParent:  TQTXHeaderBar;
Begin
  (* Make sure element is ready and inserted into the DOM *)
  if  ObjectReady
  and TQTXTools.getDocumentReady
  and Handle.Ready then
  Begin
    (* make sure parent is valid *)
    if parent<>NIL then
    begin
      (* Make sure this represents a change in state *)
      if aValue<>getVisible then
      Begin
        (* cast parent to local variable *)
        mParent:=TQTXHeaderBar(Parent);

        case aValue of
        false:
          begin
            (* move button out to the right *)
            dy:=top;
            dx:=mParent.Width;

            {$IFDEF USE_ANIMFRAME_SYNC}
            w3_requestAnimationFrame( procedure ()
            begin
            {$ENDIF}
              self.fxMoveTo(dx,dy,CNT_ANIM_DELAY,
              procedure ()
              begin
                setInheritedVisible(false);
              end);
            {$IFDEF USE_ANIMFRAME_SYNC}
            end);
            {$ENDIF}
          end;

        true:
          begin
            (* move button in to the left *)
            setInheritedVisible(true);
            dy:=top;
            dx:=(mParent.ClientWidth - mparent.margin) - self.Width;

            {$IFDEF USE_ANIMFRAME_SYNC}
            w3_requestAnimationFrame( procedure ()
            begin
            {$ENDIF}
              self.fxMoveTo(dx,dy,CNT_ANIM_DELAY);
            {$IFDEF USE_ANIMFRAME_SYNC}
            end);
            {$ENDIF}
          end;
        end;

        if assigned(OnVisibleChange) then
        OnVisibleChange(self,aValue);
      end;
    end;
  end else
  inherited setVisible(aValue);
end;

//#############################################################################
// TQTXHeaderTitle
//#############################################################################

Procedure TQTXHeaderTitle.SetInheritedCaption(const aValue:String);
Begin
  inherited setCaption(aValue);
end;

Procedure TQTXHeaderTitle.setCaption(const aValue:String);
begin
  (* Make sure we can do this *)
  if  ObjectReady
  and TQTXTools.getDocumentReady
  and Handle.Ready then
  Begin
    (* Check valid parent *)
    if Parent<>NIL then
    Begin
      (* Use fading at all? *)
      if TQTXHeaderBar(Parent).FadeTitle then
      Begin
        {$IFDEF USE_ANIMFRAME_SYNC}
        w3_requestAnimationFrame( procedure ()
        begin
        {$ENDIF}
          self.fxFadeOut(CNT_ANIM_DELAY,
            procedure ()
            Begin
              setInheritedCaption(aValue);
              self.fxFadeIn(CNT_ANIM_DELAY);
            end);
        {$IFDEF USE_ANIMFRAME_SYNC}
        end);
        {$ENDIF}
      end else
      setInheritedCaption(aValue);
    end else
    inherited setCaption(aValue);
  end else
  inherited setCaption(aValue);
end;

//#############################################################################
// TQTXHeaderBar
//#############################################################################

Procedure TQTXHeaderBar.InitializeObject;
Begin
  inherited;

  StyleClass:='TW3HeaderControl';

  FBackButton:=TQTXBackButton.Create(self);
  FBackButton.setInheritedVisible(false);
  FBackbutton.styleClass:='TW3ToolButton';
  FBackbutton.Caption:='Back';
  FBackbutton.Height:=28;

  FNextButton:=TQTXNextButton.Create(self);
  FNextButton.setInheritedVisible(false);
  FNextButton.styleClass:='TW3ToolButton';
  FNextButton.Caption:='Next';
  FNextButton.height:=28;

  FCaption:=TQTXHeaderTitle.Create(self);
  FCaption.Autosize:=False;
  FCaption.Caption:='Welcome';
  //FCaption.handle.style['border']:='1px solid #444444';
  //FCaption.handle.style['background-color']:='rgba(255,255,255,0.3)';

  (* hook up events when element is injected in the DOM *)
  TQTXTools.ExecuteOnElementReady(Handle, procedure ()
    Begin
      (* Use update mechanism, which forces an internal
         resize when sized flag is set *)
      beginUpdate;
      try
        FBackButton.OnVisibleChange:=HandleBackButtonVisibleChange;
        FNextButton.OnVisibleChange:=HandleNextButtonVisibleChange;
        setWasMoved;
        setWasSized;
      finally
        EndUpdate;
      end;
    end);
end;

Procedure TQTXHeaderBar.FinalizeObject;
Begin
  FBackbutton.free;
  FNextButton.free;
  FCaption.free;
  inherited;
end;

Procedure TQTXHeaderBar.setMargin(const aValue:Integer);
Begin
  if aValue<>FMargin then
  begin
    (* If the element is not ready, try again
       in 100 ms *)
    if  ObjectReady
    and TQTXTools.getDocumentReady
    and Handle.Ready then
    Begin
      BeginUpdate;
      FMargin:=TInteger.EnsureRange(aValue,1,MAX_INT);
      setWasSized;
      endUpdate;
    end else
    w3_callback(procedure ()
      begin
        setMargin(aValue);
      end,100);
  end;
end;

Procedure TQTXHeaderBar.HandleNextButtonVisibleChange
          (sender:TObject;aVisible:Boolean);
var
  wd,dx:  Integer;
Begin
  case aVisible of
  false:
    begin
      wd:=clientwidth;
      dec(wd,FMargin);
      if FBackButton.Visible then
      dec(wd,FBackButton.width + FMargin);

      dx:=FMargin;
      if FBackButton.visible then
      inc(dx,FBackButton.Width + FMargin);

      if ObjectReady
      and Handle.Ready then
      Begin
        wd:=wd - FMargin;

        {$IFDEF USE_ANIMFRAME_SYNC}
        w3_requestAnimationFrame( procedure ()
        begin
        {$ENDIF}
          FCaption.fxScaleTo(dx,
            (clientHeight div 2) - FCaption.Height div 2,
            wd,
            FCaption.height,
            CNT_ANIM_DELAY,
            NIL);
        {$IFDEF USE_ANIMFRAME_SYNC}
        end);
        {$ENDIF}
      end;

    end;
  true:
    Begin

        dx:=FMargin;
        if FBackButton.visible then
        inc(dx,FBackButton.Width + FMargin);

        wd:=ClientWidth - (2 * FMargin);
        if FBackButton.Visible then
        dec(wd,FBackButton.width);
        dec(wd,FNextButton.Width);

        dec(wd,FMargin * 2);

        {$IFDEF USE_ANIMFRAME_SYNC}
        w3_requestAnimationFrame( procedure ()
        begin
        {$ENDIF}
          FCaption.fxSizeTo(wd,FCaption.Height,CNT_ANIM_DELAY,
          procedure ()
          Begin
            FCaption.fxMoveTo(dx,
            (clientHeight div 2) - FCaption.Height div 2, CNT_ANIM_DELAY);
          end);
        {$IFDEF USE_ANIMFRAME_SYNC}
        end);
        {$ENDIF}

    end;
  end;
  Resize;
end;

Procedure TQTXHeaderBar.HandleBackButtonVisibleChange
          (sender:TObject;aVisible:Boolean);
var
  dx: Integer;
  wd: Integer;
Begin

  case aVisible of
  false:
    begin
      {$IFDEF USE_ANIMFRAME_SYNC}
      w3_requestAnimationFrame( procedure ()
      begin
      {$ENDIF}
        FBackButton.fxMoveTo(-FBackButton.width,
          (clientheight div 2) - FBackButton.height div 2,
          CNT_ANIM_DELAY);
      {$IFDEF USE_ANIMFRAME_SYNC}
      end);
      {$ENDIF}
    end;
  true:
    Begin
      {$IFDEF USE_ANIMFRAME_SYNC}
      w3_requestAnimationFrame( procedure ()
      begin
      {$ENDIF}
        FBackButton.fxMoveTo(FMargin,
          (clientheight div 2) - FBackButton.height div 2,
          CNT_ANIM_DELAY);
      {$IFDEF USE_ANIMFRAME_SYNC}
      end);
      {$ENDIF}
    end;
  end;

  case aVisible of
  false:
    Begin
      wd:=ClientWidth - (FMargin * 2);

      if FNextButton.Visible then
      Begin
        dec(wd,FNextButton.Width);
        dec(wd,FMargin);
      end;

      {$IFDEF USE_ANIMFRAME_SYNC}
      w3_requestAnimationFrame( procedure ()
      begin
      {$ENDIF}
        FCaption.fxScaleTo(Fmargin,
        (clientHeight div 2) - (FCaption.height div 2),
        wd,FCaption.Height,CNT_ANIM_DELAY,NIL);
      {$IFDEF USE_ANIMFRAME_SYNC}
      end);
      {$ENDIF}
    end;
  true:
    Begin
      dx:=FMargin + BackButton.Width + FMargin;

      wd:=ClientWidth - (FMargin * 2);
      dec(wd,FBackButton.width);

      if FNextButton.visible then
      Begin
        dec(wd,FNextButton.Width);
        dec(wd,FMargin * 2);
      end else
      dec(wd,FMargin);

      {$IFDEF USE_ANIMFRAME_SYNC}
      w3_requestAnimationFrame( procedure ()
      begin
      {$ENDIF}
        FCaption.fxScaleTo(dx,
          (clientHeight div 2) - (FCaption.height div 2),
          wd,FCaption.Height,
          CNT_ANIM_DELAY,
          NIL);
      {$IFDEF USE_ANIMFRAME_SYNC}
      end);
      {$ENDIF}
    end;
  end;
end;

Procedure TQTXHeaderBar.Resize;
var
  dx: Integer;
  wd: Integer;
Begin
  inherited;
  if FBackbutton.visible then
  FBackbutton.setbounds(FMargin,
    (clientheight div 2) - FBackButton.height div 2,
    FBackButton.width,
    FBackbutton.height);

  if FNextButton.visible then
  FNextButton.setBounds((clientwidth-FMargin)-FNextButton.width,
    (clientHeight div 2) - FNextButton.height div 2,
    FNextButton.width,
    FNextButton.Height);

  dx:=FMargin;
  if FBackButton.visible then
  inc(dx,FBackButton.Width + FMargin);

  wd:=ClientWidth - FMargin;
  if FBackButton.visible then
  dec(wd,FBackButton.Width + FMargin);

  if FNextButton.visible then
  begin
    dec(wd,FNextButton.width + FMargin);
    dec(wd,FMargin);
  end else
  dec(wd,FMargin);

  FCaption.SetBounds(dx,
     (clientHeight div 2) - (FCaption.height div 2),
     wd,FCaption.Height);
end;

end.

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

RTL fixup must have for Smart Mobile Studio

August 23, 2014 3 comments

If you own Smart Mobile Studio you may want to apply this fix. Please note that I have not tested this with more than 5 different apps, but it’s a very small alteration so it poses no real threat. Just revert the few lines it entails to the official release should anything negative occur.

And remember, this is not an “official site” for the product — but rather a personal, research & development blog.

Ayways – under HTML5, scaling a control causes a massive efficiency hit. The reason for this is not just that the whole DOM has to be re-calculated, but also that the internal resize function is triggered – which (depending in the complexity of your controls and code) can be processor hungry. When I write “hungry” I am talking about milliseconds — but a drop here and there is quickly a full glass.

This small patch forces the RTL to synchronize it’s resize operations using RequestAnimationFrame – which means the browser tries to collect and redraw as many updates to the screen in one go. This results in smoother display updates and in general – a more efficient UI.

RequestAnimationFrame

RequestAnimationFrame

Patching up w3components

Simply open up w3components.pas and locate the method TW3CustomControl.AfterUpdate(). Remark out the code there (just for safety in case you want to go back) and replace it with the following code. You should immediately notice that things become faster and more smooth:

 

procedure TW3CustomControl.AfterUpdate;
begin
  (* was a resize issued? *)
  if GetWasSized then
  begin
    if  ObjectReady
    and (Width&amp;gt;0)
    and (Height&amp;gt;0) then
    begin
      w3_RequestAnimationFrame(Procedure ()
        Begin
          Resize;
          if Assigned(FOnResize) then
          FOnResize(Self);
        end);
    end;

    (* do we need a re-draw? *)
    if not GetWasMoved then
    SetWasMoved;
  end;

  (* Only issue a redraw once *)
  if GetWasMoved then
  Invalidate;

  (* FWasMoved := False;
  FWasSized := False; *)
  inherited;
end;

Another vital tip for more speed, is to use BeginUpdate and EndUpdate whenever you alter your control size and/or content. This prevents resize to be called until all adjustments have executed (this is really an important point, so keep this in mind).

CaseBook – check it out!

August 22, 2014 4 comments
Welcome to casebook

Welcome to casebook

Right, I have setup a preliminary website for CaseBook, which is just a small demo of a “facebook” like clone (skeleton) written in Smart Mobile Studio. So whip out your iPhone (it is presently only written for iPhone, but you can probably use Android as well) and point your mobile browser at: http://lennartaasenden.magix.net/public/

Once loaded, remember to create a shortcut to your phone’s home-screen, then exit the browser and start it there. When you start it from your phone’s home-display it runs fullscreen rather than scaled.

The next step

At presently it only displays a handful of pre-defined articles, which of-course is not what we aim for — so the next step is to add a TQTXDataset to handle articles, and then use a RemObjects server to read data from.

And last but not least, we will add the ability to add articles. These will only exist temporarily either on the client or the server – since I suspect it would be filled with rubbish otherwise and blow the database. So we have to watch out how we write the server to avoid database exploits.

Timeframe

Fairly simple stuff

Fairly simple stuff

For those that have been following my articles, it may seem that I have spent ages on this — but fact is that I have only spent a combined total of 1 workday – most of it adapting a few custom controls. The reason it has taken so long between articles is because I try to spend 1 hour a day doing something useful, like coding the database table, the QTX library, the effects library and well as other chores. So CaseBook has not been high on my list.

I have no idea how long it would have taken to author this in pure JavaScript, but I suspect all the technology combined would take at least a week or two. If we remove the dependencies (iScroll and Font Awesome) there is still the work of components and OOP mechanisms – which under JS would be very hard to code (so the grunt of the work is actually invisible).

Why such a demonstration?

The reason is because I wanted to show that you can write exciting web applications that rivals “native” apps written in Delphi, C# or Objective C. If I compile the project with Phonegap as it stands right now – the project has full access to things like camera, disk IO, geo-location, services – and all the rest. Just like Delphi does. But at a fraction of the development time – not to mention the price (!)

Adding support for Font Awesome

August 21, 2014 1 comment

This really made my day. I was busy hacking away at caseBook when I suddenly realized – I have no glyphs that fit the bill. So I googled CSS glyphs and voila – there was Font Awesome, 100% re-usable, CSS based glyphs. Ready to be used.

Font awesome really is awesome

Font awesome really is awesome

Since Smart Mobile Studio hides the HTML template for your project, I had to add a new function for adding a reference to external files. You will find it in qtxutils.pas (see google repository for QTXLibrary), and it goes a little something like this:

class function TQTXTools.addLinkToHead(const aRel,aHref:String):THandle;
var
  mLink:  THandle;
Begin
  //REL: Can be &amp;amp;quot;stylesheet&amp;amp;quot; and many more values.
  //     See http://www.w3schools.com/tags/att_link_rel.asp
  //     for a list of all options
  asm
    @mLink = document.createElement('link');
    (@mLink).href = @aHref;
    (@mLink).rel=@aRel;
    document.head.appendChild(@mLink);
  end;
  result:=mLink;
end;

Next, I went into the project-unit, and added the following to TApplication.ApplicationStarting:

  TQTXTools.addLinkToHead('stylesheet',
  'http://maxcdn.bootstrapcdn.com/font-awesome/4.1.0/css/font-awesome.min.css');

And thats it! You can now use a plethora of CSS glyphs, including spinning cogs and all the whistles and bell’s used by popular high-end javascript websites.

And example would be, for instance, to get a home glyph + animated cog wheel (which rotates perfectly):

  FPanel.handle.style['color']:='#cccccc';
  FPanel.innerHTML:=#'<i class="fa fa-home fa-2x"></i>
    <i class="fa fa-user fa-2x"></i>
    <i class="fa fa-cog fa-spin fa-2x"></i>';
Now that was almost to easy!

Now that was almost to easy!

This really is a perfect example of just how easy it is to absorb already existing JavaScript and HTML5 libraries with Smart Mobile Studio. I could have gone with some hand painted 24×24 pixel png files — but why bother when someone has been kind enough to make such a fantastic library of scalable css based glyphs for the public?

And yes, I realize I just gave you a sneak-peek of caseBook.. It’s taking shape 🙂

Check out Font awesome here: http://fortawesome.github.io/Font-Awesome/

And as always, the QTXLibrary is located on google code, here: https://code.google.com/p/qtxlibrary/

Firemonkey like 3D for all Smart Mobile Elements

August 21, 2014 3 comments
火災猿はクールです

火災猿はクールです

Well, I might as well let the monkey out of the bag. For some time now I have spent a chunk of my spare time exploring the idea of adding 3D to Smart Mobile Studio. Not unlike what Firemonkey provides for Delphi these days. Naturally 3D and Javascript is pretty heavy duty stuff, completely dependent on webGL right? Well not really. In fact, with clever use of CSS Smart Pascal is (or will be) capable of doing most of the practical 3D stuff you find in Firemonkey. But yes, if your app can use webGL (depending on where it runs) that is the absolute best.

What I mean with practical 3D is simply that around 90% of what Firemonkey introduces is, well, a complete waste of time in a business application. I dont mean this in any negative way, I love Delphi and Firemonkey is a fantastic RTL and framework. But a rotating form or spreadsheet is .. well, perhaps not what you want when crunching numbers on a tax report due yesterday.

But Firemonkey really comes into it’s own on mobile devices, where you are almost expected to have widgets that wobble, rotate and gives visual feedback on every touch. So I’m not dizzing Firemonkey here — I am simply saying that for serious scientific or business apps – it’s out of place.

3D and Smart Mobile Studio

CSS3 have some neat features, like being able to position elements in 3d space. So using CSS you can not only animate elements between visual states (keyframe animations), you can apply those animations in 3D space. Meaning that you can rotate a DIV tag (the primary container Smart Pascal custom-controls manages) in X, Y and Z vectors. Here is a little example of a CSS animated cube.

If you define backface-visibility as active, your elements become semi-transparent, and elements behind your element (in 3d space) becomes visible. In the golden days of Amiga demo programming, we called that “glenz vectors”.

Partial classes, the death of plugins

So, the challenge at hand for me, is to provide full 3d support without messing up the RTL (which should be considered as the seed-stone and foundation for SMS apps, bordering on sacred). The only way to do this is to use partial classes. If you dont know what that is you can read up on the topic — but in general it means that you can extend a class anywhere by adding the Partial keyword. And voila, all the new features you add to your partial class, becomes a part of the class you extend.

In other words, plugins have no place in SMS because partial classes completely removes the need for them. A plugin could never really extend a class in the same way anyhow – so it’s no competition at all.

Using the force

Before you start to play around with effects, you really should download and install my QTX library. This is hosted on Google code and contains whatever classes and examples I post here. So use SVN and check it out into your $SMS/Libraries folder (! important !) so the IDE automatically locates the files.

The library also adds a wealth of “missing” tidbits and stuff to the RTL (safely, without messing up anything) so it should be considered “a must have” for all SMS hackers out there.

Simple 3d

Right. For simple 3d effects that you can apply to anything (buttons, lists, checkboxes, labels or whatever) you simply add the unit qtxTransformController (which is in the QTX library). This unit contains a class called TQTXTransformController. You can create an instance of this class, proving the handle of the control you want to play with — and voila, you can rotate your element in whatever X, Y, Z vector you want.

procedure TForm1.AnimateStuff;
var
  mObj: TQTXTransformController;
begin
  mObj:=TQTXTransformController.Create(w3button1.handle);
  mObj.x:=100;
  mobj.y:=100;
  mObj.z:=-50;
  mObj.update;
end;

But all of this is tiresome work. I mean, we cant create transform controllers for each and every element right? It may be kosher for games where you have a fixed number of sprites, but it would take ages to manage for ordinary apps. So my mission at the moment is to merge TQTXTransformController with TW3CustomControl using a partial class — thus exposing the 3D framework directly

Digging into it

Perspective is depth

Perspective is depth

But there are real challenges. For 3D perspective to work properly, it has to be defined by the container. In other words, if you want to rotate a button using the X Vector around it’s own axis — the perspective (Z vector) of the container defines it. In other words, the depth perception is set in the container tag, typically 1000 — meaning that you can then rotate your button and it can have a depth into space of 1000 pixels. Anything deeper looks.. well, a bit 80’s spaceship weirdo demo-ish.

The best way to think about this — is like a tub of water with a plank of wood floating on the surface. The Z vector of the parent defines how deep the tub is, while the X, Y and Z vectors of the element defines how deep you push the plank, and in what end you apply force.

So X = -50 means that you push the left side of the plank down by 50 pixels. X = 50 means that you push the right edge of the plank down by 50 pixels (at which point the left side of the plank goes up by the same amount). The Z factor for the element is how deep the in the water the plank is held.

-webkit-transform: rotateX(90deg);
transform: rotateX(90deg);

The above CSS will look.. well, flat, unless we apply this to the container elements (form, panel etc..):

-webkit-perspective: 1000px;
perspective: 1000px;

Once you apply that snippet of CSS to the container, suddenly the element gains depth and truly looks 3D. We also want the elements to preserve their 3D modifications after an effect has been applied, which is what the “preserve 3d” property is all about:

-webkit-transform: rotateX(90deg);
transform: rotateX(90deg);
transform-style: preserve-3d;

So what is the problem? Well, if you already have a CSS3 GPU effect running on the container – there is no telling what happens. It’s a bit like flipping a coin which effect is annulled by the browser. Hence we need to be clever about this. We need a easy to use, lightweight, ad-hoc framework which is capable of handling possible conflicts, and also – in some cases, merge effects on elements that can handle multiple adjustments.

If you want to see what can be achieved by JavaScript and 3D, check out this demo — which used webGL (sadly, because we want to avoid dependencies on that — but still support it for desktop code). Before we run, we have to make sure we walk properly first. A three.JS port to Smart Mobile Studio is available at Andre’s github repo. Sadly I dont remember the link right now, but will post it later.

Three JS, probably the best 3D library out there

Three JS, probably the best 3D library out there

Anyways, while I research the future of Object Pascal in the world of JavaScript — use my 3d controller to play around with your GUI’s. And dont forget to apply perspective to your containers! Be it the form or a TW3CustomControl (which really is the best way to do this!).

Casebook brewing in the labs..

August 20, 2014 1 comment

I havent been able to work so much on the final installment of “casebook”, but don’t worry — the final installment will be finished.

In the meantime, here is a screenshot 🙂

Casebook

Casebook

Caching effects

August 17, 2014 Leave a comment

Sort of discovered this by surprise. I was making the typical “mouse-over” effects for buttons, where they grow a few pixels when you hover over them. But, since only one webkit transformation can be applied to an element at any given time, I decided to use a callback to catch effects that overlap.

Well, it’s pretty cool! Move the mouse pointer in and out of the control very quickly to see what I mean. And naturally, the element never looses its original size. This is perfect for music vumeter’s — or perhaps a ripple effect that never loses track of the original size?

  W3Button1.OnMouseEnter:=Procedure (sender:TObject; Shift: TShiftState; X, Y: Integer)
  Begin
    if not w3button1.fxBusy then
    w3Button1.fxScaleUp(4,0.2) else
    w3_callback( procedure ()
      Begin
        w3Button1.OnMouseEnter(sender,shift,x,y);
      end, 100);
  end;

  w3button1.OnMouseExit:=procedure (sender:TObject; Shift: TShiftState; X, Y: Integer)
  Begin
    if not w3button1.fxBusy then
    w3Button1.fxScaleDown(4,0.2) else
    w3_callback( procedure ()
      Begin
        w3Button1.onMouseExit(sender,shift,x,y);
      end, 100);
  end;

Oh, and remember to update the QTX svn library before trying it !

Updated

I decided to add this form of caching to the library itself. This completely removes the need for an effect-stack (linear “list” of effects to execute one after the other).

Well, everything is now in place to write some interesting, dynamic and awesome Smart Pascal visual components. I could be tempted to write a book on advanced Smart Mobile coding — interested?

Storing stuff inside the tag

User definable per-tag attributes

User definable per-tag attributes

Another thing I learned today. As you probably understand by now, a Smart Visual Component consists of two parts. First, there is the javascript object which is what you compile from Object Pascal, secondly there is the HTML element which your control manages via code.

But what if you want to store some data which can be read/written without access to the JS code?

Let me introduce you to the problem. The effects library is capable of animating any TW3CustomControl based element, regardless of what basetype it implements (DIV, PRE, IFRAME etc..). But it does this via class helpers – so how can we store a boolean value informing us that an effect is active? We dont want to re-mold the RTL (at least not before the technology is rock solid) — so how can we attach data to an element without altering the virtual method table data?

Ta-Da! —  HTML5 introduces something called Data Attributes (read more here: http://html5doctor.com/html5-custom-data-attributes/) . Basically, whenever you use attributes on a tag which begins with “data-“, the browser leaves them in place (ordinarily it would remove them or not include them in the DOM). This allows us to do stuff like below, without affecting javascript, the RTL or the DOM negatively in any way (!):

<DIV ID="w3Panel1" data-fxActive="true">
..
</DIV>

In the QTX library read/write access to data-attributes is introduced in the class TQTXAttrAccess (qtxutils.pas). So if you include this unit in your project, all TW3CustomControl based elements gain an extra property called ElementData of type TQTXAttrAccess. Think of it like an unlimited box of TComponent.Tag values 🙂

Effects without stylesheet punching

Unless you have been living under a rock you would know that the GPU can only be accessed via CSS. This means you have to write stylesheet code (which you can edit inside Smart Mobile Studio). But this ties you down a bit, because what if you want to re-use some effects in another app? You either have to copy the CSS and Pascal code over — or… you can author the effect CSS in smart pascal directly.

And that my friend is why you will find a class called TQTXStyleSheet in the qtxstyles.pas unit. It allows you to write cutting edge GPU effects that are portable between projects — and you can leave the default, model view stylesheet intact.

You are welcome!

CSS powered scrolltext for Smart Mobile Studio

August 15, 2014 2 comments

Ok so I know scroll-texts (marquee as some call it) is so 90’s, but while it takes me back to the age of Amiga demo’s and hacker parties – it does represent a challenge under HTML5. As you probably know, JavaScript and HTML is initially poorly equipped to move things around. I write initially because with the advent of HTML5 the browser makes use of hardware accelleration – using the GPU to push pixels around.

Yet to date I have not found a single update to the classic, horizontal scrolltext. There are plenty of jQuery plugins that emulates or builds on the marquee principles, but they still tax the CPU and (in many cases) flicker just as much as the old Microsoft marquee implementation.

GPU Scroller in action

GPU Scroller in action

A challenge for the evening

So, with the release of QTX – could I put together a non-flickering, component based, GPU powered scrolltext? Well here is what I came up with, and it works like a charm. Tested in Chromium, Chrome and iOS (iPhone 5 and iPad 3). I had some issues with the Font’s not being assigned properly – but other than that it works as expected.

How does it work?

Very differently from “traditional” scrolltext’s. On the Amiga (and pc using “native” code) scrolling is typically achieved by brute force. You have to draw the characters one-by-one, and at the same time copy the scanline data pixel by pixel to the left. So it’s very CPU intensive. The good news is that effects like running the scanlines through a sinus table costs very little in terms of CPU, but in general a scroller is costly business.

Under HTML5 however there is no such thing as “scroll” in terms of pixels. In order to emulate scrolling what I do is:

  • Measure the total with of the text to be scrolled
  • Size a child container control to this size
  • place the text into the container
  • Move the container to the absolute right (clientwidth)
  • use the new fxMove method to transition-move the container towards -TotalWidth.
  • If Scroller still active, loop and repeat step #4

The only thing missing right now, which I am playing around with under QTX, is the fxAbort() method and fxBusy() function. These allows us to stop and check if a CSS animation is active on a handle. Also, fxPause() is missing, although that might defeat the “call and forget” nature of the effects library.

When this is done i’m going to aim for a sinus-scrolltext. This means dividing the text into characters – and then applying a rather massive, path generated CSS transformation to each character in realtime. I dont think I have seen anything like that in the world of JS before – so it will be fun to try 🙂

Note: You will find this unit on my Google Code repository. Just update your SVN to get the code. Also, you require the latest QTX commit for this to work properly (just update and you have it). Remember to check-out the repo into your SMS/Library folder so the Smart IDE can find the files.

unit qtxscrolltext;

interface

uses
  W3System, w3Components, w3graphics,
  qtxutils,
  qtxeffects;

  Type

  TQTXScrollText = Class(TW3CustomControl)
  private
    FActive:  Boolean;
    FText:    String;
    FSpeed:   Integer;
    FContent: TW3CustomControl;
    function  getSpeed:Integer;
    Procedure setSpeed(const aValue:Integer);
    procedure setText(aValue:String);
    Procedure setActive(const aValue:Boolean);

    Procedure UpdateScroll;

  protected
    Procedure Resize;override;
  protected
    procedure InitializeObject;Override;
    Procedure FinalizeObject;Override;
  published
    Property  Active:Boolean read FActive write setActive;
    Property  Text:String read FText write setText;
    Property  Speed:Integer read getSpeed write setSpeed;
  End;

implementation

procedure TQTXScrollText.InitializeObject;
begin
  inherited;
  FContent:=TW3CustomControl.Create(self);

  FContent.handle.style.background:='transparent';

  QTX_ExecuteOnElementReady(Handle, procedure ()
    Begin
      Font.Name:='verdana';
      Font.Size:=16;
    end);
end;

Procedure TQTXScrollText.FinalizeObject;
Begin
  FContent.free;
  inherited;
end;

function  TQTXScrollText.getSpeed:Integer;
Begin
  result:=FSpeed;
end;

Procedure TQTXScrollText.setSpeed(const aValue:Integer);
Begin
  FSpeed:=TInteger.ensureRange(aValue,1,10);
end;

procedure TQTXScrollText.setText(aValue:String);
Begin
  aValue:=trim(aValue);
  if aValue<>FText then
  Begin
    FText:=aValue;
    FContent.InnerHtml:=FText;

    if FActive then
    begin
      FContent.left:=clientWidth;
      FContent.top:=(Height div 2) - (FContent.height div 2);
    end;
  end;
end;

Procedure TQTXScrollText.UpdateScroll;
begin
  FContent.fxMoveTo(-FContent.width,
  (Height div 2) - (FContent.height div 2),
  StrToFloat(IntToStr(22-FSpeed) +'.0'),
    procedure ()
      Begin
        if FActive then
        w3_callback( Procedure ()
          Begin
            FContent.left:=clientWidth;
            FContent.top:=(Height div 2) - (FContent.height div 2);
            UpdateScroll;
          end,
          120);
      end);
end;

Procedure TQTXScrollText.setActive(const aValue:Boolean);
var
  mMetrics: TQTXTextMetric;
Begin
  if aValue<>FActive then
  Begin
    FActive:=aValue;
    if FActive then
    Begin
      FContent.handle.style['color']:='#FFFFFF';
      FContent.handle.style['font']:='22px Verdana';
      FContent.handle.style.background:='transparent';

      mMetrics:=TQTXControlTools.calcTextMetrics(FText,'Verdana',22);
      FContent.Width:=mMetrics.tmWidth;
      FContent.Height:=mMetrics.tmHeight;

      FContent.Left:=clientwidth;
      FContent.top:=(Height div 2) - (FContent.height div 2);
      UpdateScroll;
    end;
  end;
end;

Procedure TQTXScrollText.Resize;
Begin
  inherited;

  if assigned(FContent)
  and qtxutils.QTX_ElementInDOM(FContent.handle)
  and qtxutils.qtx_elementInDOM(Handle) then
  begin
    if not FActive then
    Begin
      if assigned(FContent) then
      FContent.left:=clientWidth+1;
      exit;
    end;

    if assigned(FContent) then
    FContent.top:=(Height div 2) - (FContent.height div 2);
  end;
end;

end.

QTXLibrary on google code

August 15, 2014 3 comments
Quartex is 4ever

Quartex is 4ever

To make things easier in the future I decided to push all my Smart Pascal examples into one basket and upload them to Google Code. That way people don’t have to copy & paste from this website, but rather just update their SVN repo folder.

Point your SVN client at: https://code.google.com/p/qtxlibrary/

Only human

I must however stress that these units should be considered “examples”. This blog is more about experimentation, research and education than it is anything else. I do not provide any form of support. I work full-time as a professional software developer, so there is quite frankly not enough time for large-scale hobby projects.

For new features and ideas around Smart Pascal, send an email to the Smart Mobile Studio team. It will be registered in the system and receive a proper ticket.

Ayways, do a checkout of the google repository – remember to check out to the Smart Mobile Studio Libraries folder – and have a peek 🙂

What can it do?

All sorts of cool stuff! You have the in-memory dataset class, which is very handy. And the latest additions are the effect helpers. Smart Mobile Studio RTL contains a lot of cool stuff – but for beginners it may be hard to get to grips with all the new terminology. If you are serious about y0ur HTML5 coding, you should get a book and learn some JS. It makes all the difference.

So how are the effect helpers useful? Well, for instance, to make buttons that “grow” when you move your mouse hovers over them, you would write this:

  W3Button1.OnMouseEnter:=Procedure (sender:TObject; Shift: TShiftState; X, Y: Integer)
  Begin
    //grow the button by 4x4 pixels in 0.2 seconds
    w3Button1.fxScaleUp(4,0.2);
  end;

  w3button1.OnMouseExit:=procedure (sender:TObject; Shift: TShiftState; X, Y: Integer)
  Begin
    // Shrink button back to original size
    w3Button1.fxScaleDown(4,0.2);
  end;

Effect stack

What I have been playing with lately (thought only for now) is an effect stack. Basically a “mini” effect language that you can use to script effect chains inside your SMS application.

For instance, a script could look like:

var script:String := #"fadeout(0);
  fadeIn(0.6);
  moveTo(100,100);
  rotate(-30,-60,-70);
  scale(200,200);
  Scew3d(-90,-90-36);
  warpOut(0.9);";
FStack.push(script,w3button1);
FStack.execute(esImmediate);

Reminds me a bit about the old Amos Basic “sprite language” that we used in the 90’s for writing shoot’em up enemies for games and demos.

QTXEffects unit for Smart Mobile Studio

August 14, 2014 Leave a comment

A couple of days ago I published a small class-helper for Smart Mobile Studio which implemented a couple of basic, jQuery like, effects that you can apply directly to any control.

Use effects to add that finishing touch

Use effects to add that finishing touch

Since then I have spent 5 minutes implementing the “missing” effects. After all, jQuery have a couple of variations on the theme of fading elements. Since Smart Mobile Studio comes with several interesting effect objects out of the box, I decided to wrap all of them in a handy class helper.

Why a class helper?

Whenever you include a unit with a class helper in your forms (or your own units) whatever methods provided by the class helper, becomes available as a part of the target. In this case the target is TW3CustomControl, which means that you can now call the effect methods directly from any control – and they will be applied directly on the component.

How do I use it?

Simple, first include the effect unit (qtxEffects.pas) in your uses-list, then drop a couple of controls on your form – and call one of the functions. Like fadeOut(0.5) for instance. In that case whatever control you chose will fade out in a matter of 0,5 seconds.

If you drop a button on the main-form, double-click it and write the following in the event handler, then the button will gracefully fade into opacity as described above.

if w3button1.visible then
w3button1.fadeout(0.5);

More effects

Since writing CSS3 based effects is clever stuff, I will try to expand the library once in a while when time allows. jQuery have a few methods for moving elements from/to using hardware accelerated CSS – flipping things around etc., which should be fairly easy to replicate. While not substantially important in business applications they have their uses.

Everyone loves a GUI that is alive with elements fading gracefully into view, and elements moving with elegance into position. This is tricky enough to achieve under ordinary Delphi – let alone HTML5 and JavaScript. But thankfully Smart Mobile Studio have a few tricks up it’s sleeve.

Here is the updated library — enjoy!

 

unit qtxEffects;

//#############################################################################
//
//  Unit:       qtxEffects.pas
//  Author:     Jon Lennart Aasenden
//  Company:    Jon Lennart Aasenden LTD
//  Copyright:  Copyright Jon Lennart Aasenden, all rights reserved
//
//  About:      This unit introduces a class helper for TW3CustomControl
//              which provides jQuery like "effects", such as fadeIn/out etc.
//
//  Note:       Simply add this unit to your uses-list, and all controls
//              based on TW3CustomControl will have the new methods.
//
//#############################################################################


interface

uses 
  w3System, w3Components, w3Effects;

type


  TQTXMoveAnimation = class(TW3TransitionAnimation)
  private
    FFromX: Integer;
    FFromY: Integer;
    FToX:   Integer;
    FToY:   Integer;
  protected
    function KeyFramesCSS: String; override;
  public
    Property  FromX:Integer read FFromX write FFromX;
    Property  FromY:Integer read FFromY write FFromY;
    Property  ToX:Integer read FToX write FToX;
    Property  ToY:Integer read FToY write FToY;
  end;


  TQTXEffectsHelper = Class helper for TW3CustomControl
    Procedure fxFadeOut(const Duration:Float);
    Procedure fxFadeIn(const Duration:Float);
    Procedure fxWarpOut(const Duration:Float);
    procedure fxWarpIn(const Duration:Float);
    Procedure fxZoomIn(const Duration:Float);
    Procedure fxZoomOut(const Duration:Float);
    Procedure fxMoveTo(const dx,dy:Integer;
              const Duration:Float);
    Procedure fxMoveBy(const dx,dy:Integer;
              const Duration:Float);
    Procedure fxMoveUp(const Duration:Float);
    Procedure fxMoveDown(const Duration:Float);
  End;

implementation

function TQTXMoveAnimation.KeyFramesCSS: String;
Begin
   Result := Format(#"
      from {
        left: %dpx;
        top:  %dpx;
      } to {
        left: %dpx;
        top: %dpx;
   }",[FFromX,FFromY,FToX,FToY]);
end;

//############################################################################
// TQTXEffectsHelper
//############################################################################

Procedure TQTXEffectsHelper.fxMoveUp(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TQTXMoveAnimation.Create;
  mEffect.duration:=Duration;
  TQTXMoveAnimation(mEffect).fromX:=self.left;
  TQTXMoveAnimation(mEffect).fromY:=self.top;
  TQTXMoveAnimation(mEffect).toX:=self.left;
  TQTXMoveAnimation(mEffect).toY:=0;
  TQTXMoveAnimation(mEffect).Timing:=atEaseInOut;
  mEffect.onAnimationEnds:=Procedure (sender:TObject)
    Begin
      self.top:=0;
      w3_callback( Procedure ()
      Begin
        TW3CustomAnimation(sender).free;
      end, 100);
    end;
  mEffect.execute(self);
end;

Procedure TQTXEffectsHelper.fxMoveDown(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TQTXMoveAnimation.Create;
  mEffect.duration:=Duration;
  TQTXMoveAnimation(mEffect).fromX:=self.left;
  TQTXMoveAnimation(mEffect).fromY:=self.top;
  TQTXMoveAnimation(mEffect).toX:=self.left;
  TQTXMoveAnimation(mEffect).toY:=TW3MovableControl(self.Parent).Height-Self.Height;
  TQTXMoveAnimation(mEffect).Timing:=atEaseInOut;
  mEffect.onAnimationEnds:=Procedure (sender:TObject)
    Begin
      self.top:=TW3MovableControl(self.Parent).Height-Self.Height;;
      w3_callback( Procedure ()
      Begin
        TW3CustomAnimation(sender).free;
      end, 100);
    end;
  mEffect.execute(self);
end;

Procedure TQTXEffectsHelper.fxMoveBy(const dx,dy:Integer;
              const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TQTXMoveAnimation.Create;
  mEffect.duration:=Duration;
  TQTXMoveAnimation(mEffect).fromX:=self.left;
  TQTXMoveAnimation(mEffect).fromY:=self.top;
  TQTXMoveAnimation(mEffect).toX:=self.left + dx;
  TQTXMoveAnimation(mEffect).toY:=self.top + dy;
  TQTXMoveAnimation(mEffect).Timing:=atEaseInOut;
  mEffect.onAnimationEnds:=Procedure (sender:TObject)
    Begin
      self.left:=dx;
      self.top:=dy;
      w3_callback( Procedure ()
      Begin
        TW3CustomAnimation(sender).free;
      end, 100);
    end;
  mEffect.execute(self);
end;

Procedure TQTXEffectsHelper.fxMoveTo(const dx,dy:Integer;const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TQTXMoveAnimation.Create;
  mEffect.duration:=Duration;
  TQTXMoveAnimation(mEffect).fromX:=self.left;
  TQTXMoveAnimation(mEffect).fromY:=self.top;
  TQTXMoveAnimation(mEffect).toX:=dx;
  TQTXMoveAnimation(mEffect).toY:=dy;
  TQTXMoveAnimation(mEffect).Timing:=atEaseInOut;
  mEffect.onAnimationEnds:=Procedure (sender:TObject)
    Begin
      self.left:=dx;
      self.top:=dy;
      w3_callback( Procedure ()
      Begin
        TW3CustomAnimation(sender).free;
      end, 100);
    end;
  mEffect.execute(self);
end;

Procedure TQTXEffectsHelper.fxZoomIn(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TW3ZoomInTransition.Create;
  mEffect.Duration:=Duration;
  mEffect.OnAnimationEnds:=Procedure (Sender:TObject)
    Begin
      w3_callback( Procedure ()
        Begin
          TW3CustomAnimation(sender).free;
        end, 100);
    end;
  self.Visible:=true;
  mEffect.Execute(self);
end;

Procedure TQTXEffectsHelper.fxZoomOut(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TW3ZoomOutTransition.Create;
  mEffect.Duration:=Duration;
  mEffect.OnAnimationEnds:=Procedure (Sender:TObject)
    Begin
      self.Visible:=false;
      w3_callback( Procedure ()
        Begin
          TW3CustomAnimation(sender).free;
        end, 100);
    end;
  mEffect.Execute(self);
end;

Procedure TQTXEffectsHelper.fxWarpOut(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TW3WarpOutTransition.Create;
  mEffect.Duration:=Duration;
  mEffect.OnAnimationEnds:=Procedure (Sender:TObject)
    Begin
      self.Visible:=false;
      w3_callback( Procedure ()
        Begin
          TW3CustomAnimation(sender).free;
        end, 100);
    end;
  mEffect.Execute(self);
end;

procedure TQTXEffectsHelper.fxWarpIn(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TW3WarpInTransition.Create;
  mEffect.Duration:=Duration;
  mEffect.OnAnimationEnds:=Procedure (Sender:TObject)
    Begin
      w3_callback( Procedure ()
        Begin
          TW3CustomAnimation(sender).free;
        end, 100);
    end;
  self.Visible:=true;
  mEffect.Execute(self);
end;

Procedure TQTXEffectsHelper.fxFadeIn(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TW3FadeSlideTransition.Create;
  TW3FadeSlideTransition(mEffect).fromOpacity:=0.0;
  TW3FadeSlideTransition(mEffect).toOpacity:=1.0;
  mEffect.Duration:=Duration;
  mEffect.OnAnimationEnds:=Procedure (Sender:TObject)
    Begin
      w3_callback( Procedure ()
        Begin
          TW3CustomAnimation(sender).free;
        end, 100);
    end;
  self.Visible:=true;
  mEffect.Execute(self);
end;

Procedure TQTXEffectsHelper.fxFadeOut(const Duration:Float);
var
  mEffect: TW3CustomAnimation;
Begin
  mEffect:=TW3FadeSlideTransition.Create;
  TW3FadeSlideTransition(mEffect).fromOpacity:=1.0;
  TW3FadeSlideTransition(mEffect).toOpacity:=0.0;
  mEffect.Duration:=Duration;
  mEffect.OnAnimationEnds:=Procedure (Sender:TObject)
    Begin
      self.Visible:=False;
      w3_callback( Procedure ()
        Begin
          TW3CustomAnimation(sender).free;
        end, 100);
    end;
  mEffect.Execute(self);
end;

end.

Hidden stylesheets with Smart Pascal

August 13, 2014 Leave a comment

CSS styles makes it easy to create a visual design for an application that can later be adjusted, hot-swapped or completely re-written. The benefit of this model is of course that you can change the look of your application without changing a line of your smart pascal code.

Having said that there are times when it would be handy to create a style sheet “on the fly”. Since CSS is also used to control things like animation, hardware accelerated movement and general behavior of elements — being able to create a style sheet and define rules “by code” is bordering on critical.

Well, here is a class that does exactly that. It creates a style sheet and allows you to create CSS styles (named rule-sets) which you can apply immediately to your TW3CustomControl descendants. This is fantastic because it allows you to separate the look (as defined in the CSS file of your project) from your animations (!)

Armed with this class plus other snippet’s I’ve posted here – you should be able to give jQuery and the rest of them a run for their money effect wise.

type

  TW3StyleSheet = Class(TObject)
  private
    FHandle:    THandle;
  protected
    function    getSheet:THandle;
  public
    Property    Sheet:THandle read getSheet;
    Property    Handle:THandle read FHandle;
    function    Add(aName:String;const aRules:String):String;

    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;

//############################################################################
// TW3StyleSheet
//############################################################################

Constructor TW3StyleSheet.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 TW3StyleSheet.Destroy;
Begin
  if (FHandle) then
  FHandle.parentNode.removeChild(FHandle);
  FHandle:=null;
  Inherited;
end;

function TW3StyleSheet.getSheet:THandle;
  var
    xRef: THandle;
Begin
  if (FHandle) then
  begin
    xRef:=FHandle;
    asm
      @result = (@xRef).styleSheet || (@xRef).sheet;
    end;
  end;
end;

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

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

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

function TW3StyleSheet.Add(aName:String;const aRules:String):String;
var
  mRef: THandle;
  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;

Using it

Very simple to use. Also, when you release the stylesheet object — the styles are deleted from the system. That is pretty nice. Note: if you dont add a name in the add() call, a name is automatically generated. The add() function returns the generated rule-name. Here you go:

var
  mSheet: TW3StyleSheet;
  mStyle: String;
begin
  mSheet:=TW3StyleSheet.Create;
  mStyle:=mSheet.Add('','color: rgb(255,0,0); background: rgb(255,255,0);');
  self.StyleClass:=mStyle;