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 发包方日志文件