Archive

Archive for the ‘CaseBook’ Category

Delphi developer on its own server

April 4, 2017 Leave a comment

While the Facebook group will naturally continue exactly like it has these past years, we have set up a server active Delphi developers on my Quartex server.

This has a huge benefit: first of all those that want to test the Smart Desktop can do so from the same domain – and people who want to test Smart Mobile Studio can do so with myself just a PM away. Error reports etc. will still need to be sent to the standard e-mail, but now I can take a more active role in supervising the process and help clear up whatever missunderstanding could occur.

casebook

Always good with a hardcore Smart, Laz, amibian.js forum!

Besides that we are building a lively community of Delphi, Lazarus, Smart and Oxygene/Remobjects developers! Need a job? Have work you need done? Post an add — wont cost you a penny.

So why not sign up? Its free, it wont bite you and we can continue regardless of Facebook’s up-time this year..

You enter here and just fill out user/pass and that’s it: http://quartexhq.myasustor.com/sharetronix/

Amibian + Smart pascal = A new beginning

March 25, 2017 Leave a comment

In the Amiga community there is a sub-group of people with an agenda. They hoard and collect every piece of hardware they can get their hand on – then sell them at absurd prices on ebay to enrich themselves. This is not only ruining the community at large, ensuring that ordinary users cannot get their hands on a plain, vanilla Amiga without having to fork out enough dough to buy a good car.

“We also have a working Facebook clone – but we’re not
going into competition with Mark Zuckerberg either”

Thankfully not everyone is like that. There are some very respected, very good people who buy old machines, restore them – and sell them at affordable prices. People that do this as a hobby or to make a little on the side. Nothing wrong with that. No, its people that try to sell you an A2000 for $3000, or that pimp out Vampire accelerator cards at 700€ a piece thats the problem.

As for the price sharks, well – this has to stop. And Gunnar’s Amibian distro has already given the Amiga scalpers a serious uppercut. Why buy an old Amiga when you can get a high-end A4000 experience with 4 times as much power for around $35? This is what Gunnar has made a reality – and he deserves a medal for his work!

And myself, Thomas and the others in our little band of brothers will pick up the fight and stand by Gunnar in his battle. A battle to make the Amiga affordable for ordinary human beings that love the platform.

Amiga as a service

Yesterday I did a little experiment. You know how Citrix and VMWare services work? In short, they are virtualization application servers. That means that they can create as many instances of Windows or Linux as they want – run your applications on it – and you can connect to your instance and use it. This is a big part of how cloud computing works.

While my little experiment is very humble, I am now streaming a WinUAE instance display from my basement server pc, just some old bucket of chips I use for debugging, directly into Amibian.js (!). It worked! I just created the world’s first Amiga application server. And it took me less than 30 minutes in Delphi !

Amibian, Amibian.js, appserver, what gives?

Let’s clear this up so you dont mix them:

Amibian. This is the original Linux distro made by Gunnar Kristjansson. It boots straight into UAE in full-screen. All it needs is a Raspberry PI, the Amiga rom files and your workbench or hard disk image. This is a purely native (machine code) solution.

Amibian.js – this is a JavaScript remake of AmigaOS that I’m building, with the look and feel of OS 4. It uses uae.js (also called SAE) to run 68k software directly in the browser. It is not a commercial product, but one of many demos that ship with Smart Mobile Studio. “Smart” is a compiler, editor and run-time library sold by The Smart Company AS. So Amibian.js is just a demo, just like Amos shipped with a ton of demo’s and Blitzbasic came with a whole disk full of examples.

Amiga application server (what I mentioned above) was just a 30 minute experiment I did yesterday after work. Again, just for fun.

I hope that clears up any confusion. Amibian.js is a purely JavaScript based desktop made in the spirit of the Amiga – it must not be confused with Amibian the Linux distro, which boots into UAE on a Raspberry PI. Nor is it an appserver – but rather, it can connect to an appserver if I want to.

genamiga

