Archive

Archive for October, 2017

Custom dialog and loading data from JSON in Smart Pascal

October 30, 2017 1 comment

Right now we are putting the finishing touches on our next update, which contains our new theme engine. As mentioned earlier (especially if you follow us on Facebook) the new system builds on the older – but we have separated border and background from the basic element styling.

When working with the new theme system, I needed an application that could demonstrate and show all the different border and background types, most of our visual controls – but also information about what Smart Mobile Studio is, what it’s features are and where you can buy it.

smarties_01

So it started as a personal application just to get a good overview of the CSS themes I was working on; but it has become an example in it’s own right.

Dont hardcode, just dont

If you look at the picture above, there is a MenuList with the options: “Introduction”, “Features” and “Where to buy”. When you click these I naturally want to inform the user about these things by displaying information.

I could have hardcoded the information text into the application; in many ways that would have been simpler (considering the data requirements here is practically insignificant). but all that text within the source? I hate mess like that.

Secondly, how exactly was I going to show this information? Would I use the modal framework already in place, or code something more lightweight?

As always I ended up making a new and more lightweight system. A reader style dialog appears and allows you to scroll vertically. The header contains the title of the information and a close button.

smarties_02

Typical “reader” style dialog with scrolling

I also used a block-box to prevent the user from reaching the UI until they click the close-button. You notice that the form, toolbar and header in the back is darkened. This is actually a control that is semi-transparent that does one thing: prevent anyone from clicking or interacting with the UI while the dialog is active.

The JSON file structure

json_structureThe structure I needed was very simple: our records would have a unique ID that we use to fetch and recognize content; It would also have a Title and Text property. It really doesnt have to be more difficult than that.

To work with the JSON i used the online JSON editor JSonEditorOnline, which is actually really good! It allows you to write your JSON and then format it so that special characters (like CR+LF) is properly encoded.

Putting it all together

Having coded the dialog thing first, I now sat down and finished a sort of “Turbo Pascal” record database system for this particular file format. It’s not very big nor extremely advanced – but that’s the entire point! Throwing in SQLite or MongoDB for something as simple as a few records of data – especially when the data is so simple, is just a complete waste of time and effort.

Right, let’s have a peek at the code shall we!

unit infodialog;

interface

uses
  System.Types,
  System.Types.Convert,
  System.Types.Graphics,
  System.Colors,
  System.JSON,

  SmartCL.Theme,
  SmartCL.FileUtils,
  SmartCL.System,
  SmartCL.Effects,
  SmartCL.Components,
  SmartCL.Scroll,
  SmartCL.Controls.Panel,
  SmartCL.Controls.Scrollbox,
  SmartCL.Controls.Header;

type

  TInfoDialog = class(TW3Panel)
  private
    FHead:    TW3HeaderControl;
    FBox:     TW3Scrollbox;
  protected
    procedure InitializeObject; override;
    procedure FinalizeObject; override;
    procedure Resize; override;
  public
    property  Header: TW3HeaderControl read FHead;
    property  Content: TW3Scrollbox read FBox;

    class function ShowDialog(Title, Content: string): TInfoDialog;
  end;

  TAppInfoRecord = record
    iiId:     string;
    iiTitle:  string;
    iiText:   string;
    procedure Clear;
    class function Create(const Id, Title, Text: string): TAppInfoRecord;
  end;

  TAppInfoDB = class(TObject)
  private
    FStack:     array of TStdCallback;
    FItems:     array of TAppInfoRecord;

    procedure   Parse(DBText: string);

    procedure   HandleDataLoaded(const FromUrl: string;
                const TextData: string; const Success: boolean);
  public
    property    Empty: boolean read ( (FItems.Count < 1) );
    property    Count: integer read (FItems.Count);
    property    Items[index: integer]: TAppInfoRecord
                read  (FItems[index])
                write (FItems[index] := Value);

    function    GetRecById(Id: string; var Info: TAppInfoRecord): boolean;

    procedure   LoadFrom(Url: string; const CB: TStdCallback);
    procedure   Clear;

    destructor  Destroy; override;
  end;


implementation

uses SmartCL.Application;

//#############################################################################
// TAppInfoRecord
//#############################################################################

class function TAppInfoRecord.Create(const Id, Title, Text: string): TAppInfoRecord;
begin
  result.iiId := id.trim();
  result.iiTitle := Title.trim();
  result.iiText := Text;
end;

procedure TAppInfoRecord.Clear;
begin
  iiId := '';
  iiTitle := '';
  iiText := '';
end;

//#############################################################################
// TAppInfoDB
//#############################################################################

destructor TAppInfoDB.Destroy;
begin
  if FItems.Count > 0 then
    Clear();
  inherited;
end;

procedure TAppInfoDB.Clear;
begin
  FItems.Clear();
end;

function TAppInfoDB.GetRecById(Id: string; var Info: TAppInfoRecord): boolean;
begin
  Info.Clear();
  if not Empty then
  begin
    Id := Id.trim().ToLower();
    if id.length > 0 then
    begin
      for var x := 0 to Count-1 do
      begin
        result := Items[x].iiId.ToLower() = Id;
        if result then
        begin
          Info := Items[x];
          break;
        end;
      end;
    end;
  end;
end;

procedure TAppInfoDB.Parse(DBText: string);
var
  vId:    variant;
  vTitle: variant;
  vText:  variant;
begin
  Clear();

  DbText := DbText.trim();
  if DbText.length > 0 then
  begin
    var FDb := TJSONObject.Create;
    FDb.FromJSON(DbText);

    if FDb.Exists('infotext') then
    begin
      // get the infotext-> [] array of JS objects
      var Root: TJSInstanceArray := TJSInstanceArray( FDb.Values['infotext'] );

      for var x := 0 to Root.Count-1 do
      begin
        var node := TJSONObject.Create(Root[x]);
        if node <> nil then
        begin
          Node
            .Read('id', vid)
            .Read('title', vtitle)
            .Read('text', vtext);

          FItems.add( TAppInfoRecord.Create(vId, vTitle, vText) );
        end;
      end;
    end;

  end;
end;

procedure TAppInfoDB.LoadFrom(Url: string; const CB: TStdCallback);
begin
  if assigned(CB) then
    FStack.push(CB);
  TW3Storage.LoadFile(Url, @HandleDataLoaded);
end;

procedure TAppInfoDB.HandleDataLoaded(const FromUrl: string;
          const TextData: string; const Success: boolean);
begin
  try
    // Parse if data ready
    if Success then
      Parse(TextData);
  finally
    // Perform callbacks
    while FStack.Count>0 do
    begin
      var CB := FStack.pop();
      if assigned(CB) then
        CB(Success);
    end;
  end;
end;

//#############################################################################
// TInfoDialog
//#############################################################################

procedure TInfoDialog.InitializeObject;
begin
  inherited;
  FHead := TW3HeaderControl.Create(self);
  FHead.BackButton.Visible := false;
  FHead.NextButton.Caption := 'Close';

  // By default the header text is centered within the space allocated for it,
  // which by default is 2/4. This can look a bit off when we never show
  // the left-button. So we force text-align to the left [normal].
  FHead.Title.Handle.style['text-align'] := 'left';

  FBox := TW3Scrollbox.Create(self);
  FBox.Background.FromColor(clWhite);
  FBox.ScrollBars := sbIndicator;
end;

procedure TInfoDialog.FinalizeObject;
begin
  FHead.free;
  FBox.free;
  inherited;
end;

procedure TInfoDialog.Resize;
begin
  inherited;
  var LBounds := ClientRect;
  var dy := LBounds.top;

  if FHead <> nil then
  begin
    FHead.SetBounds(LBounds.left, LBounds.top, LBounds.width, 32);
    inc(dy, FHead.Height +1);
  end;

  if FBox <> nil then
    FBox.SetBounds(LBounds.left, dy, LBounds.width, LBounds.height - dy);
end;

class function TInfoDialog.ShowDialog(Title, Content: string): TInfoDialog;
begin
  var Host := Application.Display;
  var Shade := TW3BlockBox.Create(Host);
  Shade.SizeToParent();

  var wd := Host.Width * 90 div 100;
  var hd := Host.Height * 80 div 100;
  var dx := (Host.Width div 2) - (wd div 2);
  var dy := (Host.Height div 2) - (hd div 2);

  var Dialog := TInfoDialog.Create(Shade);
  Dialog.Header.Title.Caption := Title;
  Dialog.SetBounds(dx, dy, wd, hd);
  Dialog.fxZoomIn(0.3, procedure ()
  begin
    Dialog.Content.Content.InnerHTML := Content;
    Dialog.Content.UpdateContent();
    Dialog.SetFocus();
  end);

  Dialog.Header.NextButton.OnClick := procedure (Sender: TObject)
  begin
    Dialog.fxFadeOut(0.2, procedure ()
    begin
      TW3Dispatch.Execute( procedure ()
      begin
        Dialog.free;
        Shade.free;
      end, 100);
    end);
  end;

  result := Dialog;
