Sciter — встраиваемый HTML/CSS/scripting engine

в 4:39, , рубрики: css, html, script, Графические оболочки, метки: , ,

Попросили вот здесь про Sciter слово замолвить… Собственно вот рассказываю.

Sciter есть встраиваемый HTML/CSS/scripting engine для создания UI десктопных и мобильных приложений, классических так и [occasionally-]connected.

В принципе поддерживаются разные парадигмы приложений ограниченные лишь фантазией разработчиков. Например одной фирмой была сделана телефонная система со smart desktop phones на которых работал Sciter-based client — фактически специализированный browser загружающий UI (HTML,CSS, scripts и images) с системного контроллера станции по специализированному протоколу.

Другой пример: фирма Symantec использует sciter как UI для их consumer продуктов — Norton Antivirus со товарищи (since 2007).

image
На картинке: sciter.exe demo проект из SDK + открытое окно DOM inspector'а, живет в inspector32.dll (исходники в SDK). inspector.dll можно использовать в своем проекте для отладки UI. Естественно что inspector UI есть опять же HTML/CSS/script + толика native code.

Про встраиваемость

Под встраиваемостью имеются ввиду следующие базовые принципы:

  1. Компактность, сейчас движок (sciter-x.dll) имеет размер 2 — 3mb
  2. Dependency free, sciter это одна DLL — sciter-x.dll. Не требует ничего сверх стандартной учтановки Windows.
  3. Универсальный и простой API. Использется т.н. plain Windows API. Ни COM ни .NET. Но Sciter можно использовать из например .NET или Delphi — любой среды понимающей plain API.
  4. Открытость и расширяемость основных механизмов. В коде приложения можно написать как свои собственные типы DOM элементов и элементов ввода так и использовать свои собственные протоколы и механизмы загрузки ресурсов. К DOM tree можно обращаться как из скрипта так и из native code.

Собственно процедура встраивания тривиальна. Это либо вызов ::CreateWindow(SciterClassName(),...), либо mix-in sciter'а к существующему окну добавлением в функцию окна (WinProc) такого вот кода:

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
//SCITER integration starts
  BOOL r, handled = FALSE;
  // delegating message processing to SciterProcND function:
  LRESULT lr = SciterProcND(hWnd,message,wParam,lParam, &handled);
  if( handled )
    return lr;
//SCITER integration ends

  switch (message) 
  {
    case WM_CREATE:
//SCITER integration starts
      {
        // window was created, attaching callback function that will receive SCN_LOAD_DATA requests, etc.
        SciterSetCallback(hWnd, BasicCallback, 0 /*cbParam is not ised in this sample*/ );

        // loading default document:
        LPCBYTE pb = 0; UINT   cb = 0;
        GetResource(L"default.html",pb,cb);
        SciterLoadHtml(hWnd, pb,cb, NULL ); 
      }
//SCITER integration ends
      break;
      .....
}     

Когда функция SciterProcND получит WM_CREATE сообщение она создаст sciter instance для этого окна. После этого этот HWND можно использовать как Sciter engine handler для остальных sciter функций. SciterSetCallback(hwnd, callback), например, зарегистрирует callback функцию в которую будут приходить например все запросы на загрузку ресурсов HTML, CSS, scripts и images. Таким образом ваше приложение может как предоставить собственный загрузчик ресурсов так и пропускать запросы в Sciter и его встроенный http client.

Манипулирование DOM

В составе sciter SDK есть файл sciter-x-dom.h который содержит как plain API обявления функций доступа к DOM загруженного документа так и dom::element примитив для C++. Вот например как выглядит код читающий значение 'элемента ввода <input type=number id="bottles-of-beer">:

dom::element root =  dom::element::root_element(hwnd);
dom::element numInput = root.find_first("input#bottles-of-beer");
json::value val = numInput.get_value(); // get numeric value 

Sciter DOM API по своей функциональности повторяет jQuery, только исполнен «нативно».

Та же самая задача но в скрипте sciter'а ( используется tiscript ):

var  numInput = self.select("input#bottles-of-beer");
var  val = numInput.value;
behaviors — расширения и компоненты

Приложение может описывать как свои собственные типы элементов и виджетов так и использовать набор готовых.

Native widget в коде приложения выглядит примерно так:

// sort of WinProc but for windowless DOM element:
class my_widget : public sciter::event_handler
{
      // CTOR/DTOR called when this event_handler is attached/detached to/from DOM element
      virtual void attached  (HELEMENT /*he*/ ) { }
      virtual void detached  (HELEMENT /*he*/ ) { }
       