Generation Amiga post on Amibian.js earlier when I added some look & feel

With a bit of work, and if everything works as expected (I don’t see why not), I will upload both source and binaries to github together with Amibian.js.

There is only one clause: It cannot be used, recreated, included or distributed by Cloanto. Sorry guys, but the ROMS belong to the people, and until you release those into public domain, you wont get access to anything we make. Nothing personal, but pimping out roms and even having audacity to fork UAE and sell it as your own? You should be ashamed of yourself.

Are you in competition with FriendOS?

This question has popped up a couple of times the past two weeks. So I want to address that head on.

I make a product called Smart Mobile Studio. I do that with a group of well-known developers, and we have done so for many years now. The preliminary ideas were presented on my blog during the winter 2009, early 2010 and we started working (and blogging) after that. Smart Mobile Studio and it’s language, Smart Pascal (see Wikipedia article), takes object pascal (like freepascal or Delphi) and compiles to JavaScript rather than machine code.

smsamiga

The Smart compiler is due for OS4 once i get the A1222 in my hands

One of the examples that has shipped with Smart Mobile Studio, and also been available through a library called QTX, is something called Quartex Media Desktop. Which is an example of a NAS server front-end, a kiosk front-end (ticket ordering, cash machines etc) or just an intranet desktop where you centralize media and files. It is also node.js powered to deal with the back-end filesystem. This is now called amibian.js.

In other words – it has nothing to do with Friend software labs at all. In fact, I didn’t even know Friend existed until they approached me a few weeks ago.

15590846_10154070198815906_4207939673564686511_o

The Quartex Media desktop has been around for ages

Amibian.js is just an update of the Quartex Media Desktop example. It is not a commercial venture at all, but an example of how productive you can be with Smart pascal.

And it’s just one example out of more than a hundred that showcase different aspects of our run-time library. This example has been available since version 1.2 or 1.3 of Smart, so no, this is not me trying to reverse engineer FriendOS. Because I was doing this long before FriendOS even was presented. I have just added a windowing manager and made it look like OS4, which also happened before I had any contact with my buddies over at Friend Software Labs (why do you think they were interested in me).

16805402_1914020898827741_149245853_o

Early 2017 Linux bootloader by Gunnar

So, am I in competition with Friend? NO! I have absolutely no ambition, aspiration or intent for anything of the sorts. And should you be in doubt then let me break it down for you:

  • Hogne, Arne, David, Thomas, Francois and everyone at Friend Software Labs are friends of mine. I talk almost daily with David Pleasence who is a wonderful person and an inspiration for everyone who knows him.
  • Normal people don’t sneak around stabbing friends in the back. Plain and simple. That is not how I was raised, and such behavior is completely unacceptable.
  • Amibian.js is 110% pure Amiga oriented. The core of it has been a part of Smart for years now, and it has been freely available for anyone on google code and github.
  • For every change we have made to the Smart RTL, the media desktop example has been updated to reflect this. But ultimately it’s just one out of countless examples. We also have a working Facebook clone – but we’re not going into competition with Mark Zuckerberg for that matter.
  • People can invent the same things at the same time. Thats how reality works. There is a natural evolution of ideas, and great minds often think alike.

Why did you call it Amibian.js, it’s so confusing?

Well it’s a long story but to make it short. The first “boot into uae” thing was initially outlined by me (with the help of chips, the UAE4Arm maintainer). But I didn’t do it right because Linux has never really been my thing. So I just posted it on my retro-gaming blog and forgot all about it.

Gunnar picked this up and perfected it. He has worked weeks and months making Amibian into what it is today – together with Thomas, our spanish superhero /slash/ part-time dictator /slash/ minister of propaganda 🙂

We then started talking about making a new system. Not a new UAE, but something new and ground breaking. I proposed Smart Pascal, and we wondered how the Raspberry PI would run JavaScript performance wise. I then spent a couple of hours adding the icon layout grid and the windowing manager to our existing media desktop – and then fired up some HTML5 demos. Gunnar tested them under Chrome on the Raspberry PI — and voila, Amibian.js was born.

