move rust-sciter in

This commit is contained in:
rustdesk
2021-05-23 10:55:19 +08:00
parent bdfb59dd1a
commit 237e889604
76 changed files with 16950 additions and 83 deletions

Binary file not shown.

View File

@@ -0,0 +1,17 @@
//! Sciter sample with archived resources.
extern crate sciter;
fn main() {
let resources = include_bytes!("archived.rc");
let mut frame = sciter::WindowBuilder::main_window()
.fixed()
.with_size((600, 400))
.create();
frame.archive_handler(resources).expect("Invalid archive");
frame.load_file("this://app/index.htm");
frame.run_app();
}

View File

@@ -0,0 +1,162 @@
<html>
<head>
<title>Clock</title>
<style>
body { padding: 5dip; }
#clocks {
size: *;
flow: horizontal-flow;
}
.city {
flow: vertical;
size: *;
}
.city > caption {
width: *;
padding: 5dip;
flow:horizontal;
horizontal-align: center;
font-size: 11pt;
font-weight: 700;
}
.city > clock, .edit {
margin-right: *;
margin-left: *;
margin-bottom: 5dip;
}
.city > .edit {
flow: horizontal;
width: max-content;
}
clock {
behavior: native-clock;
display: block;
min-width: 150dip;
min-height: 150dip;
}
.native-text {
behavior: native-text;
display: block;
min-height: 100dip;
min-width: 100dip;
outline: 1px solid orange;
}
.italic {
font-style: italic;
}
.bold {
font-weight: bold;
}
</style>
<script type="text/tiscript">
// called from Rust
function getLocalTime() {
var d = new Date();
return [d.hour, d.minute, d.second];
}
function getUtcTime(offset) {
var d = new Date();
d.UTChour += offset;
return [d.UTChour, d.UTCminute, d.UTCsecond];
}
var timeSet = $(#set-time);
var timeEdit = $(#edit-time);
var timeShow = $(#show-time);
timeEdit << event change {
var now = this.value;
var local = [now.hour, now.minute, now.second];
stdout.printf("timeEdit: %v %v\n", now, local);
timeShow.value = local;
}
timeSet << event click {
var now = new Date();
var local = [now.hour, now.minute, now.second];
stdout.printf("timeSet: %v %v\n", now, local);
timeEdit.value = now;
timeShow.value = local;
}
function self.ready() {
var now = new Date();
var local = [now.hour, now.minute, now.second];
stdout.printf("initial time: %v %v\n", now, local);
timeShow.value = local;
}
// update window icon
function setIcon() {
var clock = $(clock);
// get clock element size
var (w, h) = clock.box(#dimension, #border);
if (w == 0 || h == 0)
return;
// make a snapshot of it with 32x32 size
var img = new Image(clock, w, h, 32, 32);
var png = img.toBytes();
img.destroy();
// make an icon from the snapshot
var icon = Image.fromBytes(png);
view.windowIcon = icon;
return true; // continue to update icon ;)
}
self.timer(1000, setIcon);
</script>
</head>
<body>
<div id="clocks" some>
<div class="city">
<caption>London</caption>
<clock utc="0" title="UTC+0"></clock>
</div>
<div class="city">
<caption>Tokyo</caption>
<clock utc="+9" title="UTC+9"></clock>
</div>
<div class="city">
<caption>New York</caption>
<clock utc="-4" title="UTC-4"></clock>
</div>
</div>
<div id="local" some>
<div class="city">
<caption>Frozen time</caption>
<clock frozen id="show-time"></clock>
<div class="edit">
<widget type="time" value="now" id="edit-time" title="Change time of the drawn clock" />
<widget type="button" id="set-time" title="Reset back to local time">=</widget>
</div>
</div>
</div>
<div>
<div class="city">
<caption>Text</caption>
<div class="native-text">static text</div>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,368 @@
//#![windows_subsystem = "windows"]
#[macro_use]
extern crate sciter;
use sciter::dom::event::{MethodParams, DRAW_EVENTS, EVENT_GROUPS};
use sciter::dom::{Element, HELEMENT};
use sciter::graphics::{self, rgb, Graphics, HGFX};
use sciter::types::RECT;
use sciter::Value;
// 24:60:60, will be drawn as analog clock
type Time = [u8; 3usize];
/// Clock native behavior.
///
/// ## Behavior-specific HTML attributes:
///
/// * `utc="integer"` - time zone offset, positive or negative.
/// * `frozen` - time is not updated automtically.
///
/// ## Value
///
/// *read/write* Current time value in `HH::MM::SS` or `[HH, MM, SS]` form.
///
/// ## Events
///
/// N/A - this element does not generate any specific events.
///
#[derive(Default)]
struct Clock {
element: Option<Element>,
now: Time,
gmt: i8,
is_frozen: bool,
}
impl sciter::EventHandler for Clock {
/// Claim what kind of events we want to receive.
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
// we need timer and draw events
// also behavior method calls
Some(EVENT_GROUPS::HANDLE_TIMER
| EVENT_GROUPS::HANDLE_DRAW
| EVENT_GROUPS::HANDLE_METHOD_CALL
)
}
/// Our element is constructed. But scripts in HTML are not loaded yet.
fn attached(&mut self, root: HELEMENT) {
self.element = Some(Element::from(root));
let me = self.element.as_ref().unwrap();
// get attributes to initialize our clock
if let Some(attr) = me.get_attribute("utc") {
if let Ok(v) = attr.parse::<i8>() {
self.gmt = v;
}
}
// we don't update frozen clocks
if let Some(_attr) = me.get_attribute("frozen") {
self.is_frozen = true;
}
// timer to redraw our clock
if !self.is_frozen {
me.start_timer(300, 1).expect("Can't set timer");
}
}
/// Our behavior methods.
fn on_method_call(&mut self, _root: HELEMENT, params: MethodParams) -> bool {
match params {
MethodParams::GetValue(retval) => {
// engine wants out current value (e.g. `current = element.value`)
let v: Value = self.now.iter().map(|v| i32::from(*v)).collect();
println!("return current time as {:?}", v);
*retval = v;
}
MethodParams::SetValue(v) => {
// engine sets our value (e.g. `element.value = new`)
println!("set current time from {:?}", v);
// "10:20:30"
if v.is_string() {
let s = v.as_string().unwrap();
let t = s.split(':').take(3).map(|n| n.parse::<u8>());
let mut new_time = Time::default();
for (i, n) in t.enumerate() {
if let Err(_) = n {
eprintln!("clock::set_value({:?}) is invalid", v);
return true; // consume this event anyway
}
new_time[i] = n.unwrap();
}
// use it as a new time
self.set_time(new_time);
// [10, 20, 30]
} else if v.is_varray() {
let mut new_time = Time::default();
for (i, n) in v.values().take(3).map(|n| n.to_int()).enumerate() {
if n.is_none() {
eprintln!("clock::set_value({:?}) is invalid", v);
return true;
}
new_time[i] = n.unwrap() as u8
}
// use it as a new time
self.set_time(new_time);
} else {
// unknown format
eprintln!("clock::set_value({:?}) is unsupported", v);
return true;
}
}
_ => {
// unsupported event, skip it
return false;
}
}
// mark this event as handled (consume it)
return true;
}
/// Redraw our element on each tick.
fn on_timer(&mut self, root: HELEMENT, _timer_id: u64) -> bool {
if self.update_time() {
// redraw our clock
Element::from(root).refresh().expect("Can't refresh element");
}
true
}
/// Request to draw our element.
fn on_draw(&mut self, _root: HELEMENT, gfx: HGFX, area: &RECT, layer: DRAW_EVENTS) -> bool {
if layer == DRAW_EVENTS::DRAW_CONTENT {
// draw content only
// leave the back- and foreground to be default
let mut gfx = Graphics::from(gfx);
self
.draw_clock(&mut gfx, &area)
.map_err(|e| println!("error in draw_clock: {:?}", e) )
.ok();
}
// allow default drawing anyway
return false;
}
}
// 360°
const PI2: f32 = 2.0 * std::f32::consts::PI;
impl Clock {
/// Update current time and say if changed.
fn update_time(&mut self) -> bool {
if self.is_frozen {
return false;
}
// ask our script for the current time
if let Some(now) = self.get_time() {
let update = self.now != now;
self.now = now;
update
} else {
false
}
}
/// Set the new time and redraw our element.
fn set_time(&mut self, new_time: Time) {
// set new time and redraw our clock
self.now = new_time;
if let Some(el) = self.element.as_ref() {
el.refresh().ok();
}
}
/// Get current time from script.
fn get_time(&self) -> Option<Time> {
let el = self.element.as_ref().unwrap();
let script_func = if self.is_frozen { "getLocalTime" } else { "getUtcTime" };
if let Ok(time) = el.call_function(script_func, &make_args!(self.gmt as i32)) {
assert_eq!(time.len(), 3);
let mut now = Time::default();
for (i, n) in time.values().take(3).map(|n| n.to_int()).enumerate() {
now[i] = n.unwrap() as u8;
}
Some(now)
} else {
eprintln!("error: can't eval get time script");
None
}
}
/// Draw our element.
fn draw_clock(&mut self, gfx: &mut Graphics, area: &RECT) -> graphics::Result<()> {
// save previous state
let mut gfx = gfx.save_state()?;
// setup our attributes
let left = area.left as f32;
let top = area.top as f32;
let width = area.width() as f32;
let height = area.height() as f32;
let scale = if width < height { width / 300.0 } else { height / 300.0 };
// translate to its center and rotate 45° left.
gfx
.translate((left + width / 2.0, top + height / 2.0))?
.scale((scale, scale))?
.rotate(-PI2 / 4.)?;
gfx.line_color(0)?.line_cap(graphics::LINE_CAP::ROUND)?;
// draw clock background
self.draw_outline(&mut *gfx)?;
// draw clock sticks
self.draw_time(&mut *gfx)?;
Ok(())
}
/// Draw clock static area (hour/minute marks).
fn draw_outline(&mut self, gfx: &mut Graphics) -> graphics::Result<()> {
// hour marks (every 5 ticks)
{
let mut gfx = gfx.save_state()?;
gfx.line_width(8.0)?.line_color(rgb(0x32, 0x5F, 0xA2))?;
for _ in 0..12 {
gfx.rotate(PI2 / 12.)?.line((137., 0.), (144., 0.))?;
}
}
// minute marks (every but 5th tick)
{
let mut gfx = gfx.save_state()?;
gfx.line_width(3.0)?.line_color(rgb(0xA5, 0x2A, 0x2A))?;
for i in 0..60 {
if i % 5 != 0 {
// skip hours
gfx.line((143., 0.), (146., 0.))?;
}
gfx.rotate(PI2 / 60.)?;
}
}
Ok(())
}
/// Draw clock arrows.
fn draw_time(&mut self, gfx: &mut Graphics) -> graphics::Result<()> {
let time = &self.now;
let hours = f32::from(time[0]);
let minutes = f32::from(time[1]);
let seconds = f32::from(time[2]);
{
// hours
let mut gfx = gfx.save_state()?;
// 2PI*/12, 2PI/720,
gfx.rotate(hours * (PI2 / 12 as f32) + minutes * (PI2 / (12 * 60) as f32) + seconds * (PI2 / (12 * 60 * 60) as f32))?;
gfx
.line_width(14.0)?
.line_color(rgb(0x32, 0x5F, 0xA2))?
.line((-20., 0.), (70., 0.))?;
}
{
// minutes
let mut gfx = gfx.save_state()?;
gfx.rotate(minutes * (PI2 / 60 as f32) + seconds * (PI2 / (60 * 60) as f32))?;
gfx
.line_width(10.0)?
.line_color(rgb(0x32, 0x5F, 0xA2))?
.line((-28., 0.), (100., 0.))?;
}
{
// seconds
let mut gfx = gfx.save_state()?;
gfx.rotate(seconds * (PI2 / 60 as f32))?;
gfx
.line_width(6.0)?
.line_color(rgb(0xD4, 0, 0))?
.fill_color(rgb(0xD4, 0, 0))?
.line((-30., 0.), (83., 0.))?
.circle((0., 0.), 10.)?;
}
Ok(())
}
}
////////////////////////////////////
#[derive(Default)]
struct Text;
impl sciter::EventHandler for Text {
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
Some(EVENT_GROUPS::HANDLE_DRAW)
}
fn attached(&mut self, _root: HELEMENT) {
}
fn on_draw(&mut self, _root: HELEMENT, gfx: HGFX, area: &RECT, layer: DRAW_EVENTS) -> bool {
if layer == DRAW_EVENTS::DRAW_CONTENT {
// draw content only
// leave the back- and foreground to be default
let mut gfx = Graphics::from(gfx);
let e = Element::from(_root);
self
.draw_text(&e, &mut gfx, &area)
.map_err(|e| println!("error in draw_clock: {:?}", e) )
.ok();
return true;
}
// allow default drawing anyway
return false;
}
}
impl Text {
fn draw_text(&mut self, e: &Element, gfx: &mut Graphics, area: &RECT) -> graphics::Result<()> {
// save previous state
let mut gfx = gfx.save_state()?;
// setup our attributes
// let left = area.left as f32;
// let top = area.top as f32;
// let width = area.width() as f32;
// let height = area.height() as f32;
// println!("text::draw on {} at {} {} {} {}", e, left, top, width, height);
use sciter::graphics::Text;
let t = Text::with_style(&e, "native text", "font-style: italic")?;
gfx.draw_text(&t, (area.left as f32, area.top as f32), 7)?;
Ok(())
}
}
fn main() {
let mut frame = sciter::WindowBuilder::main_window().with_size((800, 600)).create();
frame.register_behavior("native-clock", || Box::new(Clock::default()));
frame.register_behavior("native-text", || Box::new(Text::default()));
frame.load_html(include_bytes!("clock.htm"), Some("example://clock.htm"));
frame.run_app();
}

View File

@@ -0,0 +1,272 @@
#![allow(unused_variables)]
#![allow(unused_must_use)]
extern crate sciter;
use sciter::{Value, Element, HELEMENT};
use sciter::dom::event::*;
#[derive(Default)]
struct DocumentHandler;
impl sciter::EventHandler for DocumentHandler {
fn attached(&mut self, _root: sciter::HELEMENT) {
println!("attached");
}
fn detached(&mut self, root: sciter::HELEMENT) {
let root = Element::from(root);
println!("detaching from {}", root);
}
fn document_complete(&mut self, root: sciter::HELEMENT, source: sciter::HELEMENT) {
println!("document is loaded.");
let root = Element::from(root);
assert_eq!(root.get_tag(), "html");
println!("root {:?}", root);
let head = root.first_child().expect("empty <html>?");
assert_eq!(head.get_tag(), "head");
assert_eq!(head.index(), 0);
println!("head {:?}", head);
let body = head.next_sibling().expect("only <head>?");
assert_eq!(body.get_tag(), "body");
assert_eq!(body.index(), 1);
println!("body {:?}", body);
assert_eq!(body.first_sibling().expect("must be head"), head);
assert_eq!(body.last_sibling().expect("must be body"), body);
println!("for loop in children");
for e in root.children() {
println!("child {:?}", e);
}
println!("for loop in ref");
for e in &root {
println!("child {:?}", e);
}
if let Ok(Some(h1)) = body.find_first("body > h1") {
println!("h1 {:?}", h1);
let h1_parent = h1.parent().expect("come on!");
assert_eq!(h1_parent, body);
let text = h1.get_text();
assert_eq!(text, "Herman Melville - Moby-Dick");
let html = h1.get_html(true);
assert_eq!(html.as_slice(), br"<h1>Herman Melville - Moby-Dick</h1>".as_ref());
let value = h1.get_value();
assert!(value.is_string());
assert_eq!(value.as_string().unwrap(), text);
}
if let Some(mut h1) = body.first_child() {
println!("changing h1 attributes");
h1.set_style_attribute("color", "green");
h1.set_style_attribute("outline", "1px solid orange");
h1.set_attribute("title", "yellow!");
}
{
let all: Vec<Element> = body.find_all("div > p").unwrap().expect("must be at least one 'div > p'");
assert!(!all.is_empty());
assert_eq!(all.len(), 1);
all.len();
}
if let Ok(Some(mut body)) = root.find_first("html > body") {
let mut div = Element::with_parent("div", &mut body).unwrap();
div.set_attribute("id", "requests");
div.set_style_attribute("outline", "1px solid orange");
div.set_style_attribute("margin", "10dip 0");
div.set_style_attribute("padding", "4dip");
let e = Element::with_text("label", "Requests:").unwrap();
div.append(&e).unwrap();
let d = Element::with_text("div", "data").unwrap();
div.append(&d).unwrap();
let c = Element::with_text("div", "footer").unwrap();
div.append(&c).unwrap();
d.request_html("https://sciter.com/test/text.txt", None).unwrap();
// d.send_get_request("https://sciter.com/test/text.txt", None).expect("can't send an http request");
// d.send_get_request("http://httpbin.org/html", None).expect("can't send an http request");
// d.send_get_request("http://httpbin.org/get?one=1&two=2", None).expect("can't send an http request");
// let params = [("one", "1"), ("two", "2")];
// let method = sciter::request::REQUEST_TYPE::AsyncGet;
// let data_type = sciter::request::RESOURCE_TYPE::HTML;
// d.send_request("http://httpbin.org/html", Some(&params), Some(method), Some(data_type)).expect("can't send an http request");
}
if let Ok(Some(mut body)) = root.find_first("html > body") {
println!("creating some elments");
// DOM manipulation.
// After creating the new Element, we can set only attributes for it until we'll attach it to the DOM.
//
let mut div = Element::with_parent("div", &mut body).unwrap();
div.set_style_attribute("outline", "1px solid orange");
div.set_style_attribute("width", "max-content");
div.set_style_attribute("padding", "5dip");
let mut lb = Element::with_text("label", "Output: ").unwrap();
div.append(&lb).expect("wtf?"); // push as reference, we can access this `lb` still.
let mut date = Element::with_type("input", "date").unwrap();
date.set_attribute("id", "mydate");
date.set_attribute("value", "now");
lb.append(&date).expect("wtf?");
date.set_style_attribute("width", "100px");
date.set_style_attribute("outline", "1px dotted gray");
date.set_style_attribute("margin", "10px");
lb.set_attribute("accesskey", "o");
lb.set_style_attribute("color", "lightblue");
lb.set_style_attribute("vertical-align", "middle");
let mut progress = Element::create("progress").unwrap();
progress.set_attribute("max", "100");
progress.set_attribute("id", "id1");
progress.set_attribute("title", "Click to start timer.");
div.append(&progress).expect("wtf?");
// after attaching Element to DOM, we can set its styles, text, html or value.
progress.set_value(Value::from(42));
progress.set_style_attribute("behavior", "progress clickable");
// attach a new handler to this element;
// since timers are not sinking/bubbling, we need to attach
// a dedicated handler to that element directly.
let handler = ProgressHandler::default();
progress.attach_handler(handler).expect("can't attach?");
let mut e = Element::with_text("span", " <-- check tooltip").unwrap();
div.append(&e);
e.set_style_attribute("font-style", "italic");
}
}
}
#[derive(Default)]
struct ProgressHandler {
progress: Option<Element>,
start_timer: bool,
}
impl sciter::EventHandler for ProgressHandler {
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
Some(default_events() | EVENT_GROUPS::HANDLE_TIMER)
}
fn attached(&mut self, root: sciter::HELEMENT) {
let root = Element::from(root);
println!("attached an element event handler to {}", root);
if root.test("progress") {
self.progress = Some(root.clone());
self.start_timer = false;
}
}
fn detached(&mut self, root: sciter::HELEMENT) {
let root = Element::from(root);
println!("detaching from {}", root);
}
fn on_event(&mut self, root: HELEMENT, source: HELEMENT, target: HELEMENT, code: BEHAVIOR_EVENTS, phase: PHASE_MASK, reason: EventReason) -> bool {
if phase != PHASE_MASK::BUBBLING {
return false;
}
match code {
BEHAVIOR_EVENTS::BUTTON_CLICK => {
let source = Element::from(source);
let mut target = Element::from(target);
println!("button click on target {}", target);
if self.progress.is_some() && *self.progress.as_ref().unwrap() == target {
self.start_timer = !self.start_timer;
if self.start_timer {
println!("starting timer");
target.set_value(Value::from(0));
target.start_timer(1000, 1).ok();
} else {
println!("stopping timer");
target.stop_timer(1);
let cur = target.get_value();
target.set_attribute("title", &format!("Current value is {}. Click to start the timer again.", cur));
}
}
true
}
_ => false
}
}
fn on_timer(&mut self, root: HELEMENT, timer_id: u64) -> bool {
println!("timer {} tick on {}", timer_id, Element::from(root));
if timer_id == 1 && self.progress.is_some() {
let e = self.progress.as_mut().unwrap();
let max_attr = e.get_attribute("max").unwrap();
let max: f64 = max_attr.parse().unwrap();
let v = e.get_value();
let next = v.to_float().unwrap() + 5.0;
if next > max {
println!("that's enough, finish.");
self.start_timer = false;
e.stop_timer(1);
}
e.set_value(Value::from(next));
e.set_attribute("title", &format!("Current value is {}. Click to stop the timer if need.", next));
return true;
}
false
}
}
fn main() {
let mut frame = sciter::WindowBuilder::main_window()
.with_size((750, 950))
.debug()
.create();
println!("attaching an event handler for the whole window");
frame.event_handler(DocumentHandler::default());
frame.set_title("DOM sample");
println!("loading the page...");
frame.load_file("http://httpbin.org/html");
println!("running the app");
frame.run_app();
}

View File

@@ -0,0 +1,69 @@
//! Download http content (Go sciter example port).
#![allow(dead_code)]
extern crate sciter;
use sciter::dom::HELEMENT;
use sciter::host;
use sciter::utf;
use std::rc::{Rc, Weak};
struct Handler {
host: Weak<sciter::Host>,
}
impl sciter::EventHandler for Handler {
fn document_complete(&mut self, _root: HELEMENT, _target: HELEMENT) {
if let Some(host) = self.host.upgrade() {
// eval script inside the document to receive a "user@machine" string.
let result = host.eval_script("[Sciter.userName(), Sciter.machineName(true)].join(`@`)");
match result {
Ok(name) => {
println!("running on {}", name);
}
Err(e) => {
println!("error! {}", e.as_string().unwrap_or("?".to_string()));
}
}
}
}
}
impl sciter::HostHandler for Handler {
fn on_data_loaded(&mut self, pnm: &host::SCN_DATA_LOADED) {
println!("data loaded, uri: `{}`, {} bytes.", utf::w2s(pnm.uri), pnm.dataSize);
}
fn on_attach_behavior(&mut self, pnm: &mut host::SCN_ATTACH_BEHAVIOR) -> bool {
let el = sciter::Element::from(pnm.element);
let name = utf::u2s(pnm.name);
println!("{}: behavior {}", el, name);
false
}
}
impl Drop for Handler {
fn drop(&mut self) {
// called 2 times because it is created 2 times
println!("Good bye, window");
}
}
fn main() {
let mut frame = sciter::WindowBuilder::main_window().with_size((1024, 768)).create();
// Can't use something like `frame.sciter_handler(Rc::new(handler))` yet.
let handler = Handler {
host: Rc::downgrade(&frame.get_host()),
};
frame.sciter_handler(handler);
let handler = Handler {
host: Rc::downgrade(&frame.get_host()),
};
frame.event_handler(handler);
frame.set_title("Download sample");
frame.load_file("http://httpbin.org/html");
frame.run_app();
}

View File

@@ -0,0 +1,18 @@
<html>
<head>
<title>extension test</title>
<style type="text/css">
</style>
<script type="text/tiscript">
// copy "extension.dll" next to the "sciter.dll"
const ext = include library "extension";
debug: ext;
debug: ext.add(1,2);
debug: ext.sub(1,2);
</script>
</head>
<body>
<h4>see logs in Inspector</h4>
</body>
</html>

View File

@@ -0,0 +1,17 @@
[package]
name = "extension"
version = "0.1.0"
description = "A simple Sciter extension library"
authors = ["pravic <ehysta@gmail.com>"]
homepage = "https://sciter.com/include-library-name-native-extensions/"
edition = "2018"
publish = false
[lib]
path = "src/extension.rs"
crate-type = ["cdylib"]
test = false
bench = false
[dependencies]
sciter-rs = { version = "0.5", path="../../", features = ["extension"] }

View File

@@ -0,0 +1,50 @@
//! Sciter extension library example.
//!
//! See the [blog post](https://sciter.com/include-library-name-native-extensions/).
#[macro_use]
extern crate sciter;
use sciter::types::{BOOL, VALUE};
use sciter::Value;
/// Extension entry point.
#[no_mangle]
pub extern "system" fn SciterLibraryInit(api: &'static sciter::ISciterAPI, exported: &mut VALUE) -> BOOL {
sciter::set_host_api(api);
let ext_api = vmap! {
"add" => add,
"sub" => sub,
};
ext_api.pack_to(exported);
true as BOOL
}
/// Calculate the sum of all the given arguments.
pub fn add(args: &[Value]) -> Value {
let sum: i32 = args
.iter()
.map(|v| v.to_int())
.filter(|v| v.is_some())
.map(|v| v.unwrap())
.sum();
sum.into()
}
/// `function sub(a, b) { return a - b; }`
pub fn sub(args: &[Value]) -> std::result::Result<Value, String> {
if let [a, b] = args {
let a = a.to_int().ok_or("`a` is not an int")?;
let b = b.to_int().ok_or("`b` is not an int")?;
let result = a - b;
Ok(result.into())
} else {
Err(format!("sub(a,b) expects 2 parameters, given {} instead.", args.len()))
}
}

View File

@@ -0,0 +1,50 @@
<html>
<head>
<title>Fire event demo</title>
<style>
html {
background: radial-gradient(75% 75%, circle farthest-side, white, orange, rgb(0, 0, 204));
color: #fff;
}
#message {
margin: 10dip 0dip;
padding: 5dip;
width: 60%%;
min-height: 60dip;
color: black;
background: #ccc;
outline: 1px solid orange;
}
</style>
<script type="text/tiscript">
view.caption = $(head > title).value;
$(#machine).text = Sciter.machineName();
$(#post).on("click", : {
$(#message).postEvent(Event.CHANGE, 0, this, "hello?");
});
$(#message).on("change", function(e) {
this.text = String.printf("Event from `%s`: %v\n", e.source.id, e.data);
});
</script>
</head>
<body>
<h1>Fire event</h1>
<p>Running on <em #machine/> machine</p>
<button id="post">Post event</button>
<button id="send">Send event</button>
<button id="fire">Fire event</button>
<div id="message"></div>
</body>
</html>

