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
|
# cargo update -p clap_lex --precise 0.5.0
|
||||||
# fi
|
# fi
|
||||||
- name: Run tests
|
- 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:
|
env:
|
||||||
SLINT_CREATE_SCREENSHOTS: 1
|
SLINT_CREATE_SCREENSHOTS: 1
|
||||||
shell: bash
|
shell: bash
|
||||||
|
@ -87,55 +87,6 @@ jobs:
|
||||||
tests/screenshots/references
|
tests/screenshots/references
|
||||||
|
|
||||||
node_test:
|
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:
|
env:
|
||||||
DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/5.15.2/clang_64/lib
|
DYLD_FRAMEWORK_PATH: /Users/runner/work/slint/Qt/5.15.2/clang_64/lib
|
||||||
QT_QPA_PLATFORM: offscreen
|
QT_QPA_PLATFORM: offscreen
|
||||||
|
@ -176,16 +127,16 @@ jobs:
|
||||||
with:
|
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.
|
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
|
- name: Run npm install
|
||||||
working-directory: ./api/napi
|
working-directory: ./api/node
|
||||||
run: npm install
|
run: npm install
|
||||||
- name: Typescript check
|
- name: Typescript check
|
||||||
working-directory: ./api/napi
|
working-directory: ./api/node
|
||||||
run: npm run syntax_check
|
run: npm run syntax_check
|
||||||
- name: Run napi tests
|
- name: Run node tests
|
||||||
working-directory: ./api/napi
|
working-directory: ./api/node
|
||||||
run: npm test
|
run: npm test
|
||||||
- name: Run test-driver-napi
|
- name: Run test-driver-nodejs
|
||||||
run: cargo test --verbose --all-features -p test-driver-napi -p slint-napi
|
run: cargo test --verbose --all-features -p test-driver-nodejs -p slint-node
|
||||||
|
|
||||||
cpp_test_driver:
|
cpp_test_driver:
|
||||||
env:
|
env:
|
||||||
|
|
|
@ -72,7 +72,7 @@ Files: tests/screenshots/references/software/*/*.png
|
||||||
Copyright: Copyright © SixtyFPS GmbH <info@slint.dev>
|
Copyright: Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
License: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
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>
|
Copyright: Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
License: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
License: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,7 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
'api/cpp',
|
'api/cpp',
|
||||||
'api/node/native',
|
'api/node',
|
||||||
'api/napi',
|
|
||||||
'api/rs/build',
|
'api/rs/build',
|
||||||
'api/rs/macros',
|
'api/rs/macros',
|
||||||
'api/rs/slint',
|
'api/rs/slint',
|
||||||
|
@ -50,7 +49,6 @@ members = [
|
||||||
'tests/driver/driverlib',
|
'tests/driver/driverlib',
|
||||||
'tests/driver/interpreter',
|
'tests/driver/interpreter',
|
||||||
'tests/driver/nodejs',
|
'tests/driver/nodejs',
|
||||||
'tests/driver/napi',
|
|
||||||
'tests/driver/rust',
|
'tests/driver/rust',
|
||||||
'tests/screenshots',
|
'tests/screenshots',
|
||||||
'tools/compiler',
|
'tools/compiler',
|
||||||
|
@ -65,7 +63,6 @@ members = [
|
||||||
default-members = [
|
default-members = [
|
||||||
'api/rs/build',
|
'api/rs/build',
|
||||||
'api/rs/slint',
|
'api/rs/slint',
|
||||||
'api/napi',
|
|
||||||
'examples/gallery',
|
'examples/gallery',
|
||||||
'examples/memory',
|
'examples/memory',
|
||||||
'examples/printerdemo_old/rust',
|
'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
|
rust-module.js
|
||||||
package-lock.json
|
rust-module.d.ts
|
||||||
dist
|
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
|
# SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
||||||
|
|
||||||
[package]
|
[package]
|
||||||
name = "slint-napi"
|
name = "slint-node"
|
||||||
description = "Internal Slint Runtime Library for NodeJS API."
|
description = "Internal Slint Runtime Library for NodeJS API."
|
||||||
authors.workspace = true
|
authors.workspace = true
|
||||||
documentation.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 -->
|
<!-- 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)
|
# Slint-node (Beta)
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/slint-ui)
|
[](https://www.npmjs.com/package/slint-ui)
|
||||||
|
|
||||||
[Slint](https://slint.dev/) is a UI toolkit that supports different programming languages.
|
[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 use the [walk-through tutorial](https://slint.dev/docs/tutorial/node).
|
||||||
To get started you can 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
|
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.
|
the code of a minimal application using Slint that can be used as a starting point to your program.
|
||||||
|
|
||||||
**Warning: Beta**
|
**Warning: Beta**
|
||||||
Slint-node is still in the early stages of development: APIs will change and important features are still being developed.
|
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
|
## Installing Slint
|
||||||
|
|
||||||
Slint is available via NPM, so you can install by running the following command:
|
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:
|
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/)**
|
* **[npm](https://www.npmjs.com/)**
|
||||||
* **[Rust compiler](https://www.rust-lang.org/tools/install)** (1.70 or newer)
|
* **[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
|
## 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:
|
To initialize the API, you first need to import the `slint-ui` module in our code:
|
||||||
|
|
||||||
```js
|
```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
|
```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:
|
Combining these two steps leads us to the obligatory "Hello World" example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
let ui = require("../ui/main.slint");
|
let ui = slint.loadFile(".ui/main.slint");
|
||||||
let main = new ui.Main();
|
let main = new ui.Main();
|
||||||
main.run();
|
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
|
## 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
|
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.
|
an object which allow to initialize the value of public properties or callbacks.
|
||||||
|
|
||||||
|
**`main.js`**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
// In this example, the main.slint file exports a module which
|
// In this example, the main.slint file exports a module which
|
||||||
// has a counter property and a clicked callback
|
// 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({
|
let component = new ui.MainWindow({
|
||||||
counter: 42,
|
counter: 42,
|
||||||
clicked: function() { console.log("hello"); }
|
clicked: function() { console.log("hello"); }
|
||||||
|
@ -77,7 +103,7 @@ let component = new ui.MainWindow({
|
||||||
|
|
||||||
### Accessing a property
|
### 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
|
```js
|
||||||
component.counter = 42;
|
component.counter = 42;
|
||||||
|
@ -86,9 +112,32 @@ console.log(component.counter);
|
||||||
|
|
||||||
### Callbacks
|
### 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
|
```js
|
||||||
|
import * as slint from "slint-ui";
|
||||||
|
|
||||||
|
let ui = slint.loadFile("ui/my-component.slint");
|
||||||
|
let component = new ui.MyComponent();
|
||||||
|
|
||||||
// connect to a callback
|
// connect to a callback
|
||||||
component.clicked.setHandler(function() { console.log("hello"); })
|
component.clicked.setHandler(function() { console.log("hello"); })
|
||||||
// emit a callback
|
// emit a callback
|
||||||
|
@ -97,20 +146,24 @@ component.clicked();
|
||||||
|
|
||||||
### Type Mappings
|
### 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 |
|
| `.slint` Type | JavaScript Type | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `int` | `Number` | |
|
| `int` | `Number` | |
|
||||||
| `float` | `Number` | |
|
| `float` | `Number` | |
|
||||||
| `string` | `String` | |
|
| `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` | |
|
| `length` | `Number` | |
|
||||||
| `physical_length` | `Number` | |
|
| `physical_length` | `Number` | |
|
||||||
| `duration` | `Number` | The number of milliseconds |
|
| `duration` | `Number` | The number of milliseconds |
|
||||||
| `angle` | `Number` | The value in degrees |
|
| `angle` | `Number` | The angle in degrees |
|
||||||
| structure | `Object` | Structures are mapped to JavaScrip objects with structure fields mapped to properties. |
|
| structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. |
|
||||||
| array | `Array` or Model Object | |
|
| array | `Array` or any implementation of Model | |
|
||||||
|
|
||||||
### Models
|
### Arrays and Models
|
||||||
|
|
||||||
For property of array type, they can either be set using an array.
|
For property of array type, they can either be set using an array.
|
||||||
In that case, getting the property also return 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
|
* `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.
|
* `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`)
|
As an example, here is the implementation of the `ArrayModel` (which is available as `slint.ArrayModel`)
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
import * as slint from "slint-ui";
|
||||||
|
|
||||||
let array = [1, 2, 3];
|
let array = [1, 2, 3];
|
||||||
let model = {
|
|
||||||
rowCount() { return a.length; },
|
export class ArrayModel<T> extends slint.Model<T> {
|
||||||
rowData(row) { return a[row]; },
|
private a: Array<T>
|
||||||
setRowData(row, data) { a[row] = data; this.notify.rowDataChanged(row); },
|
|
||||||
push() {
|
constructor(arr: Array<T>) {
|
||||||
let size = a.length;
|
super();
|
||||||
Array.prototype.push.apply(a, arguments);
|
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);
|
this.notify.rowAdded(size, arguments.length);
|
||||||
},
|
}
|
||||||
remove(index, size) {
|
|
||||||
let r = a.splice(index, size);
|
remove(index: number, size: number) {
|
||||||
this.notify.rowRemoved(size, arguments.length);
|
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;
|
component.model = model;
|
||||||
model.push(4); // this works
|
model.push(4); // this works
|
||||||
// does NOT work, getting the model does not return the right object
|
// 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 test from 'ava';
|
||||||
|
|
||||||
import { Brush, Color, ArrayModel } from '../index'
|
import { Brush, Color, ArrayModel, Timer } from '../index'
|
||||||
|
|
||||||
test('Color from fromRgb', (t) => {
|
test('Color from fromRgb', (t) => {
|
||||||
let color = Color.fromRgb(100, 110, 120);
|
let color = Color.fromRgb(100, 110, 120);
|
||||||
|
@ -87,3 +87,15 @@ test('ArrayModel remove', (t) => {
|
||||||
t.is(arrayModel.rowCount(), 1);
|
t.is(arrayModel.rowCount(), 1);
|
||||||
t.is(arrayModel.rowData(0), 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)
|
[](https://www.npmjs.com/package/slint-ui)
|
||||||
|
|
||||||
[Slint](https://slint.dev/) is a UI toolkit that supports different programming languages.
|
[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
|
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.
|
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
|
## 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.
|
in detail.
|
||||||
|
|
||||||
## Installing Slint
|
## Installing Slint
|
||||||
|
@ -30,7 +30,7 @@ npm install slint-ui
|
||||||
|
|
||||||
You need to install the following components:
|
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/)**
|
* **[npm](https://www.npmjs.com/)**
|
||||||
* **[Rust compiler](https://www.rust-lang.org/tools/install)** (1.70 or newer)
|
* **[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
|
## 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:
|
To initialize the API, you first need to import the `slint-ui` module in our code:
|
||||||
|
|
||||||
```js
|
```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
|
```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:
|
Combining these two steps leads us to the obligatory "Hello World" example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
let ui = require("../ui/main.slint");
|
let ui = slint.loadFile(".ui/main.slint");
|
||||||
let main = new ui.Main();
|
let main = new ui.Main();
|
||||||
main.run();
|
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
|
## 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
|
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.
|
an object which allow to initialize the value of public properties or callbacks.
|
||||||
|
|
||||||
|
**`main.js`**
|
||||||
|
|
||||||
```js
|
```js
|
||||||
require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
// In this example, the main.slint file exports a module which
|
// In this example, the main.slint file exports a module which
|
||||||
// has a counter property and a clicked callback
|
// 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({
|
let component = new ui.MainWindow({
|
||||||
counter: 42,
|
counter: 42,
|
||||||
clicked: function() { console.log("hello"); }
|
clicked: function() { console.log("hello"); }
|
||||||
|
@ -81,7 +102,7 @@ let component = new ui.MainWindow({
|
||||||
|
|
||||||
### Accessing a property
|
### 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
|
```js
|
||||||
component.counter = 42;
|
component.counter = 42;
|
||||||
|
@ -90,9 +111,32 @@ console.log(component.counter);
|
||||||
|
|
||||||
### Callbacks
|
### 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
|
```js
|
||||||
|
import * as slint from "slint-ui";
|
||||||
|
|
||||||
|
let ui = slint.loadFile("ui/my-component.slint");
|
||||||
|
let component = new ui.MyComponent();
|
||||||
|
|
||||||
// connect to a callback
|
// connect to a callback
|
||||||
component.clicked.setHandler(function() { console.log("hello"); })
|
component.clicked.setHandler(function() { console.log("hello"); })
|
||||||
// emit a callback
|
// emit a callback
|
||||||
|
@ -101,65 +145,36 @@ component.clicked();
|
||||||
|
|
||||||
### Type Mappings
|
### 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 |
|
| `.slint` Type | JavaScript Type | Notes |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| `int` | `Number` | |
|
| `int` | `Number` | |
|
||||||
| `float` | `Number` | |
|
| `float` | `Number` | |
|
||||||
| `string` | `String` | |
|
| `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` | |
|
| `length` | `Number` | |
|
||||||
| `physical_length` | `Number` | |
|
| `physical_length` | `Number` | |
|
||||||
| `duration` | `Number` | The number of milliseconds |
|
| `duration` | `Number` | The number of milliseconds |
|
||||||
| `angle` | `Number` | The value in degrees |
|
| `angle` | `Number` | The angle in degrees |
|
||||||
| structure | `Object` | Structures are mapped to JavaScrip objects with structure fields mapped to properties. |
|
| structure | `Object` | Structures are mapped to JavaScript objects where each structure field is a property. |
|
||||||
| array | `Array` or Model Object | |
|
| 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.
|
[Array properties](../slint/src/reference/types#arrays-and-models) can be set from JavaScript by passing
|
||||||
In that case, getting the property also return an array.
|
either `Array` objects or implementations of the {@link Model} interface.
|
||||||
If the array was set within the .slint file, the array can be obtained
|
|
||||||
|
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
|
```js
|
||||||
component.model = [1, 2, 3];
|
component.model = [1, 2, 3];
|
||||||
// component.model.push(4); // does not work, because it operate on a copy
|
// component.model.push(4); // does not work, because assignment creates a copy.
|
||||||
// but re-assigning works
|
// Use re-assignment instead.
|
||||||
component.model = component.model.concat(4);
|
component.model = component.model.concat(4);
|
||||||
```
|
```
|
||||||
|
|
||||||
Another option is to set a model object. A model object has the following function:
|
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.
|
||||||
|
|
||||||
* `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);
|
|
||||||
```
|
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
// 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";
|
import * as napi from "./rust-module";
|
||||||
export { Diagnostic, DiagnosticLevel, Window, Brush, Color, ImageData, Point, Size, SlintModelNotify } 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.
|
* 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 compiler = new napi.ComponentCompiler;
|
||||||
let definition = compiler.buildFromPath(path);
|
let definition = compiler.buildFromPath(absoluteFilePath);
|
||||||
|
|
||||||
let diagnostics = compiler.diagnostics;
|
let diagnostics = compiler.diagnostics;
|
||||||
|
|
||||||
|
@ -305,7 +310,7 @@ export function loadFile(path: string) : Object {
|
||||||
let errors = diagnostics.filter((d) => d.level == napi.DiagnosticLevel.Error);
|
let errors = diagnostics.filter((d) => d.level == napi.DiagnosticLevel.Error);
|
||||||
|
|
||||||
if (errors.length > 0) {
|
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();
|
let instance = definition!.create();
|
||||||
|
|
||||||
if (instance == null) {
|
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) {
|
for(var key in properties) {
|
||||||
|
@ -356,6 +361,14 @@ export function loadFile(path: string) : Object {
|
||||||
return slint_module;
|
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
|
* @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",
|
"name": "slint-ui",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
|
"main": "index.js",
|
||||||
|
"types": "index.d.ts",
|
||||||
"homepage": "https://github.com/slint-ui/slint",
|
"homepage": "https://github.com/slint-ui/slint",
|
||||||
"license": "SEE LICENSE IN LICENSE.md",
|
"license": "SEE LICENSE IN LICENSE.md",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/slint-ui/slint"
|
"url": "https://github.com/slint-ui/slint"
|
||||||
},
|
},
|
||||||
"main": "dist/index.js",
|
"devDependencies": {
|
||||||
"types": "dist/index.d.ts",
|
"@napi-rs/cli": "^2.15.2",
|
||||||
"dependencies": {
|
"@swc-node/register": "^1.5.5",
|
||||||
"@types/node": "^14.11.11",
|
"@swc/core": "^1.3.32",
|
||||||
"neon-cli": "^0.4",
|
"@types/node": "^20.8.6",
|
||||||
"typescript": "^4.0.3"
|
"ava": "^5.3.0",
|
||||||
|
"esbuild": "^0.14.54",
|
||||||
|
"jimp": "^0.22.8",
|
||||||
|
"typedoc": "^0.25.2"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 10"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "neon build --release && tsc",
|
"artifacts": "napi artifacts",
|
||||||
"build": "tsc",
|
"compile": "esbuild index.ts --bundle --external:*.node --format=cjs --platform=node --outfile=index.js",
|
||||||
"docs": "typedoc --hideGenerator --readme cover.md lib/index.ts"
|
"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": {
|
"ava": {
|
||||||
"typedoc": "^0.19.2"
|
"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>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-1.1 OR LicenseRef-Slint-commercial
|
// 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 std::path::PathBuf;
|
||||||
|
|
||||||
use super::JsComponentDefinition;
|
use super::JsComponentDefinition;
|
||||||
|
@ -60,6 +61,16 @@ impl JsComponentCompiler {
|
||||||
.collect()
|
.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)]
|
#[napi(setter)]
|
||||||
pub fn set_style(&mut self, style: String) {
|
pub fn set_style(&mut self, style: String) {
|
||||||
self.internal.set_style(style);
|
self.internal.set_style(style);
|
|
@ -7,6 +7,9 @@ pub use interpreter::*;
|
||||||
mod types;
|
mod types;
|
||||||
pub use types::*;
|
pub use types::*;
|
||||||
|
|
||||||
|
mod timer;
|
||||||
|
pub use timer::*;
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate napi_derive;
|
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
|
// main.js
|
||||||
let slint = require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
let ui = require("./memory.slint");
|
|
||||||
|
let ui = slint.loadFile("./memory.slint");
|
||||||
let mainWindow = new ui.MainWindow();
|
let mainWindow = new ui.MainWindow();
|
||||||
|
|
||||||
let initial_tiles = mainWindow.memory_tiles;
|
let initial_tiles = mainWindow.memory_tiles;
|
||||||
|
|
|
@ -3,8 +3,9 @@
|
||||||
|
|
||||||
// ANCHOR: main
|
// ANCHOR: main
|
||||||
// main.js
|
// main.js
|
||||||
require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
let ui = require("./memory.slint");
|
|
||||||
|
let ui = slint.loadFile("./memory.slint");
|
||||||
let mainWindow = new ui.MainWindow();
|
let mainWindow = new ui.MainWindow();
|
||||||
mainWindow.run();
|
mainWindow.run();
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
// ANCHOR: main
|
// ANCHOR: main
|
||||||
// main.js
|
// main.js
|
||||||
let slint = require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
let ui = require("./memory.slint");
|
let ui = slint.loadFile("./memory.slint");
|
||||||
let mainWindow = new ui.MainWindow();
|
let mainWindow = new ui.MainWindow();
|
||||||
|
|
||||||
let initial_tiles = mainWindow.memory_tiles;
|
let initial_tiles = mainWindow.memory_tiles;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "memory",
|
"name": "memory",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slint-ui": "^1.0.0"
|
"slint-ui": "^1.0.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
const path = require("path");
|
import * as slint from "slint-ui";
|
||||||
let slint = require("slint-ui");
|
|
||||||
|
|
||||||
let demo = require("../ui/carousel_demo.slint");
|
let demo = slint.loadFile("../ui/carousel_demo.slint");
|
||||||
let app = new demo.MainWindow();
|
let app = new demo.MainWindow();
|
||||||
|
|
||||||
app.run();
|
app.run();
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "carousel",
|
"name": "carousel",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slint-ui": "../../../api/node"
|
"slint-ui": "../../../api/node"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,8 +2,9 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
let slint = require("slint-ui");
|
import * as slint from "slint-ui";
|
||||||
let ui = require("./memory.slint");
|
|
||||||
|
let ui = slint.loadFile("memory.slint");
|
||||||
let window = new ui.MainWindow();
|
let window = new ui.MainWindow();
|
||||||
|
|
||||||
let initial_tiles = window.memory_tiles;
|
let initial_tiles = window.memory_tiles;
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "memory",
|
"name": "memory",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slint-ui": "../../api/node"
|
"slint-ui": "../../api/node"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,10 +2,9 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
const path = require("path");
|
import * as slint from "slint-ui";
|
||||||
let slint = require("slint-ui");
|
|
||||||
|
|
||||||
let demo = require("../ui/printerdemo.slint");
|
let demo = slint.loadFile("../ui/printerdemo.slint");
|
||||||
let window = new demo.MainWindow();
|
let window = new demo.MainWindow();
|
||||||
|
|
||||||
window.ink_levels = [
|
window.ink_levels = [
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "printerdemo",
|
"name": "printerdemo",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slint-ui": "../../../api/node"
|
"slint-ui": "../../../api/node"
|
||||||
},
|
},
|
||||||
|
|
|
@ -2,10 +2,8 @@
|
||||||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// import "slint";
|
import * as slint from "slint-ui";
|
||||||
require("slint-ui");
|
let demo = slint.loadFile("../ui/printerdemo.slint");
|
||||||
// import * as demo from "../ui/printerdemo.slint";
|
|
||||||
let demo = require("../ui/printerdemo.slint");
|
|
||||||
let window = new demo.MainWindow();
|
let window = new demo.MainWindow();
|
||||||
|
|
||||||
window.ink_levels = [
|
window.ink_levels = [
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "printerdemo",
|
"name": "printerdemo",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slint-ui": "../../../api/node"
|
"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>
|
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||||
// SPDX-License-Identifier: MIT
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
// import "slint";
|
import * as slint from "slint-ui";
|
||||||
let slint = require("slint-ui");
|
|
||||||
// import * as demo from "../ui/todo.slint";
|
let demo = slint.loadFile("../ui/todo.slint");
|
||||||
let demo = require("../ui/todo.slint");
|
|
||||||
let app = new demo.MainWindow();
|
let app = new demo.MainWindow();
|
||||||
|
|
||||||
let model = new slint.ArrayModel([
|
let model = new slint.ArrayModel([
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "todo",
|
"name": "todo",
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"slint-ui": "../../../api/node"
|
"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]
|
[dev-dependencies]
|
||||||
test_driver_lib = { path = "../driverlib" }
|
test_driver_lib = { path = "../driverlib" }
|
||||||
# Require `artifact` dependencies tracked by rust RFC 3028
|
# 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"
|
lazy_static = "1.4.0"
|
||||||
which = "4.0.2"
|
which = "4.0.2"
|
||||||
tempfile = "3.2"
|
tempfile = "3.2"
|
||||||
|
|
|
@ -4,16 +4,6 @@
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
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>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
// target/{debug|release}/build/package/out/ -> target/{debug|release}
|
// target/{debug|release}/build/package/out/ -> target/{debug|release}
|
||||||
let mut target_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
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();
|
||||||
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",);
|
println!("cargo:rustc-env=SLINT_ENABLE_EXPERIMENTAL_FEATURES=1",);
|
||||||
|
|
||||||
let tests_file_path =
|
let tests_file_path =
|
||||||
|
|
|
@ -12,10 +12,9 @@ lazy_static::lazy_static! {
|
||||||
// it tries to emulate CreateProcess.
|
// it tries to emulate CreateProcess.
|
||||||
let npm = which::which("npm").unwrap();
|
let npm = which::which("npm").unwrap();
|
||||||
|
|
||||||
// Ensure TypeScript is installed
|
// builds and installs the slint node package
|
||||||
std::process::Command::new(npm.clone())
|
std::process::Command::new(npm.clone())
|
||||||
.arg("install")
|
.arg("install")
|
||||||
.arg("--ignore-scripts")
|
|
||||||
.arg("--no-audit")
|
.arg("--no-audit")
|
||||||
.current_dir(node_dir.clone())
|
.current_dir(node_dir.clone())
|
||||||
.stdout(std::process::Stdio::piped())
|
.stdout(std::process::Stdio::piped())
|
||||||
|
@ -23,17 +22,7 @@ lazy_static::lazy_static! {
|
||||||
.output()
|
.output()
|
||||||
.map_err(|err| format!("Could not launch npm install: {}", err)).unwrap();
|
.map_err(|err| format!("Could not launch npm install: {}", err)).unwrap();
|
||||||
|
|
||||||
// Build the .js file of the NodeJS API from the .ts file
|
node_dir.join("index.js")
|
||||||
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")
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,7 +37,7 @@ pub fn test(testcase: &test_driver_lib::TestCase) -> Result<(), Box<dyn Error>>
|
||||||
r#"
|
r#"
|
||||||
const assert = require('assert').strict;
|
const assert = require('assert').strict;
|
||||||
let slintlib = require(String.raw`{slintpath}`);
|
let slintlib = require(String.raw`{slintpath}`);
|
||||||
let slint = require(String.raw`{path}`);
|
let slint = slintlib.loadFile(String.raw`{path}`);
|
||||||
"#,
|
"#,
|
||||||
slintpath = slintpath.to_string_lossy(),
|
slintpath = slintpath.to_string_lossy(),
|
||||||
path = testcase.absolute_path.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")
|
let output = std::process::Command::new("node")
|
||||||
.arg(dir.path().join("main.js"))
|
.arg(dir.path().join("main.js"))
|
||||||
.current_dir(dir.path())
|
.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_INCLUDE_PATH", std::env::join_paths(include_paths).unwrap())
|
||||||
.env("SLINT_LIBRARY_PATH", std::env::join_paths(library_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_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