amidesk

And that is all there is to it. No drama, no hidden agendas – and no conspiracy.

I should also add that I do not work at Friend Software Labs, but we have excellent communication and I’m sure we will combine our forces on more than one software title in the future.

On a personal note I have more than a few titles I would like to port to FriendOS. One of my best sellers of all time is an invoice and credit application – which will be re-written in Smart Pascal (its presently a mix of Delphi and C++ builder code). The same program is also due to Amiga OS 4.1 whenever I get my A1222 (looking at you Trevor *smile*).

Well, I hope that clears up any misunderstanding regarding these very separate but superficially related topics. Amibian.js will remain 100% Amiga focused – that has been and remains our goal.

Ode to our childhood

Amibian is and will always be, an ode to the people who gave us such a great childhood. People like David Pleasence who was the face of Commodore in europe. A man who embody the friendliness of the Amiga with his very being. Probably one of the warmest and kindest people I can think of.

Francois Lionet, author of Amos Basic. The man who made me a programmer and that I cannot thank enough. And I know I’m not alone about learning from him.

Mark Sibly, the author of BlitzBasic, the man who taught me all those assembler tricks. A man that deserves to go down in the history books as one of the best programmers in history.

And above all – the people who made the Amiga itself; giants like Jay Miner, Dave Haynie, Carl Sassenrath, Dave Needle, RJ Michal (forgive me for not listing all of you. Your contributions will never be forgotten).

That is what Amibian.js is all about.

Patents and greed may have killed the actual code. But we are free to implement whatever we like from scratch. And when I’m done – your patents will be worthless..

 

Smart Mobile Studio, behind the scenes part 2

March 17, 2015 Leave a comment

I’m going to give away a few more tidbits of behind the scenes as you wait for the next update. Let me assure you all that it’s on track (actually ahead of schedule) and I think everyone will feel we are putting in place “the missing pieces” of the RTL.

And being honest, It’s not easy knowing exactly what the missing pieces are, I mean, I spend just as much time in C# and C++ that I do in Delphi, and the C culture is almost fully source-code orientated. Meaning that programmers expect to write much more code than the average Delphi or FreePascal programmer. The tools and frameworks are likewise source-code only (with some exceptions), and every programmer is expected to tailor, extend and even write from-scratch controls. In the world of C you don’t have the luxury of drag & drop components. Although C# is more in the spirit of Delphi, but most component packages are source-only, only rarely do you find packages which installs into the component palette (bought commercial components tend to come that way, but very rarely open-source packages).

C# may look easy but it's a lot harder than Object Pascal

C# may look easy but it’s a lot harder than Object Pascal. And a lot less visual

So in some way I am surprised that so few people have picked up how fundamental it is to write your own custom controls with Smart. Under Delphi and FPC it can be quite a challenge to write advanced controls, but the Smart RTL is completely different and simplifies everything — once you understand how it generates HTML elements for you and how styles work.

So when we launched SMS we kinda of took it for granted that our customers would check out the competition and realize that Smart Mobile Studio was right up there with the best. Had you taken the time to look at MonoTouch from Xamarin for instance, besides being a C# framework, you would realize that it’s a much more demanding toolkit. You would have to override and extend the application object straight away for instance, so no lurking about with drag & drop designers (although they do market it much like Delphi was 10-15 years back).

In short: writing good code takes time, and there is no quick-fix to quality products, no matter how friendly your IDE is.

The good, the bad and the ugly

Right. We have noticed that quite a large chunk of our customer-base don’t really care too much about mobile applications. Which sounds odd considering it’s a mobile studio. Either way these customers can be categorized in two groups:

  • Those that want to create a full website using Smart Mobile Studio
  • Those who use Smart as a Flash replacement, writing banners, slideshows and interactive media

The demand for controls which fit into the whole “desktop” browser genre is growing. And yes, we have noticed your needs and we will not ignore you.