      virtual bool handle_mouse  (HELEMENT he, MOUSE_PARAMS& params ) { ... }
      virtual bool handle_key    (HELEMENT he, KEY_PARAMS& params ) { ... }
      virtual bool handle_focus  (HELEMENT he, FOCUS_PARAMS& params ) { ... }
      virtual bool handle_timer  (HELEMENT he,TIMER_PARAMS& params ) { ... }
      virtual void handle_size  (HELEMENT he ) { ... }
      virtual bool handle_scroll  (HELEMENT he, SCROLL_PARAMS& params ) { ... }
      virtual bool handle_gesture  (HELEMENT he, GESTURE_PARAMS& params ) { ... }
      virtual bool handle_draw   (HELEMENT he, DRAW_PARAMS& params ) { ... }
      virtual bool handle_method_call (HELEMENT he, METHOD_PARAMS& params ) { ... }
      virtual bool handle_event (HELEMENT he, BEHAVIOR_EVENT_PARAMS& params ) { ... }
      // notification event: data requested by HTMLayoutRequestData just delivered
      virtual bool handle_data_arrived (HELEMENT he, DATA_ARRIVED_PARAMS& params ) { ... }
      virtual bool handle_scripting_call(HELEMENT he, SCRIPTING_METHOD_PARAMS& params ) { ... }
};

struct my_widget_factory: public sciter::behavior_factory
{
  my_widget_factory(): behavior_factory("my-widget") {} // symbolic name for CSS
  // create the instance of our widget:
  virtual event_handler* create(HELEMENT he) { return new my_widget(); }
};

// in .cpp file:

my_widget_factory _my_widget_factory; // registering the factory in global list:

Названия методов говорят сами за себя поэтому коментировать их не буду. Прямая аналогия: event _handler это такой WinProc, но для windowless DOM элемента.

Подключение (binding) такого контроллера к DOM элементам выполняется декларативно с помощью CSS:

div.my-widget 
{
   border: 1px solid red; 
   behavior: my-widget; /* the name used in  sciter::behavior_factory */
}

Т.е. как только в документе появится <div class="my-widget">...</div> ему назначится объявленный event handler и будет вызвана функция my_widget::attached(thatElement);.

Свои behaviors можно описывать также в скрипте. Там это еще проще:

class MyWidget : Behavior {

  function attached() { this.state.visited = true; } // as an example 
  function detached() { this.state.visited = false; } 
  
  function onMouse(evt) { 
    switch(evt.type) 
    {
        case Event.MOUSE_DOWN: ...
        case Event.MOUSE_MOVE: ...
        case Event.MOUSE_UP: ...
        ...
    }
  }

  property value(v) { 
      get { return this.text; }
      set { this.text = v; }
 }

}

И в CSS:

div.my-widget 
{
   border: 1px solid red; 
   prototype: MyWidget; /* script class name */
}

После таких деклараций DOM элементу с таким behavior делается sub-classing, т.е. вот это работает:

var myWidget = self.select("div.my-widget");
myWidget instanceof MyWidget; // true, that DOM element is a MyWidget now.
var val = myWidget.value; // call of MyWidget.value/get.

CSS extensions

В Sciter (h-smile core если быть точным) используется CSS level 2 плюс некоторые фичи из level 3. Также я добавил flow и flex-units без которых использование CSS для именно desktop UI-строения занятие довольно проблематичное.

Исторически HTML и CSS используют т.н. endless tape модель — документ имеет ширину ограниченную шириной окна но высота документа не известна. Поэтому в CSS level 2 нет средств сказать «сделай высоту элемента равной высоте окна». Или, скажем, layout окна Outlook. В sciter такой layout описывается как:

<body>
   <div id="mailboxes">...</div>
   <div id="messages">...</div>
   <div id="current-message">...</div>
</body>

и стиль

body { 
   flow: horizontal; /* content replaced horizontally */
   width: *;
   height: *;  /* body spans whole window */
 }
body > div { 
   height: *; /* all children of the body have the same height and take 
                        whole height of the body */
 }

Фактически flow описывает layout manager в терминах Java AWT. В CSS level 3 появился Flexbox Module который делает нечто аналогичное моему flow, но как-то коряво и не полно. Например Java::BorderLayout на нем похоже не сотворить.

Про версии Sciter.

В настоящее время есть две версии Sciter:

  1. Sciter version 1 — GDI backend, все версии Windows включая Windows CE. Active maintenance mode.
  2. Sciter version 2 — Direct2D backend, Windows Vista и выше. Актуальная версия.

Обе версии используют совместимый API поэтому взаимозаменямы.
Основные отличия Sciter2:

  • Парсер и DOM HTML5 compatible.
  • Custom drawing механизм переработан. Sciter использует <canvas> alike рисование (в bitmap buffer). Element.graphics() метод в Sciter1 сосздает такой буфер (для любого DOM элемента, а не только для ). В Sciter2 используется т.н. direct drawing. В скрипте описываются функции Element.onPaintContent(graphics), Element.onPaintBackground(graphics) которые вызываются в момент отрисовки. Фактически эквивалент WM_PAINT, WM_ERASEBACKGRND, etc. в Windows. Direct2D достаточно производителен чтобы это стало возможным.
    В CSS Sciter2 в полном объеме работает transform свойство. Direct2D вытягивает это дело.
  • .

    Ну вот в двух словах про Sciter. Задавайте вопросы кому интересно.

Автор: csmile

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js