end;

end.

Using the code

The first thing you want to do is to create an instance of TAppInfoDb when your application starts. Remember to add your JSON file and that it’s formatted property, and then use the LoadFrom() method to load in the data:

  // Create our info database and load in the
  // introduction, features etc. JSON datafile
  FInfoDb := TAppInfoDB.Create;
  FInfoDb.LoadFrom('res/JSON1', nil);

The final parameter in the LoadFrom() method is a callback. So if you want to be notified when the file has loaded, just put an anonymous procedure there if you need it.

Showing a dialog with the information is then reduced to looking up the text you need by it’s ID, and firing up the reader dialog for it:

  W3Button1.OnClick := procedure (Sender: TObject)
  begin
    var LInfo: TAppInfoRecord;
    if FInfoDb.GetRecById('introduction', LInfo) then
      TInfoDialog.ShowDialog(LInfo.iiTitle, LInfo.iiText);
  end;

And that’s it! Simple, effective and ready to be dropped into any application. Enjoy!

Making your own DOM events in Smart Pascal

October 20, 2017 Leave a comment

Being able to listen to events is fairly standard stuff in Smart Mobile Studio and JavaScript in general. But what is not so common is to create your own event-types from scratch that fire on a target, and that users of JS can listen to and use.

The word Events in cut out magazine letters pinned to a cork not

Now before you get confused and think this is a newbie post, I am talking about DOM (document object model) level events here; these are quite different from the event model we have in object pascal. So what im talking about is being able to create events that external libraries can use for instance. Libraries written in plain JavaScript rather than Smart Pascal.

Interesting events

While you may think that events like that, which are akin to all the other DOM events, have little or no use – think again. First of all you can dispatch them on any element and event-emitter. So you can in fact register events on common elements like Document. You can then use custom events as a bridge between your Smart code and third party libraries for instance. So if you have written a kick-ass media system and wants to sell it to a customer who only knows JavaScript – then using native JS events can act as a bridge.

Right, let’s look at a little unit I wrote to simplify this:

unit userevents;

interface

uses
  System.Types,
  System.Types.Convert,
  System.JSON,
  SmartCL.System;

type

  IW3Prototype = interface
    procedure AddField(FieldName: string; const DataType: TRTLDatatype);
    function  FieldExists(FieldName: string): boolean;
    procedure SetEventName(EventName: string);
  end;

  TW3CustomEvent = class(TObject, IW3Prototype)
  private
    FName:      string;
    FData:      TJSONObject;
    FDefining:  boolean;
    procedure   SetEventName(EventName: string);
    procedure   AddField(FieldName: string; const DataType: TRTLDatatype);
    function    FieldExists(FieldName: string): boolean;
    function    GetReady: boolean;
  public
    property    Name: string read FName;
    property    Ready: boolean read GetReady;

    function    DefinePrototype(var IO: IW3Prototype): boolean;
    procedure   EndDefine(var IO: IW3Prototype);
    function    NewEventData: TJSONObject;

    procedure   Dispatch(const Handle: TControlHandle; const EventData: TJSONObject);

    constructor Create; virtual;
    destructor  Destroy; override;
  end;

implementation

//############################################################################
// TW3CustomEvent
//###########################################################################

constructor TW3CustomEvent.Create;
begin
  inherited Create;
  FData := TJSONObject.Create;
end;

destructor TW3CustomEvent.Destroy;
begin
  FData.free;
  inherited;
end;

function TW3CustomEvent.GetReady: boolean;
begin
  result := (FDefining = false) and (FName.Length > 0);
end;

procedure TW3CustomEvent.Dispatch(const Handle: TControlHandle; const EventData: TJSONObject);
var
  LEvent: THandle;
  LParamData: variant;
begin
  if GetReady() then
  begin
    if (Handle) then
    begin
      // Check for detail-fields, get javascript object if available
      if EventData <> nil then
      begin
        if EventData.Count > 0 then
          LParamData := EventData.Instance;
      end;

      if (LParamData) then
      begin
        // Create event object with detail-data
        var LName := FName.ToLower().Trim();
        asm
        @LEvent = new CustomEvent(@LName, { detail: @LParamData });
        end;
      end else
      begin
        // Create event without detail-data
        var LName := FName.ToLower().Trim();
        asm
        @LEvent = new Event(@LName);
        end;
      end;

      // Dispatch event-object
      Handle.dispatchEvent(LEvent);
    end;
  end;
end;

procedure TW3CustomEvent.SetEventName(EventName: string);
begin
  if FDefining then
  begin
    EventName := EventName.Trim().ToLower();
    if EventName.Length > 0 then
      FName := EventName
    else
      raise EW3Exception.Create
      ('Invalid or empty event-name error');
  end else
    raise EW3Exception.Create
    ('Event-name can only be written while defining error');
end;

function TW3CustomEvent.FieldExists(FieldName: string): boolean;
begin
  if FDefining then
    result := FData.Exists(FieldName)
  else
    raise EW3Exception.Create
    ('Fields can only be accessed while defining error');
end;

procedure TW3CustomEvent.AddField(FieldName: string; const DataType: TRTLDatatype);
begin
  if FDefining then
  begin
    if not FData.Exists(FieldName) then
      FData.AddOrSet(FieldName, TDataType.NameOfType(DataType))
    else
      raise EW3Exception.CreateFmt
      ('Field [%s] already exists in prototype error', [FieldName]);
  end else
  raise EW3Exception.Create
  ('Fields can only be accessed while defining error');
end;

function TW3CustomEvent.NewEventData: TJSONObject;
const
   MAX_INT_16 = 32767;
   MAX_INT_08 = 255;
begin
  result := TJSONObject.Create;
  result.FromJSON(FData.ToJSON());

  result.ForEach(
    function (Name: string; var Data: variant): TEnumState
    begin
      // clear data with datatype value to initialize
      case TDataType.TypeByName(TVariant.AsString(Data)) of
      itBoolean:  Data := false;
      itByte:
        begin
          Data := MAX_INT_08;
          Data := $00;
        end;
      itWord:
        begin
          Data := $FFFF;
          Data := $0000;
        end;
      itLong:
        begin
          Data := 00000000;
        end;
      itInt16:
        begin
          Data := MAX_INT_16;
          Data := 0000;
        end;
      itInt32:
        begin
          Data := MAX_INT;
          Data := 0;
        end;
      itFloat32:
        begin
          Data := 1.1;
          Data := 0.0;
        end;
      itFloat64:
        begin
          Data := 20.44;
          Data := 0.0;
        end;
      itChar,
      itString:   Data := '';
      else        Data := null;
      end;
      result := esContinue;
    end);
end;

function TW3CustomEvent.DefinePrototype(var IO: IW3Prototype): boolean;
begin
  result := not FDefining;
  if result then
  begin
    FDefining := true;
    IO := (Self as IW3Prototype);
  end;
end;

procedure TW3CustomEvent.EndDefine(var IO: IW3Prototype);
begin
  if FDefining then
    FDefining := false;
  IO := nil;
end;

end.

Patching the RTL

Sadly there was a bug in the RTL that prevented the TJSONObject.ForEach() to function properly. This has been fixed in the update we are preparing now, but there will still be a few days before that is released.

You can patch this manually right now with this little fix. Just go into the System.JSON.pas file and replace the TJSonObject.ForEach() method with this one:

function TJSONObject.ForEach(const Callback: TTJSONObjectEnumProc): TJSONObject;
var
  LData:  variant;
begin
  result := self;
  if assigned(CallBack) then
  begin
    var NameList := Keys();
    for var xName in NameList do
    begin
      Read(xName, LData);
      if CallBack(xName, LData) = esContinue then
        Write(xName, LData)
      else
        break;
    end;
  end;
end;

Creating events

Events come in two flavours: those with data and those without. This is why we have the DefinePrototype() and EndDefine() methods – namely to define what data fields the event should take. If you dont populate the prototype then the class will create an event without it.

Secondly, events dont need to be registered somewhere. You create it, dispatch it to a handle (or element) and if there is an event-listener attached there looking for that name – it will fire.