View File

@@ -0,0 +1,60 @@
//! Fire event Sciter sample.
#![allow(unused_variables)]
#![allow(non_snake_case)]
extern crate sciter;
use sciter::Element;
use self::sciter::dom::event::*;
use self::sciter::dom::HELEMENT;
use self::sciter::value::Value;
struct FireEvent;
impl sciter::EventHandler for FireEvent {
fn on_event(&mut self, root: HELEMENT, source: HELEMENT, target: HELEMENT, code: BEHAVIOR_EVENTS, phase: PHASE_MASK, reason: EventReason) -> bool {
if phase != PHASE_MASK::BUBBLING {
return false;
}
if code == BEHAVIOR_EVENTS::BUTTON_CLICK {
// `root` points to attached element, usually it is an `<html>`.
let root = Element::from(root).root();
let message = root.find_first("#message").unwrap().expect("div#message not found");
let source = Element::from(source);
println!("our root is {:?}, message is {:?} and source is {:?}", root, message, source);
if let Some(id) = source.get_attribute("id") {
if id == "send" {
// just send a simple event
source.send_event(BEHAVIOR_EVENTS::CHANGE, None, Some(message.as_ptr())).expect("Failed to send event");
return true;
} else if id == "fire" {
// fire event with specified params
let data = Value::from("Rusty param");
source.fire_event(BEHAVIOR_EVENTS::CHANGE, None, Some(message.as_ptr()), false, Some(data)).expect("Failed to fire event");
return true;
}
};
};
false
}
}
fn main() {
let html = include_bytes!("fire_event.htm");
let mut frame = sciter::Window::new();
frame.event_handler(FireEvent);
frame.load_html(html, Some("example://fire_event.htm"));
frame.run_app();
}

