slint/api/node/lib/index.ts
Florian Blasius 2324b35d12
napi test driver setup (#3635)
* Export interpreter stuff on private_api namespace

* Update api/napi/src/interpreter/value.rs

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>

* Update tests/driver/napi/build.rs

Co-authored-by: Simon Hausmann <simon.hausmann@slint-ui.com>

* Update tests/driver/napi/Cargo.toml

Co-authored-by: Simon Hausmann <simon.hausmann@slint-ui.com>


* Avoid unwanted prompt on Windows when running npm run build twice

Don't generate "napi.js" as then Windows will try to open it when running just "napi",
if nodejs registered .js as extension.

* Remove windows path workaround

It doesn't look like that it is needed.

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Simon Hausmann <simon.hausmann@slint-ui.com>
Co-authored-by: Simon Hausmann <simon.hausmann@slint.dev>
2023-10-11 12:48:27 +02:00

314 lines
8 KiB
TypeScript

// Copyright © SixtyFPS GmbH <info@slint.dev>
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
// Load the native library with `process.dlopen` instead of with `require`.
// This is only done for autotest that do not require nom or neon_cli to
// copy the lib to its right place
/**
* @hidden
*/
function load_native_lib() {
const os = require('os');
(process as any).dlopen(module, process.env.SLINT_NODE_NATIVE_LIB,
os.constants.dlopen.RTLD_NOW);
return module.exports;
}
/**
* @hidden
*/
let native = !process.env.SLINT_NODE_NATIVE_LIB ? require('../native/index.node') : load_native_lib();
/**
* @hidden
*/
class Component {
protected comp: any;
constructor(comp: any) {
this.comp = comp;
}
run() {
this.comp.run();
}
show() {
this.window.show();
}
hide() {
this.window.hide()
}
get window(): SlintWindow {
return new WindowAPI(this.comp.window());
}
get component(): any {
return this.comp;
}
}
interface Point {
x: number;
y: number;
}
interface Size {
width: number;
height: number;
}
interface SlintWindow {
show(): void;
hide(): void;
is_visible: boolean;
logical_position: Point;
physical_position: Point;
logical_size: Size;
physical_size: Size;
}
/**
* @hidden
*/
class WindowAPI implements SlintWindow {
protected impl: any;
constructor(impl: any) {
this.impl = impl;
}
show(): void {
this.impl.show();
}
hide(): void {
this.impl.hide();
}
get is_visible(): boolean {
return this.impl.get_is_visible();
}
get logical_position(): Point {
return this.impl.get_logical_position();
}
set logical_position(pos: Point) {
this.impl.set_logical_position(pos);
}
get physical_position(): Point {
return this.impl.get_physical_position();
}
set physical_position(pos: Point) {
this.impl.set_physical_position(pos);
}
get logical_size(): Size {
return this.impl.get_logical_size();
}
set logical_size(size: Size) {
this.impl.set_logical_size(size);
}
get physical_size(): Size {
return this.impl.get_physical_size();
}
set physical_size(size: Size) {
this.impl.set_physical_size(size);
}
}
/**
* @hidden
*/
interface Callback {
(): any;
setHandler(cb: any): void;
}
require.extensions['.60'] = require.extensions['.slint'] =
function (module, filename) {
var c = native.load(filename);
module.exports[c.name().replace(/-/g, '_')] = function (init_properties: any) {
let comp = c.create(init_properties);
let ret = new Component(comp);
c.properties().forEach((x: string) => {
Object.defineProperty(ret, x.replace(/-/g, '_'), {
get() { return comp.get_property(x); },
set(newValue) { comp.set_property(x, newValue); },
enumerable: true,
})
});
c.callbacks().forEach((x: string) => {
Object.defineProperty(ret, x.replace(/-/g, '_'), {
get() {
let callback = function () { return comp.invoke_callback(x, [...arguments]); } as Callback;
callback.setHandler = function (callback) { comp.connect_callback(x, callback) };
return callback;
},
enumerable: true,
})
});
return ret;
}
}
/**
* ModelPeer is the interface that the run-time implements. An instance is
* set on dynamic Model<T> instances and can be used to notify the run-time
* of changes in the structure or data of the model.
*/
interface ModelPeer {
/**
* Call this function from our own model to notify that fields of data
* in the specified row have changed.
* @argument row
*/
rowDataChanged(row: number): void;
/**
* Call this function from your own model to notify that one or multiple
* rows were added to the model, starting at the specified row.
* @param row
* @param count
*/
rowAdded(row: number, count: number): void;
/**
* Call this function from your own model to notify that one or multiple
* rows were removed from the model, starting at the specified row.
* @param row
* @param count
*/
rowRemoved(row: number, count: number): void;
/**
* Call this function from your own model to notify that the model has been
* changed and everything must be reloaded
*/
reset(): void;
}
/**
* Model<T> is the interface for feeding dynamic data into
* `.slint` views.
*
* A model is organized like a table with rows of data. The
* fields of the data type T behave like columns.
*/
interface Model<T> {
/**
* Implementations of this function must return the current number of rows.
*/
rowCount(): number;
/**
* Implementations of this function must return the data at the specified row.
* @param row
*/
rowData(row: number): T;
/**
* Implementations of this function must store the provided data parameter
* in the model at the specified row.
* @param row
* @param data
*/
setRowData(row: number, data: T): void;
/**
* This public member is set by the run-time and implementation must use this
* to notify the run-time of changes in the model.
*/
notify: ModelPeer;
}
/**
* @hidden
*/
class NullPeer implements ModelPeer {
rowDataChanged(row: number): void { }
rowAdded(row: number, count: number): void { }
rowRemoved(row: number, count: number): void { }
reset(): void { }
}
/**
* ArrayModel wraps a JavaScript array for use in `.slint` views. The underlying
* array can be modified with the [[ArrayModel.push]] and [[ArrayModel.remove]] methods.
*/
class ArrayModel<T> implements Model<T> {
/**
* @hidden
*/
private a: Array<T>
notify: ModelPeer;
/**
* Creates a new ArrayModel.
*
* @param arr
*/
constructor(arr: Array<T>) {
this.a = arr;
this.notify = new NullPeer();
}
rowCount() {
return this.a.length;
}
rowData(row: number) {
return this.a[row];
}
setRowData(row: number, data: T) {
this.a[row] = data;
this.notify.rowDataChanged(row);
}
/**
* Pushes new values to the array that's backing the model and notifies
* the run-time about the added rows.
* @param values
*/
push(...values: T[]) {
let size = this.a.length;
Array.prototype.push.apply(this.a, values);
this.notify.rowAdded(size, arguments.length);
}
// FIXME: should this be named splice and have the splice api?
/**
* Removes the specified number of element from the array that's backing
* the model, starting at the specified index. This is equivalent to calling
* Array.slice() on the array and notifying the run-time about the removed
* rows.
* @param index
* @param size
*/
remove(index: number, size: number) {
let r = this.a.splice(index, size);
this.notify.rowRemoved(index, size);
}
get length(): number {
return this.a.length;
}
values(): IterableIterator<T> {
return this.a.values();
}
entries(): IterableIterator<[number, T]> {
return this.a.entries()
}
}
function send_mouse_click(component: Component, x: number, y: number) {
component.component.send_mouse_click(x, y)
}
function send_keyboard_string_sequence(component: Component, s: String) {
component.component.send_keyboard_string_sequence(s)
}
module.exports = {
private_api: {
mock_elapsed_time: native.mock_elapsed_time,
send_mouse_click: send_mouse_click,
send_keyboard_string_sequence: send_keyboard_string_sequence,
},
ArrayModel: ArrayModel,
Timer: {
singleShot: native.singleshot_timer,
},
};