Ok let’s have a peek:

  // Create a custom, new, system-wide event
  var LEvent := TW3CustomEvent.Create;
  var IO: IW3Prototype = nil;
  if LEvent.DefinePrototype(IO) then
  begin
    try
      IO.SetEventName('bombastic');
      IO.AddField('name', TRTLDataType.itString);
      IO.AddField('id', TRTLDataType.itInt32);
    finally
      LEvent.EndDefine(IO);
    end;
  end;

  // Setup a normal event-listner
  Display.Handle.addEventListener('bombastic',
    procedure (ev: variant)
    begin
      var data := ev.detail;
      if (data) then
        showmessage(JSON.stringify(Data));
    end);

  // Populate some event-data
  var MyData := LEvent.NewEventData();
  MyData.Write('name','John Doe');
  MyData.Write('id', '{F6EB5680-5DC1-422E-8F72-5C60EAC0B46F}');

  // Now send the event to whomever is listening
  LEvent.Dispatch(Display.Handle, MyData);

In the above example I use the Application.Display control as the event-target. There is no special reason for this except that it’s always available. You would naturally create events like this inside your TW3CustomControl (or perhaps the Document element, under a namespace).

You will also notice that any data sent ends up in the “detail” field of the event object. We use a variant datatype since that maps directly to any JS object and also lets us access any property (and create properties for that mapper); so thats why the “ev” parameter in addEventListner() is a variant, not a fixed class.

Well, hope you enjoy the show and happy coding!

PS: Smart now uses an event-manager to deal with input events (mouse, touch), but the other events works like before. Have a look at SmartCL.Events.pas to see some time-saving event classes. So instead of having to use ASM sections and variants, you can use object pascal classes to map any event. 

Smart Mobile Studio and CSS: part 4

October 18, 2017 1 comment

If you missed the previous articles, I urge you to take the time to read through them. While not explicit to the content of this article, they will give you a better context for the subject of CSS and how Smart Mobile Studio deals with things:

Scriptable CSS

If you are into web technology you probably know that the latest fad is so-called css compilers [sigh]. One of the more popular is called Less, which you can read up on over at lesscss.org. And then you have SASS which seem to have better support in the community. I honestly could not care less (pun intended).

So what exactly is a CSS compiler and why should it matter to you as a Smart Pascal developer? That is a good question! First, it doesnt matter to you at all. Not one iota. Why? Because Smart Mobile Studio have supported scriptable CSS for years. So while the JS punters think they have invented gunpowder, they keep on re-inventing the exact same stuff native languages and their programmers have used for ages. They just bling it up with cool names to make it seem all new and dandy (said the grumpy 44 year old man child).

In short a CSS compiler allows you to:

  • Define variables and constant values you can use throughout your style-sheet
  • Define repeating sections of CSS, a poor man’s “for-next block” if you like
  • Merge styles together, which is handy at times

Smart Mobile Studio took it all one step further, because we have a lot more technology on our hands than just vanilla JavaScript. So what we did was to dump the whole onslaught of power from Delphi Web Script – and we bolted that into our CSS linker process. So while the JS guys have a parser system with a ton of cryptic identifiers – we added something akin to ASP to our CSS module. It’s complete overkill but it just makes me giggle like a little girl whenever I use it.

smartstyle

The new themes being created now all tap into scripting to automate things

But how does it work you say? Does it execute with the program or? Nope. Its purely a part of the linking process, so it executes when you compile your program. Whatever you emit (using the Print() method) or assign via the tags, ends up at that location in the output. Think php or ASP but for CSS instead:

  1. Smart takes your CSS file (with code) and feed’s it to DWScript
  2. DWScript runs it, and spits out the result to a buffer
  3. The buffer is sent to the linker
  4. The linker saves the data either as a separate CSS file, or statically links it into your HTML file.

Pretty cool or what!

So what good can that do?

It can do a world of good. For instance, when you create a theme it’s important to use the same values to ensure that things have the same general layout, colors and styles. Since you can now use constants, variables, for/next loops, classes, records and pretty much everything DWScript has to offer – you have a huge advantage over these traditional JS based compilers.

  • Gradients are generated via a pascal function
  • Font names are managed via constants
  • Font sizes can be made uniform throughout the theme
  • Standard colors that you can also define in your Smart code, thus having a unified color system, can be easily shared between the css-pascal and the smart-pascal codebases.
  • Instead of defining the same color over and over again, perhaps in hundreds of places, use a constant. When you need to adjust something you change one value instead of 200 values!

It’s no secret that browser-standards are hard to deal with. For instance, did you know that there are 3 different webkit formats for defining a top-down gradient? Then you have the firefox version, the microsoft version (edge), the microsoft IE version, the opera version and heaven-forbid: the W3C “standard” that nobody seem interested in supporting. Now having to hand-carve the same gradients over and over for the different backgrounds (of a theme) that might use them – that can be both time consuming and infuriating.

Let’s look at some code that can be used in your stylesheet’s straight away. It’s almost like a mini-unit that perhaps should be made external later. But for now, have a peek:

<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span><?pas   const EdgeRounding          = "4px";   const clDlgBtnFace          = "#ededed";   //#############################################   //Fonts   //#############################################   const fntDefaultName = '"Ubuntu"';   const fntSmallSize   = "12px";   const fntNormalSize  = "14px";   const fntMediumSize  = "18px";   const fntLargeSize   = "24px";   const fntDefaultSize =  fntNormalSize;   type   TRGBAText = record     rs: string;     gs: string;     bs: string;     ac: string;   end;   type   TBrowserFormat = (     gtWebkit1,     gtWebkit2,     gtMoz,     gtMs,     gtIE,     gtAny   );   function GetR(ColorDef: string): string;   begin     if ColorDef.StartsWith('#') then     begin       delete(ColorDef, 1, 1);       var temp := Copy(ColorDef, 1, 2);       result := HexToInt(temp).ToString();     end else     result := '00';   end;   function GetG(ColorDef: string): string;   begin     if ColorDef.StartsWith('#') then     begin       delete(ColorDef, 1, 1);       var temp := Copy(ColorDef, 3, 2);       result := HexToInt(temp).ToString();     end else     result := '00';   end;   function GetB(ColorDef: string): string;   begin     if ColorDef.StartsWith('#') then     begin       delete(ColorDef, 1, 1);       var temp := Copy(ColorDef, 5, 2);       result := HexToInt(temp).ToString();     end else     result := '00';   end;   function OpacityToStr(const Opacity: float): string;   begin     result := FloatToStr(Opacity);     if result.IndexOf(',') ><span 				data-mce-type="bookmark" 				id="mce_SELREST_end" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span> 0 then
      result := StrReplace(result, ',', '.')
  end;

  function ColorDefToRGB(const ColorDef: string): TRGBAText;
  begin
    result.rs := GetR(ColorDef);
    result.gs := GetG(ColorDef);
    result.bs := GetB(ColorDef);
    result.ac := '255';
  end;

  function ColorDefToRGBA(const ColorDef: string; Opacity: float): TRGBAText;
  begin
    result.rs := GetR(ColorDef);
    result.gs := GetG(ColorDef);
    result.bs := GetB(ColorDef);
    result.ac := OpacityToStr(Opacity);
  end;

  function GetRGB(ColorDef: string): string;
  begin
    result += 'rgb(';
    result += GetR(ColorDef) + ', ';
    result += GetG(ColorDef) + ', ';
    result += GetB(ColorDef);
    result += ')';
  end;

  function GetRGBA(ColorDef: string; Opacity: float): string;
  begin
    result += 'rgba(';
    result += GetR(ColorDef) + ', ';
    result += GetG(ColorDef) + ', ';
    result += GetB(ColorDef) + ', ';
    result += OpacityToStr(Opacity);
    result += ')';
  end;

  function SetGradientRGBSInMask(const Mask: string; First, Second: TRGBAText): string;
  begin
    result := StrReplace(Mask,   '$r1', first.rs);
    result := StrReplace(result, '$g1', first.gs);
    result := StrReplace(result, '$b1', first.bs);

    if result.contains('$a1') then
      result := StrReplace(result, '$a1', first.ac);

    result := StrReplace(result, '$r2', Second.bs);
    result := StrReplace(result, '$g2', Second.bs);
    result := StrReplace(result, '$b2', Second.bs);

    if result.contains('$a2') then
      result := StrReplace(result, '$a2', second.ac);
  end;

  function GradientTopBottomA(FromColorDef, ToColorDef: TRGBAText;
           BrowserFormat: TBrowserFormat): string;
  begin
    var xfirst := FromColorDef;
    var xSecond := ToColorDef;

    case BrowserFormat of
    gtWebkit1:
      begin
        var mask := "-webkit-gradient(linear, left top, left bottom, color-stop(0, rgba($r1,$g1,$b2,$a1)), color-stop(100, rgba($r2,$g2,$b2,$a2)))";
        result := SetGradientRGBSInMask(mask, xFirst, xSecond);
      end;

    gtWebkit2:
      begin
        var mask := "-webkit-linear-gradient(top, rgba($r1,$g1,$b2,$a1) 0%, rgba($r2,$g2,$b2,$a2) 100%)";
        result := SetGradientRGBSInMask(mask, xFirst, xSecond);
      end;

    gtMoz:
      begin
        var mask := "-moz-linear-gradient(top, rgba($r1,$g1,$b2,$a1) 0%, rgba($r2,$g2,$b2,$a2) 100%)";
        result := SetGradientRGBSInMask(mask, xFirst, xSecond);
      end;

    gtMs:
      begin
        var mask := "-ms-linear-gradient(top, rgba($r1,$g1,$b2,$a1) 0%, rgba($r2,$g2,$b2,$a2) 100%)";
        result := SetGradientRGBSInMask(mask, xFirst, xSecond);
      end;

    gtIE:
      begin
        var mask := "filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=rgba($r1,$g1,$b2,$a1), endColorstr=rgba($r2,$g2,$b2,$a2),GradientType=0)";
        result := SetGradientRGBSInMask(mask, xFirst, xSecond);
      end;

    gtAny:
      begin
        var mask := "linear-gradient(to bottom, rgba($r1,$g1,$b2,$a1) 0%, rgba($r2,$g2,$b2,$a2) 100%)";
        result := SetGradientRGBSInMask(mask, xFirst, xSecond);
      end;
    end;
  end;

  function GradientTopBottom(FromColorDef, ToColorDef: string;
           BrowserFormat: TBrowserFormat): string;
  begin
    (* var xfirst  := ColorDefToRGB(FromColorDef);
    var xSecond := ColorDefToRGB(ToColorDef);
    var mask := ''; *)

    case BrowserFormat of
    gtWebkit1:
      begin
        var mask := "-webkit-gradient(linear, left top, left bottom, color-stop(0, $a), color-stop(100, $b))";
        result := StrReplace(mask, '$a', FromColorDef);
        result := StrReplace(result, '$b', ToColorDef);
      end;

    gtWebkit2:
      begin
        var mask := "-webkit-linear-gradient(top, $a 0%, $b 100%)";
        result := StrReplace(mask, '$a', FromColorDef);
        result := StrReplace(result, '$b', ToColorDef);
      end;

    gtMoz:
      begin
        var mask := "-moz-linear-gradient(top, $a 0%, $b 100%)";
        result := StrReplace(mask, '$a', FromColorDef);
        result := StrReplace(result, '$b', ToColorDef);
      end;

    gtMs:
      begin
        var mask := "-ms-linear-gradient(top, $a 0%, $b 100%)";
        result := StrReplace(mask, '$a', FromColorDef);
        result := StrReplace(result, '$b', ToColorDef);
      end;

    gtIE:
      begin
        var mask := "filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='$a', endColorstr='$b',GradientType=0)";
        result := StrReplace(mask, '$a', FromColorDef);
        result := StrReplace(result, '$b', ToColorDef);
      end;

    gtAny:
      begin
        var mask := "linear-gradient(to bottom, $a 0%, $b 100%)";
        result := StrReplace(mask, '$a', FromColorDef);
        result := StrReplace(result, '$b', ToColorDef);
      end;
    end;
  end;