In fact, one of the controls that is getting a makeover is the scrollbars. These doesn’t really belong under iOS or Android platforms, but works very well in a desktop browser. Sadly the default CSS style I once made was butt ugly, and it was suffering from a “off by 1” bug. So time to pimp it up and get rid of the bug!

As always you can write some CSS for this yourself (and you are expected to do so, not just rely on our default schemas), but at least we now ship with something that resembles a normal scrollbar. But yes, just edit the stylesheet yourself and make it look different. CSS is easy and you will learn it in less than 1 hour.

Ta-Da: The new scrollbar look is simple, but at least you see what they are!

Ta-Da: The new scrollbar look is simple, but at least you see what they are!

I have also taken the liberty to put some glyphs on the min/max buttons. These are hardcoded into the class, but you can remove or replace them easily (just override the InitializeObject method and change the innerHTML property).

Notice that they are transparent in the picture above? Well, this is actually by design. Just assign a color or background picture through the background property, and adjust them to your liking. A simple:

w3verticalscrollbar1.background.fromColor(clWhite);

Should do the trick. But I have left them transparent by default because you are intended to adapt these to your form and CSS style. Except for the scrollbox control, there I have styled them with a plain white background.

New widgets?

A couple of new controls have been added. First there is a new label component which is simpler than the one shipping with Smart Mobile Studio from version 1.x. The default label is actually a composite label, consisting of a child control to handle horizontal alignment. This is great for most situations, but I found myself missing a simpler label when I coded the CaseBook demo.

A plain, un-decorated iOS button has been added. This is a pure duplicate of the default iOS button. A white, rounded button with a blue verdana caption and blue frame. Simple but important.

We now have full support for iScroll

We now have full support for iScroll

The next control is more exciting perhaps, namely a design surface control. This inherits from TW3Scrollbox, but introduces more functionality for building editors. So if you want to make a simple paint program (or a full photoshop clone), or perhaps a tile map editor for games, or a designer control which is better than ours– then this is the control you want to inherit from.

Connected to the above control are 2 controls which are interesting, namely horizontal and vertical rulers. This may not sound that hard to make (nor was it ) but they are tricky to get right. What I did was render the whole ruler in one go onto a TBitmap; assign it to the control as it’s background picture, and then I update the background picture position (phew) according to the position. A very neat and clever way of solving the redraw problem. And as a bonus it will trigger the GPU on mobile devices to make it snappy!

And last but not least, we now support the iScroll library. If you have no idea what this is then I suggest you google it. It’s a great library for doing iPhone type scrolling lists (which bounces when you reach the limit, has accelerated speed support and much more). I have implemented a new TW3Scrollbox based on this library, which is perfect for displaying text, menu options and news. I used this in the CaseBook demo.

More controls, and the cloud thing too!

Yes, more is on the way! I am presently going over MonoTouch and the default widgets from Apple to see what we can add. Smart Mobile Studio is first and foremost a programming environment for mobile platforms, so that comes first. But I guess it’s time to start expanding into HTML5 full frame applications (read: browser applications for desktop), so you can expect more in the future updates.

A treeview and listview control is on my list, as well as a proper database grid and string-grid.

Cloud storage, coming to a smart RTL near you in the future

Cloud storage, coming to a smart RTL near you in the future

But before we venture into map controls, grids and whatnot — I have an urge to solve a more immediate problem. Namely storage. TMS software has just released their Cloud package, and I really want to provide something similar for Smart Mobile Studio. Being able to store data directly in the cloud, per application user, is something which would set us apart from other HTML5 frameworks. At least with the simplicity and elegance I have in mind.

But it goes a bit deeper than just “being able to”. I want to amalgam cloud services with local storage to provide a unified storage mechanism; which includes full filesystem emulation.

Meaning that you would write code to store files and data once, and it would work the same no matter what the target is. Cloud, localstorage or sessionstorage – makes no difference.

Anything else?

Much more than I can fit here! I have talked over and over about the memory stuff, which is turning heads even for pure JS developers (which have not really seen real streams and encoding buffers like this before), so that is probably the biggest “wow” factor in the next update.

