mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-30 13:51:13 +00:00
Replace neon node port (#3744)
* Update api/node/README.md Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
This commit is contained in:
parent
b6b3337430
commit
bf77b064ab
73 changed files with 370 additions and 2131 deletions
63
.github/workflows/ci.yaml
vendored
63
.github/workflows/ci.yaml
vendored
|
@ -74,7 +74,7 @@ jobs:
|
|||
# cargo update -p clap_lex --precise 0.5.0
|
||||
# fi
|
||||
- name: Run tests
|
||||
run: DYLD_FRAMEWORK_PATH=$Qt5_DIR/lib cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude slint-napi --exclude test-driver-napi --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp
|
||||
run: DYLD_FRAMEWORK_PATH=$Qt5_DIR/lib cargo test --verbose --all-features --workspace ${{ matrix.extra_args }} --exclude slint-node --exclude test-driver-node --exclude slint-node --exclude test-driver-nodejs --exclude test-driver-cpp --exclude mcu-board-support --exclude printerdemo_mcu --exclude uefi-demo --exclude slint-cpp
|
||||
env:
|
||||
SLINT_CREATE_SCREENSHOTS: 1
|
||||
shell: bash
|
||||
|
@ -87,55 +87,6 @@ jobs:
|
|||
tests/screenshots/references
|
||||
|
||||
node_test:
|
||||
env:
|
||||
DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/5.15.2/clang_64/lib
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
RUSTFLAGS: -D warnings
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
CARGO_INCREMENTAL: false
|
||||
RUST_BACKTRACE: 1
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, macos-11]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: ./.github/actions/install-linux-dependencies
|
||||
# Python 3.11 breaks the neon-sys build
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install Qt
|
||||
if: runner.os != 'Windows'
|
||||
uses: jurplel/install-qt-action@v3
|
||||
with:
|
||||
version: '5.15.2'
|
||||
setup-python: false
|
||||
cache: true
|
||||
- name: Setup headless display
|
||||
uses: pyvista/setup-headless-display-action@v1
|
||||
- name: Set default style
|
||||
if: matrix.os != 'windows-2022'
|
||||
run: |
|
||||
echo "SLINT_STYLE=native" >> $GITHUB_ENV
|
||||
- name: Set default style
|
||||
if: matrix.os == 'windows-2022'
|
||||
run: |
|
||||
echo "SLINT_STYLE=fluent" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
echo "SLINT_NO_QT=1" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
- uses: ./.github/actions/install-nodejs
|
||||
id: node-install
|
||||
- uses: ./.github/actions/setup-rust
|
||||
with:
|
||||
key: x-v2-${{ steps.node-install.outputs.node-version }} # the cache key consists of a manually bumpable version and the node version, as the cached rustc artifacts contain linking information where to find node.lib, which is in a versioned directory.
|
||||
- name: Build node plugin
|
||||
run: cargo build --verbose --all-features -p slint-node
|
||||
- name: Run node tests
|
||||
run: cargo test --verbose --all-features -p test-driver-nodejs -p slint-node
|
||||
|
||||
napi_test:
|
||||
env:
|
||||
DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/5.15.2/clang_64/lib
|
||||
QT_QPA_PLATFORM: offscreen
|
||||
|
@ -176,16 +127,16 @@ jobs:
|
|||
with:
|
||||
key: x-napi-v2-${{ steps.node-install.outputs.node-version }} # the cache key consists of a manually bumpable version and the node version, as the cached rustc artifacts contain linking information where to find node.lib, which is in a versioned directory.
|
||||
- name: Run npm install
|
||||
working-directory: ./api/napi
|
||||
working-directory: ./api/node
|
||||
run: npm install
|
||||
- name: Typescript check
|
||||
working-directory: ./api/napi
|
||||
working-directory: ./api/node
|
||||
run: npm run syntax_check
|
||||
- name: Run napi tests
|
||||
working-directory: ./api/napi
|
||||
- name: Run node tests
|
||||
working-directory: ./api/node
|
||||
run: npm test
|
||||
- name: Run test-driver-napi
|
||||
run: cargo test --verbose --all-features -p test-driver-napi -p slint-napi
|
||||
- name: Run test-driver-nodejs
|
||||
run: cargo test --verbose --all-features -p test-driver-nodejs -p slint-node
|
||||
|
||||
cpp_test_driver:
|
||||
env:
|
||||
|
|
|
@ -72,7 +72,7 @@ Files: tests/screenshots/references/software/*/*.png
|
|||
Copyright: Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
License: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
Files: api/napi/__test__/resources/*.png
|
||||
Files: api/node/__test__/resources/*.png
|
||||
Copyright: Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
License: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
|
|
|
@ -4,8 +4,7 @@
|
|||
[workspace]
|
||||
members = [
|
||||
'api/cpp',
|
||||
'api/node/native',
|
||||
'api/napi',
|
||||
'api/node',
|
||||
'api/rs/build',
|
||||
'api/rs/macros',
|
||||
'api/rs/slint',
|
||||
|
@ -50,7 +49,6 @@ members = [
|
|||
'tests/driver/driverlib',
|
||||
'tests/driver/interpreter',
|
||||
'tests/driver/nodejs',
|
||||
'tests/driver/napi',
|
||||
'tests/driver/rust',
|
||||
'tests/screenshots',
|
||||
'tools/compiler',
|
||||
|
@ -65,7 +63,6 @@ members = [
|
|||
default-members = [
|
||||
'api/rs/build',
|
||||
'api/rs/slint',
|
||||
'api/napi',
|
||||
'examples/gallery',
|
||||
'examples/memory',
|
||||
'examples/printerdemo_old/rust',
|
||||
|
|
4
api/napi/.gitignore
vendored
4
api/napi/.gitignore
vendored
|
@ -1,4 +0,0 @@
|
|||
rust-module.js
|
||||
rust-module.d.ts
|
||||
index.js
|
||||
docs/
|
|
@ -1,22 +0,0 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial -->
|
||||
# Slint-napi (pre-Alpha)
|
||||
|
||||
This is a restart of `Slint-node` based on [napi-rs](https://github.com/napi-rs/napi-rs). The current state is `pre-Alpha` what means it is not yet ready for testing and use.
|
||||
|
||||
## Implemented features
|
||||
|
||||
* js/ts wrapper for the `Slint` interpreter infrastructure
|
||||
* js/ts wrapper for `Slint` types
|
||||
* `ImageData`
|
||||
* `Color`
|
||||
* `Brush`
|
||||
|
||||
## Missing features
|
||||
|
||||
* Possibility to run js/ts `async` code after window run call
|
||||
* Generate a js/ts object-wrapper for the exported `Slint` component
|
||||
* Public access to Slint `globals`
|
||||
* CI: Generate prebuild platform `node` packages
|
||||
* Documentation generation
|
||||
* js/ts wrapper for `Slint` types
|
||||
* `Model` (wip)
|
|
@ -1,180 +0,0 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: MIT -->
|
||||
# Slint-node (Beta)
|
||||
|
||||
[](https://www.npmjs.com/package/slint-ui)
|
||||
|
||||
[Slint](https://slint.dev/) is a UI toolkit that supports different programming languages.
|
||||
Slint-node is the integration with Node.js.
|
||||
|
||||
To get started you use the [walk-through tutorial](https://slint.dev/docs/tutorial/node).
|
||||
We also have a [Getting Started Template](https://github.com/slint-ui/slint-nodejs-template) repository with
|
||||
the code of a minimal application using Slint that can be used as a starting point to your program.
|
||||
|
||||
**Warning: Beta**
|
||||
Slint-node is still in the early stages of development: APIs will change and important features are still being developed.
|
||||
|
||||
## Slint Language Manual
|
||||
|
||||
The [Slint Language Documentation](../slint) covers the Slint UI description language
|
||||
in detail.
|
||||
|
||||
## Installing Slint
|
||||
|
||||
Slint is available via NPM, so you can install by running the following command:
|
||||
|
||||
```sh
|
||||
npm install slint-ui
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
You need to install the following components:
|
||||
|
||||
* **[Node.js](https://nodejs.org/download/release/)** (v16. or newer)
|
||||
* **[npm](https://www.npmjs.com/)**
|
||||
* **[Rust compiler](https://www.rust-lang.org/tools/install)** (1.70 or newer)
|
||||
|
||||
You will also need a few more dependencies, see <https://github.com/slint-ui/slint/blob/master/docs/building.md#prerequisites>
|
||||
|
||||
## Using Slint
|
||||
|
||||
First, import the API from the `slint-ui` module. In the following examples we're using [ECMAScript module syntax](https://nodejs.org/api/esm.html#modules-ecmascript-modules), but if you prefer you can also import the API using [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) syntax.
|
||||
|
||||
To initialize the API, you first need to import the `slint-ui` module in our code:
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
```
|
||||
|
||||
Next, load a slint file with the `loadFile` function:
|
||||
|
||||
```js
|
||||
let ui = slint.loadFile("ui/main.slint");
|
||||
```
|
||||
|
||||
Combining these two steps leads us to the obligatory "Hello World" example:
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
let ui = slint.loadFile(".ui/main.slint");
|
||||
let main = new ui.Main();
|
||||
main.run();
|
||||
```
|
||||
|
||||
For a full example, see [/examples/todo/node](https://github.com/slint-ui/slint/tree/master/examples/todo/node).
|
||||
|
||||
## API Overview
|
||||
|
||||
### Instantiating a Component
|
||||
|
||||
The following example shows how to instantiating a Slint component from JavaScript.
|
||||
|
||||
**`ui/main.slint`**
|
||||
|
||||
```slint
|
||||
export component MainWindow inherits Window {
|
||||
callback clicked <=> i-touch-area.clicked;
|
||||
|
||||
in property <int> counter;
|
||||
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
i-touch-area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
The exported component is exposed as a type constructor. The type constructor takes as parameter
|
||||
an object which allow to initialize the value of public properties or callbacks.
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
// In this example, the main.slint file exports a module which
|
||||
// has a counter property and a clicked callback
|
||||
let ui = slint.loadFile("ui/main.slint");
|
||||
let component = new ui.MainWindow({
|
||||
counter: 42,
|
||||
clicked: function() { console.log("hello"); }
|
||||
});
|
||||
```
|
||||
|
||||
### Accessing a property
|
||||
|
||||
Properties declared as `out` or `in-out` in `.slint` files are visible as JavaScript on the component instance.
|
||||
|
||||
```js
|
||||
component.counter = 42;
|
||||
console.log(component.counter);
|
||||
```
|
||||
|
||||
### Callbacks
|
||||
|
||||
Callback in Slint can be defined usign the `callback` keyword and can be connected to a callback of an other component
|
||||
usign the `<=>` syntax.
|
||||
|
||||
**`ui/my-component.slint`**
|
||||
|
||||
```slint
|
||||
export component MyComponent inherits Window {
|
||||
callback clicked <=> i-touch-area.clicked;
|
||||
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
i-touch-area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
The callbacks in JavaScript are exposed as property that has a setHandler function, and that can be called as a function.
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("ui/my-component.slint");
|
||||
let component = new ui.MyComponent();
|
||||
|
||||
// connect to a callback
|
||||
component.clicked.setHandler(function() { console.log("hello"); })
|
||||
// emit a callback
|
||||
component.clicked();
|
||||
```
|
||||
|
||||
### Type Mappings
|
||||
|
||||
The types used for properties in .slint design markup each translate to specific types in JavaScript. The follow table summarizes the entire mapping:
|
||||
|
||||
| `.slint` Type | JavaScript Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `int` | `Number` | |
|
||||
| `float` | `Number` | |
|
||||
| `string` | `String` | |
|
||||
| `color` | {@link Color} | |
|
||||
| `brush` | {@link Brush} | |
|
||||
| `image` | {@link ImageData} | |
|
||||
| `length` | `Number` | |
|
||||
| `physical_length` | `Number` | |
|
||||
| `duration` | `Number` | The number of milliseconds |
|
||||
| `angle` | `Number` | The angle in degrees |
|
||||
| structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. |
|
||||
| array | `Array` or any implementation of {@link Model} | |
|
||||
|
||||
### Arrays and Models
|
||||
|
||||
[Array properties](../slint/src/reference/types#arrays-and-models) can be set from JavaScript by passing
|
||||
either `Array` objects or implementations of the {@link Model} interface.
|
||||
|
||||
When passing a JavaScript `Array` object, the contents of the array are copied. Any changes to the JavaScript afterwards will not be visible on the Slint side. Similarly, reading a Slint array property from JavaScript that was
|
||||
previously initialised from a JavaScript `Array`, will return a newly allocated JavaScript `Array`.
|
||||
|
||||
```js
|
||||
component.model = [1, 2, 3];
|
||||
// component.model.push(4); // does not work, because assignment creates a copy.
|
||||
// Use re-assignment instead.
|
||||
component.model = component.model.concat(4);
|
||||
```
|
||||
|
||||
Another option is to set an object that implements the {@link Model} interface. Rreading a Slint array property from JavaScript that was previously initialised from a {@link Model} object, will return a reference to the model.
|
|
@ -1,49 +0,0 @@
|
|||
{
|
||||
"name": "slint-node",
|
||||
"version": "1.3.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"license": "GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial",
|
||||
"napi": {
|
||||
"name": "slint-ui"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.15.2",
|
||||
"@swc-node/register": "^1.5.5",
|
||||
"@swc/core": "^1.3.32",
|
||||
"@types/node": "^20.8.6",
|
||||
"ava": "^5.3.0",
|
||||
"esbuild": "^0.14.54",
|
||||
"jimp": "^0.22.8",
|
||||
"typedoc": "^0.25.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"artifacts": "napi artifacts",
|
||||
"compile": "esbuild index.ts --bundle --external:*.node --format=cjs --platform=node --outfile=index.js",
|
||||
"build": "napi build --platform --release --js rust-module.js --dts rust-module.d.ts && npm run compile",
|
||||
"build:debug": "napi build --platform --js rust-module.js --dts rust-module.d.ts && npm run compile && npm run syntax_check",
|
||||
"install": "npm run build",
|
||||
"docs": "npm run build && typedoc --hideGenerator --treatWarningsAsErrors --readme cover.md index.ts",
|
||||
"prepublishOnly": "napi prepublish -t npm",
|
||||
"test": "ava",
|
||||
"syntax_check": "tsc -noEmit index.ts",
|
||||
"universal": "napi universal",
|
||||
"version": "1.3.0"
|
||||
},
|
||||
"ava": {
|
||||
"require": [
|
||||
"@swc-node/register"
|
||||
],
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"timeout": "2m",
|
||||
"workerThreads": false,
|
||||
"environmentVariables": {
|
||||
"TS_NODE_PROJECT": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
}
|
7
api/node/.gitignore
vendored
7
api/node/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
docs
|
||||
package-lock.json
|
||||
dist
|
||||
rust-module.js
|
||||
rust-module.d.ts
|
||||
index.js
|
||||
docs/
|
|
@ -2,7 +2,7 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
[package]
|
||||
name = "slint-napi"
|
||||
name = "slint-node"
|
||||
description = "Internal Slint Runtime Library for NodeJS API."
|
||||
authors.workspace = true
|
||||
documentation.workspace = true
|
|
@ -1,19 +1,24 @@
|
|||
<!-- Copyright © SixtyFPS GmbH <info@slint.dev> ; SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial -->
|
||||
|
||||
# Slint-node (Beta)
|
||||
|
||||
[](https://www.npmjs.com/package/slint-ui)
|
||||
|
||||
[Slint](https://slint.dev/) is a UI toolkit that supports different programming languages.
|
||||
Slint-node is the integration with node.
|
||||
Slint-node is the integration with Node.js.
|
||||
|
||||
The complete Node documentation can be viewed online at https://slint.dev/docs/node/.
|
||||
To get started you can use the [Walk-through tutorial](https://slint.dev/docs/tutorial/node).
|
||||
To get started you use the [walk-through tutorial](https://slint.dev/docs/tutorial/node).
|
||||
We also have a [Getting Started Template](https://github.com/slint-ui/slint-nodejs-template) repository with
|
||||
the code of a minimal application using Slint that can be used as a starting point to your program.
|
||||
|
||||
**Warning: Beta**
|
||||
Slint-node is still in the early stages of development: APIs will change and important features are still being developed.
|
||||
|
||||
## Slint Language Manual
|
||||
|
||||
The [Slint Language Documentation](../slint) covers the Slint UI description language
|
||||
in detail.
|
||||
|
||||
## Installing Slint
|
||||
|
||||
Slint is available via NPM, so you can install by running the following command:
|
||||
|
@ -26,7 +31,7 @@ npm install slint-ui
|
|||
|
||||
You need to install the following components:
|
||||
|
||||
* **[Node.js](https://nodejs.org/download/release/v16.19.1/)** (v16. Newer versions currently not supported: [#961](https://github.com/slint-ui/slint/issues/961))
|
||||
* **[Node.js](https://nodejs.org/download/release/)** (v16. or newer)
|
||||
* **[npm](https://www.npmjs.com/)**
|
||||
* **[Rust compiler](https://www.rust-lang.org/tools/install)** (1.70 or newer)
|
||||
|
||||
|
@ -34,41 +39,62 @@ You will also need a few more dependencies, see <https://github.com/slint-ui/sli
|
|||
|
||||
## Using Slint
|
||||
|
||||
First, import the API from the `slint-ui` module. In the following examples we're using [ECMAScript module syntax](https://nodejs.org/api/esm.html#modules-ecmascript-modules), but if you prefer you can also import the API using [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) syntax.
|
||||
|
||||
To initialize the API, you first need to import the `slint-ui` module in our code:
|
||||
|
||||
```js
|
||||
let slint = require("slint-ui");
|
||||
import * as slint from "slint-ui";
|
||||
```
|
||||
|
||||
This step also installs a hook in NodeJS that allows you to import `.slint` files directly:
|
||||
Next, load a slint file with the `loadFile` function:
|
||||
|
||||
```js
|
||||
let ui = require("../ui/main.slint");
|
||||
let ui = slint.loadFile("ui/main.slint");
|
||||
```
|
||||
|
||||
Combining these two steps leads us to the obligatory "Hello World" example:
|
||||
|
||||
```js
|
||||
require("slint-ui");
|
||||
let ui = require("../ui/main.slint");
|
||||
import * as slint from "slint-ui";
|
||||
let ui = slint.loadFile(".ui/main.slint");
|
||||
let main = new ui.Main();
|
||||
main.run();
|
||||
```
|
||||
|
||||
See [/examples/todo/node](/examples/todo/node) for a full example.
|
||||
For a full example, see [/examples/todo/node](https://github.com/slint-ui/slint/tree/master/examples/todo/node).
|
||||
|
||||
## API Overview
|
||||
|
||||
### Instantiating a component
|
||||
### Instantiating a Component
|
||||
|
||||
The following example shows how to instantiating a Slint component from JavaScript.
|
||||
|
||||
**`ui/main.slint`**
|
||||
|
||||
```slint
|
||||
export component MainWindow inherits Window {
|
||||
callback clicked <=> i-touch-area.clicked;
|
||||
|
||||
in property <int> counter;
|
||||
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
i-touch-area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
The exported component is exposed as a type constructor. The type constructor takes as parameter
|
||||
an object which allow to initialize the value of public properties or callbacks.
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
require("slint-ui");
|
||||
import * as slint from "slint-ui";
|
||||
// In this example, the main.slint file exports a module which
|
||||
// has a counter property and a clicked callback
|
||||
let ui = require("ui/main.slint");
|
||||
let ui = slint.loadFile("ui/main.slint");
|
||||
let component = new ui.MainWindow({
|
||||
counter: 42,
|
||||
clicked: function() { console.log("hello"); }
|
||||
|
@ -77,7 +103,7 @@ let component = new ui.MainWindow({
|
|||
|
||||
### Accessing a property
|
||||
|
||||
Properties are exposed as properties on the component instance
|
||||
Properties declared as `out` or `in-out` in `.slint` files are visible as JavaScript on the component instance.
|
||||
|
||||
```js
|
||||
component.counter = 42;
|
||||
|
@ -86,9 +112,32 @@ console.log(component.counter);
|
|||
|
||||
### Callbacks
|
||||
|
||||
The callbacks are also exposed as property that have a setHandler function, and that can can be called.
|
||||
Callback in Slint can be defined usign the `callback` keyword and can be connected to a callback of an other component
|
||||
usign the `<=>` syntax.
|
||||
|
||||
**`ui/my-component.slint`**
|
||||
|
||||
```slint
|
||||
export component MyComponent inherits Window {
|
||||
callback clicked <=> i-touch-area.clicked;
|
||||
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
i-touch-area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
The callbacks in JavaScript are exposed as property that has a setHandler function, and that can be called as a function.
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("ui/my-component.slint");
|
||||
let component = new ui.MyComponent();
|
||||
|
||||
// connect to a callback
|
||||
component.clicked.setHandler(function() { console.log("hello"); })
|
||||
// emit a callback
|
||||
|
@ -97,20 +146,24 @@ component.clicked();
|
|||
|
||||
### Type Mappings
|
||||
|
||||
The types used for properties in .slint design markup each translate to specific types in JavaScript. The follow table summarizes the entire mapping:
|
||||
|
||||
| `.slint` Type | JavaScript Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `int` | `Number` | |
|
||||
| `float` | `Number` | |
|
||||
| `string` | `String` | |
|
||||
| `color` | `String` | Colors are represented as strings in the form `"#rrggbbaa"`. When setting a color property, any CSS compliant color is accepted as a string. |
|
||||
| `color` | `Color` | |
|
||||
| `brush` | `Brush` | |
|
||||
| `image` | `ImageData` | |
|
||||
| `length` | `Number` | |
|
||||
| `physical_length` | `Number` | |
|
||||
| `duration` | `Number` | The number of milliseconds |
|
||||
| `angle` | `Number` | The value in degrees |
|
||||
| structure | `Object` | Structures are mapped to JavaScrip objects with structure fields mapped to properties. |
|
||||
| array | `Array` or Model Object | |
|
||||
| `angle` | `Number` | The angle in degrees |
|
||||
| structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. |
|
||||
| array | `Array` or any implementation of Model | |
|
||||
|
||||
### Models
|
||||
### Arrays and Models
|
||||
|
||||
For property of array type, they can either be set using an array.
|
||||
In that case, getting the property also return an array.
|
||||
|
@ -129,31 +182,60 @@ Another option is to set a model object. A model object has the following funct
|
|||
* `rowData(index)`: return the row at the given index
|
||||
* `setRowData(index, data)`: called when the model need to be changed. `this.notify.rowDataChanged` must be called if successful.
|
||||
|
||||
When such an object is set to a model property, it gets a new `notify` object with the following function
|
||||
|
||||
* `rowDataChanged(index)`: notify the view that the row was changed.
|
||||
* `rowAdded(index, count)`: notify the view that rows were added.
|
||||
* `rowRemoved(index, count)`: notify the view that a row were removed.
|
||||
* `reset()`: notify the view that everything may have changed.
|
||||
|
||||
As an example, here is the implementation of the `ArrayModel` (which is available as `slint.ArrayModel`)
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let array = [1, 2, 3];
|
||||
let model = {
|
||||
rowCount() { return a.length; },
|
||||
rowData(row) { return a[row]; },
|
||||
setRowData(row, data) { a[row] = data; this.notify.rowDataChanged(row); },
|
||||
push() {
|
||||
let size = a.length;
|
||||
Array.prototype.push.apply(a, arguments);
|
||||
|
||||
export class ArrayModel<T> extends slint.Model<T> {
|
||||
private a: Array<T>
|
||||
|
||||
constructor(arr: Array<T>) {
|
||||
super();
|
||||
this.a = arr;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
push(...values: T[]) {
|
||||
let size = this.a.length;
|
||||
Array.prototype.push.apply(this.a, values);
|
||||
this.notify.rowAdded(size, arguments.length);
|
||||
},
|
||||
remove(index, size) {
|
||||
let r = a.splice(index, size);
|
||||
this.notify.rowRemoved(size, arguments.length);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
let model = new ArrayModel(array);
|
||||
|
||||
component.model = model;
|
||||
model.push(4); // this works
|
||||
// does NOT work, getting the model does not return the right object
|
||||
|
|
Before Width: | Height: | Size: 272 B After Width: | Height: | Size: 272 B |
|
@ -3,7 +3,7 @@
|
|||
|
||||
import test from 'ava';
|
||||
|
||||
import { Brush, Color, ArrayModel } from '../index'
|
||||
import { Brush, Color, ArrayModel, Timer } from '../index'
|
||||
|
||||
test('Color from fromRgb', (t) => {
|
||||
let color = Color.fromRgb(100, 110, 120);
|
||||
|
@ -87,3 +87,15 @@ test('ArrayModel remove', (t) => {
|
|||
t.is(arrayModel.rowCount(), 1);
|
||||
t.is(arrayModel.rowData(0), 1);
|
||||
})
|
||||
|
||||
test('Timer negative duration', (t) => {
|
||||
t.throws(() => {
|
||||
Timer.singleShot(-1, function () {})
|
||||
},
|
||||
{
|
||||
code: "GenericFailure",
|
||||
message: "Duration cannot be negative"
|
||||
}
|
||||
);
|
||||
})
|
||||
|
|
@ -4,9 +4,9 @@
|
|||
[](https://www.npmjs.com/package/slint-ui)
|
||||
|
||||
[Slint](https://slint.dev/) is a UI toolkit that supports different programming languages.
|
||||
Slint-node is the integration with node.
|
||||
Slint-node is the integration with Node.js.
|
||||
|
||||
To get started you can use the [Walk-through tutorial](https://slint.dev/docs/tutorial/node).
|
||||
To get started you use the [walk-through tutorial](https://slint.dev/docs/tutorial/node).
|
||||
We also have a [Getting Started Template](https://github.com/slint-ui/slint-nodejs-template) repository with
|
||||
the code of a minimal application using Slint that can be used as a starting point to your program.
|
||||
|
||||
|
@ -15,7 +15,7 @@ Slint-node is still in the early stages of development: APIs will change and imp
|
|||
|
||||
## Slint Language Manual
|
||||
|
||||
The [Slint language manual](../slint) covers the Slint UI description language
|
||||
The [Slint Language Documentation](../slint) covers the Slint UI description language
|
||||
in detail.
|
||||
|
||||
## Installing Slint
|
||||
|
@ -30,7 +30,7 @@ npm install slint-ui
|
|||
|
||||
You need to install the following components:
|
||||
|
||||
* **[Node.js](https://nodejs.org/download/release/v16.19.1/)** (v16. Newer versions currently not supported: [#961](https://github.com/slint-ui/slint/issues/961))
|
||||
* **[Node.js](https://nodejs.org/download/release/)** (v16. or newer)
|
||||
* **[npm](https://www.npmjs.com/)**
|
||||
* **[Rust compiler](https://www.rust-lang.org/tools/install)** (1.70 or newer)
|
||||
|
||||
|
@ -38,41 +38,62 @@ You will also need a few more dependencies, see <https://github.com/slint-ui/sli
|
|||
|
||||
## Using Slint
|
||||
|
||||
First, import the API from the `slint-ui` module. In the following examples we're using [ECMAScript module syntax](https://nodejs.org/api/esm.html#modules-ecmascript-modules), but if you prefer you can also import the API using [CommonJS](https://nodejs.org/api/modules.html#modules-commonjs-modules) syntax.
|
||||
|
||||
To initialize the API, you first need to import the `slint-ui` module in our code:
|
||||
|
||||
```js
|
||||
let slint = require("slint-ui");
|
||||
import * as slint from "slint-ui";
|
||||
```
|
||||
|
||||
This step also installs a hook in NodeJS that allows you to import `.slint` files directly:
|
||||
Next, load a slint file with the `loadFile` function:
|
||||
|
||||
```js
|
||||
let ui = require("../ui/main.slint");
|
||||
let ui = slint.loadFile("ui/main.slint");
|
||||
```
|
||||
|
||||
Combining these two steps leads us to the obligatory "Hello World" example:
|
||||
|
||||
```js
|
||||
require("slint-ui");
|
||||
let ui = require("../ui/main.slint");
|
||||
import * as slint from "slint-ui";
|
||||
let ui = slint.loadFile(".ui/main.slint");
|
||||
let main = new ui.Main();
|
||||
main.run();
|
||||
```
|
||||
|
||||
See [/examples/todo/node](https://github.com/slint-ui/slint/tree/master/examples/todo/node) for a full example.
|
||||
For a full example, see [/examples/todo/node](https://github.com/slint-ui/slint/tree/master/examples/todo/node).
|
||||
|
||||
## API Overview
|
||||
|
||||
### Instantiating a component
|
||||
### Instantiating a Component
|
||||
|
||||
The following example shows how to instantiating a Slint component from JavaScript.
|
||||
|
||||
**`ui/main.slint`**
|
||||
|
||||
```
|
||||
export component MainWindow inherits Window {
|
||||
callback clicked <=> i-touch-area.clicked;
|
||||
|
||||
in property <int> counter;
|
||||
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
i-touch-area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
The exported component is exposed as a type constructor. The type constructor takes as parameter
|
||||
an object which allow to initialize the value of public properties or callbacks.
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
require("slint-ui");
|
||||
import * as slint from "slint-ui";
|
||||
// In this example, the main.slint file exports a module which
|
||||
// has a counter property and a clicked callback
|
||||
let ui = require("ui/main.slint");
|
||||
let ui = slint.loadFile("ui/main.slint");
|
||||
let component = new ui.MainWindow({
|
||||
counter: 42,
|
||||
clicked: function() { console.log("hello"); }
|
||||
|
@ -81,7 +102,7 @@ let component = new ui.MainWindow({
|
|||
|
||||
### Accessing a property
|
||||
|
||||
Properties are exposed as properties on the component instance
|
||||
Properties declared as `out` or `in-out` in `.slint` files are visible as JavaScript on the component instance.
|
||||
|
||||
```js
|
||||
component.counter = 42;
|
||||
|
@ -90,9 +111,32 @@ console.log(component.counter);
|
|||
|
||||
### Callbacks
|
||||
|
||||
The callbacks are also exposed as property that have a setHandler function, and that can can be called.
|
||||
Callback in Slint can be defined usign the `callback` keyword and can be connected to a callback of an other component
|
||||
usign the `<=>` syntax.
|
||||
|
||||
**`ui/my-component.slint`**
|
||||
|
||||
```
|
||||
export component MyComponent inherits Window {
|
||||
callback clicked <=> i-touch-area.clicked;
|
||||
|
||||
width: 400px;
|
||||
height: 200px;
|
||||
|
||||
i-touch-area := TouchArea {}
|
||||
}
|
||||
```
|
||||
|
||||
The callbacks in JavaScript are exposed as property that has a setHandler function, and that can be called as a function.
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("ui/my-component.slint");
|
||||
let component = new ui.MyComponent();
|
||||
|
||||
// connect to a callback
|
||||
component.clicked.setHandler(function() { console.log("hello"); })
|
||||
// emit a callback
|
||||
|
@ -101,65 +145,36 @@ component.clicked();
|
|||
|
||||
### Type Mappings
|
||||
|
||||
The types used for properties in .slint design markup each translate to specific types in JavaScript. The follow table summarizes the entire mapping:
|
||||
|
||||
| `.slint` Type | JavaScript Type | Notes |
|
||||
| --- | --- | --- |
|
||||
| `int` | `Number` | |
|
||||
| `float` | `Number` | |
|
||||
| `string` | `String` | |
|
||||
| `color` | `String` | Colors are represented as strings in the form `"#rrggbbaa"`. When setting a color property, any CSS compliant color is accepted as a string. |
|
||||
| `color` | {@link Color} | |
|
||||
| `brush` | {@link Brush} | |
|
||||
| `image` | {@link ImageData} | |
|
||||
| `length` | `Number` | |
|
||||
| `physical_length` | `Number` | |
|
||||
| `duration` | `Number` | The number of milliseconds |
|
||||
| `angle` | `Number` | The value in degrees |
|
||||
| structure | `Object` | Structures are mapped to JavaScrip objects with structure fields mapped to properties. |
|
||||
| array | `Array` or Model Object | |
|
||||
| `angle` | `Number` | The angle in degrees |
|
||||
| structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. |
|
||||
| array | `Array` or any implementation of {@link Model} | |
|
||||
|
||||
### Models
|
||||
### Arrays and Models
|
||||
|
||||
For property of array type, they can either be set using an array.
|
||||
In that case, getting the property also return an array.
|
||||
If the array was set within the .slint file, the array can be obtained
|
||||
[Array properties](../slint/src/reference/types#arrays-and-models) can be set from JavaScript by passing
|
||||
either `Array` objects or implementations of the {@link Model} interface.
|
||||
|
||||
When passing a JavaScript `Array` object, the contents of the array are copied. Any changes to the JavaScript afterwards will not be visible on the Slint side. Similarly, reading a Slint array property from JavaScript that was
|
||||
previously initialised from a JavaScript `Array`, will return a newly allocated JavaScript `Array`.
|
||||
|
||||
```js
|
||||
component.model = [1, 2, 3];
|
||||
// component.model.push(4); // does not work, because it operate on a copy
|
||||
// but re-assigning works
|
||||
// component.model.push(4); // does not work, because assignment creates a copy.
|
||||
// Use re-assignment instead.
|
||||
component.model = component.model.concat(4);
|
||||
```
|
||||
|
||||
Another option is to set a model object. A model object has the following function:
|
||||
|
||||
* `rowCount()`: returns the number of element in the model.
|
||||
* `rowData(index)`: return the row at the given index
|
||||
* `setRowData(index, data)`: called when the model need to be changed. `this.notify.rowDataChanged` must be called if successful.
|
||||
|
||||
When such an object is set to a model property, it gets a new `notify` object with the following function
|
||||
|
||||
* `rowDataChanged(index)`: notify the view that the row was changed.
|
||||
* `rowAdded(index, count)`: notify the view that rows were added.
|
||||
* `rowRemoved(index, count)`: notify the view that a row were removed.
|
||||
* `reset()`: notify the view that everything may have changed.
|
||||
|
||||
As an example, here is the implementation of the `ArrayModel` (which is available as `slint.ArrayModel`)
|
||||
|
||||
```js
|
||||
let array = [1, 2, 3];
|
||||
let model = {
|
||||
rowCount() { return a.length; },
|
||||
rowData(row) { return a[row]; },
|
||||
setRowData(row, data) { a[row] = data; this.notify.rowDataChanged(row); },
|
||||
push() {
|
||||
let size = a.length;
|
||||
Array.prototype.push.apply(a, arguments);
|
||||
this.notify.rowAdded(size, arguments.length);
|
||||
},
|
||||
remove(index, size) {
|
||||
let r = a.splice(index, size);
|
||||
this.notify.rowRemoved(size, arguments.length);
|
||||
},
|
||||
};
|
||||
component.model = model;
|
||||
model.push(4); // this works
|
||||
// does NOT work, getting the model does not return the right object
|
||||
// component.model.push(5);
|
||||
```
|
||||
Another option is to set an object that implements the {@link Model} interface. Rreading a Slint array property from JavaScript that was previously initialised from a {@link Model} object, will return a reference to the model.
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
import * as path from "path";
|
||||
|
||||
import * as napi from "./rust-module";
|
||||
export { Diagnostic, DiagnosticLevel, Window, Brush, Color, ImageData, Point, Size, SlintModelNotify } from "./rust-module";
|
||||
|
||||
|
@ -292,9 +294,12 @@ export class CompileError extends Error {
|
|||
/**
|
||||
* Loads the given slint file and returns a constructor to create an instance of the exported component.
|
||||
*/
|
||||
export function loadFile(path: string) : Object {
|
||||
export function loadFile(filePath: string) : Object {
|
||||
// this is a workaround that fixes an issue there resources in slint files cannot be loaded if the
|
||||
// file path is given as relative path
|
||||
let absoluteFilePath = path.resolve(filePath);
|
||||
let compiler = new napi.ComponentCompiler;
|
||||
let definition = compiler.buildFromPath(path);
|
||||
let definition = compiler.buildFromPath(absoluteFilePath);
|
||||
|
||||
let diagnostics = compiler.diagnostics;
|
||||
|
||||
|
@ -305,7 +310,7 @@ export function loadFile(path: string) : Object {
|
|||
let errors = diagnostics.filter((d) => d.level == napi.DiagnosticLevel.Error);
|
||||
|
||||
if (errors.length > 0) {
|
||||
throw new CompileError("Could not compile " + path, errors);
|
||||
throw new CompileError("Could not compile " + filePath, errors);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -316,7 +321,7 @@ export function loadFile(path: string) : Object {
|
|||
let instance = definition!.create();
|
||||
|
||||
if (instance == null) {
|
||||
throw Error("Could not create a component handle for" + path);
|
||||
throw Error("Could not create a component handle for" + filePath);
|
||||
}
|
||||
|
||||
for(var key in properties) {
|
||||
|
@ -356,6 +361,14 @@ export function loadFile(path: string) : Object {
|
|||
return slint_module;
|
||||
}
|
||||
|
||||
// This api will be removed after teh event loop handling is merged check PR #3718.
|
||||
// After that this in no longer necessary.
|
||||
export namespace Timer {
|
||||
export function singleShot(duration: number, handler: () => void) {
|
||||
napi.singleshotTimer(duration, handler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hidden
|
||||
*/
|
|
@ -1,314 +0,0 @@
|
|||
// 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,
|
||||
},
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
import { URL, pathToFileURL } from 'url';
|
||||
|
||||
const extensionsRegex = /\.(60|slint)$/;
|
||||
const baseURL = pathToFileURL(`${process.cwd()}/`).href;
|
||||
|
||||
export function resolve(specifier, context, defaultResolve) {
|
||||
|
||||
const { parentURL = baseURL } = context;
|
||||
|
||||
if (extensionsRegex.test(specifier)) {
|
||||
return { url: new URL(specifier, parentURL).href };
|
||||
}
|
||||
|
||||
return defaultResolve(specifier, context, defaultResolve);
|
||||
}
|
||||
|
||||
|
||||
export function getFormat(url, context, defaultGetFormat) {
|
||||
if (extensionsRegex.test(url)) {
|
||||
return {
|
||||
format: 'module'
|
||||
};
|
||||
}
|
||||
return defaultGetFormat(url, context, defaultGetFormat);
|
||||
}
|
||||
|
||||
export function transformSource(source, context, defaultTransformSource) {
|
||||
const { url, format } = context;
|
||||
|
||||
if (extensionsRegex.test(url)) {
|
||||
console.log(`This is where one can compile ${url}`)
|
||||
return {
|
||||
source: "console.log('Hey'); export function foo(x) { return x + 55 }"
|
||||
};
|
||||
}
|
||||
|
||||
// Let Node.js handle all other sources.
|
||||
return defaultTransformSource(source, context, defaultTransformSource);
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
[package]
|
||||
name = "slint-node"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
build = "build.rs"
|
||||
# This is not meant to be used as a library from crate.io
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
path = "lib.rs"
|
||||
crate-type = ["cdylib"]
|
||||
name = "slint_node_native"
|
||||
|
||||
[dependencies]
|
||||
i-slint-compiler = { workspace = true, features = ["default"] }
|
||||
i-slint-core = { workspace = true, features = ["default"] }
|
||||
slint-interpreter = { workspace = true, features = ["default", "display-diagnostics", "internal"] }
|
||||
|
||||
vtable = { version = "0.1.6", path="../../../helper_crates/vtable" }
|
||||
|
||||
css-color-parser2 = { workspace = true }
|
||||
generativity = "1"
|
||||
itertools = { workspace = true }
|
||||
neon = "0.8.0"
|
||||
once_cell = "1.5"
|
||||
rand = "0.8"
|
||||
scoped-tls-hkt = "0.1"
|
||||
spin_on = "0.1" #FIXME: remove and delegate to async JS instead
|
||||
|
||||
# Enable image-rs' default features to make all image formats available for nodejs builds
|
||||
image = { version = "0.24.0" }
|
||||
|
||||
[build-dependencies]
|
||||
neon-build = "0.8.0"
|
|
@ -1,6 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
fn main() {
|
||||
neon_build::setup();
|
||||
}
|
|
@ -1,155 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use i_slint_compiler::langtype::Type;
|
||||
use i_slint_core::model::Model;
|
||||
use neon::prelude::*;
|
||||
use std::cell::Cell;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
/// Model coming from JS
|
||||
pub struct JsModel {
|
||||
notify: i_slint_core::model::ModelNotify,
|
||||
/// The index of the value in the PersistentContext
|
||||
value_index: u32,
|
||||
data_type: Type,
|
||||
}
|
||||
|
||||
impl JsModel {
|
||||
pub fn new<'cx>(
|
||||
obj: Handle<'cx, JsObject>,
|
||||
data_type: Type,
|
||||
cx: &mut impl Context<'cx>,
|
||||
persistent_context: &crate::persistent_context::PersistentContext<'cx>,
|
||||
) -> NeonResult<Rc<Self>> {
|
||||
let val = obj.as_value(cx);
|
||||
let model = Rc::new(JsModel {
|
||||
notify: Default::default(),
|
||||
value_index: persistent_context.allocate(cx, val),
|
||||
data_type,
|
||||
});
|
||||
|
||||
let mut notify = SlintModelNotify::new::<_, JsValue, _>(cx, std::iter::empty())?;
|
||||
cx.borrow_mut(&mut notify, |mut notify| notify.0 = Rc::downgrade(&model));
|
||||
let notify = notify.as_value(cx);
|
||||
obj.set(cx, "notify", notify)?;
|
||||
|
||||
Ok(model)
|
||||
}
|
||||
|
||||
pub fn get_object<'cx>(
|
||||
&self,
|
||||
cx: &mut impl Context<'cx>,
|
||||
persistent_context: &crate::persistent_context::PersistentContext<'cx>,
|
||||
) -> JsResult<'cx, JsObject> {
|
||||
persistent_context.get(cx, self.value_index)?.downcast_or_throw(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Model for JsModel {
|
||||
type Data = slint_interpreter::Value;
|
||||
|
||||
fn row_count(&self) -> usize {
|
||||
let r = Cell::new(0usize);
|
||||
crate::run_with_global_context(&|cx, persistent_context| {
|
||||
let obj = self.get_object(cx, persistent_context).unwrap();
|
||||
let _ = obj
|
||||
.get(cx, "rowCount")
|
||||
.ok()
|
||||
.and_then(|func| func.downcast::<JsFunction>().ok())
|
||||
.and_then(|func| func.call(cx, obj, std::iter::empty::<Handle<JsValue>>()).ok())
|
||||
.and_then(|res| res.downcast::<JsNumber>().ok())
|
||||
.map(|num| r.set(num.value() as _));
|
||||
});
|
||||
r.get()
|
||||
}
|
||||
|
||||
fn row_data(&self, row: usize) -> Option<Self::Data> {
|
||||
if row >= self.row_count() {
|
||||
None
|
||||
} else {
|
||||
let r = Cell::new(slint_interpreter::Value::default());
|
||||
crate::run_with_global_context(&|cx, persistent_context| {
|
||||
let row = JsNumber::new(cx, row as f64);
|
||||
let obj = self.get_object(cx, persistent_context).unwrap();
|
||||
let _ = obj
|
||||
.get(cx, "rowData")
|
||||
.ok()
|
||||
.and_then(|func| func.downcast::<JsFunction>().ok())
|
||||
.and_then(|func| func.call(cx, obj, std::iter::once(row)).ok())
|
||||
.and_then(|res| {
|
||||
crate::to_eval_value(res, self.data_type.clone(), cx, persistent_context)
|
||||
.ok()
|
||||
})
|
||||
.map(|res| r.set(res));
|
||||
});
|
||||
Some(r.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
fn model_tracker(&self) -> &dyn i_slint_core::model::ModelTracker {
|
||||
&self.notify
|
||||
}
|
||||
|
||||
fn set_row_data(&self, row: usize, data: Self::Data) {
|
||||
crate::run_with_global_context(&|cx, persistent_context| {
|
||||
let row = JsNumber::new(cx, row as f64).as_value(cx);
|
||||
let data = crate::to_js_value(data.clone(), cx, persistent_context).unwrap();
|
||||
let obj = self.get_object(cx, persistent_context).unwrap();
|
||||
let _ = obj
|
||||
.get(cx, "setRowData")
|
||||
.ok()
|
||||
.and_then(|func| func.downcast::<JsFunction>().ok())
|
||||
.and_then(|func| func.call(cx, obj, [row, data].iter().cloned()).ok());
|
||||
});
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn core::any::Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
struct WrappedJsModel(Weak<JsModel>);
|
||||
|
||||
declare_types! {
|
||||
class SlintModelNotify for WrappedJsModel {
|
||||
init(_) {
|
||||
Ok(WrappedJsModel(Weak::default()))
|
||||
}
|
||||
method rowDataChanged(mut cx) {
|
||||
let this = cx.this();
|
||||
let row = cx.argument::<JsNumber>(0)?.value() as usize;
|
||||
if let Some(model) = cx.borrow(&this, |x| x.0.upgrade()) {
|
||||
model.notify.row_changed(row)
|
||||
}
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
method rowAdded(mut cx) {
|
||||
let this = cx.this();
|
||||
let row = cx.argument::<JsNumber>(0)?.value() as usize;
|
||||
let count = cx.argument::<JsNumber>(1)?.value() as usize;
|
||||
if let Some(model) = cx.borrow(&this, |x| x.0.upgrade()) {
|
||||
model.notify.row_added(row, count)
|
||||
}
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
method rowRemoved(mut cx) {
|
||||
let this = cx.this();
|
||||
let row = cx.argument::<JsNumber>(0)?.value() as usize;
|
||||
let count = cx.argument::<JsNumber>(1)?.value() as usize;
|
||||
if let Some(model) = cx.borrow(&this, |x| x.0.upgrade()) {
|
||||
model.notify.row_removed(row, count)
|
||||
}
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
method reset(mut cx) {
|
||||
let this = cx.this();
|
||||
if let Some(model) = cx.borrow(&this, |x| x.0.upgrade()) {
|
||||
model.notify.reset()
|
||||
}
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,703 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use core::cell::RefCell;
|
||||
use i_slint_compiler::langtype::Type;
|
||||
use i_slint_core::model::{Model, ModelRc};
|
||||
use i_slint_core::window::WindowInner;
|
||||
use i_slint_core::{ImageInner, SharedVector};
|
||||
use itertools::Itertools;
|
||||
use neon::prelude::*;
|
||||
use rand::RngCore;
|
||||
use slint_interpreter::ComponentHandle;
|
||||
|
||||
mod js_model;
|
||||
mod persistent_context;
|
||||
|
||||
struct WrappedComponentType(Option<slint_interpreter::ComponentDefinition>);
|
||||
struct WrappedItemTreeRc(Option<slint_interpreter::ComponentInstance>);
|
||||
struct WrappedWindow(Option<std::rc::Rc<dyn i_slint_core::window::WindowAdapter>>);
|
||||
|
||||
/// We need to do some gymnastic with closures to pass the ExecuteContext with the right lifetime
|
||||
type GlobalContextCallback<'c> =
|
||||
dyn for<'b> Fn(&mut ExecuteContext<'b>, &persistent_context::PersistentContext<'b>) + 'c;
|
||||
scoped_tls_hkt::scoped_thread_local!(static GLOBAL_CONTEXT:
|
||||
for <'a> &'a dyn for<'c> Fn(&'c GlobalContextCallback<'c>));
|
||||
|
||||
/// This function exists as a workaround so one can access the ExecuteContext from callback handler
|
||||
fn run_scoped<'cx, T>(
|
||||
cx: &mut impl Context<'cx>,
|
||||
object_with_persistent_context: Handle<'cx, JsObject>,
|
||||
functor: impl FnOnce() -> Result<T, String>,
|
||||
) -> NeonResult<T> {
|
||||
let persistent_context =
|
||||
persistent_context::PersistentContext::from_object(cx, object_with_persistent_context)?;
|
||||
cx.execute_scoped(|cx| {
|
||||
let cx = RefCell::new(cx);
|
||||
let cx_fn = move |callback: &GlobalContextCallback| {
|
||||
callback(&mut *cx.borrow_mut(), &persistent_context)
|
||||
};
|
||||
GLOBAL_CONTEXT.set(&&cx_fn, functor)
|
||||
})
|
||||
.or_else(|e| cx.throw_error(e))
|
||||
}
|
||||
|
||||
fn run_with_global_context(f: &GlobalContextCallback) {
|
||||
GLOBAL_CONTEXT.with(|cx_fn| cx_fn(f))
|
||||
}
|
||||
|
||||
/// Load a .slint files.
|
||||
///
|
||||
/// The first argument of this function is a string to the .slint file
|
||||
///
|
||||
/// The return value is a SlintComponentType
|
||||
fn load(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
let path = cx.argument::<JsString>(0)?.value();
|
||||
let path = std::path::Path::new(path.as_str());
|
||||
let include_paths = match std::env::var_os("SLINT_INCLUDE_PATH") {
|
||||
Some(paths) => {
|
||||
std::env::split_paths(&paths).filter(|path| !path.as_os_str().is_empty()).collect()
|
||||
}
|
||||
None => vec![],
|
||||
};
|
||||
let library_paths = match std::env::var_os("SLINT_LIBRARY_PATH") {
|
||||
Some(paths) => std::env::split_paths(&paths)
|
||||
.filter_map(|entry| {
|
||||
entry
|
||||
.to_str()
|
||||
.unwrap_or_default()
|
||||
.split('=')
|
||||
.collect_tuple()
|
||||
.map(|(k, v)| (k.into(), v.into()))
|
||||
})
|
||||
.collect(),
|
||||
None => std::collections::HashMap::new(),
|
||||
};
|
||||
let mut compiler = slint_interpreter::ComponentCompiler::default();
|
||||
compiler.set_include_paths(include_paths);
|
||||
compiler.set_library_paths(library_paths);
|
||||
let c = spin_on::spin_on(compiler.build_from_path(path));
|
||||
|
||||
slint_interpreter::print_diagnostics(compiler.diagnostics());
|
||||
|
||||
let c = if let Some(c) = c { c } else { return cx.throw_error("Compilation error") };
|
||||
|
||||
let mut obj = SlintComponentType::new::<_, JsValue, _>(&mut cx, std::iter::empty())?;
|
||||
cx.borrow_mut(&mut obj, |mut obj| obj.0 = Some(c));
|
||||
Ok(obj.as_value(&mut cx))
|
||||
}
|
||||
|
||||
fn make_callback_handler<'cx>(
|
||||
cx: &mut impl Context<'cx>,
|
||||
persistent_context: &persistent_context::PersistentContext<'cx>,
|
||||
fun: Handle<'cx, JsFunction>,
|
||||
return_type: Option<Box<Type>>,
|
||||
) -> Box<dyn Fn(&[slint_interpreter::Value]) -> slint_interpreter::Value> {
|
||||
let fun_value = fun.as_value(cx);
|
||||
let fun_idx = persistent_context.allocate(cx, fun_value);
|
||||
Box::new(move |args| {
|
||||
let args = args.to_vec();
|
||||
let ret = core::cell::Cell::new(slint_interpreter::Value::Void);
|
||||
let borrow_ret = &ret;
|
||||
let return_type = &return_type;
|
||||
run_with_global_context(&move |cx, persistent_context| {
|
||||
let args = args
|
||||
.iter()
|
||||
.map(|a| to_js_value(a.clone(), cx, persistent_context).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let ret = persistent_context
|
||||
.get(cx, fun_idx)
|
||||
.unwrap()
|
||||
.downcast::<JsFunction>()
|
||||
.unwrap()
|
||||
.call::<_, _, JsValue, _>(cx, JsUndefined::new(), args)
|
||||
.unwrap();
|
||||
if let Some(return_type) = return_type {
|
||||
borrow_ret.set(
|
||||
to_eval_value(ret, (**return_type).clone(), cx, persistent_context).unwrap(),
|
||||
);
|
||||
}
|
||||
});
|
||||
ret.into_inner()
|
||||
})
|
||||
}
|
||||
|
||||
fn create<'cx>(
|
||||
cx: &mut CallContext<'cx, impl neon::object::This>,
|
||||
component_type: slint_interpreter::ComponentDefinition,
|
||||
) -> JsResult<'cx, JsValue> {
|
||||
let component = component_type.create().unwrap();
|
||||
let persistent_context = persistent_context::PersistentContext::new(cx);
|
||||
|
||||
if let Some(args) = cx.argument_opt(0).and_then(|arg| arg.downcast::<JsObject>().ok()) {
|
||||
let properties = component_type
|
||||
.properties_and_callbacks()
|
||||
.map(|(k, v)| (k.replace('_', "-"), v))
|
||||
.collect::<std::collections::HashMap<_, _>>();
|
||||
for x in args.get_own_property_names(cx)?.to_vec(cx)? {
|
||||
let prop_name = x.to_string(cx)?.value().replace('_', "-");
|
||||
let value = args.get(cx, x)?;
|
||||
let ty = properties
|
||||
.get(&prop_name)
|
||||
.ok_or(())
|
||||
.or_else(|()| {
|
||||
cx.throw_error(format!("Property {} not found in the component", prop_name))
|
||||
})?
|
||||
.clone();
|
||||
if let Type::Callback { return_type, .. } = ty {
|
||||
let fun = value.downcast_or_throw::<JsFunction, _>(cx)?;
|
||||
component
|
||||
.set_callback(
|
||||
prop_name.as_str(),
|
||||
make_callback_handler(cx, &persistent_context, fun, return_type),
|
||||
)
|
||||
.or_else(|_| cx.throw_error("Cannot set callback"))?;
|
||||
} else {
|
||||
let value = to_eval_value(value, ty, cx, &persistent_context)?;
|
||||
component
|
||||
.set_property(prop_name.as_str(), value)
|
||||
.or_else(|_| cx.throw_error("Cannot assign property"))?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut obj = SlintComponent::new::<_, JsValue, _>(cx, std::iter::empty())?;
|
||||
persistent_context.save_to_object(cx, obj.downcast().unwrap());
|
||||
cx.borrow_mut(&mut obj, |mut obj| obj.0 = Some(component));
|
||||
Ok(obj.as_value(cx))
|
||||
}
|
||||
|
||||
fn to_eval_value<'cx>(
|
||||
val: Handle<'cx, JsValue>,
|
||||
ty: i_slint_compiler::langtype::Type,
|
||||
cx: &mut impl Context<'cx>,
|
||||
persistent_context: &persistent_context::PersistentContext<'cx>,
|
||||
) -> NeonResult<slint_interpreter::Value> {
|
||||
use slint_interpreter::Value;
|
||||
match ty {
|
||||
Type::Float32
|
||||
| Type::Int32
|
||||
| Type::Duration
|
||||
| Type::Angle
|
||||
| Type::PhysicalLength
|
||||
| Type::LogicalLength
|
||||
| Type::Rem
|
||||
| Type::Percent
|
||||
| Type::UnitProduct(_) => {
|
||||
Ok(Value::Number(val.downcast_or_throw::<JsNumber, _>(cx)?.value()))
|
||||
}
|
||||
Type::String => Ok(Value::String(val.to_string(cx)?.value().into())),
|
||||
Type::Color | Type::Brush => {
|
||||
let c = val
|
||||
.to_string(cx)?
|
||||
.value()
|
||||
.parse::<css_color_parser2::Color>()
|
||||
.or_else(|e| cx.throw_error(&e.to_string()))?;
|
||||
Ok((i_slint_core::Color::from_argb_u8((c.a * 255.) as u8, c.r, c.g, c.b)).into())
|
||||
}
|
||||
Type::Array(a) => match val.downcast::<JsArray>() {
|
||||
Ok(arr) => {
|
||||
let vec = arr.to_vec(cx)?;
|
||||
Ok(Value::Model(ModelRc::new(i_slint_core::model::SharedVectorModel::from(
|
||||
vec.into_iter()
|
||||
.map(|i| to_eval_value(i, (*a).clone(), cx, persistent_context))
|
||||
.collect::<Result<SharedVector<_>, _>>()?,
|
||||
))))
|
||||
}
|
||||
Err(_) => {
|
||||
let obj = val.downcast_or_throw::<JsObject, _>(cx)?;
|
||||
obj.get(cx, "rowCount")?.downcast_or_throw::<JsFunction, _>(cx)?;
|
||||
obj.get(cx, "rowData")?.downcast_or_throw::<JsFunction, _>(cx)?;
|
||||
let m = js_model::JsModel::new(obj, *a, cx, persistent_context)?;
|
||||
Ok(Value::Model(m.into()))
|
||||
}
|
||||
},
|
||||
Type::Image => {
|
||||
let path = val.to_string(cx)?.value();
|
||||
Ok(Value::Image(
|
||||
i_slint_core::graphics::Image::load_from_path(std::path::Path::new(&path))
|
||||
.or_else(|_| cx.throw_error(format!("cannot load image {:?}", path)))?,
|
||||
))
|
||||
}
|
||||
Type::Bool => Ok(Value::Bool(val.downcast_or_throw::<JsBoolean, _>(cx)?.value())),
|
||||
Type::Struct { fields, .. } => {
|
||||
let obj = val.downcast_or_throw::<JsObject, _>(cx)?;
|
||||
Ok(Value::Struct(
|
||||
fields
|
||||
.iter()
|
||||
.map(|(pro_name, pro_ty)| {
|
||||
Ok((
|
||||
pro_name.clone(),
|
||||
to_eval_value(
|
||||
obj.get(cx, pro_name.replace('-', "_").as_str())?,
|
||||
pro_ty.clone(),
|
||||
cx,
|
||||
persistent_context,
|
||||
)?,
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, _>>()?,
|
||||
))
|
||||
}
|
||||
Type::Enumeration(_) => todo!(),
|
||||
Type::Invalid
|
||||
| Type::Void
|
||||
| Type::InferredProperty
|
||||
| Type::InferredCallback
|
||||
| Type::Function { .. }
|
||||
| Type::Model
|
||||
| Type::Callback { .. }
|
||||
| Type::ComponentFactory { .. }
|
||||
| Type::Easing
|
||||
| Type::PathData
|
||||
| Type::LayoutCache
|
||||
| Type::ElementReference => cx.throw_error("Cannot convert to a Slint property value"),
|
||||
}
|
||||
}
|
||||
|
||||
fn to_js_value<'cx>(
|
||||
val: slint_interpreter::Value,
|
||||
cx: &mut impl Context<'cx>,
|
||||
persistent_context: &persistent_context::PersistentContext<'cx>,
|
||||
) -> NeonResult<Handle<'cx, JsValue>> {
|
||||
use slint_interpreter::Value;
|
||||
Ok(match val {
|
||||
Value::Void => JsUndefined::new().as_value(cx),
|
||||
Value::Number(n) => JsNumber::new(cx, n).as_value(cx),
|
||||
Value::String(s) => JsString::new(cx, s.as_str()).as_value(cx),
|
||||
Value::Bool(b) => JsBoolean::new(cx, b).as_value(cx),
|
||||
Value::Image(r) => match (&r).into() {
|
||||
&ImageInner::None => JsUndefined::new().as_value(cx),
|
||||
&ImageInner::EmbeddedImage { .. }
|
||||
| &ImageInner::StaticTextures { .. }
|
||||
| &ImageInner::Svg(..)
|
||||
| &ImageInner::BackendStorage(..)
|
||||
| &ImageInner::BorrowedOpenGLTexture(..) => JsNull::new().as_value(cx), // TODO: maybe pass around node buffers?
|
||||
},
|
||||
Value::Model(model) => {
|
||||
if let Some(js_model) = model.as_any().downcast_ref::<js_model::JsModel>() {
|
||||
js_model.get_object(cx, persistent_context)?.as_value(cx)
|
||||
} else {
|
||||
// TODO: this should probably create a proxy object instead of extracting the entire model. On the other hand
|
||||
// we should encounter this only if the model was created in .slint, which is when it'll be an array
|
||||
// of values.
|
||||
let js_array = JsArray::new(cx, model.row_count() as _);
|
||||
for i in 0..model.row_count() {
|
||||
let v = to_js_value(model.row_data(i).unwrap(), cx, persistent_context)?;
|
||||
js_array.set(cx, i as u32, v)?;
|
||||
}
|
||||
js_array.as_value(cx)
|
||||
}
|
||||
}
|
||||
Value::Struct(o) => {
|
||||
let js_object = JsObject::new(cx);
|
||||
for (k, e) in o.iter() {
|
||||
let v = to_js_value(e.clone(), cx, persistent_context)?;
|
||||
js_object.set(cx, k.replace('-', "_").as_str(), v)?;
|
||||
}
|
||||
js_object.as_value(cx)
|
||||
}
|
||||
Value::Brush(i_slint_core::Brush::SolidColor(c)) => JsString::new(
|
||||
cx,
|
||||
&format!("#{:02x}{:02x}{:02x}{:02x}", c.red(), c.green(), c.blue(), c.alpha()),
|
||||
)
|
||||
.as_value(cx),
|
||||
_ => todo!("converting {:?} to js has not been implemented", val),
|
||||
})
|
||||
}
|
||||
|
||||
declare_types! {
|
||||
class SlintComponentType for WrappedComponentType {
|
||||
init(_) {
|
||||
Ok(WrappedComponentType(None))
|
||||
}
|
||||
method create(mut cx) {
|
||||
let this = cx.this();
|
||||
let ct = cx.borrow(&this, |x| x.0.clone());
|
||||
let ct = ct.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
create(&mut cx, ct)
|
||||
}
|
||||
method name(mut cx) {
|
||||
let this = cx.this();
|
||||
let ct = cx.borrow(&this, |x| x.0.clone());
|
||||
let ct = ct.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
Ok(cx.string(ct.name()).as_value(&mut cx))
|
||||
}
|
||||
method properties(mut cx) {
|
||||
let this = cx.this();
|
||||
let ct = cx.borrow(&this, |x| x.0.clone());
|
||||
let ct = ct.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let properties = ct.properties_and_callbacks().filter(|(_, prop_type)| prop_type.is_property_type());
|
||||
let array = JsArray::new(&mut cx, 0);
|
||||
for (len, (p, _)) in properties.enumerate() {
|
||||
let prop_name = JsString::new(&mut cx, p);
|
||||
array.set(&mut cx, len as u32, prop_name)?;
|
||||
}
|
||||
Ok(array.as_value(&mut cx))
|
||||
}
|
||||
method callbacks(mut cx) {
|
||||
let this = cx.this();
|
||||
let ct = cx.borrow(&this, |x| x.0.clone());
|
||||
let ct = ct.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let callbacks = ct.properties_and_callbacks().filter(|(_, prop_type)| matches!(prop_type, Type::Callback{..}));
|
||||
let array = JsArray::new(&mut cx, 0);
|
||||
for (len , (p, _)) in callbacks.enumerate() {
|
||||
let prop_name = JsString::new(&mut cx, p);
|
||||
array.set(&mut cx, len as u32, prop_name)?;
|
||||
}
|
||||
Ok(array.as_value(&mut cx))
|
||||
}
|
||||
}
|
||||
|
||||
class SlintComponent for WrappedItemTreeRc {
|
||||
init(_) {
|
||||
Ok(WrappedItemTreeRc(None))
|
||||
}
|
||||
method run(mut cx) {
|
||||
let this = cx.this();
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
run_scoped(&mut cx,this.downcast().unwrap(), || {
|
||||
component.run().unwrap();
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
method window(mut cx) {
|
||||
let this = cx.this();
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let window_adapter = WindowInner::from_pub(component.window()).window_adapter();
|
||||
let mut obj = SlintWindow::new::<_, JsValue, _>(&mut cx, std::iter::empty())?;
|
||||
cx.borrow_mut(&mut obj, |mut obj| obj.0 = Some(window_adapter));
|
||||
Ok(obj.as_value(&mut cx))
|
||||
}
|
||||
method get_property(mut cx) {
|
||||
let prop_name = cx.argument::<JsString>(0)?.value();
|
||||
let this = cx.this();
|
||||
let persistent_context =
|
||||
persistent_context::PersistentContext::from_object(&mut cx, this.downcast().unwrap())?;
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let value = run_scoped(&mut cx,this.downcast().unwrap(), || {
|
||||
component.get_property(prop_name.as_str())
|
||||
.map_err(|_| "Cannot read property".to_string())
|
||||
})?;
|
||||
to_js_value(value, &mut cx, &persistent_context)
|
||||
}
|
||||
method set_property(mut cx) {
|
||||
let prop_name = cx.argument::<JsString>(0)?.value();
|
||||
let this = cx.this();
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let ty = component.definition().properties_and_callbacks()
|
||||
.find_map(|(name, proptype)| if name == prop_name { Some(proptype) } else { None })
|
||||
.ok_or(())
|
||||
.or_else(|()| {
|
||||
cx.throw_error(format!("Property {} not found in the component", prop_name))
|
||||
})?;
|
||||
|
||||
let persistent_context =
|
||||
persistent_context::PersistentContext::from_object(&mut cx, this.downcast().unwrap())?;
|
||||
|
||||
let value = to_eval_value(cx.argument::<JsValue>(1)?, ty, &mut cx, &persistent_context)?;
|
||||
run_scoped(&mut cx, this.downcast().unwrap(), || {
|
||||
component.set_property(prop_name.as_str(), value)
|
||||
.map_err(|_| "Cannot assign property".to_string())
|
||||
})?;
|
||||
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
method invoke_callback(mut cx) {
|
||||
let callback_name = cx.argument::<JsString>(0)?.value();
|
||||
let arguments = cx.argument::<JsArray>(1)?.to_vec(&mut cx)?;
|
||||
let this = cx.this();
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let ty = component.definition().properties_and_callbacks()
|
||||
.find_map(|(name, proptype)| if name == callback_name { Some(proptype) } else { None })
|
||||
.ok_or(())
|
||||
.or_else(|()| {
|
||||
cx.throw_error(format!("Callback {} not found in the component", callback_name))
|
||||
})?;
|
||||
let persistent_context =
|
||||
persistent_context::PersistentContext::from_object(&mut cx, this.downcast().unwrap())?;
|
||||
let args = if let Type::Callback {args, ..} = ty {
|
||||
let count = args.len();
|
||||
let args = arguments.into_iter()
|
||||
.zip(args.into_iter())
|
||||
.map(|(a, ty)| to_eval_value(a, ty, &mut cx, &persistent_context))
|
||||
.collect::<Result<Vec<_>, _>>()?;
|
||||
if args.len() != count {
|
||||
cx.throw_error(format!("{} expect {} arguments, but {} where provided", callback_name, count, args.len()))?;
|
||||
}
|
||||
args
|
||||
|
||||
} else {
|
||||
cx.throw_error(format!("{} is not a callback", callback_name))?;
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let res = run_scoped(&mut cx,this.downcast().unwrap(), || {
|
||||
component.invoke(callback_name.as_str(), args.as_slice())
|
||||
.map_err(|_| "Cannot emit callback".to_string())
|
||||
})?;
|
||||
to_js_value(res, &mut cx, &persistent_context)
|
||||
}
|
||||
|
||||
method connect_callback(mut cx) {
|
||||
let callback_name = cx.argument::<JsString>(0)?.value();
|
||||
let handler = cx.argument::<JsFunction>(1)?;
|
||||
let this = cx.this();
|
||||
let persistent_context =
|
||||
persistent_context::PersistentContext::from_object(&mut cx, this.downcast().unwrap())?;
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
|
||||
let ty = component.definition().properties_and_callbacks()
|
||||
.find_map(|(name, proptype)| if name == callback_name { Some(proptype) } else { None })
|
||||
.ok_or(())
|
||||
.or_else(|()| {
|
||||
cx.throw_error(format!("Callback {} not found in the component", callback_name))
|
||||
})?;
|
||||
if let Type::Callback {return_type, ..} = ty {
|
||||
component.set_callback(
|
||||
callback_name.as_str(),
|
||||
make_callback_handler(&mut cx, &persistent_context, handler, return_type)
|
||||
).or_else(|_| cx.throw_error("Cannot set callback"))?;
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
} else {
|
||||
cx.throw_error(format!("{} is not a callback", callback_name))?;
|
||||
unreachable!()
|
||||
}
|
||||
}
|
||||
|
||||
method send_mouse_click(mut cx) {
|
||||
let x = cx.argument::<JsNumber>(0)?.value() as f32;
|
||||
let y = cx.argument::<JsNumber>(1)?.value() as f32;
|
||||
let this = cx.this();
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
run_scoped(&mut cx,this.downcast().unwrap(), || {
|
||||
slint_interpreter::testing::send_mouse_click(&component, x, y);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
method send_keyboard_string_sequence(mut cx) {
|
||||
let sequence = cx.argument::<JsString>(0)?.value();
|
||||
let this = cx.this();
|
||||
let component = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone_strong()));
|
||||
let component = component.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
run_scoped(&mut cx,this.downcast().unwrap(), || {
|
||||
slint_interpreter::testing::send_keyboard_string_sequence(&component, sequence.into());
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
}
|
||||
|
||||
class SlintWindow for WrappedWindow {
|
||||
init(_) {
|
||||
Ok(WrappedWindow(None))
|
||||
}
|
||||
|
||||
method show(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
window_adapter.window().show().unwrap();
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
method hide(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
window_adapter.window().hide().unwrap();
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
method get_is_visible(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
Ok(JsBoolean::new(&mut cx, window_adapter.window().is_visible()).as_value(&mut cx))
|
||||
}
|
||||
|
||||
method get_logical_position(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let pos = window_adapter.position().unwrap_or_default().to_logical(window_adapter.window().scale_factor());
|
||||
|
||||
let point_object = JsObject::new(&mut cx);
|
||||
let x_value = JsNumber::new(&mut cx, pos.x).as_value(&mut cx);
|
||||
point_object.set(&mut cx, "x", x_value)?;
|
||||
let y_value = JsNumber::new(&mut cx, pos.y).as_value(&mut cx);
|
||||
point_object.set(&mut cx, "y", y_value)?;
|
||||
Ok(point_object.as_value(&mut cx))
|
||||
}
|
||||
|
||||
method get_physical_position(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let pos = window_adapter.position().unwrap_or_default();
|
||||
|
||||
let point_object = JsObject::new(&mut cx);
|
||||
let x_value = JsNumber::new(&mut cx, pos.x).as_value(&mut cx);
|
||||
point_object.set(&mut cx, "x", x_value)?;
|
||||
let y_value = JsNumber::new(&mut cx, pos.y).as_value(&mut cx);
|
||||
point_object.set(&mut cx, "y", y_value)?;
|
||||
Ok(point_object.as_value(&mut cx))
|
||||
}
|
||||
|
||||
method set_logical_position(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
|
||||
let point_object = cx.argument::<JsObject>(0)?;
|
||||
let x = point_object.get(&mut cx, "x")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
let y = point_object.get(&mut cx, "y")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
|
||||
window_adapter.set_position(i_slint_core::api::LogicalPosition::new(x as f32, y as f32).into());
|
||||
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
method set_physical_position(mut cx) {
|
||||
let this = cx.this();
|
||||
let window = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
|
||||
let point_object = cx.argument::<JsObject>(0)?;
|
||||
let x = point_object.get(&mut cx, "x")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
let y = point_object.get(&mut cx, "y")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
|
||||
window_adapter.set_position(i_slint_core::api::PhysicalPosition::new(x as i32, y as i32).into());
|
||||
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
method get_logical_size(mut cx) {
|
||||
let this = cx.this();
|
||||
let window_adapter = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window_adapter.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let size = window_adapter.window().size().to_logical(window_adapter.window().scale_factor());
|
||||
|
||||
let size_object = JsObject::new(&mut cx);
|
||||
let width_value = JsNumber::new(&mut cx, size.width).as_value(&mut cx);
|
||||
size_object.set(&mut cx, "width", width_value)?;
|
||||
let height_value = JsNumber::new(&mut cx, size.height).as_value(&mut cx);
|
||||
size_object.set(&mut cx, "height", height_value)?;
|
||||
Ok(size_object.as_value(&mut cx))
|
||||
}
|
||||
|
||||
method get_physical_size(mut cx) {
|
||||
let this = cx.this();
|
||||
let window_adapter = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window_adapter.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let size = window_adapter.window().size();
|
||||
|
||||
let size_object = JsObject::new(&mut cx);
|
||||
let width_value = JsNumber::new(&mut cx, size.width).as_value(&mut cx);
|
||||
size_object.set(&mut cx, "width", width_value)?;
|
||||
let height_value = JsNumber::new(&mut cx, size.height).as_value(&mut cx);
|
||||
size_object.set(&mut cx, "height", height_value)?;
|
||||
Ok(size_object.as_value(&mut cx))
|
||||
}
|
||||
|
||||
method set_logical_size(mut cx) {
|
||||
let this = cx.this();
|
||||
let window_adapter = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window_adapter.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let window = window_adapter.window();
|
||||
|
||||
let size_object = cx.argument::<JsObject>(0)?;
|
||||
let width = size_object.get(&mut cx, "width")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
let height = size_object.get(&mut cx, "height")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
|
||||
window.set_size(i_slint_core::api::LogicalSize::new(width as f32, height as f32));
|
||||
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
|
||||
method set_physical_size(mut cx) {
|
||||
let this = cx.this();
|
||||
let window_adapter = cx.borrow(&this, |x| x.0.as_ref().cloned());
|
||||
let window_adapter = window_adapter.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
|
||||
let window = window_adapter.window();
|
||||
|
||||
let size_object = cx.argument::<JsObject>(0)?;
|
||||
let width = size_object.get(&mut cx, "width")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
let height = size_object.get(&mut cx, "height")?.downcast_or_throw::<JsNumber, _>(&mut cx)?.value();
|
||||
|
||||
window.set_size(i_slint_core::api::PhysicalSize::new(width as u32, height as u32));
|
||||
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn singleshot_timer_property(id: u32) -> String {
|
||||
format!("$__slint_singleshot_timer_{}", id)
|
||||
}
|
||||
|
||||
fn singleshot_timer(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
let duration_in_msecs = cx.argument::<JsNumber>(0)?.value() as u64;
|
||||
let handler = cx.argument::<JsFunction>(1)?;
|
||||
|
||||
let global_object: Handle<JsObject> = cx.global().downcast().unwrap();
|
||||
let unique_timer_property = {
|
||||
let mut rng = rand::thread_rng();
|
||||
loop {
|
||||
let id = rng.next_u32();
|
||||
let key = singleshot_timer_property(id);
|
||||
if global_object.get(&mut cx, &*key)?.is_a::<JsUndefined>() {
|
||||
break key;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let handler_value = handler.as_value(&mut cx);
|
||||
global_object.set(&mut cx, &*unique_timer_property, handler_value).unwrap();
|
||||
let callback = move || {
|
||||
run_with_global_context(&move |cx, _| {
|
||||
let global_object: Handle<JsObject> = cx.global().downcast().unwrap();
|
||||
|
||||
let callback = global_object
|
||||
.get(cx, &*unique_timer_property)
|
||||
.unwrap()
|
||||
.downcast::<JsFunction>()
|
||||
.unwrap();
|
||||
|
||||
global_object.set(cx, &*unique_timer_property, JsUndefined::new()).unwrap();
|
||||
|
||||
callback.call::<_, _, JsValue, _>(cx, JsUndefined::new(), vec![]).unwrap();
|
||||
});
|
||||
};
|
||||
|
||||
i_slint_core::timers::Timer::single_shot(
|
||||
std::time::Duration::from_millis(duration_in_msecs),
|
||||
callback,
|
||||
);
|
||||
|
||||
Ok(JsUndefined::new().upcast())
|
||||
}
|
||||
|
||||
register_module!(mut m, {
|
||||
m.export_function("load", load)?;
|
||||
m.export_function("mock_elapsed_time", mock_elapsed_time)?;
|
||||
m.export_function("singleshot_timer", singleshot_timer)?;
|
||||
Ok(())
|
||||
});
|
||||
|
||||
/// let some time elapse for testing purposes
|
||||
fn mock_elapsed_time(mut cx: FunctionContext) -> JsResult<JsValue> {
|
||||
let ms = cx.argument::<JsNumber>(0)?.value();
|
||||
i_slint_core::tests::slint_mock_elapsed_time(ms as _);
|
||||
Ok(JsUndefined::new().as_value(&mut cx))
|
||||
}
|
|
@ -1,37 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
/*!
|
||||
Since neon does not allow to have a persistent handle, use this hack.
|
||||
*/
|
||||
|
||||
use neon::prelude::*;
|
||||
pub struct PersistentContext<'a>(Handle<'a, JsArray>);
|
||||
|
||||
const KEY: &str = "$__persistent_context";
|
||||
|
||||
/// Since neon does not allow to have a persistent handle, this allocates property in an array.
|
||||
/// This array is gonna be kept as a property somewhere.
|
||||
impl<'a> PersistentContext<'a> {
|
||||
pub fn new(cx: &mut impl Context<'a>) -> Self {
|
||||
PersistentContext(JsArray::new(cx, 0))
|
||||
}
|
||||
|
||||
pub fn allocate(&self, cx: &mut impl Context<'a>, value: Handle<'a, JsValue>) -> u32 {
|
||||
let idx = self.0.len();
|
||||
self.0.set(cx, idx, value).unwrap();
|
||||
idx
|
||||
}
|
||||
|
||||
pub fn get(&self, cx: &mut impl Context<'a>, idx: u32) -> JsResult<'a, JsValue> {
|
||||
self.0.get(cx, idx)
|
||||
}
|
||||
|
||||
pub fn save_to_object(&self, cx: &mut impl Context<'a>, o: Handle<'a, JsObject>) {
|
||||
o.set(cx, KEY, self.0).unwrap();
|
||||
}
|
||||
|
||||
pub fn from_object(cx: &mut impl Context<'a>, o: Handle<'a, JsObject>) -> NeonResult<Self> {
|
||||
Ok(PersistentContext(o.get(cx, KEY)?.downcast_or_throw(cx)?))
|
||||
}
|
||||
}
|
|
@ -1,25 +1,48 @@
|
|||
{
|
||||
"name": "slint-ui",
|
||||
"version": "1.3.0",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"homepage": "https://github.com/slint-ui/slint",
|
||||
"license": "SEE LICENSE IN LICENSE.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/slint-ui/slint"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"dependencies": {
|
||||
"@types/node": "^14.11.11",
|
||||
"neon-cli": "^0.4",
|
||||
"typescript": "^4.0.3"
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.15.2",
|
||||
"@swc-node/register": "^1.5.5",
|
||||
"@swc/core": "^1.3.32",
|
||||
"@types/node": "^20.8.6",
|
||||
"ava": "^5.3.0",
|
||||
"esbuild": "^0.14.54",
|
||||
"jimp": "^0.22.8",
|
||||
"typedoc": "^0.25.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "neon build --release && tsc",
|
||||
"build": "tsc",
|
||||
"docs": "typedoc --hideGenerator --readme cover.md lib/index.ts"
|
||||
"artifacts": "napi artifacts",
|
||||
"compile": "esbuild index.ts --bundle --external:*.node --format=cjs --platform=node --outfile=index.js",
|
||||
"build": "napi build --platform --release --js rust-module.js --dts rust-module.d.ts && npm run compile",
|
||||
"build:debug": "napi build --platform --js rust-module.js --dts rust-module.d.ts && npm run compile && npm run syntax_check",
|
||||
"install": "npm run build",
|
||||
"docs": "npm run build && typedoc --hideGenerator --treatWarningsAsErrors --readme cover.md index.ts",
|
||||
"test": "ava",
|
||||
"syntax_check": "tsc -noEmit index.ts"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typedoc": "^0.19.2"
|
||||
"ava": {
|
||||
"require": [
|
||||
"@swc-node/register"
|
||||
],
|
||||
"extensions": [
|
||||
"ts"
|
||||
],
|
||||
"timeout": "2m",
|
||||
"workerThreads": false,
|
||||
"environmentVariables": {
|
||||
"TS_NODE_PROJECT": "./tsconfig.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use super::JsComponentDefinition;
|
||||
|
@ -60,6 +61,16 @@ impl JsComponentCompiler {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[napi(setter)]
|
||||
pub fn set_library_paths(&mut self, paths: HashMap<String, String>) {
|
||||
let mut library_paths = HashMap::new();
|
||||
for (key, path) in paths {
|
||||
library_paths.insert(key, PathBuf::from(path));
|
||||
}
|
||||
|
||||
self.internal.set_library_paths(library_paths);
|
||||
}
|
||||
|
||||
#[napi(setter)]
|
||||
pub fn set_style(&mut self, style: String) {
|
||||
self.internal.set_style(style);
|
|
@ -7,6 +7,9 @@ pub use interpreter::*;
|
|||
mod types;
|
||||
pub use types::*;
|
||||
|
||||
mod timer;
|
||||
pub use timer::*;
|
||||
|
||||
#[macro_use]
|
||||
extern crate napi_derive;
|
||||
|
27
api/node/src/timer.rs
Normal file
27
api/node/src/timer.rs
Normal file
|
@ -0,0 +1,27 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use napi::{Env, JsFunction, Result};
|
||||
|
||||
use crate::RefCountedReference;
|
||||
|
||||
/// Starts the timer with the duration, in order for the callback to called when the timer fires. It is fired only once and then deleted.
|
||||
#[napi]
|
||||
pub fn singleshot_timer(env: Env, duration_in_msecs: f64, handler: JsFunction) -> Result<()> {
|
||||
if duration_in_msecs < 0. {
|
||||
return Err(napi::Error::from_reason("Duration cannot be negative"));
|
||||
}
|
||||
let duration_in_msecs = duration_in_msecs as u64;
|
||||
|
||||
let handler_ref = RefCountedReference::new(&env, handler)?;
|
||||
|
||||
i_slint_core::timers::Timer::single_shot(
|
||||
std::time::Duration::from_millis(duration_in_msecs),
|
||||
move || {
|
||||
let callback: JsFunction = handler_ref.get().unwrap();
|
||||
callback.call_without_args(None).unwrap();
|
||||
},
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||
"outDir": "./dist", /* Redirect output structure to the directory. */
|
||||
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||
/* Strict Type-Checking Options */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||
// "types": [], /* Type declaration files to be included in compilation. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
/* Source Map Options */
|
||||
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||
/* Experimental Options */
|
||||
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
/* Advanced Options */
|
||||
"skipLibCheck": true, /* Skip type checking of declaration files. */
|
||||
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||
},
|
||||
"typedocOptions": {
|
||||
"mode": "file",
|
||||
"out": "docs",
|
||||
"readme": "README.md",
|
||||
"disableSources": true,
|
||||
"theme": "minimal",
|
||||
"hideGenerator": true,
|
||||
"name": "Slint Node"
|
||||
}
|
||||
}
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
|
||||
// main.js
|
||||
let slint = require("slint-ui");
|
||||
let ui = require("./memory.slint");
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("./memory.slint");
|
||||
let mainWindow = new ui.MainWindow();
|
||||
|
||||
let initial_tiles = mainWindow.memory_tiles;
|
||||
|
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
// ANCHOR: main
|
||||
// main.js
|
||||
require("slint-ui");
|
||||
let ui = require("./memory.slint");
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("./memory.slint");
|
||||
let mainWindow = new ui.MainWindow();
|
||||
mainWindow.run();
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@
|
|||
|
||||
// ANCHOR: main
|
||||
// main.js
|
||||
let slint = require("slint-ui");
|
||||
let ui = require("./memory.slint");
|
||||
import * as slint from "slint-ui";
|
||||
let ui = slint.loadFile("./memory.slint");
|
||||
let mainWindow = new ui.MainWindow();
|
||||
|
||||
let initial_tiles = mainWindow.memory_tiles;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "memory",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "^1.0.0"
|
||||
},
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
const path = require("path");
|
||||
let slint = require("slint-ui");
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let demo = require("../ui/carousel_demo.slint");
|
||||
let demo = slint.loadFile("../ui/carousel_demo.slint");
|
||||
let app = new demo.MainWindow();
|
||||
|
||||
app.run();
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "carousel",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "../../../api/node"
|
||||
},
|
||||
|
|
|
@ -2,8 +2,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
let slint = require("slint-ui");
|
||||
let ui = require("./memory.slint");
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("memory.slint");
|
||||
let window = new ui.MainWindow();
|
||||
|
||||
let initial_tiles = window.memory_tiles;
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "memory",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "../../api/node"
|
||||
},
|
||||
|
|
|
@ -2,10 +2,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
const path = require("path");
|
||||
let slint = require("slint-ui");
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let demo = require("../ui/printerdemo.slint");
|
||||
let demo = slint.loadFile("../ui/printerdemo.slint");
|
||||
let window = new demo.MainWindow();
|
||||
|
||||
window.ink_levels = [
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "printerdemo",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "../../../api/node"
|
||||
},
|
||||
|
|
|
@ -2,10 +2,8 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// import "slint";
|
||||
require("slint-ui");
|
||||
// import * as demo from "../ui/printerdemo.slint";
|
||||
let demo = require("../ui/printerdemo.slint");
|
||||
import * as slint from "slint-ui";
|
||||
let demo = slint.loadFile("../ui/printerdemo.slint");
|
||||
let window = new demo.MainWindow();
|
||||
|
||||
window.ink_levels = [
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "printerdemo",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "../../../api/node"
|
||||
},
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
Run with
|
||||
|
||||
# pushd ../../../api/napi
|
||||
# npm install
|
||||
# popd
|
||||
# npm install
|
||||
# npm start
|
|
@ -1,61 +0,0 @@
|
|||
#!/usr/bin/env node
|
||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let demo = slint.loadFile("../ui/todo.slint");
|
||||
let app = new demo.MainWindow();
|
||||
|
||||
let model = new slint.ArrayModel([
|
||||
{
|
||||
title: "Implement the .slint file",
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
title: "Do the Rust part",
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
title: "Make the C++ code",
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
title: "Write some JavaScript code",
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
title: "Test the application",
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
title: "Ship to customer",
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
title: "???",
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
title: "Profit",
|
||||
checked: false
|
||||
},
|
||||
]);
|
||||
app.todo_model = model;
|
||||
|
||||
app.todo_added.setHandler(function (text) {
|
||||
model.push({ title: text, checked: false })
|
||||
})
|
||||
|
||||
app.remove_done.setHandler(function () {
|
||||
let offset = 0;
|
||||
const length = model.length;
|
||||
for (let i = 0; i < length; ++i) {
|
||||
if (model.rowData(i - offset).checked) {
|
||||
model.remove(i - offset, 1);
|
||||
offset++;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
app.run();
|
|
@ -1,12 +0,0 @@
|
|||
{
|
||||
"name": "todo",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "../../../api/napi"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node ."
|
||||
}
|
||||
}
|
|
@ -2,10 +2,9 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// import "slint";
|
||||
let slint = require("slint-ui");
|
||||
// import * as demo from "../ui/todo.slint";
|
||||
let demo = require("../ui/todo.slint");
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let demo = slint.loadFile("../ui/todo.slint");
|
||||
let app = new demo.MainWindow();
|
||||
|
||||
let model = new slint.ArrayModel([
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
"name": "todo",
|
||||
"version": "1.3.0",
|
||||
"main": "main.js",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"slint-ui": "../../../api/node"
|
||||
},
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
# Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
[package]
|
||||
name = "test-driver-napi"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
homepage.workspace = true
|
||||
license.workspace = true
|
||||
repository.workspace = true
|
||||
rust-version.workspace = true
|
||||
version.workspace = true
|
||||
publish = false
|
||||
|
||||
[[bin]]
|
||||
path = "main.rs"
|
||||
name = "test-driver-napi"
|
||||
|
||||
[dev-dependencies]
|
||||
test_driver_lib = { path = "../driverlib" }
|
||||
# Require `artifact` dependencies tracked by rust RFC 3028
|
||||
#slint-napi = { path = "../../../api/napi", artifact = ["cdylib"] }
|
||||
lazy_static = "1.4.0"
|
||||
which = "4.0.2"
|
||||
tempfile = "3.2"
|
||||
|
||||
[build-dependencies]
|
||||
test_driver_lib = { path = "../driverlib" }
|
|
@ -1,48 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// target/{debug|release}/build/package/out/ -> target/{debug|release}
|
||||
let mut target_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
target_dir.pop();
|
||||
target_dir.pop();
|
||||
target_dir.pop();
|
||||
|
||||
println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1",);
|
||||
|
||||
let tests_file_path =
|
||||
std::path::Path::new(&std::env::var_os("OUT_DIR").unwrap()).join("test_functions.rs");
|
||||
|
||||
let mut tests_file = std::fs::File::create(&tests_file_path)?;
|
||||
|
||||
for testcase in test_driver_lib::collect_test_cases("cases")? {
|
||||
println!("cargo:rerun-if-changed={}", testcase.absolute_path.display());
|
||||
let test_function_name = testcase.identifier();
|
||||
let ignored = testcase.is_ignored("js");
|
||||
|
||||
write!(
|
||||
tests_file,
|
||||
r##"
|
||||
#[test]
|
||||
{ignore}
|
||||
fn test_nodejs_{function_name}() {{
|
||||
nodejs::test(&test_driver_lib::TestCase{{
|
||||
absolute_path: std::path::PathBuf::from(r#"{absolute_path}"#),
|
||||
relative_path: std::path::PathBuf::from(r#"{relative_path}"#),
|
||||
}}).unwrap();
|
||||
}}
|
||||
"##,
|
||||
ignore = if ignored { "#[ignore]" } else { "" },
|
||||
function_name = test_function_name,
|
||||
absolute_path = testcase.absolute_path.to_string_lossy(),
|
||||
relative_path = testcase.relative_path.to_string_lossy(),
|
||||
)?;
|
||||
}
|
||||
|
||||
println!("cargo:rustc-env=TEST_FUNCTIONS={}", tests_file_path.to_string_lossy());
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
#[cfg(test)]
|
||||
mod nodejs;
|
||||
|
||||
include!(env!("TEST_FUNCTIONS"));
|
||||
|
||||
fn main() {
|
||||
println!("Nothing to see here, please run me through cargo test :)");
|
||||
}
|
|
@ -1,78 +0,0 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||
|
||||
use std::error::Error;
|
||||
use std::{fs::File, io::Write, path::PathBuf};
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref NODE_API_JS_PATH: PathBuf = {
|
||||
let node_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../api/napi");
|
||||
|
||||
// On Windows npm is 'npm.cmd', which Rust's process::Command doesn't look for as extension, because
|
||||
// it tries to emulate CreateProcess.
|
||||
let npm = which::which("npm").unwrap();
|
||||
|
||||
// builds and installs the slint node package
|
||||
std::process::Command::new(npm.clone())
|
||||
.arg("install")
|
||||
.arg("--no-audit")
|
||||
.current_dir(node_dir.clone())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.output()
|
||||
.map_err(|err| format!("Could not launch npm install: {}", err)).unwrap();
|
||||
|
||||
node_dir.join("index.js")
|
||||
};
|
||||
}
|
||||
|
||||
pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>> {
|
||||
let slintpath = NODE_API_JS_PATH.clone();
|
||||
|
||||
let dir = tempfile::tempdir()?;
|
||||
|
||||
let mut main_js = File::create(dir.path().join("main.js"))?;
|
||||
write!(
|
||||
main_js,
|
||||
r#"
|
||||
const assert = require('assert').strict;
|
||||
let slintlib = require(String.raw`{slintpath}`);
|
||||
let slint = slintlib.loadFile(String.raw`{path}`);
|
||||
"#,
|
||||
slintpath = slintpath.to_string_lossy(),
|
||||
path = testcase.absolute_path.to_string_lossy()
|
||||
)?;
|
||||
let source = std::fs::read_to_string(&testcase.absolute_path)?;
|
||||
let include_paths = test_driver_lib::extract_include_paths(&source);
|
||||
let library_paths = test_driver_lib::extract_library_paths(&source)
|
||||
.map(|(k, v)| {
|
||||
let mut abs_path = testcase.absolute_path.clone();
|
||||
abs_path.pop();
|
||||
abs_path.push(v);
|
||||
format!("{}={}", k, abs_path.to_string_lossy())
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for x in test_driver_lib::extract_test_functions(&source).filter(|x| x.language_id == "js") {
|
||||
write!(main_js, "{{\n {}\n}}\n", x.source.replace("\n", "\n "))?;
|
||||
}
|
||||
|
||||
let output = std::process::Command::new("node")
|
||||
.arg(dir.path().join("main.js"))
|
||||
.current_dir(dir.path())
|
||||
.env("SLINT_INCLUDE_PATH", std::env::join_paths(include_paths).unwrap())
|
||||
.env("SLINT_LIBRARY_PATH", std::env::join_paths(library_paths).unwrap())
|
||||
.env("SLINT_SCALE_FACTOR", "1") // We don't have a testing backend, but we can try to force a SF1 as the tests expect.
|
||||
.env("SLINT_ENABLE_EXPERIMENTAL_FEATURES", "1")
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.output()
|
||||
.map_err(|err| format!("Could not launch npm start: {}", err))?;
|
||||
|
||||
if !output.status.success() {
|
||||
print!("{}", String::from_utf8_lossy(output.stdout.as_ref()));
|
||||
print!("{}", String::from_utf8_lossy(output.stderr.as_ref()));
|
||||
return Err(String::from_utf8_lossy(output.stderr.as_ref()).to_owned().into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -19,7 +19,7 @@ name = "test-driver-nodejs"
|
|||
[dev-dependencies]
|
||||
test_driver_lib = { path = "../driverlib" }
|
||||
# Require `artifact` dependencies tracked by rust RFC 3028
|
||||
#slint-node = { path = "../../../api/node/native", artifact = ["cdylib"] }
|
||||
#slint-node = { path = "../../../api/node", artifact = ["cdylib"] }
|
||||
lazy_static = "1.4.0"
|
||||
which = "4.0.2"
|
||||
tempfile = "3.2"
|
||||
|
|
|
@ -4,16 +4,6 @@
|
|||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn os_dylib_prefix_and_suffix() -> (&'static str, &'static str) {
|
||||
if cfg!(target_os = "windows") {
|
||||
("", "dll")
|
||||
} else if cfg!(target_os = "macos") || cfg!(target_os = "ios") {
|
||||
("lib", "dylib")
|
||||
} else {
|
||||
("lib", "so")
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// target/{debug|release}/build/package/out/ -> target/{debug|release}
|
||||
let mut target_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
|
@ -21,14 +11,6 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
target_dir.pop();
|
||||
target_dir.pop();
|
||||
|
||||
let nodejs_native_lib_name = {
|
||||
let (prefix, suffix) = os_dylib_prefix_and_suffix();
|
||||
format!("{}slint_node_native.{}", prefix, suffix)
|
||||
};
|
||||
println!(
|
||||
"cargo:rustc-env=SLINT_NODE_NATIVE_LIB={}",
|
||||
target_dir.join(nodejs_native_lib_name).display()
|
||||
);
|
||||
println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1",);
|
||||
|
||||
let tests_file_path =
|
||||
|
|
|
@ -12,10 +12,9 @@ lazy_static::lazy_static! {
|
|||
// it tries to emulate CreateProcess.
|
||||
let npm = which::which("npm").unwrap();
|
||||
|
||||
// Ensure TypeScript is installed
|
||||
// builds and installs the slint node package
|
||||
std::process::Command::new(npm.clone())
|
||||
.arg("install")
|
||||
.arg("--ignore-scripts")
|
||||
.arg("--no-audit")
|
||||
.current_dir(node_dir.clone())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
|
@ -23,17 +22,7 @@ lazy_static::lazy_static! {
|
|||
.output()
|
||||
.map_err(|err| format!("Could not launch npm install: {}", err)).unwrap();
|
||||
|
||||
// Build the .js file of the NodeJS API from the .ts file
|
||||
std::process::Command::new(npm)
|
||||
.arg("run")
|
||||
.arg("build")
|
||||
.current_dir(node_dir.clone())
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::piped())
|
||||
.output()
|
||||
.map_err(|err| format!("Could not launch npm run build: {}", err)).unwrap();
|
||||
|
||||
node_dir.join("dist").join("index.js")
|
||||
node_dir.join("index.js")
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,7 +37,7 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
|||
r#"
|
||||
const assert = require('assert').strict;
|
||||
let slintlib = require(String.raw`{slintpath}`);
|
||||
let slint = require(String.raw`{path}`);
|
||||
let slint = slintlib.loadFile(String.raw`{path}`);
|
||||
"#,
|
||||
slintpath = slintpath.to_string_lossy(),
|
||||
path = testcase.absolute_path.to_string_lossy()
|
||||
|
@ -70,7 +59,6 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
|||
let output = std::process::Command::new("node")
|
||||
.arg(dir.path().join("main.js"))
|
||||
.current_dir(dir.path())
|
||||
.env("SLINT_NODE_NATIVE_LIB", std::env::var_os("SLINT_NODE_NATIVE_LIB").unwrap())
|
||||
.env("SLINT_INCLUDE_PATH", std::env::join_paths(include_paths).unwrap())
|
||||
.env("SLINT_LIBRARY_PATH", std::env::join_paths(library_paths).unwrap())
|
||||
.env("SLINT_SCALE_FACTOR", "1") // We don't have a testing backend, but we can try to force a SF1 as the tests expect.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue