mirror of
https://github.com/slint-ui/slint.git
synced 2025-09-28 21:04:47 +00:00
node: better ergonomics for structs and enums (#6500)
This commit is contained in:
parent
9138105d7f
commit
499a522f99
6 changed files with 470 additions and 221 deletions
|
@ -273,3 +273,72 @@ model.push(4); // this works
|
|||
// does NOT work, getting the model does not return the right object
|
||||
// component.model.push(5);
|
||||
```
|
||||
|
||||
### structs
|
||||
|
||||
An exported struct can be created either by defing of an object literal or by using the new keyword.
|
||||
|
||||
**`my-component.slint`**
|
||||
|
||||
```slint
|
||||
export struct Person {
|
||||
name: string,
|
||||
age: int
|
||||
}
|
||||
|
||||
export component MyComponent inherits Window {
|
||||
in-out property <Person> person;
|
||||
}
|
||||
```
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("my-component.slint");
|
||||
let component = new ui.MyComponent();
|
||||
|
||||
// object literal
|
||||
component.person = { name: "Peter", age: 22 };
|
||||
|
||||
// new keyword (sets property values to default e.g. '' for string)
|
||||
component.person = new ui.Person();
|
||||
|
||||
// new keyword with parameters
|
||||
component.person = new ui.Person({ name: "Tim", age: 30 });
|
||||
```
|
||||
|
||||
### enums
|
||||
|
||||
A value of an exported enum can be set as string or by usign the value from the exported enum.
|
||||
|
||||
**`my-component.slint`**
|
||||
|
||||
```slint
|
||||
export enum Position {
|
||||
top,
|
||||
bottom
|
||||
}
|
||||
|
||||
export component MyComponent inherits Window {
|
||||
in-out property <Position> position;
|
||||
}
|
||||
```
|
||||
|
||||
**`main.js`**
|
||||
|
||||
```js
|
||||
|
||||
import * as slint from "slint-ui";
|
||||
|
||||
let ui = slint.loadFile("my-component.slint");
|
||||
let component = new ui.MyComponent();
|
||||
|
||||
// set enum value as string
|
||||
component.position = "top";
|
||||
|
||||
// use the value of the enum
|
||||
component.position = ui.Position.bottom;
|
||||
```
|
||||
|
|
|
@ -204,3 +204,67 @@ test("loadSource component instances and modules are sealed", (t) => {
|
|||
{ instanceOf: TypeError },
|
||||
);
|
||||
});
|
||||
|
||||
test("loadFile struct", (t) => {
|
||||
const demo = loadFile(
|
||||
path.join(dirname, "resources/test-struct.slint"),
|
||||
) as any;
|
||||
|
||||
const test = new demo.Test({
|
||||
check: new demo.TestStruct(),
|
||||
});
|
||||
|
||||
t.deepEqual(test.check, { text: "", flag: false, value: 0 });
|
||||
});
|
||||
|
||||
test("loadFile struct constructor parameters", (t) => {
|
||||
const demo = loadFile(
|
||||
path.join(dirname, "resources/test-struct.slint"),
|
||||
) as any;
|
||||
|
||||
const test = new demo.Test({
|
||||
check: new demo.TestStruct({ text: "text", flag: true, value: 12 }),
|
||||
});
|
||||
|
||||
t.deepEqual(test.check, { text: "text", flag: true, value: 12 });
|
||||
|
||||
test.check = new demo.TestStruct({
|
||||
text: "hello world",
|
||||
flag: false,
|
||||
value: 8,
|
||||
});
|
||||
t.deepEqual(test.check, { text: "hello world", flag: false, value: 8 });
|
||||
});
|
||||
|
||||
test("loadFile struct constructor more parameters", (t) => {
|
||||
const demo = loadFile(
|
||||
path.join(dirname, "resources/test-struct.slint"),
|
||||
) as any;
|
||||
|
||||
const test = new demo.Test({
|
||||
check: new demo.TestStruct({
|
||||
text: "text",
|
||||
flag: true,
|
||||
value: 12,
|
||||
noProp: "hello",
|
||||
}),
|
||||
});
|
||||
|
||||
t.deepEqual(test.check, { text: "text", flag: true, value: 12 });
|
||||
});
|
||||
|
||||
test("loadFile enum", (t) => {
|
||||
const demo = loadFile(
|
||||
path.join(dirname, "resources/test-enum.slint"),
|
||||
) as any;
|
||||
|
||||
const test = new demo.Test({
|
||||
check: demo.TestEnum.b,
|
||||
});
|
||||
|
||||
t.deepEqual(test.check, "b");
|
||||
|
||||
test.check = demo.TestEnum.c;
|
||||
|
||||
t.deepEqual(test.check, "c");
|
||||
});
|
||||
|
|
12
api/node/__test__/resources/test-enum.slint
Normal file
12
api/node/__test__/resources/test-enum.slint
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
export enum TestEnum {
|
||||
a,
|
||||
b,
|
||||
c
|
||||
}
|
||||
|
||||
export component Test {
|
||||
in-out property <TestEnum> check;
|
||||
}
|
12
api/node/__test__/resources/test-struct.slint
Normal file
12
api/node/__test__/resources/test-struct.slint
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
export struct TestStruct {
|
||||
text: string,
|
||||
flag: bool,
|
||||
value: float
|
||||
}
|
||||
|
||||
export component Test {
|
||||
in-out property <TestStruct> check;
|
||||
}
|
|
@ -1,10 +1,16 @@
|
|||
// Copyright © SixtyFPS GmbH <info@slint.dev>
|
||||
// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
|
||||
|
||||
use crate::to_js_unknown;
|
||||
|
||||
use super::JsComponentDefinition;
|
||||
use super::JsDiagnostic;
|
||||
use i_slint_compiler::langtype::Type;
|
||||
use itertools::Itertools;
|
||||
use napi::Env;
|
||||
use napi::JsUnknown;
|
||||
use slint_interpreter::Compiler;
|
||||
use slint_interpreter::Value;
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
|
||||
|
@ -13,6 +19,7 @@ use std::path::PathBuf;
|
|||
#[napi(js_name = "ComponentCompiler")]
|
||||
pub struct JsComponentCompiler {
|
||||
internal: Compiler,
|
||||
structs_and_enums: Vec<Type>,
|
||||
diagnostics: Vec<slint_interpreter::Diagnostic>,
|
||||
}
|
||||
|
||||
|
@ -44,7 +51,7 @@ impl JsComponentCompiler {
|
|||
|
||||
compiler.set_include_paths(include_paths);
|
||||
compiler.set_library_paths(library_paths);
|
||||
Self { internal: compiler, diagnostics: vec![] }
|
||||
Self { internal: compiler, diagnostics: vec![], structs_and_enums: vec![] }
|
||||
}
|
||||
|
||||
#[napi(setter)]
|
||||
|
@ -99,12 +106,72 @@ impl JsComponentCompiler {
|
|||
self.diagnostics.iter().map(|d| JsDiagnostic::from(d.clone())).collect()
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn structs(&self, env: Env) -> HashMap<String, JsUnknown> {
|
||||
fn convert_type(env: &Env, ty: &Type) -> Option<(String, JsUnknown)> {
|
||||
match ty {
|
||||
Type::Struct { fields, name: Some(name), node: Some(_), .. } => {
|
||||
let struct_instance = to_js_unknown(
|
||||
env,
|
||||
&Value::Struct(slint_interpreter::Struct::from_iter(fields.iter().map(
|
||||
|(name, field_type)| {
|
||||
(
|
||||
name.to_string(),
|
||||
slint_interpreter::default_value_for_type(field_type),
|
||||
)
|
||||
},
|
||||
))),
|
||||
);
|
||||
|
||||
return Some((name.to_string(), struct_instance.ok()?));
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
self.structs_and_enums
|
||||
.iter()
|
||||
.filter_map(|ty| convert_type(&env, ty))
|
||||
.into_iter()
|
||||
.collect::<HashMap<String, JsUnknown>>()
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
pub fn enums(&self, env: Env) -> HashMap<String, JsUnknown> {
|
||||
fn convert_type(env: &Env, ty: &Type) -> Option<(String, JsUnknown)> {
|
||||
match ty {
|
||||
Type::Enumeration(en) => {
|
||||
let mut o = env.create_object().ok()?;
|
||||
|
||||
for value in en.values.iter() {
|
||||
let value = value.replace('-', "_");
|
||||
o.set_property(
|
||||
env.create_string(&value).ok()?,
|
||||
env.create_string(&value).ok()?.into_unknown(),
|
||||
)
|
||||
.ok()?;
|
||||
}
|
||||
return Some((en.name.clone(), o.into_unknown()));
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
self.structs_and_enums
|
||||
.iter()
|
||||
.filter_map(|ty| convert_type(&env, ty))
|
||||
.into_iter()
|
||||
.collect::<HashMap<String, JsUnknown>>()
|
||||
}
|
||||
|
||||
/// Compile a .slint file into a ComponentDefinition
|
||||
///
|
||||
/// Returns the compiled `ComponentDefinition` if there were no errors.
|
||||
#[napi]
|
||||
pub fn build_from_path(&mut self, path: String) -> HashMap<String, JsComponentDefinition> {
|
||||
let r = spin_on::spin_on(self.internal.build_from_path(PathBuf::from(path)));
|
||||
self.structs_and_enums =
|
||||
r.structs_and_enums(i_slint_core::InternalToken {}).cloned().collect::<Vec<_>>();
|
||||
self.diagnostics = r.diagnostics().collect();
|
||||
r.components().map(|c| (c.name().to_owned(), c.into())).collect()
|
||||
}
|
||||
|
@ -118,6 +185,8 @@ impl JsComponentCompiler {
|
|||
) -> HashMap<String, JsComponentDefinition> {
|
||||
let r = spin_on::spin_on(self.internal.build_from_source(source_code, PathBuf::from(path)));
|
||||
self.diagnostics = r.diagnostics().collect();
|
||||
self.structs_and_enums =
|
||||
r.structs_and_enums(i_slint_core::InternalToken {}).cloned().collect::<Vec<_>>();
|
||||
r.components().map(|c| (c.name().to_owned(), c.into())).collect()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -266,6 +266,10 @@ type LoadData =
|
|||
from: "source";
|
||||
};
|
||||
|
||||
function translateName(key: string): string {
|
||||
return key.replace(/-/g, "_");
|
||||
}
|
||||
|
||||
function loadSlint(loadData: LoadData): Object {
|
||||
const { filePath, options } = loadData.fileData;
|
||||
|
||||
|
@ -309,20 +313,55 @@ function loadSlint(loadData: LoadData): Object {
|
|||
|
||||
const slint_module = Object.create({});
|
||||
|
||||
// generate structs
|
||||
const structs = compiler.structs;
|
||||
|
||||
for (const key in compiler.structs) {
|
||||
Object.defineProperty(slint_module, translateName(key), {
|
||||
value: function (properties: any) {
|
||||
const defaultObject = structs[key];
|
||||
const newObject = Object.create({});
|
||||
|
||||
for (const propertyKey in defaultObject) {
|
||||
const propertyName = translateName(propertyKey);
|
||||
const propertyValue =
|
||||
properties !== undefined &&
|
||||
Object.hasOwn(properties, propertyName)
|
||||
? properties[propertyName]
|
||||
: defaultObject[propertyKey];
|
||||
|
||||
Object.defineProperty(newObject, propertyName, {
|
||||
value: propertyValue,
|
||||
writable: true,
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
|
||||
return Object.seal(newObject);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// generate enums
|
||||
const enums = compiler.enums;
|
||||
|
||||
for (const key in enums) {
|
||||
Object.defineProperty(slint_module, translateName(key), {
|
||||
value: Object.seal(enums[key]),
|
||||
enumerable: true,
|
||||
});
|
||||
}
|
||||
|
||||
Object.keys(definitions).forEach((key) => {
|
||||
const definition = definitions[key];
|
||||
|
||||
Object.defineProperty(
|
||||
slint_module,
|
||||
definition.name.replace(/-/g, "_"),
|
||||
{
|
||||
Object.defineProperty(slint_module, translateName(definition.name), {
|
||||
value: function (properties: any) {
|
||||
const instance = definition.create();
|
||||
|
||||
if (instance == null) {
|
||||
throw Error(
|
||||
"Could not create a component handle for" +
|
||||
filePath,
|
||||
"Could not create a component handle for" + filePath,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -338,12 +377,10 @@ function loadSlint(loadData: LoadData): Object {
|
|||
|
||||
const componentHandle = new Component(instance!);
|
||||
instance!.definition().properties.forEach((prop) => {
|
||||
const propName = prop.name.replace(/-/g, "_");
|
||||
const propName = translateName(prop.name);
|
||||
|
||||
if (componentHandle[propName] !== undefined) {
|
||||
console.warn(
|
||||
"Duplicated property name " + propName,
|
||||
);
|
||||
console.warn("Duplicated property name " + propName);
|
||||
} else {
|
||||
Object.defineProperty(componentHandle, propName, {
|
||||
get() {
|
||||
|
@ -358,7 +395,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
});
|
||||
|
||||
instance!.definition().callbacks.forEach((cb) => {
|
||||
const callbackName = cb.replace(/-/g, "_");
|
||||
const callbackName = translateName(cb);
|
||||
|
||||
if (componentHandle[callbackName] !== undefined) {
|
||||
console.warn(
|
||||
|
@ -367,7 +404,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
} else {
|
||||
Object.defineProperty(
|
||||
componentHandle,
|
||||
cb.replace(/-/g, "_"),
|
||||
translateName(cb),
|
||||
{
|
||||
get() {
|
||||
return function () {
|
||||
|
@ -387,7 +424,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
});
|
||||
|
||||
instance!.definition().functions.forEach((cb) => {
|
||||
const functionName = cb.replace(/-/g, "_");
|
||||
const functionName = translateName(cb);
|
||||
|
||||
if (componentHandle[functionName] !== undefined) {
|
||||
console.warn(
|
||||
|
@ -396,7 +433,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
} else {
|
||||
Object.defineProperty(
|
||||
componentHandle,
|
||||
cb.replace(/-/g, "_"),
|
||||
translateName(cb),
|
||||
{
|
||||
get() {
|
||||
return function () {
|
||||
|
@ -415,9 +452,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
// globals
|
||||
instance!.definition().globals.forEach((globalName) => {
|
||||
if (componentHandle[globalName] !== undefined) {
|
||||
console.warn(
|
||||
"Duplicated property name " + globalName,
|
||||
);
|
||||
console.warn("Duplicated property name " + globalName);
|
||||
} else {
|
||||
const globalObject = Object.create({});
|
||||
|
||||
|
@ -425,10 +460,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
.definition()
|
||||
.globalProperties(globalName)
|
||||
.forEach((prop) => {
|
||||
const propName = prop.name.replace(
|
||||
/-/g,
|
||||
"_",
|
||||
);
|
||||
const propName = translateName(prop.name);
|
||||
|
||||
if (globalObject[propName] !== undefined) {
|
||||
console.warn(
|
||||
|
@ -465,11 +497,9 @@ function loadSlint(loadData: LoadData): Object {
|
|||
.definition()
|
||||
.globalCallbacks(globalName)
|
||||
.forEach((cb) => {
|
||||
const callbackName = cb.replace(/-/g, "_");
|
||||
const callbackName = translateName(cb);
|
||||
|
||||
if (
|
||||
globalObject[callbackName] !== undefined
|
||||
) {
|
||||
if (globalObject[callbackName] !== undefined) {
|
||||
console.warn(
|
||||
"Duplicated property name " +
|
||||
cb +
|
||||
|
@ -479,16 +509,14 @@ function loadSlint(loadData: LoadData): Object {
|
|||
} else {
|
||||
Object.defineProperty(
|
||||
globalObject,
|
||||
cb.replace(/-/g, "_"),
|
||||
translateName(cb),
|
||||
{
|
||||
get() {
|
||||
return function () {
|
||||
return instance!.invokeGlobal(
|
||||
globalName,
|
||||
cb,
|
||||
Array.from(
|
||||
arguments,
|
||||
),
|
||||
Array.from(arguments),
|
||||
);
|
||||
};
|
||||
},
|
||||
|
@ -509,11 +537,9 @@ function loadSlint(loadData: LoadData): Object {
|
|||
.definition()
|
||||
.globalFunctions(globalName)
|
||||
.forEach((cb) => {
|
||||
const functionName = cb.replace(/-/g, "_");
|
||||
const functionName = translateName(cb);
|
||||
|
||||
if (
|
||||
globalObject[functionName] !== undefined
|
||||
) {
|
||||
if (globalObject[functionName] !== undefined) {
|
||||
console.warn(
|
||||
"Duplicated function name " +
|
||||
cb +
|
||||
|
@ -523,16 +549,14 @@ function loadSlint(loadData: LoadData): Object {
|
|||
} else {
|
||||
Object.defineProperty(
|
||||
globalObject,
|
||||
cb.replace(/-/g, "_"),
|
||||
translateName(cb),
|
||||
{
|
||||
get() {
|
||||
return function () {
|
||||
return instance!.invokeGlobal(
|
||||
globalName,
|
||||
cb,
|
||||
Array.from(
|
||||
arguments,
|
||||
),
|
||||
Array.from(arguments),
|
||||
);
|
||||
};
|
||||
},
|
||||
|
@ -553,8 +577,7 @@ function loadSlint(loadData: LoadData): Object {
|
|||
|
||||
return Object.seal(componentHandle);
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
return Object.seal(slint_module);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue