10 years road to Sciter

Last year it was a jubilee of my BlockNote editor – it is 10 years old now. Quite a serious age for software product I think.

BlockNote

BlockNote, in particular its HTML WYSIWYG editing engine, was the very first version of H-SMILE core that works now inside the Sciter. H-SMILE core was used in other software products, for example in EverNote. In fact I was one of four developers who implemented excellent idea of Stepan Pachikov (EverNote idea-monger and founder) and created first public versions of EverNote.

In parallel and after that I was designing various UIs and editing components. WordProcessor engine that I’ve created for Top Producer Systems (The move.com company). That WordProcessor still works in their flagship product AFAIK. Current stack of Web technologies still does not provide facilities for page oriented WYSIWYG editing so there is nothing to replace WordProcessor yet. I’ve got quite interesting experience implementing text layout and editing algorithms there. Ideas from there have found their way in Sciter code in one way or another.

At some point I’ve got an impression that C++ is not the best language for UI programming. In particular GC as a memory management mechanism is significantly better suitable for typical UI tasks – many small objects with quite non-trivial ownership graphs. I’ve started looking on Java but having dependency on installed Java VM didn’t look right at the moment. So I’ve created so called j-SMILE JavaVM (based on SuperWaba) that was tailored specifically for UI purposes – designed to be combined into executable of the applications together with bytecodes it is running. J-SMILE demo (buggy but works somehow) and sources of class files are available here.
J-SMILE screenshot.

In J-SMILE I started experimenting with HTML as a UI layout manager and sinking/bubbling event propagation mechanism used in Web browsers. These mechanisms were proven to be highly useful for desktop UI purposes too.

Pretty much at the same time with my J-SMILE development Sun-Microsoft “Java Wars” had begun. And this immediately put the end to my J-SMILE development. I realized that I have no chances on this front and started looking for other options.

Some time later D-language raised in the sky and gave me real hope for the best ever language-behind-UI. GCable, natively compileable so fast and with pretty sufficient functional features. So I’ve started porting J-SMILE to it under the name Harmonia framework.

Harmonia testbed application

Harmonia testbed application

For those who are interested to see Harmonia in action: Harmonia demo, Harmonia sources and class tree.

D is really very cool and convenient language but after some time I’ve decided to stop Harmonia development for the following reasons:

  1. It was time when D-language was changing very frequently. Pretty much each nightly build was changing core grammar. So Harmonia development was similar to shooting a moving target. Far from being normal development process.
  2. Having each and every object manageable (garbage collectible) is far from being optimal. HTML DOM tree is a large set of small objects. All these small objects are flooding manageable heap making GC cycles very CPU intense. That’s not about Harmonia only by the way – I believe Android suffers from this problem too when its UI freezes noticeably.
  3. I’ve got very strong feeling that each part of UI framework shall use its own memory management and ownership principles that are optimal for the role they are playing. HTML DOM tree has very regular structure with clear one parent – many children ownership graph. Life cycle of DOM elements (read “UI elements”) is also quite deterministic. There is absolutely no need for GC if to speak about HTML/CSS.
  4. But code-behind-UI – code that does UI automation in “on-click-here-push-item-there-and-collapse-panel-over-there” fashion has to be manageable. GC there is the must – ownership graph is unknown upfront and frequently contains loops.

So I reverted back to C++ and created pure DOM and rendering implementation – HTMLayout. And started looking on embeddable languages that can be used with it as “language behind UI”. Here are languages that I considered for that purpose:

  1. Ruby and Python – were rejected almost immediately. HTML friendly language shall have syntax that is not dependent on tabs, line feeds, etc. For quite many reasons. And yet Python was using C/C++ heap for object allocations, UI objects lifecycle require compacting GC – objects created and destroyed frequently.
  2. Lua – pretty much for the same syntax reasons. But it was a good candidate. I even started creating JS syntax alike compiling front-end for its VM. But discovered in process that Lua’s object/class model is too rudimentary I would say.
  3. JavaScript – as a natural choice was always there. But its prototyping schema, “hackish” way of doing classes there and that famous “automatic semicolon injection” problem were stopping me each time I was trying to consider it. Only ECMAScript version 6 has started to appear solid enough. But that’s just recently.

Needless to say that my investigation ended up in creation of first C-SMILE and then TIScipt. TIScript is almost JavaScript (JavaScript++ if you wish) but with modular system (namespaces) and classes. It also got built-in persistence. And it inherits base syntax and runtime library from JavaScript.