But let us not forget all the fixes and additions to the IDE! But for that you have to wait a bit more.. but rest assured, they are awesome!

QTX Library for Smart Mobile Studio updated

January 27, 2015 4 comments

QTX (Quartex) is a library written for Smart Mobile Studio users. It extends the run-time library with a wide range of capabilities, including database (dataset) support, custom effects, tag-attribute storage and much, much more.

If you are a serious HTML5 developer using Smart Mobile Studio, then this library is a must. The font and content measurement alone is worth the download – and did I mention that it’s free?

Facebook clone, coded in a couple of hours using QTX for Smart Mobile Studio

Facebook clone, coded in a couple of hours using QTX for Smart Mobile Studio

Library overview

QTX is packed with classes and methods, and I have done my best to make it as easy as possible to understand. I take it for granted that you understand the concept of HTML-Tags, and that you understand that Smart Mobile Studio code creates TAGS when it’s constructor fires, and removes the tag when the destructor executes. As such, we have no need for libraries like JQuery – because the handle (reference) to the element is always known. JQuery is ultimately about extracting tags within a criteria from the DOM (hence “Query” in the title, as in SQL for querying a database).

What we do like however, is plenty of effects – and dead simple syntax for using them. Well it doesnt get any easier than with QTX. The QTX library extends TW3CustomControl with it’s own class helper, meaning that once you have included the qtx.effects unit – all your controls supports the whole range of effects.

Also, effects are executed in sequence. You can trigger 10 effects and they will execute one by one, which is no small feat under HTML5 (but easy with QTX tag attribute support).

Here is a brief overview of the classes and methods you get when installing QTX:

Database

  • TQTXDataset
  • TQTXDatasetField
  • TQTXBooleanField
  • TQTXIntegerField
  • TQTXFloatField
  • TQTXStringField
  • TQTXDatasetFields
  • TQTXFieldDef
  • TQTXFieldDefs

Tag-Attribute Storage

  • TQTXAttrAccess

CSS3 GPU powered animations

  • TQTXMoveAnimation
  • TQTXFadeAnimation
  • TQTXSizeAnimation
  • TQTXAnimationHelper

Effect management

  • TQTXEffectsHelper
    • fxSetBusy
    • fxBusy:boolean
    • fxScaleUp
    • fxScaleDown
    • fxSizeTo
    • fxMoveDown
    • fxMoveUp
    • fxMoveBy
    • fxMoveTo
    • fxScaleTo
    • fxZoomOut
    • fxZoomIn
    • fxWarpIn
    • fxWarpOut
    • fxFadeIn
    • fxFadeOut

Font and content measurement

  • TQTXTextMetric
  • TQTXFontInfo
  • TQTXFontDetector

Helper classes

  • TQTXHandleHelper
  • TQTXIntegerHelper
  • TQTXStringHelper

Media IO management

  • TQTXIOAccess
    • LoadXML
    • LoadFile
    • LoadCSS
    • LoadScript
    • LoadImage
    • PreloadImages

Delayed execution

  • TQTXRuntime
    • DelayedDispatch
    • CancelDelayedDispatch
    • Execute
    • Ready
    • ExecuteDocumentReady

Dynamic stylesheets

  • TQTXStyleSheet

Flicker free, smooth momentum scroll baseclasses

  • TQTXScrollOptions,
  • TQTXScrollController
  • TQTXScrollWindow

3D for any HTML elements

  • TQTXSprite3DController

Custom controls

  • TQTXHeaderButton
  • TQTXBackButton
  • TQTXNextButton
  • TQTXHeaderTitle
  • TQTXHeaderBar
  • TQTXLabel
  • TQTXScrollText

Downloading

Simply point your SVN client to: svn checkout http://qtxlibrary.googlecode.com/svn/trunk/ and grab the units.

Installing

