Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML5

Sciter - multiplatform embeddable HTML/CSS/scripting UI engine

4.97/5 (25 votes)
11 Nov 2015CPOL9 min read 111.5K   1.7K  
Basics of Sciter embedding principles, high-level overlook of Sciter SDK structure.

Introduction

Sciter is an HTML/CSS/scripting engine designed to render modern desktop application UI. It's a compact, single dll/dylib/so file (4-8 mb), engine without any additional dependencies. It works on Microsoft Windows (XP and above), Apple OS X (v 10.7 and above) and Linux/GTK (GTK v 3.0 and above). Sciter uses Direct2D GPU accelerated graphics on modern Windows versions and GDI+ on XP. On OS X, it uses standard CoreGraphics primitives, while the Linux version uses Cairo.

Background

Sciter is used in production by various companies since 2006. Currently, Sciter-based UI runs on over 120 millon PCs and Macs worldwide. Therefore, it's likely that you already have some application that uses Sciter under the hood on your machine.

Sciter uses HTML5 set of elements, implements CSS level 2.1 in full, plus the most popular features of CSS level 3. It also contains custom CSS extensions that are required to support desktop UI cases. For example, flex units and various layout managers. Sciter is the next major version of HTMLayout with the addition of hardware acceleration, multiplatform support, and TIScript.

In this article, I describe a simple application that uses the same source code and runs on all supported platforms.

This example is a part of the official Sciter SDK distribution that is available here. The source code is attached to the article and you also can find it in the sciter-sdk/demos/ulayered/ folder. The SDK also includes this sample as compiled executables for each platform, so you can try it right away.

The Sciter clock application

Our application will be a wall clock that is rendered in the so-called layered window. All modern OSes support "alpha-aware" windows, so we can have an arbitrarily shaped UI:  

Sciter Clock application

Source code / project layout

Structure of the Sciter clock project:

  • /builds/ -  contains Visual Studio, XCode and Code::Blocks project files.
  • /res/ - application UI resources - HTML, CSS, script and images are usually here.
    • /res/default.htm - the main, and only, markup file that's used.
    • /res/analog-clock.tis - scripting component, aka the "behavior" responsible of drawing clock hands, etc.
    • /res/movable-view.tis - scripting code that allows to move the window on the desktop surface by dragging the mouse.
  • /pack-resources.bat - bat file that calls sciter-sdk/bin/packfolder.exe utility to compress content of the /res/ folder into resources.cpp. 
  • /resources.cpp - contains resources from /res/ folder compressed into unsigned char resources[] = { ... }; blob.
  • /ulayered.cpp - native code that defines uimain() function, which creates the application window.

Native code

Sciter clock is fairly simple application, so its native code is also simple.

ulayered.cpp:

C++
#include "sciter-x-window.hpp"

static RECT wrc = { 100, 100, 800, 800 };

class frame: public sciter::window {
public:
  frame() : window( SW_MAIN | SW_ALPHA | SW_POPUP, wrc) {}

  // define native functions exposed to the script:
  BEGIN_FUNCTION_MAP
    FUNCTION_0("architecture", architecture);
  END_FUNCTION_MAP

  int architecture() {
    // this function is here just for the demo purposes,
    // it shows native function callable from script as view.architecture();
#if defined(TARGET_32)
    return 32;
#elif defined(TARGET_64)
    return 64;
#endif
  }
};

#include "resources.cpp" // packed /res/ folder

int uimain(std::function<int()> run ) {

  sciter::archive::instance().open(aux::elements_of(resources)); // bind resources[] (defined in "resources.cpp") with the archive

  frame *pwin = new frame(); // will be self destroyed on window close.

  // note: this:://app URL is dedicated to the sciter::archive content associated with the application
  pwin->load( WSTR("this://app/default.htm") );

  pwin->expand();

  return run();
}

It defines class frame that is a specialization of sciter::window class. Our frame contains one native frame::architecture() function, intended to be called from script as view.architecture().  Any additional needed native functions should be included into the BEGIN_FUNCTION_MAP/END_FUNCTION_MAP script<->native binding block as well.