?>

This code has to be placed on top of your CSS. It should be the very first of the css file. Now let’s make some gradients!

.TW3ButtonBackground {
  background-color: <?pas=clDlgBtnFace?>;
  background-image: <?pas=GradientTopBottom('#FFFFFF','#F0F0F0', gtWebkit1)?>;
  background-image: <?pas=GradientTopBottom('#FFFFFF','#F0F0F0', gtWebkit2)?>;
  background-image: <?pas=GradientTopBottom('#FFFFFF','#F0F0F0', gtMoz)?>;
  background-image: <?pas=GradientTopBottom('#FFFFFF','#F0F0F0', gtMs)?>;
  background-image: <?pas=GradientTopBottom('#FFFFFF','#F0F0F0', gtIE)?>;
  background-image: <?pas=GradientTopBottom('#FFFFFF','#F0F0F0', gtAny)?>;
}

.TW3ButtonBackground:active {
  background-color: <?pas=clDlgBtnFace?>;
  background-image: <?pas=GradientTopBottom('#E7E7E7','#FFFFFF', gtWebkit1)?>;
  background-image: <?pas=GradientTopBottom('#E7E7E7','#FFFFFF', gtWebkit2)?>;
  background-image: <?pas=GradientTopBottom('#E7E7E7','#FFFFFF', gtMoz)?>;
  background-image: <?pas=GradientTopBottom('#E7E7E7','#FFFFFF', gtMs)?>;
  background-image: <?pas=GradientTopBottom('#E7E7E7','#FFFFFF', gtIE)?>;
  background-image: <?pas=GradientTopBottom('#E7E7E7','#FFFFFF', gtAny)?>;
}

Surely you agree that the above makes gradients a lot easier to work with? (and we can simplify it even more later). You can also abstract it further right now by putting the start and stop colors into constants – thus making it super easy to maintain and change whatever style use those constant-colors.

Now let’s use our styles for something. Start a new Smart Mobile Studio Visual Project. Do as mentioned in the previous articles and make the stylesheet visible (project options, use custom css).

Now copy and paste in the code on top of your css-file, then copy and paste in the style-code above at the end of the css-file.

In the Smart IDE, drop a button on the form, then go into the code-editor and locate InitializeForm. Add the following to the procedure:

w3button1.StyleClass := '';
w3Button1.TagStyle.add('TW3ButtonBackground');

Compile and run the progra, and voila, you will now have a button with a nice, gradient background. A gradient that will work in all modern browsers, and that will be easy to maintain and change later should you want to.

Start today

Smart has had support for scriptable CSS files for quite some time. If you go into the Themes folder of your Smart Mobile Studio installation, you will find plenty of CSS files. Many of these use scripting as a part of their makeup. But it’s only recently that we have started to actively use it as it was meant to be used.

But indeed, spend a little time looking at the code in the existing stylesheets, and feel free to play around with the code I have posted here. The sky is the limit when it comes to creative and elegant solutions – so I’m sure you guys will do miracles with it.

Smart Mobile Studio and CSS: part 3

October 12, 2017 Leave a comment

In the first article we looked at some ground rules for how Smart deals with CSS. The most important part is how Smart Mobile Studio maps pascal class names to CSS style names. Simple, but extremely effective.

In the second article we looked at how you should write classes to make styling easy. We also talked about code discipline and that you should never use TW3CustomControl directly, because it makes styling time-consuming and cumbersome.

In this article we are going to cover two things: first we are going to look at probably the most powerful feature CSS has to offer, namely cascades. And then we are going to talk a bit about the new theme system we are working on. Please note that the new theme system is not yet available in the alpha releases yet. Like all things it has to go through the testing stage. All our visual controls needs a little adjustment to support the new themes as well, which doesn’t affect you – but it is time-consuming work.

Cascades

Writing a CSS style for your control should be pretty easy if you have read our previous two articles. But do you really want a large, complex and monolithic style? If you have a look at the stylesheet that ships with Smart Mobile Studio (any of them, there are several), you probably agree that it’s not easy to understand at times. Each control have it’s style definition, that part is clear, but every style includes font, backgrounds, text colors, text shadowing, margins, borders, border shadows, gradients (ad nauseum). Long story short: stylesheets like this is hell to maintain and extremely time-consuming to make.

CSS have this cool feature where you can actually take any number of styles and apply them to the same element. This might sound nutty at first but think it through, because it is going to make your like a lot easier:

  • We can isolate the border style separately
  • We can have multiple border styles and pick the ones we want, rather than a single, hardcoded and fixed version
  • We can define the backgrounds, any number of them, as separate styles

That doesn’t sound to bad does it? But wait, there is more!

Remember how I told you that animations are also defined in CSS? Since CSS allows you to add multiple styles to a control, this also means you can define a style with an animation – and then just add it when you want something to happen, and then remove the style when you don’t need it any more.

You have probably seen these spinners that websites use right? So while the website is loading something, a circle or dot keeps rotating to signal that work is being performed in the background? Well, that’s pretty easy to achieve when you understand how cascades work. You just define the animation, use it in a style — and then add that style to your control. When you want to stop this behavior you just remove the style. Thats it!