Copy the target folder into your Smart Mobile Studio -> Libraries folder and re-start the IDE. That’s it! Now include the units in your uses-clause and kick some serious HTML5 butt!

SMS fonts, once and for all, part 3

September 1, 2014 Leave a comment

Right, in the past two articles (first here and second here) we established two set of routines in the same class. In short, what we have finished is as such:

  • The ability to check if a font is installed
  • The ability to traverse and find the font used by an html element
  • The ability to measure text

Now the last point on that list is extremely tricky. While measuring static, one-liner text (read: text that does not break) is indeed working as expected, perfect for captions and other forms of lengths — more complex segments of html measurement is proving to require more work.

To illustrate what we want to do here:

DIsplay width vs. scroll width

Display width vs. scroll width

As you can see from this highly scientific diagram i slapped together in paint (the typos are free by the way), by setting the “display” css property to “scroll” (please see css documentation for the display property here), we basically turn the yellow DIV element into a Delphi like TScrollbox. So what we are effectively looking for is clientwidth/clientheight for a TScrollbox which has content more content than is being displayed.

HTML5 is a bit silly regarding this, in that the scrollWidth and scrollHeight properties, which contains the full size of the blue box (the values we want to get) are only there if we have set the display property right — and that the content actually spans beyond the visible display. If not, scrollWidth or scrollHeight may be nil (undefined) depending on what fits within it. This is why i scale the element to 4×4 pixels before measuring – to make sure both values are kosher.

And now for the problem (or feature, depending on how you regard the collected wisdom of the WC3), we have to write to the style for the element, but read back values from the calculated style. Why? Because the world of browsers are not (believe it or not) an exact science. As discussed ad infinitum by now, the browser can assign several styles to an element (hence the “cascading” word in CSS) which results in a final style simply called “the calculated style”. Which the WC3 in their infinite wisdom have decided to store in a completely separate location (document.topframe as opposed to, oh say, element.calculatedStyle).

To make things even more fun, the browser may in fact end up breaking things up to much when we scale down — so truth be told, we may fall back to clientWidth/clientHeight or offsetWidth/offsetHeight instead. Confused yet? Your welcome.

Fixing it

Now, the problem with the old model was simply that by setting the element to 4×4 pixels in size, I was not in fact measuring the actual content – instead I was measuring the largest word in the string. Which gave some pretty funky visual results to be mild.

I have since updated the routine and I am very happy to report that it now works as expected — and finally my casebook demo auto-calculates the height of a new-item perfectly (at least under webkit). I have yet to do battle with Mozilla and Opera, but at least I suspect that Opera will have it’s ducks in a row.

So here is the updated version. I will omit the word “final” because the browser can still throw some quirks at us – but at least now we know what we are dealing with!

Something awesome

QTXLibrary expands the normal TW3CustomControl with attribute storage. I decided to add two methods for measuring text directly on the level of TW3Customcontrol. What it does is grab the font information for that element directly – so you dont have to worry about those parameters, and just provide the text you want to measure. Used the fixed version if you know the width of the target placeholder. Pretty cool!


  TQTXTextMetric  = Record
    tmWidth:  Integer;
    tmHeight: Integer;
    function  toString:String;
  End;

  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.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;

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;
         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("p");
      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.innerHTML := aContent;
        Fh.appendChild(mElement);

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

      end;
    end;
  end;
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("p");
      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.innerHTML := aContent;
        Fh.appendChild(mElement);

        result.tmWidth:=mElement.scrollWidth;
        result.tmHeight:=mElement.scrollHeight;

        Fh.removeChild(mElement);

      end;
    end;
  end;
end;

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!

QTXLibrary + CaseBook on google code

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

Casebook, a QTXLibrary demo

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

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

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

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

Remember to *star* the project!

Also, check out the live casebook version here:

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

Casebook updated

August 26, 2014 Leave a comment

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

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

CaseBook

CaseBook

Written for IPhone 5

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

CaseBook welcome screen

CaseBook welcome screen

Future development

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

Casebook "edit article" form

Casebook “edit article” form

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