The main point of interest here is the uimain() function. It does three major things:

  1. Binds the packed resource blob with the sciter::archive instance. The sciter::archive instance supplies resources when the engine requests them via the SC_LOAD_DATA notification. See the sciter::host::on_load_data() implementation in sciter-sdk/include/sciter-x-host-callback.h file for more details. Your application may store resources in any other way, sciter::archive is just one possible way of doing it.
  2. Creates the window instance and instructs it to load the this://app/default.htm markup file (see /res/default.htm).
  3. Shows the window and runs the so called "message pump" loop - that the std::function<int()> run passed as a parameter. Each platform has its own way of defining the "main" function, so you need to include one of these in your project:
    • sciter-sdk/include/sciter-win-main.cpp - on Windows
    • sciter-sdk/include/sciter-osx-main.mm - on OS X
    • sciter-sdk/include/sciter-gtk-main.cpp - on Linux

Note that ulayered.cpp is used as-is on all platforms. The only platform-dependent file you need is one of three mentioned above. If the rest of your code uses universal C++ libraries and functions from std, boost, POCO, etc, then C++ and Sciter allow you to write the code once, and it will run on all popular desktop OSes.

UI markup

Our markup (res/default.htm) is also quite simple:

HTML
<html>
<head>
  <title>Sciter clock</title>
  <style>
    ... see below ...
  </style>
  <script type="text/tiscript">
    ... see below ...
  </script>
</head>
<body>
  <header><span #platform /> <span #arch />bit time</header>
  <footer>
    <button #minimize>Minimize Window</button>
    <button #close>Close Window</button>
  </footer>
</body>
</html>

Everything is self-explanatory, except for things like <button #minimize>  which is a short form of <button id="minimize">. I've extended HTML parser in Sciter to support this, and other frequently used UI constructs.

CSS, content of <style>...</style> section.

Normally, you include your styles in separate files. For brevity, I've included them inline in this sample:

CSS
   html  { background:transparent; } // the window is transparent
   body  
   { 
     prototype: Clock url(analog-clock.tis); // will draw clock in between background and content layers
     border-radius:50%; 
     border:3dip solid brown; 
     background:gold; 
     margin:*; // flex margins will move the body in the mddle of the root
     size:300dip; 
     flow:vertical;
     transform:scale(0.1); // initially it is small - collapsed to center
     overflow:hidden;
     font-size:10pt;
     font-family: "Segoe UI", Tahoma, Helvetica, sans-serif;
  }
        
  body.shown 
  {
    transform:scale(1);
    transition: transform(back-out,600ms); // initial show - expanding animation  
  }
   
  body.hidden 
  {
    transform:scale(0.1);
    transition: transform(linear,600ms);  // closing animation
  }
  
  body > header { text-align:center; color:brown; margin-top:36dip; font-weight:bold; }
   
  body > footer { flow:vertical; margin-top:*; margin-bottom:20dip; }
  body > footer > button { display:block; background:transparent; margin:8dip *; border: 1px solid brown; border-radius:4dip; }
  body > footer > button:hover { background-color:white; transition: background-color(linear,300ms);  }

This is pretty much standard CSS, except for two things. First, this rule/property: 

CSS
body {
   prototype: Clock url(analog-clock.tis);
}

tells the following:  "<body> DOM Element shall be derived from (subclassed by) class Clock found in the analog-clock.tis file". Class Clock component (a.k.a. "behavior") contains the script code that draws the clock hands and marks using graphics primitives.  Sciter offers two CSS mechanisms of declartive element-to-script bindings. Refer to the Sciter. Declarative behavior assignment by CSS: ‘prototype’ and ‘aspect’ properties article for an in-depth explanation. 

Second, this construct:

CSS
body {
   margin:*;  
}

defines flexible margins on body element. In essence, it puts 4 "springs" on the sides of the body element, moving it to the center of its container (window):

spring margins illustration

You can read about flex units and layout managers used by Sciter in my Flexible Flow proposal to W3C/CSS WG. 

Script, content of <script type="text/tiscript">...</script> section

The script of this application is relatively simple too:

JavaScript
include "moveable-view.tis";

const body = $(body);