But let’s start with something simple. Let’s define a border and a background and apply that to a control using code. And remember: styles you add does not exclude the initial style. Like we talked about earlier – Smart will take the pascal classname and use a CSS style with that name. So whatever you add to the control is extra. Which is really powerful!

You probably want to start a new visual project for this one. And remember to pick a theme in the project options (in my case I picked the “Android-HoloLight.,css” theme, just so you know), save the project, then go back into the options and check the “Use custom theme” checkbox. Again exit the dialog and click save – the IDE will now create a copy of whatever theme you picked and give you direct access to it from the IDE.

Remember to check the custom theme in project options

With that out of the way you should now have a fresh, blank visual application with an item called “Custom CSS” in the project manager list. Now double click on that like before so we can get cracking, and go down to the end of the file. Add the following text:

<?pas   const EdgeRounding = "4px";   const clDlgBtnFace = "#ededed"; ?>

.TMyButtonBorder {
  border-radius:  <!--?pas=EdgeRounding?-->;
  border-top:     1px solid rgba(250, 250, 250, 0.7);
  border-left:    1px solid rgba(250, 250, 250, 0.7);
  border-right:   1px solid rgba(240, 240, 240, 0.5);
  border-bottom:  1px solid rgba(240, 240, 240, 0.5);

  -webkit-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
     -moz-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
          box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
}

.TMyButtonBorder:active {
  border-radius:  <!--?pas=EdgeRounding?-->;
  border-top:     1px solid rgba(240, 240, 240, 0.5);
  border-left:    1px solid rgba(240, 240, 240, 0.5);
  border-right:   1px solid rgba(250, 250, 250, 0.7);
  border-bottom:  1px solid rgba(250, 250, 250, 0.7);

  -webkit-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
     -moz-box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
          box-shadow: 0px 0px 1px 1px rgba(81, 81, 81, 0.8);
}

.TMyButtonBackground {
  background-color: <!--?pas=clDlgBtnFace?-->;
  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,color-stop(0, rgb(255, 255, 255)),color-stop(1, rgb(240, 240, 240)));
  background-image: -webkit-repeating-linear-gradient(top,rgb(255, 255, 255) 0%,rgb(240, 240, 240) 100%);
  background-image: repeating-linear-gradient(to bottom,rgb(255, 255, 255) 0%,rgb(240, 240, 240) 100%);
  background-image: -ms-repeating-linear-gradient(top,rgb(255, 255, 255) 0%,rgb(240, 240, 240) 100%);
}

.TMyButtonBackground:active {
  background-color: <!--?pas=clDlgBtnFace?-->;
  background-image: -webkit-gradient(linear, 0% 0%, 0% 100%,color-stop(0, rgb(231, 231, 231)),color-stop(0.496, rgb(231, 231, 231)),color-stop(0.5, rgb(231, 231, 231)),color-stop(1, rgb(255, 255, 255)));
  background-image: -webkit-repeating-linear-gradient(top,rgb(231, 231, 231) 0%,rgb(231, 231, 231) 49.6%,rgb(231, 231, 231) 50%,rgb(255, 255, 255) 100%);
  background-image: repeating-linear-gradient(to bottom,rgb(231, 231, 231) 0%,rgb(231, 231, 231) 49.6%,rgb(231, 231, 231) 50%,rgb(255, 255, 255) 100%);
  background-image: -ms-repeating-linear-gradient(top,rgb(231, 231, 231) 0%,rgb(231, 231, 231) 49.6%,rgb(231, 231, 231) 50%,rgb(255, 255, 255) 100%);
}

Now this might look like a huge mess, but most of this is gradient coloring. If you look closer you will notice that it’s the exact same gradients but with different browser prefixing. This is to ensure that things look exactly the same no matter what browser people use. Making gradients like this is easy, there are a ton of websites that deals with this. One of my favorites is ColorZilla, which will make all this code for you.

If you don’t know your CSS you might be wondering – what is that :active postfix? You have two declarations with the same name – but one of them has :active appended to it? The active selector (which is the fancy name) simply tells the browser that whenever someone interacts with an element that has this state – it should switch and display the :active one instead. Typically a button will look 3d when it’s not pressed, and sunken when you press it. This is automated and you just need to define how an element should look when it’s pressed via the :active postfix (note: since different controls do different things, “active” can hold different meanings. But for most controls it means when you click it, touch it or otherwise interact with it).

And now for the big question: what on earth is that first segment that looks like pascal code? Well, that is pascal code! All your CSS stylesheets are processed by a scripting engine and only the result is actually given to the linker. So yes indeed, you can write both functions and procedures and use them to make your CSS like easier (take that Adobe!).

What we have done in the pascal snippet is to define a standard rounding value. That way we don’t have to update 300 places where border-radius is set (or you can blank it out if you dont want round edges). We change the constant and it will spread to any style that uses it. Clever huh?

OK, lets use our styles for something fun! What we have here is a nice border definition, both active and non-active, and also a nice background. Let’s use cascades to change how a button looks like!

What is a button anyways

If you switch to Form1 in your application and place a TW3Button on the form, we can start to work with it. The first thing you need to do is to clear out the styleclass so that Smart doesn’t apply the default styling. That way it’s easier to see what happens. So here is how it looks when I just run and compile it:

cascade_01

Now go into the procedure TForm1.InitializeForm() in the unit Form1. And write the following code:

procedure TForm1.InitializeForm;
begin
  inherited;

  // Remove the default styling
  w3button1.StyleClass := '';

  // Add our border
  w3Button1.TagStyle.Add('TMyButtonBorder');

  // And add our background
  w3button1.TagStyle.Add('TMyButtonBackground');

  // Make the font autosize to the container
  w3button1.font.AutoSize := true;
end;

Now save, compile and run and we get the following result:

cascade_02

Suddenly our vanilla Android button have all the majesty of Ubuntu Linux! And all we did was define a couple of styles and then manually add them. We could of course have stuffed all of this into a single, monolithic style – no shame in that, but im sure you agree that by fragmenting border from background, and background from content – we have a lot of power on our hands!

As an experiment: Remove the line that clears the StyleClass string and see what happens. When you click the button the browser actually blends the two backgrounds together! Had we used RGBA values in our background gradients – the browser would have blended the standard theme button with our added styles. It’s pretty frickin’ awesome if you ask me.

Here is a more extensive example of our upcoming Ubuntu Linux theme. This is not yet ready for alpha, but it represents the first theme system where all our controls makes use of multiple styles. It looks and behaves beautifully.

cascade_03

From the labs: A Ubuntu Linux inspired theme that is done using cascading exclusively

Brave new themes

So far we I have written exclusively about things you can do right now. But we are working every single day on Smart Mobile Studio, and right now my primary task is to finish a working prototype of our theme engine. As you can see from the picture above we still have a few controls that needs to be adjusted. In the previous article I mentioned the importance of respecting borders, padding and margins from the stylesheet; well let’s just say that I have learnt that the hard way.

Most of our controls were written with no consideration regarding these things, we use an absolute boxing model after all so we don’t have to. But not having to do something and taking the time to do something is often the difference between quality and fluff. And this time we are doing things right every step of the way.

Much like the effect system (SmartCL.Effects.pas) the theming system makes use of partial classes. This means that it simply doesn’t exist until you include the unit SmartCL.Theme in your project.

With the theme unit included (actually it’s included by the RTL so it’s there no matter what, but it wont be visible unless you include it in your unit scope) TW3CustomControl suddenly gains a couple of properties and methods:

  • ThemeBorder property
  • ThemeBackground property
  • ThemeReset() method

When you create custom controls you can (if you need to) define a style for that control, but this time you don’t need to define borders or backgrounds. A style is now reduced to padding, margins, some font stuff and perhaps shading if you need that. Then simply assign a ThemeBorder and ThemeBackground in the StyleTagObject() method of your control – and you can make your control look and feel at home with everything else using that theme.

Lets look at the standard borders first:

  • btNone
  • btFlatBorder
  • btControlBorder
  • btContainerBorder
  • btButtonBorder
  • btDialogButtonBorder
  • btDecorativeBorder
  • btEditBorder
  • btListBorder
  • btToolContainerBorder
  • btToolButtonBorder
  • btToolControlBorder
  • btToolControlFlatBorder

And then we have pre-defined backgrounds matching these:

  • bsNone
  • bsDisplay
  • bsControl
  • bsContainer
  • bsList
  • bsListItem
  • bsListItemSelected
  • bsEdit
  • bsButton
  • bsDialogButton
  • bsDecorative
  • bsDecorativeInvert
  • bsDecorativeDark
  • bsToolContainer
  • bsToolButton
  • bsToolControl