HTMLayout combined with TIScript became Sciter as you see it now.

Oh, and in parallel with all this I was working as UI Architect and team lead at Top Producer Systems (move.com company) on various desktop UI, Web and mobile applications projects. And yet participated in HTML5 and CSS3 specification development as invited expert in W3C HTML Working Group.

“Theory” of URLs for developers

We are using URLs these days quite a lot, but not all of us understand what they actually are.

By this post I will try to explain their structure and how Sciter/HTMLayout deals with them.
Please note that this is sort of informal explanation and I am using term “URL” here while “URI” is more correct name for that entity strictly speaking.

URLs are made from these 5 major parts:

<scheme name> : [//] <resource identification > [ ? <query> ] [ # <fragment> ]

Where:

  • scheme name – is in principle any name token – name of the URL scheme. Some of names are well known: “file:”, “http:”, etc.
  • // part – if present, means that the following “resource identification” part uses hierarchical naming convention, with ‘/’ character as a delimiter.
  • resource identification – is either ‘/’ separated path name if ‘//’ is provided. Or it is some “flat” name.
  • query part is in principle meaningful only for dynamic client/server scenarios. It is an instruction to the server to provide that resources with those parameters.
  • fragment – is a name or ID of some sort that identifies some location or part inside the resource.

Consider that you have the same HTML test-file.html available from these three locations:

  1. file:///c:/test-folder/test-file.htm
  2. http://example.com/test-folder/test-file.htm
  3. res:test-file.htm

Let’s imagine that the document contains image with relative path:

  <html>
  <img src="image.png">
  </html>

Sciter, while loading the document, will resolve relative path name “image.png” to these:

  1. file:///c:/test-folder/image.png – as base URL is hierarchical one.
  2. http://example.com/test-folder/image.png – ditto.
  3. res:image.png – but here only resource schema is inherited as base URL uses “flat” naming convention.

In principle your Sciter application can use your own URL schema. You just need to decide will it be hierarchical or flat.

For example if you will call SciterLoadHtml(html, "app://module/main.htm"); then all relative links inside that document
will be resolved against app://module/ base name. So in your SCN_LOAD_DATA handler you will get: “app://module/images/img1.png”, “app://module/styles.css” and so on.

But if you plan to put all your files into resource section of your executable then you should use “flat” schema, like “res:”.

And the last note: if you use hierarchical URLs in SciterLoadHtml and SciterLoadFile calls then these urls must be absolute ones otherwise Sciter will not be able to resolve them reliably.

Sciter 2.0.0.12

Just published Sciter 2.0.0.12 SDK that is available at terrainformatica.com/sciter/sciter2-tech-preview.zip.

Sciter 2.0.0.12 screenshot

What’s new in the build:

  • Demos:
    • bin/layered.exe (sdk/demos/layered) demonstrates use of WS_EX_LAYERED windows. That is about windows of non-rectangular shapes. W7 has better support of such windows so even animations are feasible on such windows. Just run the demo to see.
  • CSS features:
    • New layout method: flow:row(tag1,tag2) – that is a variation of flow:grid | "template" that allows to define grid-alike layouts when number of rows is unknown upfront. For example list of info items on the screen shot above is defined as:
    • <dl #info>
        <header>Sciter</header>
           <dt>version:</dt>        <dd #version>...</dd>
        <header>System</header>
           <dt>OS type:</dt>        <dd #os-type>...</dd>
           <dt>OS version:</dt>     <dd #os-version>...</dd>
      ... </dl>

      and styled simply as flow:row(dt,dl) to replace <dt>/<dd>s in grid rows.

    • The design of font-rendering-mode property is finalized and now it looks as this:
    • font-rendering-mode: snap-pixel | sub-pixel;
      

      Default value is sub-pixel and it is an inheritable property. For large fonts and fonts in elements under 2D transforms look better when rendered in sub-pixel mode. Normal and small UI fonts are better readable in snap-pixel mode.

  • Core changes:
    • flow:text layout module ( used to render text block containers like <p> ) was rewritten from a scratch. Previously I was using IDWriteTextLayout for that but was forced to get rid of it. IDWriteTextLayout has no means to support things like floats (require "jagged" text boundaries) and text-align:justify.
    • So now the engine passes all floats related tests from W3C CSS test suite.
  • List of intrinsic behaviors supported so far:
    • buttons:
      • behaviror:button  attached to <input type=button> and <button>;
      • behavior:clickable – focus-less buttons like toolbar buttons, etc.
      • behaviror:check  attached to <input type=checkbox> and <button type=checkbox>;
      • behavior:radio attached to <input type=radio> and <button type=radio>;
      • behavior:switch – flawor of behavior radio but activation happens on MOUSE_DOWN rather than on MOUSE_UP, used for tabs alike containers.
      • behavior:hyperlink – attached to all elements having href DOM attribute, including <a>.
    • date and time:
      • behavior:calendar – shows month/year/century calendar. Attached to <input type=calendar> and <widget type=calendar>.
      • behavior:date – date input with popup calendar, <input type=date>
      • behavior:time – time input <input type=time>.
    • edits:
      • behavior:edit – text input, <input type=text>;
      • behavior:password – password input, <input type=password>;
      • behavior:textarea – multiline input, <texarea>;
      • behavior:number – numbers (integer or decimal) input element, <input type=number>;
      • behavior:integer – integer number input element, <input type=integer>;
      • behavior:decimal – decimal number input element, <input type=decimal>;
      • behavior:masked-edit – masked input element, <input type="masked" mask="...">
      • behavior:slider
    • selects:
      • behavior:select – single select list, <select size=2,3,...>, <select type=select>;
      • behavior:select-multiple – multi-select list, <select size=2,3,... multiple>, <select type=select multiple>;
      • behavior:select-checkmarks – multi-select list, <select size=2,3,... multiple=checkmarks>, <select type=select multiple=checkmarks>;
      • behavior:tree – single select tree, <select type=tree>. Select with <option>s containing other <option>s;
      • behavior:tree-checkmarks – multi-select tree, <select type=tree multiple=checkmarks>;
    • menus:
      • behavior:menu-bar – menu bar;
      • behavior:menu – context/popup menu;
      • behavior:popup-menu – button with popup menu <button type="menu" menu="css selector">;
      • behavior:popup-selector – button with popup menu list, <button type="selector" menu="css selector"> ;
    • indicators:
      • behavior: progress – progress bar, <progress>, <meter> elements.
    • gestures recognition:
      • behavior:swipe – recognizes swipe gestures and generates BEHAVIOR_EVENTS::SWIPE / Event.SWIPE events.
    • frame/form:
      • behavior:frame – the behavior behind <frame>. Used for loading other documents inside elements thus to support DOM elements with "foreign" DOM sub-trees;
      • behavior:history – navigation history in system of <frame>s;
      • behavior:frame-set – that is just a handler of containers with splitters, by default attached to <frameset cols=...> and <frameset rows=...>;
      • behavior:form – the behavior behind the <form>. Form here is a "submitable/resetable" collection of input items. Note: behavior:form can be  assigned to any container. Value of the form is collection(map) of name/value pairs – names and values of input elements.

Sciter v.2 technology preview

You can download Sciter v.2 SDK preview from here http://terrainformatica.com/sciter/sciter2-tech-preview.zip

/bin/sciter.exe in the archive is a demo application to play with. Its sources are in /demos/sciter/ folder. After start you should see something like this:
article2

Note: this version works only on Vista/W7 as it uses Direct2D/Write graphics backend. Aero and W7 Basic DWMs only for a while.

New features in this version:

  • Direct2D/Write graphics backend – pretty fast on some operations. Expect animations to work faster.
  • Support of CSS 2D Transforms Module Level 3: http://www.w3.org/TR/css3-2d-transforms/ – everything except matrix() function. Will add it later if needed.
  • Internal DOM representation was redesigned to avoid need of that ugly <text> element injected by the parser. DOM is more standard now and in principle H-SMILE core can pass ACID2 test (if I will have a time to tweak it for that). DOM is represented by Element and Node entities. Node wraps text and comment nodes in HTML.
  • TIScript got support of "tagged n-tuples" data type. http://en.wikipedia.org/wiki/N-tuple with optional tag (a.k.a. name).

Some observations about Direct2D/Write:

Font rendering

Font rendering as I’ve mentioned already is significantly different in GDI and DW. Small an basic UI fonts looks better in GDI mode. Larger fonts are better in DW. For white text on black background with small and medium font sizes the GDI rendering is the only option I think. Just load /samples/basics/test-font-rendering.htm into the Sciter and you will see what I mean.

I was forced to introduce font-rendering-mode proprietary property for applications to be able to tweak this aspect.

Direct2D performance

While it is good on medium/high-end graphic cards it may behave very badly on low-end graphic cards or when there are applications on desktop that do graphic intense operations. Notably running Adobe Flash may affect speed of rendereing of other applications. That is not only about Sciter per se but other applications. Windows Live application suite is an example of graphics sensitive application. You may notice frame drops in animations inside Windows Live Mail or Messenger.

It appears as Direct2D/Write is a foundation for future high-DPI displays. As for now that font-rendering-mode workaround is the must I believe.

C++0x: Running code in GUI thread from worker threads.

One of topics in design of multi-threading GUI applications is to choose method of calling GUI code from so called worker threads – threads that do some work in background. At some point they need to report results to the GUI. But GUI is a shareable resource so some form of synchronization is required. One approach is to use some global lock/mutex and capture it each time when any thread need to access tree of GUI objects.

Another method is to enqueue update tasks to the GUI thread. GUI thread will execute them when it can. This approach does not require synchronization code to be spread across whole application. The whole system will work faster and probability of deadlocks/synchronization issues is almost zero in this case.

The only problem: for each action that you need to do in GUI thread you will need to create separate task/object for deferred execution (or to use some other mechanism like marshalling in COM).

With new features of C++0x we can accomplish this with almost zero overhead. In particular we will need lambdas.

Here is how our code of worker thread function may look like:

void worker_thread()
{
  dom::element some_el = ...; 
  string html_to_set = ...;
  int result;
  ...
  auto gui_code_block = [some_el,html_to_set,&result] () 
  { // executed in GUI thread
    if(some.children() > 10)
      some.set_html(html_to_set);
    result = 20; // report some result if needed.
  };
  gui_exec(gui_code_block); 
  ...
  if( result == 20 ) ...;
  ...
}

As you see here I am declaring inline code block that will be
executed in GUI thread while the rest of the function body will run in its own thread.

The key point here is the gui_exec() function that may look like as:

void gui_exec( std::function<void()> gui_block )
{
  event evt;
  PostMessage(hwnd, WM_NULL, WPARAM(&evt),LPARAM(&gui_block));
  evt.wait(); // suspend worker thread until GUI will execute the block.
}

It posts the message to the GUI with address of synchronization object and
address of our GUI code block (function in fact). It is simple as you see.

And the last piece – we need message handler in GUI thread that will actually execute that code block. The best place for it is inside so called “GUI message pump” – Get/DispatchMessage loop that exist in any GUI application:

typedef std::function<void(void)> gui_block;

while (GetMessage(&msg, NULL, 0, 0))
{
    if( msg.message == WM_NULL )
    {
      gui_block* pf = reinterpret_cast<gui_block*>(msg.lParam);
      event* pe = reinterpret_cast<event*>(msg.wParam);
      (*pf)();  // execute the block
      pe->signal(); // signal that we've done with it
                        // this will resume execution of worker thread.
    }
    else 
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

And that is it.

The only note: I am using WM_NULL message here but in reality you should use some other not that popular message for it. RegisterWindowMessage() will help to get that message number.

DirectWrite font rendering.

While experimenting with Direct2D/Write back-ends for htmlayout/sciter got these results:

Rendering of <textarea> in default DirectWrite mode:
dw-default
The same but in GDI:
GDI

Probably subjective but classic GDI rendering is better for typical UI font. DirectWrite variant is more blurry (especially note the selection).

So I forced to add one more proprietary CSS property:

font-rendering-mode: classic | enhanced ;

with the default value ‘enhanced’ (that is default DirectWrite mode). So UI developer will be able to tune this up when needed.

Default DirectWrite font rendering mode handles better zooming and rotation situations:
dw-scaled
dw-rotated
(first element uses DirectWrite rendering mode, second – GDI)

Wondering why there are no high-DPI display monitors for desktop use … at least 150 DPI or so …
Tired fighting with graphics anti-aliasing each time to be honest. Last 15 years or so and still no progress 🙁 …

Style sets in H-SMILE core.

HTMLayout and Sciter are both use H-SMILE core to render HTML/CSS. And so both of them support so called style sets.

In this article I’ll try to explain what style set is about and why I decided to introduce them.

First of all: Style set is a named block of style rules – definition of system of style declarations that are applied to some DOM sub-tree.

Style set declaration starts from @style-set at-keyword followed by the name of the style set and block of style rules enclosed in ‘{‘ and ‘}’ brackets:

Code Block A:

@style-set MyStyleSet 
{
   :root { font:12pt Verdana; margin:0; padding:0 }
   :root > li { font:12pt Verdana; display:block; }
   strong { color:red; }
}

As we see this style set is named as ‘MyStyleSet’ and contains three rules. Note the use of :root pseudo-class above – inside style set declaration the :root designates element to what this style set is applied. Such an element is a root of DOM sub-tree that uses this style set.

To apply such style set to particular DOM sub-tree we use style-set property in CSS as this:

Code Block B:

ul#my-list
{
   style-set: MyStyleSet;
}

This way we are telling our CSS engine that ul#my-list element and its all sub-elements will use styles from the set above.

Style sets allow to define system of related style declarations in single and compact block. If you have multiple style sets that can be applied to the same sub-tree it is enough to change name of the style set on single element – root element of the sub-tree:

Code Block C:

html[theme="MyCoolTheme"] ul#my-list {  style-set: MyStyleSet; }
html[theme="AnotherCoolTheme"] ul#my-list {  style-set: AnotherStyleSet; }

This way, by changing theme attribute on html element, we can apply two different and independent style sets to ul#my-list element and its children.

Without style sets (in conventional CSS) we will need to write bunch of rules like these:
Code Block D:

html[theme="MyCoolTheme"] ul#my-list { font:12pt Verdana; margin:0; padding:0 }
html[theme="MyCoolTheme"] ul#my-list > li { font:12pt Verdana; display:block; }
html[theme="MyCoolTheme"] ul#my-list strong { color:red; }
html[theme="AnotherCoolTheme"] ul#my-list { font:10pt Arial; margin:0; padding:0 }
html[theme="AnotherCoolTheme"] ul#my-list > li { font:10pt Arial; display:list-item; }
html[theme="AnotherCoolTheme"] ul#my-list strong { color:blue; }

that is not so pretty and quite error prone as any other cut-n-paste approach.

Another benefit of having style set is that they work great in cases when you need to have style libraries. Consider for example jQuery UI library or any other library of Web components. As an example Tabs UI Component may have all its styles defined as a style set:

@style-set Tabs {
   :root > ul.ui-tabs-nav { ... }
   :root > div.ui-tabs-panel { ...; display:none; }
   :root > div.ui-tabs-panel.current { ... ; display:block; }
}

and different theme style files of jQuery UI library might have different and independent declarations of the Tabs style set.

Styles sets are not only convenient and compact way of defining system of styles but they also is a great way to reduce computational complexity of resolving styles of DOM elements. Consider following markup and system of styles defined in Code Block D above:

   <ul id="my-list"><li>Item</li></ul>

To resolve style of the LI element above CSS processor shall scan all rules found in all style sheets (6 of them in this particular example).
For example to determine if this rule:

html[theme="MyCoolTheme"] ul#my-list > li

can be applied to the LI element or not the CSS processor should scan not only the element itself but also all its parents in order to find HTML element with theme attribute set. In general the computational complexity of the style resolution of the DOM having N elements and with S number of style rules is O(N*S).

With style sets the complexity is significantly less because number of styles rules needs to be resolved is less. There are 6 rules in Code Block D (conventional CSS) versus 2 rulers in Code Block C (style sets). Style rules in particular style set will be scanned only for elements that have corresponding style-set property defined.
The style-set property is an inheritable one so any element in sub-tree of “styled root” element will have the same value of the style-set.
In practice this means for example that your UI may have many themes that can be switched in runtime and adding new theme will not slow down style resolution.
You can also create libraries of styles of your components that can be applied to your components by simply defining style-set property in CSS.

More about computational complexity of CSS selectors.

Resolution of styles with style sets of DOM elements

Under the hood resolution of styles is using following steps:

  1. For each particular element styles are getting resolved as usual. Styles inside style-set blocks are not considered at all at this step.
  2. If after step #1 the element gets some value in its style-set property then and only then system is trying to find defined style block. If such block is found then rules from the block are applied on top of properties resolved in step 1 according to specificity of CSS selectors in that style block.

As you see weight of rules defined in style-sets is greater than conventional CSS rules. To override property defined in style set by conventional style rule the later one shall use !important modifier, like this:

ul#my-list 
{ 
   style-set: MyStyleSet; 
} 
ul#my-list strong 
{  
   color: yellow !important; /*overriding color set by strong { color:red; } in MyStyleSet */
} 

Style sets and OOP

To make style-sets even more modular and reusable I’ve added inheritance in style-sets – you can define new style set that will inherit all rules from the base one.

@style-set Tabs {
   :root > ul.ui-tabs-nav { display: block; margin:Xpx; ... }
   /* all other rules that define layout properties */
}
@style-set BlueTabs < Tabs /* inherits all rules from Tabs set */
{
   :root > ul.ui-tabs-nav { color: blue; }
   /* other "color" rules */
}
@style-set RedTabs < Tabs /* also inherits all rules from Tabs set */
{
   :root > ul.ui-tabs-nav { color: red; }
   /* other "color" rules */
}

The declaration above introduces three style sets that can be used independently and yet RedTabs and BlueTabs use the same shareable prototype – the Tab set.

Style sets are used a lot in so called Master CSS (UA default style sheet) by H-SMILE core engine to give default styling of DOM elements and inputs. Styles sets there allow to minimize time needed for resolution of default styles.

Aero, Windows V/7

In the middle of adding Windows Aero DWM support to HTMLayout and Sciter:
htmlayout-aero-w7

This is standard sample htmlayoutsdk/html_samples/border-radius/rounded-tabs.htm from the SDK

Seems like I need to add “aero” as an additional value to CSS @media selector in order to support such screens. This document is using simple html { background-color:transparent; } declaration to make it transparent.

And w7aero sample application is calling this function

void ExtendFrameIntoClient(HWND hwnd)
{
   // Negative margins have special meaning to DwmExtendFrameIntoClientArea.
   // Negative margins create the "sheet of glass" effect, where the client area
   //  is rendered as a solid surface with no window border.
   MARGINS margins = {-1};
   HRESULT hr = S_OK;
   // Extend frame across entire window.
   hr = DwmExtendFrameIntoClientArea(hwnd,&margins);
   assert (SUCCEEDED(hr)); hr;
}

Pretty easy I would say…

INPUT, WIDGET and custom elements in h-smile core

What is a difference between <input> and <widget> elements [in h-smile core]?

<input> is intrinsically display:inline-block element and <widget> is intrinsically display:block element.

So <input> can live only in some element that has display-model:inline-inside model, in other words in some text container like <p> (paragraph). So if you have markup as:

<div>
  Text: <input type=... />
</div>

engine is forced to wrap content of the div into anonymous text container block.(See: anonymous-block-level ).

In h-smile core that anonymous text container is not so anonymous – it is an element of type <text> (see menu: browse.exe -> Debug -> Element probe on). That <text> is just a <p> but without margins applied by default. You can style that <text> as any other element.

Concept of the Layout Parent Box

For inline-block elements Layout Parent Box is its line box (see: inline-formatting) so when you say height:100%% for inline-blocks their height will be set to the full height of line box.

For block elements Layout Parent Box is a content box of block’s parent (in normal flow). So in this markup:

<div style="height:200px; flow:vertical">
  <widget #first style=height:50%% />
  <widget #second style=height:50%% />
</div>

both widgets will each get 100px height (if no margins, paddings or borders were defined for the widgets).

Can markup parser be extended to support non-standard html elements?

Answer is “yes”.

Let’s say we want htmlayout to support element <checkbox>. We want such element 1) to contain only text (or other inline element) inside and 2) to behave exactly as <input type="checkbox">. To do so we need to define its model for the markup parser. The best place for this information is to put in the Master Style Sheet by using function HTMLayoutAppendMasterCSS().

Here is how such definition may look like:

checkbox {
  display: inline-block;        /* inline with outer text */
  display-model: inline-inside; /* contains only inlines */
  style-set: "std-checkbox";    /* all other visual and behavioral styles are
                                   derived from standard checkbox */
}

After such declaration parser will know how to parse such an element and how it shall be placed into the DOM.

Note: all custom elements shall be XML compatible in other words properly closed. So for this case our checkbox can appear only as <checkbox /> or <checkbox>some text</checkbox> in markup.

For an example of HTMLayoutAppendMasterCSS function use see htmlayoutsdk/wtl/browse/browse.cpp, function _tWinMain().


This article was prepared in Sciter/<richtext> editor.