self.ready = function() // html loaded - DOM ready
{
  view.caption = "Sciter Clock";

  // positioning of the window in the middle of the screen:
  var (sx,sy,sw,sh) = view.screenBox(#workarea,#rectw); // gettting screen/monitor size
  var (w,h) = self.$(body).box(#dimension);
  w += w/2; h += h/2; // to accomodate expanding animation

  view.move( sx + (sw - w) / 2, sy + (sh - h) / 2, w, h);

  body.timer(40ms, function() { body.attributes.addClass("shown") });
  $(span#platform).text = System.PLATFORM;
  $(span#arch).text = view.architecture(); // calling native function defined in ulayered.cpp
}

// <button #close> click handler
$(#close).onClick = function()
{
  body.onAnimationEnd = function() { view.close(); };
  body.attributes.removeClass("shown");
}

// <button #minimize> click handler
$(#minimize).onClick = function()
{
  view.state = View.WINDOW_MINIMIZED;
}

// setup movable window handler:
movableView();

There two button click handlers, and the DOM ready handler that triggers initial expansion animation ( body.timer(40ms, function() { ... }); ) 40ms after launch.

Probably the most interesting script code is in /res/analog-clock.tis (included by prototype statement in CSS above):

JavaScript
class Clock: Behavior 
{

  function attached() { 
    this.paintForeground = this.drawclock; // attaching draw handler to paintForeground layer
    this.timer(300ms,::this.refresh());
    
    this.borderWidth = this.style["border-width"] || 3;
    this.borderColor = this.style["border-color"] || color("brown");
  }

  function drawclock(gfx)
  {
    var (x,y,w,h) = this.box(#rectw);
    var scale = w < h? w / 300.0: h / 300.0;
    var now = new Date();
    gfx.save();
    gfx.translate(w/2.0,h/2.0);
    gfx.scale(scale,scale);    
    gfx.rotate(-Math.PI/2);
    gfx.lineColor(color(0,0,0));
    gfx.lineWidth(8);
    gfx.lineCap = Graphics.CAP_ROUND;
       
    // Hour marks
    gfx.save();
    gfx.lineColor(color(0x32,0x5F,0xA2));
    for (var i in 12) {
      gfx.rotate(Math.PI/6);
      gfx.line(137,0,144,0);
    }
    gfx.restore();

    // Minute marks
    gfx.save();
    gfx.lineWidth(this.borderWidth);
    gfx.lineColor(this.borderColor);
    for (var i in 60) {
      if ( i % 5 != 0)
        gfx.line(143,0,146,0);
      gfx.rotate(Math.PI/30);
    }
    gfx.restore();

    var sec = now.second;
    var min = now.minute;
    var hr  = now.hour;
    hr = hr >= 12 ? hr-12 : hr;
  
    // draw Hours hand
    gfx.save();
    gfx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec )
    gfx.lineWidth(14);
    gfx.line(-20,0,70,0);
    gfx.restore();

    // draw Minutes hand
    gfx.save();
    gfx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec )
    gfx.lineWidth(10);
    gfx.line(-28,0,100,0);
    gfx.restore();
      
    // draw Seconds hand
    gfx.save();
    gfx.rotate(sec * Math.PI/30);
    gfx.lineColor(color(0xD4,0,0));
    gfx.fillColor(color(0xD4,0,0));
    gfx.lineWidth(6);
    gfx.line(-30,0,83,0);
    gfx.ellipse(0,0,10);
    
    gfx.noFill();
    gfx.ellipse(95,0,10);
    gfx.restore();
    
    gfx.restore();
    
  }    
}

Sciter supports the so-called immediate drawing mode. If you think of the DOM element as a window (HWND), then immediate mode drawing is a code that handles the WM_PAINT window message. It gets called every time when a surface area needs to be drawn.

That function attached() above is called when the DOM element (<body> here) gets "subclassed" by the instance of class Clock.  This statement:

JavaScript
this.paintForeground = this.drawclock;

installs the drawclock() function as a foreground layer drawing handler of the element, while this:

JavaScript
this.timer(300ms,::this.refresh());

causes the element to be refreshed every 300ms. Therefore, the clock face will be repainted once every 300ms.  ::this.refresh() is a lambda function declaration in TIScript.

Brief outlook of Sciter SDK structure:

sciter tools

Public Sciter SDK contains the following folders:

  • bin, bin.osx and bin.gtk - folders with the compiled Sciter engine: sciter32/64.dll (Windows), sciter-osx-64.dylib (OS X), sciter-gtk-64.so (Linux) and sciter.exe variations - demo "browser" with builtin DOM inspector, script debugger and Sciter documentation browser (see screenshot above). There are compiled versions of samples from the demo folder.
  • include - C and C++ include files that define the public Sciter engine API: window level functions, DOM access methods and utilities.
  • demos, demos.osx, demos.gtk - demo projects/applications demonstrating various aspects of Sciter embedding.
  • doc - documentation in HTML format, viewable in conventional browsers and the built-in help viewer. 
  • samples - HTML/CSS/script ready to use code snippets and samples demonstrating various Sciter features. 

Libraries and samples included in the public Sciter SDK

  • samples/+plus - that is AngularJS alike data-binding library. Small (480 LOC) and non-intrusive Model-View-Whatever library.
  • samples/+lib - underscore.js alike primitives. 
  • samples/+promise - Promises/A+ specification implementation. 
  • samples/+query - port of basic jQuery/Zepto features. Most of the basic jQuery features are implemented natively in Sciter, so this library is quite compact - 700 LOC. 
  • samples/+lang - i18n primitives.
  • samples/+vlist - virtual list, grid library, and samples. Use it when you need to browse large sets of records. +vlist uses a live data binding mechanism. Just provide an array[] of records and a AngularJS alike repeatable template.
  • samples/animations - library and demos of animation framework, similar to GreenSock.js animation platform (GSAP).
  • samples/animated-png - Animated PNG demos.
  • samples/animations-transitions-css - CSS based transitions. Historically, Sciter uses slightly different syntax of defining CSS transitions, but the feature set is similar to the CSS3 Transitions module. 
  • samples/basics - basic CSS samples, including the CSS3 transform property.
  • samples/communication - AJAX/JSON client, WebSockets and DataSockets duplex inter/intra-net communication.
  • samples/css++ - demonstration of various CSS extnesions introduced in Sciter.
  • samples/dialogs+windows - demonstation of View.window, View.dialog and View.msgbox features: desktop windows defined by HTML/CSS/script means.
  • samples/drag-n-drop-manager - drag-n-drop manager.
  • samples/effects.css++ - transition:blend and transition: slide-xxx demos - Sciter specific transition extensions.
  • samples/font-@-awesome - CSS3 @font-face feature demo using FontAwesome integration.
  • samples/forms - demonstration of Sciter extended set of <input> widgets including <select type=tree>, <input type=number>,  <input type=masked> and many others.
  • samples/goodies - Sciter extras, including behavior:file-icon - shell icons rendering.
  • samples/graphics - use of Graphics class - immediate and buffered drawing primitives including "render-element-to-bitmap" and "dynamic-CSS-background-image" features, Graphics in Sciter is a superset of <canvas> functionality in browsers.
  • samples/ideas/ - bunch of implementation ideas including:Virtual list
    • callout - dynamic callout
    • carousel   
    • KiTE - {{mustache}} alike template engine.
    • lightbox-dialog - lightbox in-window modal dialogs.
    • moveable-windows - Sciter supports so called airborn DOM elements - elements rendered in separate windows. The sample demonstrates this feature.
    • tray-notifications - shell tray notifications defined by HTML/CSS means.
    • virtual-list - yet another virtual list with kinetic scroll that supports unlimited number of items of variable height (see image on the right). 
  • samples/image-map - "image catalogs for humans" and DPI-aware images.Sciter tooltips
  • samples/image-transformations.css++ - yet another Sciter specific CSS feature: image filters.
  • samples/menu - real menus defined in HTML and styled by CSS.
  • samples/popup - yet another demo of "airborn" DOM elements - popup windows. 
  • samples/replace-animator - demo of animated transitions between different layouts.
  • samples/richtext - demonstration of the <richtext> widget - WYSIWYG HTML editor,
  • samples/scrollbars-n-scrolling - scrollbar and scroll manner styling.
  • samples/selection - demo of textual/range selections on DOM tree.
  • samples/sqlite - demo of SQLite integration (tiscript-sqlite.dll).
  • samples/svg - demonstration of extended SVG support in Sciter.
  • samples/tooltips++ -  demo of tooltips/calltips definitions & styling (see image on the right).
  • samples/video - <video> playback in Sciter. 
  • samples/xml - XML handling using builtin XMLParser based on the XML tokenizer.

History

Article: 10 years road to Sciter.

HTMLayout CodeProject article. Conceptually, Sciter is next major version of HTMLayout. Sciter API is a superset of HTMLayout API, so the embedding principles defined there are applicable to Sciter too.

Sciter forums and discussion

  1. Main Sciter discussion (English);
  2. Sciter on RSDN.RU (Russian);
  3. Sciter in Chinese;
  4. Here on CodeProject.

References and further reading

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)