And as mentioned, you can assign these to any control you like

Same defines, many themes

The cool thing about the new system is that it’s not just one theme. We start with one of course but ultimately all our themes will follow the new styling scheme. The goal is to use pure constants, much like what Delphi did with colors (clBtnFace and so on) so that we only need to change the coloring constants – and then the changes will spread to the whole theme.

You as a Smart Mobile Studio developer don’t need to care about the details. As long as you stick to the normal types listed above, your custom controls will always match whatever theme is being used. And it will always look good and match the theme.

cascade_04

Still  few controls to style, but I’m sure you agree that its starting to look nice

Well that has been a rather long introduction to Smart and CSS. I hope you have enjoyed reading it. I will keep you all posted on the progress we make, which is moving ahead very fast these days!

Personally I can’t wait until Smart Mobile Studio 3.0 is ready, and I hope people value the effort we have put into this. And we are just getting started!

Smart Mobile Studio and CSS: part 2

October 11, 2017 Leave a comment

In my previous article we had a quick look at some fundamental concepts regarding CSS. These concepts are not unique to Smart Mobile Studio, but simply just the way things work with CSS in general. The exception being the way Smart maps your pascal class-name to a CSS style of the same name.

To sum up what we covered last time:

  • Smart maps a control’s class-name to a CSS style with the same name. So if your control is called TMyControl, it expects to find a CSS style cleverly named “.TMyControl”. This works very well and is easy to apply.
  • CSS can affect elements recursively, so you can write CSS that changes the appearance and behavior of child controls. This technique is typically used if you inject HTML directly via the InnerHTML property
  • CSS is cascading, meaning that you can add multiple styles to the same control. The browser will merge them into a final, computed style. The rule of thumb is to avoid styles that affect the same properties
  • CSS can define more than colors; things like animations, gradients, animated gradients and whatnot can all be defined in CSS
  • Smart Mobile Studio ships with units for creating, applying and working with CSS from your pascal code. It also ships with effect classes that can trigger defined CSS animations.
  • Smart Mobile Studio has a special effect unit (SmartCL.Effects) that when added to the uses list, adds quite a few effect procedures to TW3MovableControl. These effect methods are prefixed with Fx (ex: fxMoveTo, fxFadeOut, fxScaleTo).

Best practices

When you write your own controls, don’t cheat. I have seen a lot of code where people create instances of TW3CustomControl for instance, and then jump through hoops trying to make that look good. TW3CustomControl is a base-class, it’s designed to be inherited from – not used “as is”. I can understand the confusion to some extent, I mean since TW3CustomControl manage a DIV by default – people with some HTML background probably think creating one of these is the same as making a DIV. But by doing so they essentially short-circuit the whole theme-system since (as underlined above) all pascal classes will use a style with the same name. And TW3CustomControl is just a transparent block of nothing.

No matter how small a thing you are creating, always inherit out your own classes and give them distinct names. This is extremely important with regards to styling, but also as a discipline of writing readable, maintainable code. Using TW3CustomControl all over the place will make the code a mess to maintain – let alone share with others who don’t have a clue what you are doing.

A practical example

To show how easy it is to style things once you have written code that uses distinct class names and clear-cut structure, let’s take the time to write a little list-box. Nothing fancy, just a control that can take X number of child rows, style them and display the items vertically like a list. Let’s begin with the class code:

type

  // Define an exception especially for our control
  EMyControl = class(EW3Exception);

  // Define a baseclass, that way we can grow in the future
  TMyChild = class(TW3CustomControl)
  end;

  // Define a class type, good when working with lists or
  // collections of elements that share ancestors
  TMyChildClass = class of TMyChild;

  // Define a clear child class, that way we can apply
  // styling without problems
  TMyChildRed = class(TMyChild)
  end;

  // Create a custom version with sensitive properties only
  // available to ancestors. Here we place these in the protected
  // section (items and count)
  TCustomMyControl = class(TW3CustomControl)
  protected
    property Items[const index: integer]: TMyChild
            read ( TMyChild(GetChildObject(Index)) ); default;
    property Count: integer read ( GetChildCount );

    procedure Resize; override;
  public
    function Add(const NewItem: TMyChild): TMyChild; overload;
    function Add(const &Type: TMyChildClass): TMyChild; overload;
  end;

  // The actual control we use, this is the one we write
  // CSS code for and that we create and use in our applications.
  // This step is optional ofcourse, but it has it's perks
  TMyControl = class(TCustomMyControl)
  public
    property Items;
    property Count;
  end;

Implementation

procedure TCustomMyControl.Resize;
var
  LCount: integer;
  bl, bt, br, bb: integer;
  wd, dy: integer;
  LItem: TMyChild;
begin
  inherited;

  // Avoid doing work if there is nothing there
  LCount := GetChildCount();
  if LCount > 0 then
  begin
    // Get the values of the borders/padding etc from CSS
    // We need to respect these when working in the client-rect
    bl := Border.Left.Width + Border.Left.Padding + Border.Left.Margin;
    bt := Border.Top.Width + Border.Top.Padding + Border.Top.Margin;
    br := Border.Right.Width + Border.Right.Padding + Border.Right.Margin;
    bb := Border.Bottom.Width + Border.Bottom.Padding + Border.Bottom.Margin;

    // This is the maximum width an element can have without
    // bleeding over whatever styling is present
    wd := ClientWidth - (bl + br);

    // Start at the top
    dy := bt;

    // Now layout each element vertically
    for var x := 0 to LCount-1 do
    begin
      LItem := Items[x];
      LItem.SetBounds(bl, dy, wd, LItem.Height);
      inc(dy, LItem.Height);
    end;
  end;
end;

function TCustomMyControl.Add(const &Type: TMyChildClass): TMyChild;
begin
  if &Type <> nil then
  begin
    // Start update
    BeginUpdate();

    // Create our control & return it
    result := &Type.Create(self);

      // Define that a resize must be issued
    AddToComponentState([csSized]);

    // End update. If update was not called elsewhere
    // the resize will happen now. If not, it will happen
    // when the last EndUpdate() is called (clever stuff!)
    EndUpdate();
  end else
  raise EMyControl.Create('Failed to add item, classtype was nil error');
end;

function TCustomMyControl.Add(const NewItem: TMyChild): TMyChild;
begin
  result := NewItem;
  if NewItem <> nil then
  begin
    // Are we the current parent?
    if not Handle.Contains(NewItem.Handle) then
    begin
      // Remove from other parent
      NewItem.RemoveFrom();

      // Start update
      BeginUpdate();

      // Add child to ourselves
      RegisterChild(NewItem);

      // Define that a resize must be issued
      AddToComponentState([csSized]);

      // End update. If update was not called elsewhere
      // the resize will happen now. If not, it will happen
      // when the last EndUpdate() is called (clever stuff!)
      EndUpdate();
    end;
  end else
  raise EMyControl.Create('Failed to add item, instance was nil error');
end;

If you are wondering about the strange property getter’s, where we don’t call a function but instead have some code inside (), that is another perk of Smart Pascal. The GetChildObject() method is a part of TW3TagContainer which ultimately TW3CustomControl inherits from, so we simply typecast and call that. This is perfectly legal in Smart as long as it’s a simple function or expression with matching type.

And now lets look at the CSS for our new control and its red child:

.TMyChildRed {
  padding: 2px;
  background-color: #FF0000;
  font-family: "Ubuntu", "Helvetica Neue", Helvetica, Verdana;
  color: #FFFFFF;
  border-bottom: 1px solid #AA0000;
}

.TMyControl {
  padding: 4px;
  background-color: #FFFFFF;
  border: 1px solid #000000;
  border-radius: 4px;
  margin: 1px;
}

We need to populate the list before we can see anything of course, so if we add the following code to InitializeForm() things will start to happen:

  // Lets create our control. We use an inline variable
  // here since this is just an example and I wont be
  // accessing it later. Otherwise you want to define it
  // as a form field in the form-class
  var LTemp := TMyControl.Create(self);
  LTemp.SetBounds(100, 100, 300, 245);

  // We call beginupdate here to prevent the
  // control calling Resize() for every elements.
  // It will only resize when the last EndUpdate (below)
  // is called. Also see how we use this inside the
  // procedures that needs to force a change
  LTemp.BeginUpdate();
  for var x := 1 to 10 do
  begin
    // Create a new "red" child
    var NewItem := LTemp.Add(TMyChildRed);

    // Fill the content with something
    NewItem.InnerHTML := 'Item number ' + x.ToString();
  end;
  LTemp.EndUpdate;

