Archive
Amibian + Smart pascal = A new beginning
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.

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.

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.

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

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.
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..
QTX Library for Smart Mobile Studio updated
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?
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
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:
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
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.
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!
QTX updated + casebook
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
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:='&lt;b&gt;Back&lt;/b&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.
Meeeh.. let’s try that again, and this time with the CSS used by TW3HeaderControl
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
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
You must be logged in to post a comment.