View File

@@ -0,0 +1,35 @@
//! An example showing various information about Sciter.
extern crate sciter;
fn main() {
// can be called as `examples/first ~/lib/libsciter.so`
if cfg!(feature = "dynamic") {
if let Some(arg) = std::env::args().nth(1) {
println!("using {:?}", arg);
if let Err(e) = sciter::set_library(&arg) {
panic!("Invalid library path specified: {}", e);
}
}
}
let arch = if cfg!(target_arch = "x86_64") { "x64" } else { "x86" };
println!("calling SciterAPI {}", arch);
// bypass the ABI compatability checks (e.g. in windowless builds)
let scapi = sciter::SciterAPI_unchecked();
let abi_version = scapi.version;
println!("sciter abi version: {:#0x}, windowless: {}", abi_version, abi_version >= 0x0001_0001);
let class_name = sciter::utf::w2s((scapi.SciterClassName)());
println!("sciter class name: {:?}", class_name);
// Sciter library version
use sciter::types::BOOL;
let v1 = (scapi.SciterVersion)(true as BOOL);
let v2 = (scapi.SciterVersion)(false as BOOL);
let num = [v1 >> 16, v1 & 0xFFFF, v2 >> 16, v2 & 0xFFFF];
let version = num.iter().map(|&x| x.to_string()).collect::<Vec<_>>().join(".");
println!("sciter version: {} {:?}", version, num);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

View File

@@ -0,0 +1,90 @@
<html window-icon="https://sciter.com/wp-content/themes/sciter/!images/favicon.ico">
<head>
<title>Rust-sciter sample</title>
<style>
html {
background: radial-gradient(75% 75%, circle farthest-side, white, orange, rgb(0,0,204));
color:#fff;
}
html:rtl {
mapping: left-to-right(background);
}
</style>
<script type="text/tiscript">
view.caption = $(head > title).value;
$(#machine).text = Sciter.machineName();
$(#backend).text = view.backendName;
$(#version).text = String.printf("%d.%d.%d.%d",
(Sciter.VERSION >> 16) & 0xffff, Sciter.VERSION & 0xffff,
(Sciter.REVISION >> 16) & 0xffff, Sciter.REVISION & 0xffff);
var counter = 0;
$(button#append).on("click", function() {
$(body).$append(<h1#test>{ ++counter }</h1>);
});
$(button#open).on("click", function() {
var fn = view.selectFile(#open,
"HTML Files (*.htm,*.html)|*.HTM;*.HTML|All Files (*.*)|*.*" , "html" );
stdout.println("selected file: " + fn);
if (fn) {
$(body).$append(<h1#test>{fn}</h1>);
}
});
$(button#ti2py).on("click", function() {
var answer = view.NativeCall(view.caption);
$(body).$append(<h1#test>script -&gt; native: {answer}</h1>);
})
$(button#py2ti).on("click", function() {
var answer = view.ScriptCallTest("call arg");
})
$(button#sum).on("click", function() {
stdout.printf("2 + 3 = %d\n", view.calc_sum(2, 3));
})
function hello(who) {
$(body).$append(<h1#test>native -&gt; script: {who}</h1>);
return "its working!";
}
function raise_error(arg1, arg2, arg3) {
throw new Error(String.$(Unexpected type of input: {typeof arg1}, {typeof arg2}, {typeof arg3}.));
}
self.timer(2000, function() {
if(!view.api)
view.api = view.GetNativeApi();
// {add: function(a,b) { return a + b; }};
stdout.printf("2 + 3 = %d\n", view.api.add(2, 3));
stdout.printf("2 * 3 = %d\n", view.api.mul(2, 3));
stdout.printf("2 - 3 = %d\n", view.api.sub(2, 3));
});
</script>
</head>
<body>
<h1>Rust Sciter Application</h1>
<p>Running on <strong #machine /> machine via <strong #backend/> (<strong #version/>).</p>
<button #append>Append</button>
<button #open>Open</button>
<button #ti2py>Call native</button>
<button #py2ti>Call script</button>
<button #sum>Calc sum</button>
</body>
</html>

View File

@@ -0,0 +1,180 @@
//! Sciter interop with native code and vice versa.
#![allow(unused_variables)]
#![allow(non_snake_case)]
#[macro_use]
extern crate sciter;
use sciter::{HELEMENT, Element, Value};
struct EventHandler {
root: Option<Element>,
}
impl Drop for EventHandler {
fn drop(&mut self) {
println!("interop::EventHandler: Bye bye, HTML!");
}
}
impl EventHandler {
fn script_call_test(&self, args: &[Value], root: &Element) -> Option<Value> {
println!("root: {:?}", root);
// return None;
println!("calling 'hello'");
let answer = root.call_function("hello", &make_args!("hello, rust!"));
println!(" answer {:?}", answer);
println!("get and call 'hello'");
let answer = root.eval_script(r"hello");
if answer.is_err() {
return None;
}
let obj = answer.unwrap();
let answer = obj.call(None, &make_args!("argument"), None);
println!(" answer is {:?}", answer);
println!("eval 'hello'");
let answer = root.eval_script(r#"hello("42");"#);
println!(" answer is {:?}", answer);
println!("calling 'raise_error'; the following exceptions are expected then:");
let answer = root.call_function("raise_error", &make_args!(17, "42", false));
println!(" answer is {:?}", answer);
println!("calling inexisting function");
let answer = root.call_function("raise_error2", &[]);
println!(" answer is {:?}", answer);
Some(Value::from(true))
}
fn NativeCall(&mut self, arg: String) -> Value {
Value::from(format!("Rust window ({})", arg))
}
fn GetNativeApi(&mut self) -> Value {
fn on_add(args: &[Value]) -> Value {
let ints = args.iter().map(|x| x.to_int().unwrap());
// let sum: i32 = ints.sum(); // error: issue #27739
let sum: i32 = ints.sum();
Value::from(sum)
}
fn on_sub(args: &[Value]) -> Value {
if args.len() != 2 || args.iter().any(|x| !x.is_int()) {
return Value::error("sub requires 2 integer arguments!");
}
let ints: Vec<_> = args.iter().map(|x| x.to_int().unwrap()).collect();
let (a,b) = (ints[0], ints[1]);
Value::from(a - b)
}
let on_mul = |args: &[Value]| -> Value {
let prod: i32 = args.iter().map(|x| x.to_int().unwrap()).product();
Value::from(prod)
};
let mut api = Value::new();
api.set_item("add", on_add);
api.set_item("sub", on_sub);
api.set_item("mul", on_mul);
println!("returning {:?}", api);
api
}
fn calc_sum(&mut self, a: i32, b: i32) -> i32 {
a + b
}
}
impl sciter::EventHandler for EventHandler {
fn attached(&mut self, root: HELEMENT) {
self.root = Some(Element::from(root));
}
dispatch_script_call! {
fn NativeCall(String);
fn GetNativeApi();
fn calc_sum(i32, i32);
}
fn on_script_call(&mut self, root: HELEMENT, name: &str, argv: &[Value]) -> Option<Value> {
let args = argv.iter().map(|x| format!("{:?}", &x)).collect::<Vec<String>>().join(", ");
println!("script->native: {}({}), root {:?}", name, args, Element::from(root));
let handled = self.dispatch_script_call(root, name, argv);
if handled.is_some() {
return handled;
}
if name == "ScriptCallTest" {
return self.script_call_test(argv, &Element::from(root));
}
None
}
}
fn check_options() {
sciter::set_options(sciter::RuntimeOptions::ScriptFeatures(
sciter::SCRIPT_RUNTIME_FEATURES::ALLOW_SYSINFO as u8 // Enables `Sciter.machineName()`
| sciter::SCRIPT_RUNTIME_FEATURES::ALLOW_FILE_IO as u8 // Enables opening file dialog (`view.selectFile()`)
)).ok();
for arg in std::env::args() {
if arg.starts_with("--sciter-gfx=") {
use sciter::GFX_LAYER;
let backend = match arg.split_at("--sciter-gfx=".len()).1.trim() {
"auto" => GFX_LAYER::AUTO,
"cpu" => GFX_LAYER::CPU,
"skia" | "skia-cpu" => GFX_LAYER::SKIA_CPU,
"skia-opengl" => GFX_LAYER::SKIA_OPENGL,
#[cfg(windows)]
"d2d" => GFX_LAYER::D2D,
#[cfg(windows)]
"warp" => GFX_LAYER::WARP,
_ => GFX_LAYER::AUTO,
};
println!("setting {:?} backend", backend);
let ok = sciter::set_options(sciter::RuntimeOptions::GfxLayer(backend));
if let Err(e) = ok {
println!("failed to set backend: {:?}", e);
}
} else if arg.starts_with("--ux-theme") {
#[cfg(windows)]
sciter::set_options(sciter::RuntimeOptions::UxTheming(true)).ok();
}
}
}
fn main() {
// interop --sciter-gfx=cpu --ux-theme
check_options();
let html = include_bytes!("interop.htm");
let handler = EventHandler { root: None };
let mut frame = sciter::Window::new();
frame.event_handler(handler);
frame.load_html(html, Some("example://interop.htm"));
frame.run_app();
}

View File

@@ -0,0 +1,88 @@
<html window-icon="https://sciter.com/wp-content/themes/sciter/!images/favicon.ico">
<head>
<title>Minimalistic Sciter demo</title>
<style>
html {
background: radial-gradient(75% 75%, circle farthest-side, white, orange, rgb(0,0,204));
color: #fff;
}
html:rtl {
mapping: left-to-right(background);
}
a { color: white; }
code { font-weight: bold; }
</style>
<script type="text/tiscript">
view.caption = $(head > title).value;
$(#machine).text = Sciter.machineName();
$(#version).text = String.printf("%d.%d.%d.%d",
(Sciter.VERSION >> 16) & 0xffff, Sciter.VERSION & 0xffff,
(Sciter.REVISION >> 16) & 0xffff, Sciter.REVISION & 0xffff);
try {
// since 4.2.5.0
$(#revision).text = Sciter.BUILD.toString();
} catch(e) {
$(#revision).text = "N/A";
}
var counter = 0;
$(button#append).on("click", function() {
$(body).$append(<h1#test>{ ++counter }</h1>);
});
$(button#open).on("click", function() {
var fn = view.selectFile(#open,
"HTML Files (*.htm,*.html)|*.HTM;*.HTML|All Files (*.*)|*.*" , "html" );
// if the dialog was closed or
// filesystem interaction was disabled via `ALLOW_FILE_IO`
// the selected file `fn` would be `undefined`.
stdout.println("selected file: " + fn);
if (fn) {
$(body).$append(<h1#test>{fn}</h1>);
}
});
// Some tricks with hyperlinks:
$(a).on("click", function() {
Sciter.launch(this.attributes["href"]);
return true;
});
for (var a in $$(a)) {
a.attributes["title"] = a.attributes["href"];
}
</script>
</head>
<body>
<h1>Minimal Sciter Application</h1>
<p>Running on <em #machine /> machine</p>
<p>Sciter version <span #version /> rev <span #revision /></p>
<button #append>Append</button>
<button #open>Open</button>
<select>
<option>Some</option>
<option>Items</option>
<option>in select</option>
</select>
<section class=footer>
<p>You can inspect this window in the <a href="https://sciter.com/developers/development-tools/">Inspector tool</a>
from the <a href="https://sciter.com/download/">Sciter SDK</a>.</p>
<p>Run the Inspector first and then press <code>CTRL+SHIFT+I</code> in this window.</p>
</section>
</body>
</html>

View File

@@ -0,0 +1,43 @@
//! Minimalistic Sciter sample.
// Specify the Windows subsystem to eliminate console window.
// Requires Rust 1.18.
#![windows_subsystem="windows"]
extern crate sciter;
fn main() {
// Step 1: Include the 'minimal.html' file as a byte array.
// Hint: Take a look into 'minimal.html' which contains some tiscript code.
let html = include_bytes!("minimal.htm");
// Step 2: Enable the features we need in our tiscript code.
sciter::set_options(sciter::RuntimeOptions::ScriptFeatures(
sciter::SCRIPT_RUNTIME_FEATURES::ALLOW_SYSINFO as u8 // Enables `Sciter.machineName()`
| sciter::SCRIPT_RUNTIME_FEATURES::ALLOW_FILE_IO as u8 // Enables opening file dialog (`view.selectFile()`)
)).unwrap();
// Enable debug mode for all windows, so that we can inspect them via Inspector.
sciter::set_options(sciter::RuntimeOptions::DebugMode(true)).unwrap();
// Step 3: Create a new main sciter window of type `sciter::Window`.
// Hint: The sciter Window wrapper (src/window.rs) contains more
// interesting functions to open or attach to another existing window.
let mut frame = sciter::Window::new();
if cfg!(target_os="macos") {
// a temporary workaround for OSX, see
// https://sciter.com/forums/topic/global-sciter_set_debug_mode-does-not-work-in-osx/
frame.set_options(sciter::window::Options::DebugMode(true)).unwrap();
}
// Step 4: Load HTML byte array from memory to `sciter::Window`.
// Hint: second parameter is an optional uri, it can be `None` in simple cases,
// but it is useful for debugging purposes (check the Inspector tool from the Sciter SDK).
// Also you can use a `load_file` method, but it requires an absolute path
// of the main document to resolve HTML resources properly.
frame.load_html(html, Some("example://minimal.htm"));
// Step 5: Show window and run the main app message loop until window been closed.
frame.run_app();
}

View File

@@ -0,0 +1,67 @@
<html>
<head>
<title>SOM test</title>
<style type="text/css">
</style>
<script type="text/tiscript">
if(0) {
debug(asset traits);
debug: Asset.typeOf(TestGlobal);
debug: Asset.hasProperty(TestGlobal, #age);
debug: Asset.hasMethod(TestGlobal, #print);
debug(asset globals);
debug: TestGlobal;
debug: view.TestGlobal;
debug: view.root.TestGlobal;
debug(asset properties);
debug: TestGlobal.age;
TestGlobal.age = 17;
debug: TestGlobal.age;
TestGlobal.name = "Demogor";
debug: TestGlobal.name;
debug: TestGlobal.print();
}
/////////////////////////
if(1) {
debug(event handler);
debug(implicit access);
var prop = view.TestGlobal;
debug: prop;
var int_prop = prop.age;
debug: int_prop;
debug(explicit access);
debug: view.TestGlobal;
var val = view.TestGlobal.age;
debug: val;
debug: view.TestGlobal.age;
view.TestGlobal.age = 12;
debug: view.TestGlobal.age;
debug: view.TestGlobal.print();
debug: view.TestGlobal.add_year(12);
debug: view.TestGlobal.age;
}
</script>
</head>
<body>
<div>Hello, body</div>
<p>but open Inspector to see the logs</p>
</body>
</html>

View File

@@ -0,0 +1,193 @@
// there are logs in console window
// #![windows_subsystem="windows"]
extern crate sciter;
use sciter::{HELEMENT, types::{BOOL, VALUE}};
#[derive(Default)]
pub struct Object {
age: i32,
name: String,
}
impl Object {
pub fn print(&self) -> String {
format!("name: {}, age: {}", self.name, self.age)
}
pub fn add_year(&mut self, v: i32) -> i32 {
self.age += v;
self.age
}
}
// SOM Passport of the asset.
// TODO: should be auto-generated.
impl sciter::om::Passport for Object {
fn get_passport(&self) -> &'static sciter::om::som_passport_t {
use sciter::om::*;
extern "C" fn on_print(thing: *mut som_asset_t, _argc: u32, _argv: *const VALUE, p_result: &mut VALUE) -> BOOL
{
let me = IAsset::<Object>::from_raw(&thing);
let r = me.print();
let r: sciter::Value = r.into();
r.pack_to(p_result);
return true as BOOL;
}
extern "C" fn on_add_year(thing: *mut som_asset_t, argc: u32, argv: *const VALUE, p_result: &mut VALUE) -> BOOL
{
let me = IAsset::<Object>::from_raw(&thing);
let args = unsafe { sciter::Value::unpack_from(argv, argc) };
let required = 1;
if args.len() != required {
let r = sciter::Value::error(&format!("{} error: {} of {} arguments provided.", "Object::add_year", args.len(), required));
r.pack_to(p_result);
return true as BOOL;
}
let r = me.add_year(
match sciter::FromValue::from_value(&args[0]) {
Some(arg) => arg,
None => {
let r = sciter::Value::error(&format!("{} error: invalid type of {} argument ({} expected, {:?} provided).",
"Object::add_year", 0, "i32", &args[0]
));
r.pack_to(p_result);
return true as BOOL;
}
},
);
let r: sciter::Value = r.into();
r.pack_to(p_result);
return true as BOOL;
}
extern "C" fn on_get_age(thing: *mut som_asset_t, p_value: &mut VALUE) -> BOOL
{
let me = IAsset::<Object>::from_raw(&thing);
let r = sciter::Value::from(&me.age);
r.pack_to(p_value);
return true as BOOL;
}
extern "C" fn on_set_age(thing: *mut som_asset_t, p_value: &VALUE) -> BOOL
{
let me = IAsset::<Object>::from_raw(&thing);
use sciter::FromValue;
let v = sciter::Value::from(p_value);
if let Some(v) = FromValue::from_value(&v) {
me.age = v;
true as BOOL
} else {
false as BOOL
}
}
extern "C" fn on_get_name(thing: *mut som_asset_t, p_value: &mut VALUE) -> BOOL
{
let me = IAsset::<Object>::from_raw(&thing);
let r = sciter::Value::from(&me.name);
r.pack_to(p_value);
return true as BOOL;
}
extern "C" fn on_set_name(thing: *mut som_asset_t, p_value: &VALUE) -> BOOL
{
let me = IAsset::<Object>::from_raw(&thing);
use sciter::FromValue;
let v = sciter::Value::from(p_value);
if let Some(v) = FromValue::from_value(&v) {
me.name = v;
true as BOOL
} else {
false as BOOL
}
}
type ObjectMethods = [som_method_def_t; 2];
let mut methods = Box::new(ObjectMethods::default());
let mut method = &mut methods[0];
method.name = atom("print");
method.func = Some(on_print);
method.params = 0;
let mut method = &mut methods[1];
method.name = atom("add_year");
method.func = Some(on_add_year);
method.params = 1;
type ObjectProps = [som_property_def_t; 2];
let mut props = Box::new(ObjectProps::default());
let mut prop = &mut props[0];
prop.name = atom("age");
prop.getter = Some(on_get_age);
prop.setter = Some(on_set_age);
let mut prop = &mut props[1];
prop.name = atom("name");
prop.getter = Some(on_get_name);
prop.setter = Some(on_set_name);
let mut pst = Box::new(som_passport_t::default());
pst.name = atom("TestGlobal");
pst.n_methods = 2;
pst.methods = Box::into_raw(methods) as *const _;
pst.n_properties = 2;
pst.properties = Box::into_raw(props) as *const _;
Box::leak(pst)
}
}
#[derive(Debug)]
struct Handler {
asset: sciter::om::IAssetRef<Object>,
}
impl sciter::EventHandler for Handler {
fn attached(&mut self, _root: HELEMENT) {
println!("attached");
}
fn detached(&mut self, _root: HELEMENT) {
println!("detached");
}
fn document_complete(&mut self, _root: HELEMENT, _target: HELEMENT) {
println!("loaded");
}
fn get_asset(&mut self) -> Option<&sciter::om::som_asset_t> {
Some(self.asset.as_ref())
}
}
fn main() {
sciter::set_options(sciter::RuntimeOptions::DebugMode(true)).unwrap();
let mut frame = sciter::Window::new();
let object = Object::default();
let object = sciter::om::IAsset::new(object);
sciter::om::into_global(object);
let object2 = Object::default();
let object2 = sciter::om::IAsset::new(object2);
let object2 = sciter::om::IAssetRef::from(object2);
let ptr = object2.as_ptr();
let psp = object2.get_passport();
println!{"asset {:?} psp {:?}", ptr, psp as *const _};
println!("asset: {:?}", object2);
let handler = Handler { asset: object2 };
frame.event_handler(handler);
let html = include_bytes!("som.htm");
frame.load_html(html, Some("example://som.htm"));
frame.run_app();
}

View File

@@ -0,0 +1,82 @@
<html window-icon="https://cdn2.iconfinder.com/data/icons/arts-crafts-sewing/24/sewing_thread_handcraft_craft_1-32.png">
<head>
<title>Threads demo</title>
<style>
div#content { flow:horizontal; size:*; }
div#explanation { size:*; padding:20px; overflow:auto; }
div#explanation > pre { padding:10px; border:1px dotted #999; background:#ffffef; }
div#tasks { width:300px; height:*; }
div#tasks > select { size:*; display:block; }
div#tasks > select progress { margin-left: 5px; }
</style>
<script type="text/tiscript">
var taskNo = 0;
$(#start-task).onClick = function()
{
++taskNo;
var taskElem = $(div#tasks > select).$append(<option>Task { taskNo }<progress max=100 /> <span.result /></option>);
function onProgress(p100) { taskElem.$(progress).value = p100; }
function onDone(taskId) {
taskElem.$(span.result).text = "Done!";
taskElem.$(progress).remove();
}
view.exec_task(taskNo, onProgress, onDone);
}
</script>
</head>
<body>
<h2>Sciter UI, threads demo</h2>
<div id="content">
<div id="tasks">
<button id="start-task">Start Task</button>
<select type="select"></select>
</div>
<div id="explanation">
<p>The Start Task onClick handler is defined as</p>
<pre>
$(#start-task).onClick = function()
{
var taskElem = $(div#tasks > select)
.$append(&lt;option>Task { ++taskNo }
&lt;progress max=100 />
&lt;span.result />&lt;/option>);
function onProgress(p100) {
taskElem.$(progress).value = p100;
}
function onDone(taskId) {
taskElem.$(span.result).text = "Done!";
taskElem.$(progress).remove();
}
view.exec_task(taskId, onProgress, onDone);
}
</pre>
<p>It defines couple of callback functions and calls <code>view.exec_task()</code> with them.</p>
<p>The <code>view.exec_task()</code> native method is implemented in <code>EventHandler::exec_task()</code>.</p>
<p>The <code>EventHandler::exec_task()</code> starts worker thread passing <em>taskNo</em>, <em>onProgress</em> and
<em>onDone</em> parameters to it.</p>
<p>Worker thread body is defined in Rust code as:</p>
<pre>
// worker thread body, simulate time consuming task
fn thread_body(task_no: i32, progress: Value, done: Value)
{
for i in 1..100 {
std::thread::sleep(std::time::Duration::from_millis(100));
progress.call(None, &amp;make_args!(i), None).unwrap(); // report task progress
}
// report task completion,
// we can pass some result data here, for now just taskId
done.call(None, &amp;make_args!(task_no), None).unwrap();
}
</pre>
<p>As you see it calls passed callback functions.</p>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,42 @@
#[macro_use]
extern crate sciter;
use sciter::Value;
struct EventHandler;
impl EventHandler {
// script handler
fn exec_task(&self, task_no: i32, progress: sciter::Value, done: sciter::Value) -> bool {
use std::{thread, time};
thread::spawn(move || {
for i in 1..100 {
// call `onProgress` callback
thread::sleep(time::Duration::from_millis(100));
progress.call(None, &make_args!(i), None).unwrap();
}
// call `onDone` callback
done.call(None, &make_args!(task_no), None).unwrap();
});
true
}
}
impl sciter::EventHandler for EventHandler {
// route script calls to our handler
dispatch_script_call! {
fn exec_task(i32, Value, Value);
}
}
fn main() {
let html = include_bytes!("threads.htm");
let mut frame = sciter::WindowBuilder::main_window()
.with_size((1200, 900))
.create();
frame.event_handler(EventHandler);
frame.load_html(html, None);
frame.run_app();
}

View File

@@ -0,0 +1,122 @@
<html window-icon="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB1UlEQVR4nJWTP2hTcRDHP3epS4TqqHHtVNNCFzNFaZD8sxQkKQrOUnCLBoIoOIkG0nYQBbO4OiRLadpEIQ6dxEGxcdclg6Ngpt/vPZd78vqwBQ+O3933vtz97o4TEvKodDkP3ADyQNbgCXAIDJ6Ovh3G+RIZD0vZDNAENoF0MrHJDHgNdJ6NJlMABWiVljIBshUgjQBJB8iHANkMEAKkZu/QYo0A2WqVljJ/E3i06dHbHv3u0QceXWuPjroexaP99uhIPFqz2NS4TQBplFfywPCUb0fS2xl+3miUV+4CXWunnLqycOleiKyGSC1EboUIJ+hibiEzDZFiiCyGyJkQ+Tnn0LxV6Ccq/gBemv0YmAeKQB2oGT+vDs06FIcuvxp+ErNx6HmHVhxadui8YRcc2nNo3/ysOklhKgAx/5yT1KqTVCGGvegOP244SS1HmHp0YtO+ZhuJ9ItHczbxCHtinK/mT+RO5epzoAX8sj6TkgPenxBrq0MHDp3F+kyqOvTNP/CZQwcCUK8UtoFGbLpnewfjWbxUvVJIA79jnJ3ewfj+HIAX7QAXY6vcu1m9vguMzS94WI+t+y3QgdgxrVeL/3VMu/vvpscSRLJWLZ16znv7o2Pn/AeeJco8j/W3vQAAAABJRU5ErkJggg==">
<head>
<title>Video behavior demo</title>
<style>
body { background: gold; padding: 5dip; margin: 0; }
form { margin: 5dip; }
p { padding: 4dip; }
video.generator {
behavior: video-generator video;
border: 1px solid orange;
size: *;
foreground-size: contain;
}
</style>
<script type="text/tiscript">
$(video).onSize = function() {
var (w,h) = this.box(#dimension);
$(form).value = { width: w, height: h };
}
function self.ready() {
var (w,h) = this.box(#dimension);
$(form).value = { width: w, height: h };
}
event click $(#snapshot) {
var curframe = $(video).style.foregroundImage;
var fname = view.selectFile(#save, "Images|*.png;*.jpg|All files|*.*", "png");
if (fname) {
var image = curframe.toBytes();
image.save(fname);
}
}
var video = $(video);
var host;
var body = $(body);
event click $(#show-detached) {
if (body && !video.parent) {
// recreate element
video = body.$append(<video class="generator" />);
}
// It creates a new window and moves the current video element to it.
if( this.value )
{
host = view.window {
type: View.TOOL_WINDOW,
html: $(#templates).text,
width: 640,
height: 480,
alignment: 5,
};
video.detach();
host.root.$(body).append(video);
host.on("closing", function(evt) {
// detach
video.detach();
body.append(video);
$(#show-detached).value = false;
});
} else {
// and back
video.detach();
body.append(video);
host.close();
}
}
</script>
<script id="templates" type="text/html+template">
<html window-resizable>
<head>
<title>Hosted element</title>
<style>
body { margin: 0; padding: 0; }
video.generator {
display: block;
behavior: video-generator video;
size: *;
foreground-size: contain;
}
</style>
</head>
<body>
</body>
</html>
</script>
</head>
<body>
<p>This demo simulates partial video frame update.
On each frame (24 FPS) it updates another portion of the frame.</p>
<div>
<button id="snapshot">Save the current frame</button>
<button type=checkbox id="show-detached">Show video in a separate window</button>
</div>
<div>
<form>frame size: <output name=width /> x <output name=height /></form>
</div>
<video class="generator" />
</body>
</html>

View File

@@ -0,0 +1,177 @@
#![allow(unused_variables, unused_must_use)]
extern crate sciter;
use sciter::dom::event::*;
use sciter::{Element, HELEMENT};
use sciter::video::{fragmented_video_destination, AssetPtr};
struct VideoGen {
thread: Option<std::thread::JoinHandle<()>>,
}
impl Drop for VideoGen {
fn drop(&mut self) {
println!("[video] behavior is destroyed");
}
}
impl VideoGen {
fn new() -> Self {
Self { thread: None }
}
fn generation_thread(site: AssetPtr<fragmented_video_destination>) {
println!("[video] thread is started");
// our video frame size and its part to update
const FRAME: (i32, i32) = (1200, 800);
const UPDATE: (i32, i32) = (256, 32);
// our frame data (RGBA)
let figure = [0xFF_FFA500u32; (UPDATE.0 * UPDATE.1) as usize];
// configure video output
let mut site = site;
let ok = site.start_streaming(FRAME, sciter::video::COLOR_SPACE::Rgb32, None);
println!("[video] initialized: {:?}", ok);
let mut x = 0;
let mut xd = 1;
let mut y = 0;
let mut yd = 1;
while site.is_alive() {
// send an update portion
let buf: &[u8] = unsafe { std::mem::transmute(figure.as_ref()) };
site.render_frame_part(buf, (x, y), UPDATE);
// set the next position
x += xd;
y += yd;
if x == 0 {
xd = 1;
} else if x + UPDATE.0 == FRAME.0 {
xd = -1;
}
if y == 0 {
yd = 1;
} else if y + UPDATE.1 == FRAME.1 {
yd = -1;
}
// simulate 25 FPS
std::thread::sleep(std::time::Duration::from_millis(1000 / 25));
}
site.stop_streaming();
println!("[video] thread is finished");
}
}
impl sciter::EventHandler for VideoGen {
fn get_subscription(&mut self) -> Option<EVENT_GROUPS> {
Some(EVENT_GROUPS::HANDLE_BEHAVIOR_EVENT)
}
fn detached(&mut self, _root: HELEMENT) {
println!("[video] <video> element is detached");
if let Some(h) = self.thread.take() {
h.join();
}
}
fn on_event(
&mut self,
root: HELEMENT,
source: HELEMENT,
target: HELEMENT,
code: BEHAVIOR_EVENTS,
phase: PHASE_MASK,
reason: EventReason,
) -> bool {
if phase != PHASE_MASK::BUBBLING {
return false;
}
match code {
BEHAVIOR_EVENTS::VIDEO_BIND_RQ => {
let source = Element::from(source);
println!("[video] {:?} {} ({:?})", code, source, reason);
if let EventReason::VideoBind(ptr) = reason {
if ptr.is_null() {
// first, consume the event to announce us as a video producer.
return true;
}
use sciter::video::*;
// `VideoBind` comes with a video_destination interface
let mut site = AssetPtr::from(ptr as *mut video_destination);
// query a fragmented video destination interface
if let Ok(fragmented) = AssetPtr::<fragmented_video_destination>::try_from(&mut site) {
// and use it
println!("[video] start video thread");
let tid = ::std::thread::spawn(|| VideoGen::generation_thread(fragmented));
self.thread = Some(tid);
}
}
}
BEHAVIOR_EVENTS::VIDEO_INITIALIZED => {
println!("[video] {:?}", code);
}
BEHAVIOR_EVENTS::VIDEO_STARTED => {
println!("[video] {:?}", code);
let source = Element::from(source);
use sciter::dom::ELEMENT_AREAS;
let flags = ELEMENT_AREAS::CONTENT_BOX as u32 | ELEMENT_AREAS::SELF_RELATIVE as u32;
let rc = source.get_location(flags).unwrap();
println!("[video] start video thread on <{}> which is about {:?} pixels", source, rc.size());
}
BEHAVIOR_EVENTS::VIDEO_STOPPED => {
println!("[video] {:?}", code);
}
_ => return false,
};
return true;
}
}
fn main() {
if cfg!(all(target_os = "windows", target_arch = "x86")) {
println!("\nerror: Sciter video will not work on Windows x86.");
println!("error: Consider using a nightly Rust version to enable `abi_thiscall`,");
println!("error: see https://github.com/rust-lang/rust/issues/42202");
println!("");
std::process::exit(126);
}
if sciter::version_num() < 0x04_04_02_0E {
// since 4.4.2.14
println!("\nerror: `sciter::video` requires SOM support.");
println!("error: Sciter API was changed in '4.4.2.14'");
println!("error: Sciter version is '{}' now", sciter::version());
println!("error: see https://sciter.com/native-code-exposure-to-script/");
println!("error: and https://sciter.com/developers/for-native-gui-programmers/sciter-object-model/");
println!("");
std::process::exit(126);
}
let mut frame = sciter::WindowBuilder::main_window()
.with_size((750, 750))
.create();
frame.set_title("Video renderer sample");
frame.register_behavior("video-generator", || Box::new(VideoGen::new()));
frame.load_html(include_bytes!("video.htm"), Some("example://video.htm"));
frame.run_app();
}

View File

@@ -0,0 +1,320 @@
//! Windowless mode example (for Sciter.Lite build).
extern crate sciter;
extern crate winit;
extern crate winapi;
extern crate raw_window_handle;
fn main() {
// "Windowless" Sciter builds are incompatible with the regular ones.
if !cfg!(feature = "windowless") {
panic!("This example requires \"windowless\" feature!");
}
// We need this to explicitly set path to the windowless sciter dll.
if !cfg!(feature = "dynamic") {
panic!("This example requires the \"dynamic\" feature enabled.")
}
if let Some(arg) = std::env::args().nth(1) {
println!("loading sciter from {:?}", arg);
if let Err(_) = sciter::set_options(sciter::RuntimeOptions::LibraryPath(&arg)) {
panic!("Invalid sciter-lite dll specified.");
}
}
// prepare and create a new window
println!("create window");
let mut events = winit::EventsLoop::new();
use raw_window_handle::HasRawWindowHandle;
let wnd = winit::WindowBuilder::new();
let wnd = wnd.build(&events).expect("Failed to create window");
let window_handle = wnd.raw_window_handle();
// configure Sciter
println!("create sciter instance");
sciter::set_options(sciter::RuntimeOptions::UxTheming(true)).unwrap();
sciter::set_options(sciter::RuntimeOptions::DebugMode(true)).unwrap();
sciter::set_options(sciter::RuntimeOptions::ScriptFeatures(0xFF)).unwrap();
// create an engine instance with an opaque pointer as an identifier
use sciter::windowless::{Message, handle_message};
let scwnd = { &wnd as *const _ as sciter::types::HWINDOW };
handle_message(scwnd, Message::Create { backend: sciter::types::GFX_LAYER::SKIA_OPENGL, transparent: false, });
#[cfg(windows)]
{
// Windows-specific: we need to redraw window in responce to the corresponding notification.
// winit 0.20 has an explicit `Window::request_redraw` method,
// here we use `winapi::InvalidateRect` for this.
struct WindowlessHandler {
hwnd: winapi::shared::windef::HWND,
}
impl sciter::HostHandler for WindowlessHandler {
fn on_invalidate(&mut self, pnm: &sciter::host::SCN_INVALIDATE_RECT) {
unsafe {
let rc = &pnm.invalid_rect;
let dst = winapi::shared::windef::RECT {
left: rc.left,
top: rc.top,
right: rc.right,
bottom: rc.bottom,
};
winapi::um::winuser::InvalidateRect(self.hwnd, &dst as *const _, 0);
// println!("- {} {}", rc.width(), rc.height());
}
}
}
let handler = WindowlessHandler {
hwnd: match window_handle {
raw_window_handle::RawWindowHandle::Windows(data) => data.hwnd as winapi::shared::windef::HWND,
_ => unreachable!(),
},
};
let instance = sciter::Host::attach_with(scwnd, handler);
let html = include_bytes!("minimal.htm");
instance.load_html(html, Some("example://minimal.htm"));
}
// events processing
use sciter::windowless::{MouseEvent, KeyboardEvent, RenderEvent};
use sciter::windowless::{MOUSE_BUTTONS, MOUSE_EVENTS, KEYBOARD_STATES, KEY_EVENTS};
let mut mouse_button = MOUSE_BUTTONS::NONE;
let mut mouse_pos = (0, 0);
let as_keys = |modifiers: winit::ModifiersState| {
let mut keys = 0;
if modifiers.ctrl {
keys |= 0x01;
}
if modifiers.shift {
keys |= 0x02;
}
if modifiers.alt {
keys |= 0x04;
}
KEYBOARD_STATES::from(keys)
};
println!("running...");
use winit::{Event, WindowEvent};
let skip = ();
let mut poll_break = false;
let startup = std::time::Instant::now();
loop {
// release CPU a bit, hackish
std::thread::sleep(std::time::Duration::from_millis(0));
// Sciter processes timers and fading effects here
handle_message(scwnd, Message::Heartbit {
milliseconds: std::time::Instant::now().duration_since(startup).as_millis() as u32,
});
// the actual event loop polling
events.poll_events(|event: winit::Event| {
match event {
Event::WindowEvent { event, window_id: _ } => {
match event {
WindowEvent::Destroyed => {
// never called due to loop break on close
println!("destroy");
handle_message(scwnd, Message::Destroy);
poll_break = true;
},
WindowEvent::CloseRequested => {
println!("close");
poll_break = true;
},
WindowEvent::Resized(size) => {
// println!("{:?}, size: {:?}", event, size);
let (width, height): (u32, u32) = size.into();
handle_message(scwnd, Message::Size { width, height });
skip
},
WindowEvent::Refresh => {
let on_render = move |bitmap_area: &sciter::types::RECT, bitmap_data: &[u8]|
{
#[cfg(unix)]
{
let _ = bitmap_area;
let _ = bitmap_data;
let _ = window_handle;
}
// Windows-specific bitmap rendering on the window
#[cfg(windows)]
{
use winapi::um::winuser::*;
use winapi::um::wingdi::*;
use winapi::shared::minwindef::LPVOID;
let hwnd = match window_handle {
raw_window_handle::RawWindowHandle::Windows(data) => data.hwnd as winapi::shared::windef::HWND,
_ => unreachable!(),
};
unsafe {
// NOTE: we use `GetDC` here instead of `BeginPaint`, because the way
// winit 0.19 processed the `WM_PAINT` message (it always calls `DefWindowProcW`).
// let mut ps = PAINTSTRUCT::default();
// let hdc = BeginPaint(hwnd, &mut ps as *mut _);
let hdc = GetDC(hwnd);
let (w, h) = (bitmap_area.width(), bitmap_area.height());
let mem_dc = CreateCompatibleDC(hdc);
let mem_bm = CreateCompatibleBitmap(hdc, w, h);
let mut bmi = BITMAPINFO::default();
{
let mut info = &mut bmi.bmiHeader;
info.biSize = std::mem::size_of::<BITMAPINFO>() as u32;
info.biWidth = w;
info.biHeight = -h;
info.biPlanes = 1;
info.biBitCount = 32;
}
let old_bm = SelectObject(mem_dc, mem_bm as LPVOID);
let _copied = StretchDIBits(mem_dc, 0, 0, w, h, 0, 0, w, h, bitmap_data.as_ptr() as *const _, &bmi as *const _, 0, SRCCOPY);
let _ok = BitBlt(hdc, 0, 0, w, h, mem_dc, 0, 0, SRCCOPY);
SelectObject(mem_dc, old_bm);
// EndPaint(hwnd, &ps as *const _);
ReleaseDC(hwnd, hdc);
// println!("+ {} {}", w, h);
}
}
};
let cb = RenderEvent {
layer: None,
callback: Box::new(on_render),
};
handle_message(scwnd, Message::RenderTo(cb));
skip
},
WindowEvent::Focused(enter) => {
println!("focus {}", enter);
handle_message(scwnd, Message::Focus { enter });
skip
},
WindowEvent::CursorEntered { device_id: _ } => {
println!("mouse enter");
let event = MouseEvent {
event: MOUSE_EVENTS::MOUSE_ENTER,
button: mouse_button,
modifiers: KEYBOARD_STATES::from(0),
pos: sciter::types::POINT {
x: mouse_pos.0,
y: mouse_pos.1,
},
};
handle_message(scwnd, Message::Mouse(event));
skip
},
WindowEvent::CursorLeft { device_id: _ } => {
println!("mouse leave");
let event = MouseEvent {
event: MOUSE_EVENTS::MOUSE_LEAVE,
button: mouse_button,
modifiers: KEYBOARD_STATES::from(0),
pos: sciter::types::POINT {
x: mouse_pos.0,
y: mouse_pos.1,
},
};
handle_message(scwnd, Message::Mouse(event));
skip
},
WindowEvent::CursorMoved { device_id: _, position, modifiers } => {
mouse_pos = position.into();
let event = MouseEvent {
event: MOUSE_EVENTS::MOUSE_MOVE,
button: mouse_button,
modifiers: as_keys(modifiers),
pos: sciter::types::POINT {
x: mouse_pos.0,
y: mouse_pos.1,
},
};
handle_message(scwnd, Message::Mouse(event));
skip
},
WindowEvent::MouseInput { device_id: _, state, button, modifiers } => {
mouse_button = match button {
winit::MouseButton::Left => MOUSE_BUTTONS::MAIN,
winit::MouseButton::Right => MOUSE_BUTTONS::PROP,
winit::MouseButton::Middle => MOUSE_BUTTONS::MIDDLE,
_ => MOUSE_BUTTONS::NONE,
};
println!("mouse {:?} as {:?}", mouse_button, mouse_pos);
let event = MouseEvent {
event: if state == winit::ElementState::Pressed { MOUSE_EVENTS::MOUSE_DOWN } else { MOUSE_EVENTS::MOUSE_UP },
button: mouse_button,
modifiers: as_keys(modifiers),
pos: sciter::types::POINT {
x: mouse_pos.0,
y: mouse_pos.1,
},
};
handle_message(scwnd, Message::Mouse(event));
skip
},
WindowEvent::KeyboardInput { device_id: _, input } => {
println!("key {} {}", input.scancode, if input.state == winit::ElementState::Pressed { "down" } else { "up" });
let event = KeyboardEvent {
event: if input.state == winit::ElementState::Pressed { KEY_EVENTS::KEY_DOWN } else { KEY_EVENTS::KEY_UP },
code: input.scancode,
modifiers: as_keys(input.modifiers),
};
handle_message(scwnd, Message::Keyboard(event));
skip
},
_ => (),
}
},
_ => (),
}
});
if poll_break {
break;
}
}
println!("done, quit");
}