The end result might not look fancy but it demonstrates some very basic concepts that is fundamental to working with Smart Mobile Studio. Namely how to define CSS that map to your classes, and also how to use BeginUpdate() and EndUpdate() to prevent a ton of calls to Resize() when adding multiple items.

mycontrol

It wont win any prices for looks, but it demonstrates some very important principles when writing controls

Effects

Being able to style and layout child elements in your own controls is cool, but applications can quickly become dull and static without visual feedback. This is why I wrote the effect unit, namely to make it so easy to use GPU powered effects in your applications that anyone can make stuff move around.

So let’s make a little change to our mini-list control. When a user press one of the items, we want the item to scale up while the mouse is pressed, and then gracefully shrink back to normal size when you let go of the mouse. We could make it spin around for that matter, but let’s start with something a bit more down to earth.

This is where defining our own classes comes into play. We are going to add some code to our root child class, TW3MyChild, because this behavior should be universal. For sake of simplicity im just going to use the controls own events for this purpose. So Let’s expand our ancestor class to the following:

  TMyChild = class(TW3CustomControl)
  private
    FDown: boolean;
    procedure HandleMouseDown(Sender: TObject; Button: TMouseButton;
                        Shift: TShiftState; X, Y: integer);
    procedure HandleMouseUp(Sender: TObject; Button: TMouseButton;
                        Shift: TShiftState; X, Y: integer);
  protected
    procedure InitializeObject; override;
  end;

The implementation needs to keep track of when a scale is in process, otherwise we can scale the element out of sync with the UI. Again this is just an example, there are many ways to keep track of things but let’s keep it simple:

procedure TMyChild.InitializeObject;
begin
  inherited;
  self.OnMouseDown := HandleMouseDown;
  self.OnMouseUp := HandleMouseUp;
end;

procedure TMyChild.HandleMouseDown(Sender: TObject; Button: TMouseButton;
                    Shift: TShiftState; X, Y: integer);
begin
  if Button = TMouseButton.mbLeft then
  begin
    if not FDown then
    begin
      FDown := true;
      fxScaleUp(1.0, 1.5, 0.3);
    end;
  end;
end;

procedure TMyChild.HandleMouseUp(Sender: TObject; Button: TMouseButton;
                    Shift: TShiftState; X, Y: integer);
begin
  if Button=TMouseButton.mbLeft then
  begin
    if FDown then
    begin
      fxScaleDown(1.5, 1.0, 0.3, procedure ()
      begin
        FDown := false;
      end);
    end;
  end;
end;

The result? Well, when we press one of the items in our list that items grows to 1.5 of it’s original size (the parameter names for the effects are easy to understand). So we scale from 1.0 (normal size) to 1.5, and we tell the system to execute this transition in 0.3 seconds.

All the effect methods have an optional callback procedure you can use (anonymous procedure) that will fire when the effect is finished. As you can see in the HandleMouseUp() method we use this to reset the FDown field, allowing the effect to be executed again on the next click.

mycontrol2

Smooth scaling via hardware

Next time

Hopefully the past two articles have been interesting. In our next article we will look at some of the stuff we are building in our labs. That means talking about styling and how we are working to improving this (read: not yet available but in the process).

In the meantime, have a peek at what you can do with proper use of CSS effects

mycontrol3

You can do some amazing things with effects and JS (click image)

Happy coding!

Smart Mobile Studio and CSS: part 1

October 9, 2017 Leave a comment

If I were to pinpoint a single feature of the modern HTML5 rendering engine that demands both respect and care, it would have to be CSS. While it’s true that no other piece of technology has seen the level of development as “the browser” for the past 20 years – the piece that has seen the most is without a doubt CSS.

When we designed Smart Mobile Studio styling became an issue almost from the start. I knew CSS well and I was reluctant to create a theming engine for Smart, because it’s so easy to fall into the same pit that Macromedia once did; namely that you end up boxing the user into a corner with the best of intentions. So instead of writing a large and complex styling engine, we designed the simplest possible system we could imagine – and left the rest to our users.

For advanced users that know their way around CSS, HTML and Javascript as well as they know object pascal, this has been a great bonus. But for users that come directly from Delphi or Lazarus with little or no background in web technology – CSS has been a black box they would rather not touch. Which is really sad because well written CSS makes up as much as 40% of a good application. If not more (!).

CSS for smarties

Most Delphi developers in their 40’s who never really got into Web development (because they were too busy coding in Delphi) probably think of CSS as a coloring language. I keep on hearning the same thing over and over “CSS? You can set colors, background pictures and stuff”. In part they are right, back in the late 90s that is. Yes CSS allows you to define how things should be colored and stuff like that – but CSS have evolved side by side with modern JavaScript and HTML, and as such it’s capable of a lot more than just setting colors.

