mirror of
https://github.com/rustdesk/rustdesk.git
synced 2024-12-19 14:37:51 +08:00
369 lines
9.7 KiB
Rust
369 lines
9.7 KiB
Rust
//#![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();
|
|
}
|