sciter/window.rs
1/*! High-level native window wrapper.
2
3To create an instance of Sciter you will need either to create a new Sciter window
4or to attach (mix-in) the Sciter engine to an existing window.
5
6The handle of the Sciter engine is defined as `HWINDOW` type which is:
7
8* `HWND` handle on Microsoft Windows.
9* `NSView*` – a pointer to [`NSView`](https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ApplicationKit/Classes/NSView_Class/) instance that is a contentView of Sciter window on OS X.
10* `GtkWidget*` – a pointer to [`GtkWidget`](https://developer.gnome.org/gtk3/stable/GtkWidget.html) instance
11that is a root widget of Sciter window on Linux/GTK.
12
13## Creation of a new window:
14
15```no_run
16extern crate sciter;
17
18fn main() {
19 let mut frame = sciter::Window::new();
20 frame.load_file("minimal.htm");
21 frame.run_app();
22}
23```
24
25Also you can register a [host](../host/trait.HostHandler.html) and a [DOM](../dom/event/index.html) event handlers.
26
27.
28*/
29use ::{_API};
30use capi::sctypes::*;
31
32use platform::{BaseWindow, OsWindow};
33use host::{Host, HostHandler};
34use dom::{self, event::{EventHandler}};
35use crate::Value;
36
37use std::rc::Rc;
38
39
40/// `SCITER_CREATE_WINDOW_FLAGS` alias.
41pub type Flags = SCITER_CREATE_WINDOW_FLAGS;
42
43pub use capi::scdef::{SCITER_CREATE_WINDOW_FLAGS};
44
45
46/// Per-window Sciter engine options.
47///
48/// Used by [`Window::set_options()`](struct.Window.html#method.set_options).
49///
50/// See also [global options](../enum.RuntimeOptions.html).
51#[derive(Copy, Clone)]
52pub enum Options {
53 /// Enable smooth scrolling, enabled by default.
54 SmoothScroll(bool),
55
56 /// Font rendering, value: `0` - system default, `1` - no smoothing, `2` - standard smoothing, `3` - ClearType.
57 FontSmoothing(u8),
58
59 /// Windows Aero support, value: `false` - normal drawing, `true` - window has transparent background after calls
60 /// [`DwmExtendFrameIntoClientArea()`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa969512(v=vs.85).aspx)
61 /// or [`DwmEnableBlurBehindWindow()`](https://msdn.microsoft.com/en-us/library/windows/desktop/aa969508(v=vs.85).aspx).
62 TransparentWindow(bool),
63
64 /// Transparent windows support. When enabled, window uses per pixel alpha
65 /// (e.g. [`WS_EX_LAYERED`](https://msdn.microsoft.com/en-us/library/ms997507.aspx?f=255&MSPPError=-2147217396) window).
66 AlphaWindow(bool),
67
68 /// global or per-window; enables Sciter Inspector for this window, must be called before loading HTML.
69 DebugMode(bool),
70
71 /// global or per-window; value: combination of [`SCRIPT_RUNTIME_FEATURES`](../enum.SCRIPT_RUNTIME_FEATURES.html) flags.
72 ScriptFeatures(u8),
73
74 /// Window is main, will destroy all other dependent windows on close, since 4.3.0.12
75 MainWindow(bool),
76
77 /// global or per-window; value: `true` - `1px` in CSS is treated as `1dip`, otherwise `1px` is a physical pixel (by default).
78 ///
79 /// since [4.4.5.0](https://rawgit.com/c-smile/sciter-sdk/aafb625bb0bc317d79c0a14d02b5730f6a02b48a/logfile.htm).
80 LogicalPixel(bool),
81}
82
83
84/// Sciter window.
85pub struct Window
86{
87 base: OsWindow,
88 host: Rc<Host>,
89}
90
91// `Window::new()` is rather expensive operation to make it default.
92#[allow(clippy::new_without_default)]
93impl Window {
94
95 /// Create a new main window.
96 // #[cfg(not(feature = "windowless"))]
97 #[cfg_attr(feature = "windowless", deprecated = "Sciter.Lite doesn't have OS windows in windowless mode.")]
98 pub fn new() -> Window {
99 Builder::main_window().create()
100 }
101
102 /// Create a new window with the specified position, flags and an optional parent window.
103 #[cfg_attr(feature = "windowless", deprecated = "Sciter.Lite doesn't have OS windows in windowless mode.")]
104 pub fn create(rect: RECT, flags: Flags, parent: Option<HWINDOW>) -> Window {
105 if cfg!(feature = "windowless")
106 {
107 panic!("Sciter.Lite doesn't have OS windows in windowless mode!");
108 }
109
110 let mut base = OsWindow::new();
111 let hwnd = base.create(rect, flags as UINT, parent.unwrap_or(0 as HWINDOW));
112 assert!(!hwnd.is_null());
113
114 let wnd = Window { base: base, host: Rc::new(Host::attach(hwnd))};
115 return wnd;
116 }
117
118 /// Attach Sciter to an existing native window.
119 ///
120 /// Most likely, there is no need for [`run_app`](#method.run_app) or [`run_loop`](#method.run_loop) after that.
121 /// However, to get UI working, you have to route window events
122 /// to Sciter - see the [blog article](https://sciter.com/developers/embedding-principles/).
123 pub fn attach(hwnd: HWINDOW) -> Window {
124 // suppress warnings about unused method when compiled as "windowless"
125 let _ = &OsWindow::new;
126
127 assert!(!hwnd.is_null());
128 Window { base: OsWindow::from(hwnd), host: Rc::new(Host::attach(hwnd)) }
129 }
130
131 /// Attach Sciter to an existing native window and intercept its messages.
132 ///
133 /// This will automatically intercept specific messages needed by Sciter
134 /// and pass them via [`SciterProcND`](https://sciter.com/developers/embedding-principles/).
135 #[cfg(all(windows, not(feature = "windowless")))]
136 pub fn attach_intercepted(hwnd: HWINDOW) -> Window {
137 assert!(!hwnd.is_null());
138
139 #[cfg(target_pointer_width = "64")]
140 #[link(name="user32")]
141 extern "system"
142 {
143 fn SetWindowLongPtrW(hwnd: HWINDOW, index: i32, new_data: WndProc) -> WndProc;
144 fn CallWindowProcW(prev: WndProc, hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
145 }
146
147 #[cfg(target_pointer_width = "32")]
148 #[link(name="user32")]
149 extern "system"
150 {
151 fn SetWindowLongW(hwnd: HWINDOW, index: i32, new_data: WndProc) -> WndProc;
152 fn CallWindowProcW(prev: WndProc, hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
153 }
154
155 #[cfg(target_pointer_width = "64")]
156 let set_window_proc = SetWindowLongPtrW;
157
158 #[cfg(target_pointer_width = "32")]
159 let set_window_proc = SetWindowLongW;
160
161 type WndProc = extern "system" fn (hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT;
162 type PrevProcs = std::collections::HashMap<HWINDOW, WndProc>;
163
164 thread_local! {
165 static PREV_PROC: std::cell::RefCell<PrevProcs> = Default::default();
166 }
167
168 // https://sciter.com/developers/embedding-principles/
169 extern "system" fn wnd_proc(hwnd: HWINDOW, msg: UINT, wp: WPARAM, lp: LPARAM) -> LRESULT {
170 // first, pass the message to Sciter.
171 let mut handled = false as BOOL;
172 let lr = (_API.SciterProcND)(hwnd, msg, wp, lp, &mut handled);
173
174 // if it was handled by Sciter, we're done here.
175 if handled != 0 {
176 return lr;
177 }
178
179 // if not, call the original window proc.
180 let mut lr: LRESULT = 0;
181 PREV_PROC.with(|procs| {
182 let prev_proc = *procs.borrow().get(&hwnd).expect("An unregistered WindowProc is called somehow.");
183 lr = unsafe { CallWindowProcW(prev_proc, hwnd, msg, wp, lp) }
184 });
185
186 // and return its result
187 lr
188 }
189
190 // Subclass the window in order to receive its messages.
191 const GWLP_WNDPROC: i32 = -4;
192 let prev_proc = unsafe { set_window_proc(hwnd, GWLP_WNDPROC, wnd_proc) };
193 PREV_PROC.with(|procs| {
194 procs.borrow_mut().insert(hwnd, prev_proc);
195 });
196
197 Window { base: OsWindow::from(hwnd), host: Rc::new(Host::attach(hwnd)) }
198 }
199
200 /// Obtain a reference to [`Host`](../host/struct.Host.html) which offers some advanced control over the Sciter engine instance.
201 pub fn get_host(&self) -> Rc<Host> {
202 self.host.clone()
203 }
204
205 /// Set a [callback](../host/trait.HostHandler.html) for Sciter engine events.
206 pub fn sciter_handler<Callback: HostHandler + Sized>(&mut self, handler: Callback) {
207 self.host.setup_callback(handler);
208 }
209
210 /// Attach [`dom::EventHandler`](../dom/event/trait.EventHandler.html) to the Sciter window.
211 ///
212 /// You should install a window event handler only once - it will survive all document reloads.
213 /// Also it can be registered on an empty window before the document is loaded.
214 pub fn event_handler<Handler: EventHandler>(&mut self, handler: Handler) {
215 self.host.attach_handler(handler);
216 }
217
218 /// Register an archive produced by `packfolder` tool.
219 ///
220 /// The resources can be accessed via the `this://app/` URL.
221 ///
222 /// See documentation of the [`Archive`](../host/struct.Archive.html).
223 ///
224 pub fn archive_handler(&mut self, resource: &[u8]) -> Result<(), ()> {
225 self.host.register_archive(resource)
226 }
227
228 /// Register a native event handler for the specified behavior name.
229 ///
230 /// Behavior is a named event handler which is created for a particular DOM element.
231 /// In Sciter’s sense, it is a function that is called for different UI events on the DOM element.
232 /// Essentially it is an analog of the [WindowProc](https://en.wikipedia.org/wiki/WindowProc) in Windows.
233 ///
234 /// In HTML, there is a `behavior` CSS property that defines the name of a native module
235 /// that is responsible for the initialization and event handling on the element.
236 /// For example, by defining `div { behavior:button; }` you are asking all `<div>` elements in your markup
237 /// to behave as buttons: generate [`BUTTON_CLICK`](../dom/event/enum.BEHAVIOR_EVENTS.html#variant.BUTTON_CLICK)
238 /// DOM events when the user clicks on that element, and be focusable.
239 ///
240 /// When the engine discovers an element having `behavior: xyz;` defined in its style,
241 /// it sends the [`SC_ATTACH_BEHAVIOR`](../host/trait.HostHandler.html#method.on_attach_behavior) host notification
242 /// with the name `"xyz"` and an element handle to the application.
243 /// You can consume the notification and respond to it yourself,
244 /// or the default handler walks through the list of registered behavior factories
245 /// and creates an instance of the corresponding [`dom::EventHandler`](../dom/event/trait.EventHandler.html).
246 ///
247 /// ## Example:
248 ///
249 /// ```rust,no_run
250 /// struct Button;
251 ///
252 /// impl sciter::EventHandler for Button {}
253 ///
254 /// let mut frame = sciter::Window::new();
255 ///
256 /// // register a factory method that creates a new event handler
257 /// // for each element that has "custom-button" behavior:
258 /// frame.register_behavior("custom-button", || { Box::new(Button) });
259 /// ```
260 ///
261 /// And in HTML it can be used as:
262 ///
263 /// ```html
264 /// <button style="behavior: custom-button">Rusty button</button>
265 /// ```
266 pub fn register_behavior<Factory>(&mut self, name: &str, factory: Factory)
267 where
268 Factory: Fn() -> Box<dyn EventHandler> + 'static
269 {
270 self.host.register_behavior(name, factory);
271 }
272
273 /// Load an HTML document from file.
274 ///
275 /// The specified `uri` should be either an absolute file path
276 /// or a full URL to the HTML to load.
277 ///
278 /// Supported URL schemes are: `http://`, `file://`; `this://app/` (when used with [`archive_handler`](#method.archive_handler)).
279 ///
280 /// ZIP archives [are also supported](https://sciter.com/zip-resource-packaging-in-sciter/).
281 pub fn load_file(&mut self, uri: &str) -> bool {
282 self.host.load_file(uri)
283 }
284
285 /// Load an HTML document from memory.
286 ///
287 /// For example, HTML can be loaded from a file in compile time
288 /// via [`include_bytes!`](https://doc.rust-lang.org/nightly/std/macro.include_bytes.html).
289 pub fn load_html(&mut self, html: &[u8], uri: Option<&str>) -> bool {
290 self.host.load_html(html, uri)
291 }
292
293 /// Get a native window handle.
294 pub fn get_hwnd(&self) -> HWINDOW {
295 self.base.get_hwnd()
296 }
297
298 /// Minimize or hide the window.
299 pub fn collapse(&self, hide: bool) {
300 self.base.collapse(hide)
301 }
302
303 /// Show or maximize the window.
304 pub fn expand(&self, maximize: bool) {
305 self.base.expand(maximize)
306 }
307
308 /// Close the window.
309 pub fn dismiss(&self) {
310 self.base.dismiss()
311 }
312
313 /// Set a new window title.
314 pub fn set_title(&mut self, title: &str) {
315 self.base.set_title(title)
316 }
317
318 /// Get the native window title.
319 pub fn get_title(&self) -> String {
320 self.base.get_title()
321 }
322
323 /// Set various Sciter engine options, see the [`Options`](enum.Options.html).
324 pub fn set_options(&self, options: Options) -> Result<(), ()> {
325 use capi::scdef::SCITER_RT_OPTIONS::*;
326 use self::Options::*;
327 let (option, value) = match options {
328 SmoothScroll(enable) => (SCITER_SMOOTH_SCROLL, enable as usize),
329 FontSmoothing(technology) => (SCITER_FONT_SMOOTHING, technology as usize),
330 TransparentWindow(enable) => (SCITER_TRANSPARENT_WINDOW, enable as usize),
331 AlphaWindow(enable) => (SCITER_ALPHA_WINDOW, enable as usize),
332 MainWindow(enable) => (SCITER_SET_MAIN_WINDOW, enable as usize),
333 DebugMode(enable) => (SCITER_SET_DEBUG_MODE, enable as usize),
334 ScriptFeatures(mask) => (SCITER_SET_SCRIPT_RUNTIME_FEATURES, mask as usize),
335 LogicalPixel(enable) => (SCITER_SET_PX_AS_DIP, enable as usize),
336 };
337 let ok = (_API.SciterSetOption)(self.get_hwnd(), option, value);
338 if ok != 0 {
339 Ok(())
340 } else {
341 Err(())
342 }
343 }
344
345
346 /// Set a global variable by its path to a single window.
347 ///
348 /// This variable will be accessible in the _current_ window via `globalThis[path]` or just `path`.
349 ///
350 /// Note that the document doesn't have to be loaded yet at this point.
351 ///
352 /// See also [`sciter::set_variable`](../fn.set_variable.html) to assign a global to all windows.
353 pub fn set_variable(&self, path: &str, value: Value) -> dom::Result<()> {
354 let ws = s2u!(path);
355 let ok = (_API.SciterSetVariable)(self.get_hwnd(), ws.as_ptr(), value.as_cptr());
356 if ok == dom::SCDOM_RESULT::OK {
357 Ok(())
358 } else {
359 Err(ok)
360 }
361 }
362
363 /// Get a global variable by its path.
364 ///
365 /// It can be a variable previously assigned by [`sciter::set_variable`](../fn.set_variable.html)
366 /// or [`Window::set_variable`](struct.Window.html#method.set_variable),
367 /// or a [Sciter.JS variable](https://github.com/c-smile/sciter-js-sdk/blob/main/docs/md/README.md#global-properties),
368 /// like `document`, `location`, `console`, etc.
369 pub fn get_variable(&self, path: &str) -> dom::Result<Value> {
370 let ws = s2u!(path);
371 let mut value = Value::new();
372 let ok = (_API.SciterGetVariable)(self.get_hwnd(), ws.as_ptr(), value.as_mut_ptr());
373 if ok == dom::SCDOM_RESULT::OK {
374 Ok(value)
375 } else {
376 Err(ok)
377 }
378 }
379
380 /// Show window and run the main app message loop until the main window is closed.
381 pub fn run_app(self) {
382 self.base.expand(false);
383 self.base.run_app();
384 }
385
386 /// Run the main app message loop with the already shown window.
387 pub fn run_loop(self) {
388 self.base.run_app();
389 }
390
391 /// Post a quit message for the app.
392 pub fn quit_app(&self) {
393 self.base.quit_app()
394 }
395}
396
397
398/// Generic rectangle struct.
399/// NOTE that this is different from the [`RECT`](../types/struct.RECT.html) type as it specifies width and height.
400#[derive(Clone, Copy)]
401pub struct Rectangle {
402 pub x: i32,
403 pub y: i32,
404 pub width: i32,
405 pub height: i32
406}
407
408
409/// Builder pattern for window creation.
410///
411/// For example,
412///
413/// ```rust,no_run
414/// let mut frame = sciter::window::Builder::main_window()
415/// .with_size((800,600))
416/// .resizeable()
417/// .glassy()
418/// .create();
419/// ```
420#[derive(Default)]
421pub struct Builder {
422 flags: Flags,
423 rect: RECT,
424 parent: Option<HWINDOW>,
425}
426
427// Note: https://rust-lang-nursery.github.io/api-guidelines/type-safety.html#non-consuming-builders-preferred
428impl Builder {
429
430 /// Main application window (resizeable with min/max buttons and title).
431 /// Will terminate the app on close.
432 pub fn main_window() -> Self {
433 Builder::main()
434 .resizeable()
435 .closeable()
436 .with_title()
437 }
438
439 /// Popup window (with min/max buttons and title).
440 pub fn popup_window() -> Self {
441 Builder::popup()
442 .closeable()
443 .with_title()
444 }
445
446 /// Child window style. if this flag is set all other flags are ignored.
447 pub fn child_window() -> Self {
448 Builder::with_flags(SCITER_CREATE_WINDOW_FLAGS::SW_CHILD)
449 }
450
451 /// If you want to start from scratch.
452 pub fn none() -> Self {
453 Builder::with_flags(SCITER_CREATE_WINDOW_FLAGS::SW_CHILD) // 0
454 }
455
456 /// Start with some flags.
457 pub fn with_flags(flags: Flags) -> Self {
458 Self {
459 flags,
460 ..Default::default()
461 }
462 }
463
464 /// Main window style (appears in taskbar).
465 /// Will terminate the app on close.
466 pub fn main() -> Self {
467 Builder::with_flags(SCITER_CREATE_WINDOW_FLAGS::SW_MAIN)
468 }
469
470 /// Popup style, window is created as topmost.
471 pub fn popup() -> Self {
472 Builder::with_flags(SCITER_CREATE_WINDOW_FLAGS::SW_POPUP)
473 }
474
475 /// Tool window style (with thin titlebar).
476 pub fn tool() -> Self {
477 Builder::with_flags(SCITER_CREATE_WINDOW_FLAGS::SW_TOOL)
478 }
479
480 /// Specify the parent window (e.g. for child creation).
481 pub fn with_parent(mut self, parent: HWINDOW) -> Self {
482 self.parent = Some(parent);
483 self
484 }
485
486 /// Specify the precise window size in `(width, height)` form.
487 pub fn with_size(mut self, size: (i32, i32)) -> Self {
488 self.rect.right = self.rect.left + size.0;
489 self.rect.bottom = self.rect.top + size.1;
490 self
491 }
492
493 /// Specify the precise window position in `(X, Y)` form.
494 pub fn with_pos(mut self, position: (i32, i32)) -> Self {
495 let size = self.rect.size();
496 self.rect.left = position.0;
497 self.rect.top = position.1;
498 self.rect.right = position.0 + size.cx;
499 self.rect.bottom = position.1 + size.cy;
500 self
501 }
502
503 /// Specify the exact window rectangle in `(X, Y, W, H)` form.
504 pub fn with_rect(mut self, rect: Rectangle) -> Self {
505 self.rect = RECT {
506 left: rect.x,
507 top: rect.y,
508 right: rect.x + rect.width,
509 bottom: rect.y + rect.height,
510 };
511 self
512 }
513
514 /// Top level window, has titlebar.
515 pub fn with_title(self) -> Self {
516 self.or(SCITER_CREATE_WINDOW_FLAGS::SW_TITLEBAR)
517 }
518
519 /// Can be resized.
520 pub fn resizeable(self) -> Self {
521 self.or(SCITER_CREATE_WINDOW_FLAGS::SW_RESIZEABLE)
522 }
523
524 /// Can not be resized.
525 pub fn fixed(self) -> Self {
526 self.and(SCITER_CREATE_WINDOW_FLAGS::SW_RESIZEABLE)
527 }
528
529 /// Has minimize / maximize buttons.
530 pub fn closeable(self) -> Self {
531 self.or(SCITER_CREATE_WINDOW_FLAGS::SW_CONTROLS)
532 }
533
534 /// Glassy window ("Acrylic" on Windows and "Vibrant" on macOS).
535 pub fn glassy(self) -> Self {
536 self.or(SCITER_CREATE_WINDOW_FLAGS::SW_GLASSY)
537 }
538
539 /// Transparent window.
540 pub fn alpha(self) -> Self {
541 self.or(SCITER_CREATE_WINDOW_FLAGS::SW_ALPHA)
542 }
543
544 /// Can be debugged with Inspector.
545 pub fn debug(self) -> Self {
546 self.or(SCITER_CREATE_WINDOW_FLAGS::SW_ENABLE_DEBUG)
547 }
548
549 fn or(mut self, flag: Flags) -> Self {
550 self.flags = self.flags | flag;
551 self
552 }
553
554 fn and(mut self, flag: Flags) -> Self {
555 let masked = self.flags as u32 & !(flag as u32);
556 self.flags = unsafe { ::std::mem::transmute(masked) };
557 self
558 }
559
560 /// Consume the builder and call [`Window::create()`](struct.Window.html#method.create) with built parameters.
561 #[cfg_attr(feature = "windowless", deprecated = "Sciter.Lite doesn't have OS windows in windowless mode.")]
562 pub fn create(self) -> Window {
563 Window::create(self.rect, self.flags, self.parent)
564 }
565}