The most important features you want to know about is:

  • You can define gradients as backgrounds, not just a static color or picture
  • You can use alpha blending (rgba) rather than fixed colors (#rrggbb)
  • You can define elaborate animations
  • Animations can use most CSS properties: colors, size, opacity and / or position
  • CSS is recursive, you can define rules that applies to child elements of a control using a style. You can also target child elements by name.
  • CSS is no longer just 2D but also 3d (Note: Sprite3d has been ported to Smart, see SmartCL.Sprite3d.pas), so you can place elements in 3d space
  • Rotation is now standard, be it purely 2d or 3d
  • You can define transitions directly on a property, like how long a move should take
  • CSS is cascading (hence the term “cascading style sheets”)
  • CSS allows elements to inherit properties from their parents, which is extremely handy if you want all child elements to use the font you set in the first, actual control you are making.
  • Filters! You can now apply great graphics filters on your content
  • CSS is powered by the GPU (graphical processing unit) and makes full use of the graphics chipset on the target device

This is just the tip of the iceberg of what modern CSS has to offer, but before you dive in, lets look at some fundamental facts you need to know when working in Smart Mobile Studio.

Class to style mapping

Have you ever wondered how a custom control in Smart knows what css style to use? For instance, if you drop a TW3Panel on a form – where does the style come from? Is there some magical spell that automatically assigns a piece of css to the visual control? Sure you know there is a CSS file that’s generated for the application, and you can pick between a few themes, but how is the panel CSS style attached to an instance of TW3Panel?

Like I mentioned above, we tried to leave CSS alone in fear of boxing the user into a system that was too limited or too lose; But we did one stroke of genius, and that was to automatically map the pascal class-name to the CSS class name. And this turned out to be a very efficient method of dealing with styling.

So to make this crystal clear: Let’s say you create a new control called TMyControl right? When you create an instance of that control in your pascal code, it will automatically try to use a CSS style with the same name. So far that is the only rule we have enforced. But it is extremely important to know this and understand how powerful that is.

Recursive CSS

The next thing I want to explain is how you can define recursive styles. Again Let’s say you have created a new custom-control called TMyControl. You go into your project options, click on “Linker” from the treeview on the left – and then check the “Use custom theme” checkbox. This makes a copy of whatever theme you picked for your application and stores that copy within your project file. When you click “OK” to exit the project options dialog and click “Save”, your project will get a new item cleverly named “Custom CSS”. This is where you add your own styles.

projectOptions

So ok, we have a control called TMyControl and now we want to style it. So we double-click on the “Custom CSS” node in our project, and we are greeted with a ton of weird looking CSS code.

So let’s go ahead and create a style with the same name as our pascal class, that way they will find each other:

.TMyControl {
  background-color: #FF0000;
}

Click “Save” again (or “CTRL + S” on your keyboard) and compile + run your program. If you had created an instance of TMyControl on your form, you should now see a red box. Not much to look at just yet, but we will deal with that later.

But a blank control is really not much fun. So for sake of argument let’s say you want to display a header inside your control. So you create a second class called TMyHeader and then create that in the constructor of TMyControl. And we want to place it at the top of our TW3MyControl display, 32 pixels high. So we end up with something like this:

unit Unit1;

interface

uses
  System.Types, System.Colors, System.Types.Convert,
  SmartCL.System, SmartCL.Graphics, SmartCL.Components, SmartCL.Forms,
  SmartCL.Fonts, SmartCL.Borders;

type

// our header
TMyHeader = class(TW3CustomControl)
end;

// our new cool control
TMyControl = class(TW3CustomControl)
private
  FHeader: TMyHeader;
protected
  procedure InitializeObject; override;
  procedure FinalizeObject; override;
  procedure Resize; override;
public
  property Header: TMyHeader read FHeader;
end;

implementation

procedure TMyControl.InitializeObject;
begin
  inherited;
  FHeader := TMyHeader.Create(self);
end;

procedure TMyControl.FinalizeObject;
begin
  FHeader.free;
  inherited;
end;

procedure TMyControl.Resize;
begin
  inherited;
  FHeader.SetBounds(0, 0, ClientWidth, 32);
end;

end.

At this point we can ofcourse do the same as we just did, namely to add a CSS style called “.TMyHeader” and define our header there – which is also how you should do things. But there will be cases where you dont have this fine control over things – perhaps you are using a library or maybe you are generating html and just injecting it via the innerHTML property? Who knows, but the point is we can actually write CSS that targets ANY child element without knowing much about it. And we do that using something called a CSS selector.

So let’s say I want to color all children of TMyControl, regardless of type, green (just for the hell of it). Well, then I can do like this in our CSS:

.TMyControl {
  background-color: #FF0000;
}

/* Color all (*) children green! */
.TMyControl > * {
  background-color: #00FF00;
}

We can also be more spesific and say: Color the first P (paragraph) inside the first DIV child green! And I should mention that the default tag that TW3CustomControl manages is a DIV. Well, to target the text paragraph inside the first child we would write:

.TMyControl {
  background-color: #FF0000;
}

/* Color the P inside the first DIV green! */
.TMyControl > :first-child > P {
  background-color: #00FF00;
}

Now you are probably wondering, but where did that “P” come from? There is no paragraph in my code? Well, like mentioned we can add that via the innerHTML property if we like:

procedure TMyControl.InitializeObject;
begin
  inherited;
  FHeader := TMyHeader.Create(self);
  FHeader.innerHTML := '<p>This is the text!</p>';
end;

Note: WordPress has a tendency to kill html tags, so if you dont see a paragraph tag in the string above, wordpress gobbled it up.

Now the point of this code so far has not been to teach how to write good code. In fact, you really should try to avoid code like this unless you really know what you are doing. The point here was to show you how CSS can be made to operate on structures. If a style is selected by a control, selector code like I demonstrated above will kick-in automatically and you can do some pretty amazing things with it. Just changing the background doesnt really give this system the credit it deserves. You can add animations, change the row-color of every odd listitem, add a glowing rectangle only around a particular element — the sky is the limit!

The cascading part

This is probably one of the simplest features ever, yet it’s one that people fail to remember when they sit down to write CSS code. So please make a note of this one because it will save you so much time.

So far we have looked at single styles attached to a control. But truth be told, you can assign 100 styles to the same control – at the same time (!). What happens is that the rendering engine will merge them all together and draw whatever the outcome is onto the display. The only rule is: they must not collide. If you define two backgrounds the style engine will try to merge them, but odds are only one of them will survive.

But let’s stop for a minute and think about what this means:

  • Instead of one large, monolithic style for a control, you can divide it into smaller and more managable parts
  • You can define borders in one style, background in another and fonts in a third
  • You can have two separate animations running at the same time targeting the same element – and as long as they dont manipulate the same properties – it will work just fine.

It can take a while for the true potential of this to really sink in.

To give you a practical example: This is how Smart Mobile Studio deals with disabled states. Whenever you disable a control, a style called “DisabledState” is added to the control. This takes over opacity, disables mouse and touch events, changes the mouse cursor and draws a diagonal pattern that covers the control.

When the control is enabled again, we simply remove the style and it reverts back to normal. It’s pretty cool if I say so myself!

TW3CustomControl, which is the foundation for all visible controls on the palette, has a property called “CSSClasses”. This has been deprecated and replaced by “TagStyles”, but both still works. This class gives you easy methods for adding, removing and checking if any extra styles (apart from the default style) has been added.

It looks like this:

TW3TagStyle = class(TW3OwnedObject)
  private
    FCache:     TStrArray;
    FCheck:     integer;
    FHandle:    TControlHandle;
  protected
    function    GetCount: integer; virtual;
    function    GetItem(const Index: integer): string; virtual;
    procedure   SetItem(const Index: integer; const Value: string); virtual;
    {$IFDEF USE_CSS_PARSER}
    procedure   ParseToCache(CssStyleText: String); virtual;
    {$ENDIF}
    procedure   CacheToTag; virtual;
    procedure   TagToCache; virtual;
    function    AcceptOwner(const CandidateObject: TObject): Boolean; override;
  public
    property    Handle: TControlHandle read FHandle;
    property    Count: integer read GetCount;
    property    Items[const Index: integer]: string read GetItem write SetItem;

    procedure   Update; virtual;

    class procedure AddClassToControl(const Handle: TControlHandle; CssClassName: string);
    class function ControlContainsClass(const Handle: TControlHandle; CssClassName: string): boolean;
    class procedure RemoveClassFromControl(const Handle: TControlHandle; CssClassName: string);

    function    Contains(const CssClassName: string): boolean;
    function    Add(CssClassName: string): integer;
    function    Remove(const Index: integer): string;
    function    RemoveByName(CssClassName: string): string;
    function    IndexOf(CssClassName: string): integer;
    function    ToString: string;
    procedure   Clear;
    constructor Create(AOwner: TObject); override;
    destructor Destroy; override;
  end;

So Let’s say you have a fancy animated background you want to show while doing something, then simply call the AddClassToControl() method.

I should mention that I have used the word “style” so far to avoid confusion. A css definition is not really called a style in HTML land, but a style class. I just used style to make the distinction easier for everyone.

Summing up

In this short article we have had a look at the fundamental rules of CSS. We have looked at how a control match and finds it’s css style, how to define your own styles. We also brushed into the concept of CSS selectors, which can recursively affect child elements in your controls — and last but not least, we have talked about cascading and how you can assign multiple styles to the same element.

In our next article we are going to look at some of the next-generation features in our RTL regarding styles, and also talk a bit about what we have cooking in our labs. Needless to say, CSS is going to become easier and much more powerful in the weeks to come, so it’s important that you pick up on the basics now!

Homework (if you need it) is to have a look at the CSS pascal classes in our RTL. They contain a lot of nice features, helper classes and more to generate platform independent CSS code that you can use right now.

You want to go through the following units:

  • SmartCL.CSS.StyleSheet
  • SmartCL.CSS.Classes
  • SmartCL.Effects

Have a peek at the methods “TSuperStyle.AnimGlow” and see how CSS can be written as code, although in most cases it’s easier to just write it as vanilla CSS. You will also be happy to know that stylesheets can be created as normal pascal objects, so you dont have to put all your eggs into one basket.

The last unit in that list, SmartCL.Effects is special. It uses something called “partial classes” which is not supported by Delphi or Lazarus. In general it means that you can spread the declaration of a class over many units.

When you add SmartCL.Effects to your form’s uses clause, TW3CustomControl suddenly gains a ton of effect methods (prefixed by “fx”). These are CSS animation effects that you can call on any control. You can also daisy-chain them together and they will execute in sequence. Again this demonstrates what you can achieve with CSS and some clever programming.

Until next time!

Webfonts in Smart Mobile Studio

October 4, 2017 2 comments

Webfonts is something I have wanted to include in Smart for ages now. It’s such a simple feature, but when you use it right it becomes powerful and assuring.

What is a webfont?

Well, you know how you have to define what fonts you use under html right? And if the user doesn’t have that font, you have fallback fonts it can use instead? If you have worked with web technology for a while you no doubt know how haphazardly the results can be. You would think that a font like “verdana” looks exactly the same from system to system -but that is not always the case.

webfonts

Adding webfonts to your project is very easy

Apple for instance have their own tweak on just about every typeface; Linux often have alternatives that looks good but might not be 100% identical (on some distros, Linux is not exactly “one thing”). And Microsoft tends to live in their own universe.

The solution? Webfonts. In short it means that the browser will double-check if the user has the font you need installed. And if they don’t – the font is downloaded from a font provider (like Google) when your web application starts.

Fonts, glorious fonts!

The result is that your application will look and feel the same no matter what device is used. And that is a very important thing – because coding flexible, adaptive UI’s that should work on Android, iOS, TV’s and ordinary browsers is no picnic to begin with. Having to worry that your fancy Ubuntu based UI is rendered using vanilla Sans-Serif (read: looking like something out of the 80s) has been an ever-present reality for two decades now.

webfonts2

Plenty of good looking fonts on Google

If you head over to https://fonts.google.com and take a gander at the fonts available, I’m sure you agree that this is a fabulous idea. And as always, when you combine good-looking fonts with some cool CSS – the results can be spectacular.

Still in Alpha

We are still in Alpha for Smart Mobile Studio 3.0, so there might be hiccups along the way. But all in all you should be able to enjoy webfonts in our next update.

Cheers!