For native GUI programmers

Windowless UI

Sciter window hosts tree of DOM elements – lightweight and uniform building blocks constituting window content.

DOM tree as it seen in DOM inspector tool from SDK

DOM tree as it seen in DOM inspector tool from SDK (Sciter UI by itself)

Each window controlled by the engine has single root element. All other elements are descendants of the root. Any element can contain other elements and each element has strictly one parent element. The only exception is the root element that has no parent. Its parent is its host window ([DOM tree] view in terms of Sciter).

DOM element is a structure (in C/C++ sense) that holds:

  • list of child elements
  • reference to element’s style that describes
    • visual appearance of the element;
    • layout manager – the way how this element replaces its children;
    • behavioral style of the element – list of various event handlers attached to the element.
  • list of DOM attributes – collection on name/value pairs associated with the element.
  • so called “behaviors” – list of named event handling procedures.
  • run-time flags to style various UI states of the element: :active, :hover, :visited, :checked, :expanded, :collapsed and so on.

Styles and layouts

After loading of the document each DOM element gets resolved style. Style resolution is a process of combining multiple CSS rules. Each CSS rule is a selector and associated list of style properties. Procedure of combining multiple matching CSS rules into final (a.k.a. used) list of properties is known as a cascading. Cascading means that element can get its styles from different sources and style files and with respect of its attributes (including class attribute) and position in the DOM tree.

In Sciter used element style is a structure that contains defined properties to be used by element. Style properties in Sciter can be split into these groups:

  • Visual/appearance properties: text color, borders, background styles, fonts, opacity and so on;
  • Layout and visibility properties that define how element is replaced in its environment (the display property) and how its children are replaced. In Sciter the flow property defines layout manager used by the container element to replace its children.
  • Behavioral properties: behavior, prototype and aspect.
    The behavior property defines name of native module that is responsible for initialization and event handling on the element. For example, by defining div {behavior:button} you are asking all <div> elements in your markup to behave as buttons: generate BUTTON_CLICK DOM events and be focusable.
    The prototype and aspect properties define scripting class to be assigned to the element (subclassing by changing element’s prototype) or [aspect] scripting function to be called for the element when it first appears in the DOM.

Master style sheet

The Master style sheet is a built-in resource that assigns default visual appearance and intrinsic behaviors of standard HTML elements, like <input>s and others.

Host application can provide its own version of master style sheet redefining all defaults.

Behaviors/Event handlers

Behavior in Sciter’s sense is a function that is called for different lifetime and UI events on the DOM element. Essentially it is an analog of WindowProc in Windows. Sciter SDK contains C++ helper struct event_handler wrapping such function into C++ friendly construct:

struct event_handler
{
  event_handler() {} // EVENT_GROUPS flags
  
  virtual void detached  (HELEMENT /*he*/ ) { }
  virtual void attached  (HELEMENT /*he*/ ) { }

  virtual bool handle_mouse  (HELEMENT he, MOUSE_PARAMS& params )         { return false; }
  virtual bool handle_key    (HELEMENT he, KEY_PARAMS& params )           { return false; }
  virtual bool handle_focus  (HELEMENT he, FOCUS_PARAMS& params )         { return false; }
  virtual bool handle_timer  (HELEMENT he )                               { return false; }
  virtual void handle_size   (HELEMENT he )                               { }
  virtual bool handle_draw   (HELEMENT he, DRAW_PARAMS& params )          { return false; }
  virtual bool handle_method_call (HELEMENT he, METHOD_PARAMS& params )   { return false; }
  virtual bool handle_event (HELEMENT he, BEHAVIOR_EVENT_PARAMS& params ) { return false; }
  virtual bool handle_data_arrived (HELEMENT he, DATA_ARRIVED_PARAMS& params ) { return false; }
  virtual bool handle_scripting_call(HELEMENT he, SCRIPTING_METHOD_PARAMS& params ) { return false; }
}      

When the engine discovers element having behavior: xyz; defined in its style it sends SC_ATTACH_BEHAVIOR notification with the name “xyz” and element handle to the application.
Default SC_ATTACH_BEHAVIOR handler walks through list of registered behavior factories and creates instance of the corresponding event_handler.

See: {sdk}/includes/sciter-x-host-callback.h, method

LRESULT on_attach_behavior( LPSCN_ATTACH_BEHAVIOR lpab ) { 
   return create_behavior(lpab); 
}

Events and event propagation schema

When UI event occurs it gets dispatched by the engine in two phases:

  1. Sinking phase – direction: from containers to target child element. This phase is also known as “capturing” phase as it allows containers to intercept/capture events before they reach children;
  2. Bubbling phase – direction: from a child element to all its containers. Normally you will handle events in this phase.
