Sciter - 多平臺嵌入式 HTML/CSS/腳本 UI 引擎
Sciter - 多平臺嵌入式 HTML/CSS/腳本 UI 引擎
簡介
Sciter 是一個用 HTML/CSS 來渲染現代應用程序 UI 的腳本引擎。 它非常緊湊,簡單(只有一個 4~8M 的動態鏈接庫文件 dll/dylib/so),沒有其他依賴。它可以工作在 Microsoft Windows(XP 及以上版本), Apple OS X (10.7 及以上版本)和 Linux/GTK (GTK 3.0 及以上版本)。 Sciter 在較新的 Windows 版本上使用 Direct2D GPU 圖形加速技術,在 Windows XP 上則使用 GDI+ 技術。 在 OS X 上,它使用標準的 CoreGraphics 技術, 而在 Linux 上使用是 Cairo 庫。
背景
早在 2006 年,Sciter 就被許多公司應用到生產環境中。目前基于 Sciter 的 UI 程序已經運行在分布于世界各地的超過 1.2 億臺 PC 和 Mac 上。 說不定您的機器上的某些程序使用的就是 Sciter 的 UI。
Sciter使用HTML5元素, 完全實現css2.1,加上css3上最流行的功能. 它還包含了自定義css擴展來支持桌面UI. 例如, flex units and various layout managers. Sciter是 HTMLayout 與硬件加速,多平臺支持和 TIScript 的下一個主要版本
在這篇文章中,我將描述一個多平臺支持的的簡單應用。
本例子是官方SDK中的樣例 official Sciter SDK distribution 可以去 這里.找到它. 你可以從 sciter-sdk/demos/ulayered/folder.這個目錄找到它們. 該SDK還會教您如何為每個平臺編譯可執行文件,所以現在是你動手的時候了.
Sciter 時鐘應用程序
我們的應用程序將會是一個在所謂的分層多窗口環境被渲染的壁掛鐘表. 所有的現代操作系統支持“透明感知”的窗口, 如此我們就可以得到一個任意形狀的UI:
源代碼/項目布局
Sciter 時鐘項目的架構:
-
/builds/- 包含 Visual Studio, XCode 和 Code::Blocks 項目文件.
-
/res/- 應用程序的 UI 資源 - HTML, CSS, 腳本 和 圖片 一般都在這兒.
-
/res/default.htm- 主要且本應用程序要用到的唯一一個HTML文件.
-
/res/analog-clock.tis- 腳本組件,也叫做“動作”,負責繪制時鐘的指針,等等工作.
-
/res/movable-view.tis- 可以通過鼠標的拖拽動作讓窗口可以在桌面上移動的腳本代碼.
-
/pack-resources.bat- 調用 sciter-sdk/bin/packfolder.exe 工具的命令行腳本,用來將 /res/ 文件夾下面的內容壓縮到 resources.cpp.
-
/resources.cpp- 包含來自 /res/ 文件夾中資源,被壓縮成了無符號的字符型 resources[] = { ...}; 對應為 blob 類型的資源.
-
/ulayered.cpp- 定義了 uimain() 函數的本地代碼,就是這個方法創建應用程序的窗口.
本地代碼
Sciter 時鐘是一個相當簡單的應用程序, 因而它的本地代碼也同樣簡單.
ulayered.cpp:
#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(); }
它所定義的 frame 類是一個特殊化了的 sciter::window 類. 我們的 frame 包含了一個本地的 frame::architecture() 函數, 意在從腳本能調用到 asview.architecture(). 任何額外需要的本地函數都應該被包含到BEGIN_FUNCTION_MAP/END_FUNCTION_MAP腳本中去,最好是本地的綁定塊.
這里最主要的有趣之處是 uimain() 函數。它主要做了如下三件事情:
-
用sciter:archive實體綁定已經打包好了的資源blob大對象. 當引擎通過SC_LOAD_DATA標識請求這些資源時,sciter::archive 實體就會提供.:更詳細的信息可以參見See the insciter-sdk/include/sciter-x-host-callback.h 文件中有關 sciter::host::on_load_data() 的實現。你的應用程序也可能用任何其他方式存儲資源,sciter::archive 只是做這件事情的一種可能的方式而已.
-
創建窗口實體,并指示它去加載 this://app/default.htm 文件(參見 /res/default.htm).
-
顯示窗口,并運行所謂的 "消息泵(message pump)" 循環 - 其實就是 std::function<int()> 運行時所傳入的一個參數. 每個操作系統平臺都有其各自定義“主”函數的獨特方式, 因此你需要將如下幾個文件的其中之一包含到你的項目中去:
-
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
注意 ulayered.cpp 在所有的平臺上都會被用到。唯一對平臺獨立的文件就是上述的三個文件之一。如果你用到的其它代碼使用來自std,boost,POCO等等來源的通用C++庫和函數, 那么 C++ 和 Sciter 就允許你只寫一次代碼,就能在所有流行的桌面操作系統智商運行它。
UI 標記
我們的標記(res/default.htm)同樣也是相當的簡單:
<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>
除開像 <button #minimize> 這樣的東西 —— 它是 <button id="minimize"> 的一種簡寫形式——一切都不言自明。我已經早Sciter中擴展的HTML解析器來支持這個特性,當然還有其它常用的UI 構建。
CSS, <style>...</style> 中間的內容.
通常,你需要在獨立的文件中放入你的樣式定義。為了簡便起見,我已經如下例所示,就把它們內嵌到了HTML文件中:
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); }
這就是相當標準的CSS,除了兩樣東西。首先,是下面這個規則(rule)/屬性(property)對::
body { prototype: Clock url(analog-clock.tis); }
它告知了如下信息: "<body> DOM 元素應該由位于analog-clock.tis文件中的Clockfound類型(的子類型子類型)驅動". Clock 類 組件(也叫做 "動作") 包含了繪制時鐘指針并使用 原生圖像 進行標記的腳本代碼. Sciter 提供了兩種CSS機制,來聲明元素到腳本的綁定. 你可以參考 S借助CSS的‘原型(prototype)’和‘切面(aspect)'特性來聲明行為分配 這篇文章,以深入了解其機制。
其次,是如下這種結構:
body { margin:*; }
定義了body元素上的靈活外邊距設置. 本質上,它是在body元素四周的放了四個“彈簧”,它們能將其移動到其容器(窗口)的中心位置:
你可以參考一下 Sciter 要用到的flex單元和布局管理器,這可以在我向W3C/CSS WG提出的有關 柔性流 建言中可以了解到。
腳本, <script type="text/tiscript">...</script> 一節里面的腳本
應用程序的腳本相對而言也很簡單:
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();
這里有兩個按鈕點擊事件處理器, 還有 DOM ready 事件處理器,它會在觸發時初始化啟動后40ms展開的動畫 ( body.timer(40ms, function() { ... }); ) 。
最有趣 腳本代碼可能就在 /res/analog-clock.tis 中 (通過上面提到過得CSS原型語句引入):
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 支持所謂的實時繪制模式。如果你把DOM看做是一個窗口(HWND), 那么實時模式下的繪制就是一段處理WM_PAINT窗口消息的代碼。每當有一個界面區域需要被繪制時,它就會被調用。
上述 attached() 函數會在DOM元素 (這里是<body>) 獲得Clock類實體的 "繼承(subclassed)"。 這段語句:
this.paintForeground = this.drawclock;
將 drawclock() 函數作為元素前景層繪制處理器進行安裝, 而這一句:
this.timer(300ms,::this.refresh());
則會讓元素每 300ms 被刷新一次。因而時鐘界面會每 300ms 就重畫一次. ::this.refresh() 是TIScript中聲明的一個 Lambda 函數.
Sciter SDK 架構概觀:
公共的 Sciter SDK 包含下面這些目錄:
-
bin, bin.osx 和 bin.gtk - 放了編譯好了的 Sciter 引擎: sciter32/64.dll (Windows), sciter-osx-64.dylib (OS X), sciter-gtk-64.so (Linux) 以及 sciter.exe variations - 帶有內置DOM檢查器、腳本調試器和Sciter文件瀏覽器的"瀏覽器"demo(參見上面截圖). 從這個demo文件夾里面可以找到編譯好了的示例。
-
include - C 和 C++ 的包含文件,定義了公共的Sciter引擎API: 窗口級別的函數, DOM訪問方法和工具。
-
demos, demos.osx, demos.gtk - 示例的項目/應用程序展示Sciter包含的各個方面。
-
doc - HTML格式的文檔, 可以用傳統的瀏覽器或者內建的幫助查看器進行查閱。
-
samples - 用于展示不同的Sciter特性的 HTML/CSS/script 代碼塊和示例。
公共的Sciter SDK中包含字庫和案例
-
samples/+plus 那是跟angularJS同樣的綁定字庫。小(480LDC)和非干擾性模型-界面-什么的字庫。
-
samples/+lib 像underscore.js一樣簡單。
-
samples/+promise 承諾/A+規范執行
-
samples/+query 基本的jQuery/Zepto端口。絕大多數jQuery的特性在Scriter中可以實現,因此這個庫可以相當于700LDC。
-
samples/+lang - i18n .
-
samples/+vlist 虛擬清單,柵格庫和案例。當你需要去瀏覽大量的記錄時可以使用vlist。+vlist使用一個動態信息的捆綁機制。這提供了一個array[]矩陣記錄和類似于AngularJS的一個可重復的模板。
-
samples/animations 庫和動畫框架的演示,與GreenSock.js動畫平臺相似(GSAP)。
-
samples/animated-png Animated PNG 演示。
-
samples/animations-transitions-css 基于css的轉換。從過去的歷史來看,Sciter定義css轉換使用了稍微不同的語法,但是這個特性與css3像類似。
-
samples/basics 基本的css案例,包括css3的轉換熟悉。
-
samples/communication AJAX/JSON 客戶端,WebSockets和DataSockets的雙間/內網絡通信。
-
samples/css++ 在Sciter中介紹了不同的css的擴展示范
-
samples/dialogs+windows View.window, View.dialog 和 View.msgbox特性的演示:通過HTML/CSS/script來定義桌面窗口。
-
samples/drag-n-drop-manager drag-n-drop 管理。
-
samples/effects.css++ 轉換:blend 和轉換:slide-xxx 演示-Sciter特殊轉換的擴展
-
samples/font-@-awesome 集成了FontAwesome的CSS3 @font-face演示。
-
samples/forms 示范Sciter擴展<input>部件,包括<select type=tree>,<input type=number>, <input type=masked>等待。
-
samples/goodies Sciter的配件包括,行為:文件圖標 殼圖標渲染。
-
samples/graphics 圖形類的使用 -直接和調整原始圖案包括render-element-to-bitmap和dynamic-CSS-background-image特性,Sciter中的圖形類在瀏覽器中是<canvas>的擴展集。
-
samples/ideas 應用想法的集成包括
動態滾動的清單
-
callout-動態標注
-
carousel
-
KiTE 類似于{{mustache}}的模板引擎。
-
lightbox-dialog 在窗口模型對話框中的燈箱
-
moveable-windows Sciter支持所謂的機載DOM元素-元素可以呈現在不同的窗口界面中。這個例子描述了這個特性。
-
tray-notifications HTML/CSS定義了任務欄命令。
-
virtual-list 帶動態滾動欄的虛擬清單支持無線數量帶有不同高度的項。(看右邊的圖)
-
samples/image-map 關于人的圖像目錄和DPI圖像
-
samples/image-transformations.css++ Sciter另外的CSS特性:圖像過濾。
-
samples/menu HTML和CSS定義的真實菜單。
-
samples/popup 另外的機載DOM元素-浮動窗口。
-
samples/replace-animator 在不同層之間的動畫轉換。
-
samples/richtext 富文本框架的演示-WYSIWYG HTML編輯器。
-
samples/scrollbars-n-scrolling 滾動條和滾動方式風格。
-
samples/selection DOM樹的文本和范圍選擇的示例。
-
samples/sqlite 集成SQLite的演示(tiscript-sqlite.dll)。
-
samples/svg Sciter SVG擴展的示例。
-
samples/tooltips++ tooltips/calltips定義和風格的示例(看右邊的圖)。
-
samples/video 看Sciter中video的回放。
-
samples/xml 基于XML tokenizer的XMLParser構建的XML處理。
歷史
文章:10年的Sciter歷程。
HTMLayout代碼項目文章。從概念上講,Sciter是下一代HTMLayout主要的版本。Sciter API是HTMLayout API的擴展,因此嵌入原則也被應用在Sciter中。
Sciter論壇和討論
1.Sciter主題討論(英語)
2.Sciter在RSDN.RU(俄文)
3.Sciter在中國
4.代碼項目
參考和進一步閱讀
-
Sciter主頁
-
文章:在21分鐘內集成sciter
-
文章:Sciter UI和應用框架
-
Sciter博客
-
Sciter 發包方日志文件