sinking and bubbling event dispatching

sinking and bubbling event dispatching

Above is an illustration of MOUSE_DOWN event handling on <input type="button"> in the following markup when user presses mouse button on the button:

<html>
  <body>
     <p><input type="button"></p>
  </body>
</html>

If the user clicks on the input element then the event will be first delivered to the event handler of <html> element, then to <body>, and then to <p> element and <input> in it. That is in sinking phase. In bubbling phase direction is exactly opposite – from child to the root on chain of parents.

Sinking phase is marked by PHASE_MASK::SINKING bit of MOUSE_PARAMS::cmd field in event parameters structure. See: sdk/include/sciter-x-behavior.h

You can handle mouse down event in both phases, in sinking phase, before it will be dispatched to children, or in bubbling phase, after it was handled by children:

virtual bool handle_mouse  (HELEMENT he, MOUSE_PARAMS& params ) overriden {
    switch(params.cmd) {
       case MOUSE_DOWN | SINKING : ... break; // sinking event 
       case MOUSE_DOWN : ... break;           // bubbling event *not* consumed by children
       case MOUSE_DOWN | HANDLED: ... break;  // bubbling event consumed by some child
    }
    return false; 
}

Each event handler may “consume” the event by returning true value from the handler. In this case the event code is modified by OR’ing with the HANDLED bit but still be dispatched on the parent-child chain. When you see MOUSE_DOWN | HANDLED event code in your event handler it means that the event was consumed by one of children of your container.

DOM element and window handles

Sciter handle

Handle of Sciter engine instance is a window/view handle controlled by the Sciter. That handle is defined as HWINDOW type which is:

  • HWND handle on Microsoft Windows.
  • NSView* – pointer to NSView instance that is a contentView of Sciter window on OS X.
  • GtkWidget* – pointer to GtkWidget instance that is a root widget of Sciter window on Linux/GTK.

DOM element handle

DOM element reference is defined as HELEMENT type which is a pointer to internal dom element structure. DOM element is a reference counted entity and so for the storage purposes it shall be add-ref’ed/released when needed. Use Sciter_UseElement and Sciter_UnuseElement functions for that purpose.

SDK contains convenient smart pointer / helper class sciter::dom::element that is combined with element methods. It is advised to use that class instead of using HELEMENT variables directly.

Custom native functions for the script

To handle UI-to-logic calls the application defines sciter::event_handler and attaches its instance to the Sciter window (view). Its event_handler::on_script_call method will be invoked each time when script executes code like this:

view.getSomeData(param1, param2);

That script function invocation will end up in this C/C++ call:

event_handler::on_script_call(NULL,
         "getSomeData", 
         2 /*argc*/ , 
         argv[2], 
         sciter::value& retval /* return value */ );

Sciter SDK contains convenient macro wrapper/dispatcher for such on_script_call function:

class window
    : public sciter::host<window>
    , public sciter::event_handler
  {
    HWND   _hwnd;
    ...
    
    sciter::value  debug(unsigned argc, const sciter::value* arg);      
    sciter::value  getSomeData(sciter::value param1, sciter::value param2);      

BEGIN_FUNCTION_MAP
  FUNCTION_V("debug", debug);  
  FUNCTION_2("getSomeData", getSomeData); 
END_FUNCTION_MAP
  }

Declaration FUNCTION_2("getSomeData", getSomeData); binds view.getSomeData() in script with native window::getSomeData call.

Therefore functionality exposed to the UI layer by logic layer can be defined as a content of single, simple and observable BEGIN_FUNCTION_MAP/END_FUNCTION_MAP block.

Sciter’s headers allow to wrap C++ lambda functions into sciter::value’s (C++11 is required).

Here is an example of native_api() function that in its turn returns collection of native functions to be used in script:

static sciter::value native_api() {

  sciter::value api_map;
  sciter::value api_math_map;

  std::function<int(int,int)> native_sum = [](int a, int b) { return a + b; };
  std::function<int(int,int)> native_sub = [](int a, int b) { return a - b; };

  api_math_map.set_item(sciter::value("sum"), sciter::vfunc( native_sum ));  
  api_math_map.set_item(sciter::value("sub"), sciter::vfunc( native_sub ));

  api_map.set_item(sciter::value("math"), api_math_map);

  return api_map;
}

native_api() C++ function above is an exact equivalent of this scripting function:

function View.native_api() {

    function native_sum(a,b) { return a + b; }
    function native_sub(a,b) { return a - b; }

    return {
      math: {
        sum: {native_sum},
        sub: {native_sub},
      }
    };
}

Both such functions can be used in script as:

const NativeApi = view.native_api();

NativeApi.math.sum(2,2); // -> 4
NativeApi.math.sub(2,2); // -> 0