Added window js wrapper for napi (#3544)

* Added window js wrapper for napi

* Remove position tests.

* Update api/napi/src/types/size.rs

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

* Code review fixes

---------

Co-authored-by: Simon Hausmann <simon.hausmann@slint-ui.com>
This commit is contained in:
Florian Blasius 2023-09-29 08:29:36 +02:00 committed by GitHub
parent 8ba6fac21f
commit d43f5fe99a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 274 additions and 5 deletions

View file

@ -3,7 +3,7 @@
import test from 'ava' import test from 'ava'
import { ComponentCompiler, ComponentDefinition, ComponentInstance, Property, ValueType } from '../index' import { ComponentCompiler, ComponentDefinition, ComponentInstance, ValueType} from '../index'
test('get/set include paths', (t) => { test('get/set include paths', (t) => {
let compiler = new ComponentCompiler; let compiler = new ComponentCompiler;
@ -236,4 +236,4 @@ test('non-existent properties and callbacks', (t) => {
}); });
t.is(callback_err!.code, 'GenericFailure'); t.is(callback_err!.code, 'GenericFailure');
t.is(callback_err!.message, 'Callback non-existent-callback not found in the component'); t.is(callback_err!.message, 'Callback non-existent-callback not found in the component');
}) })

View file

@ -3,7 +3,7 @@
import test from 'ava'; import test from 'ava';
import { Brush, Color, ArrayModel } from '../index' import { Brush, Color, ArrayModel, Size } 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);
@ -76,7 +76,6 @@ test('ArrayModel setRowData', (t) => {
t.is(arrayModel.rowData(0), 2); t.is(arrayModel.rowData(0), 2);
}) })
test('ArrayModel remove', (t) => { test('ArrayModel remove', (t) => {
let arrayModel = new ArrayModel([0, 2, 1]); let arrayModel = new ArrayModel([0, 2, 1]);
@ -87,4 +86,4 @@ test('ArrayModel remove', (t) => {
arrayModel.remove(0, 2); arrayModel.remove(0, 2);
t.is(arrayModel.rowCount(), 1); t.is(arrayModel.rowCount(), 1);
t.is(arrayModel.rowData(0), 1); t.is(arrayModel.rowData(0), 1);
}) })

View file

@ -0,0 +1,38 @@
// 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 test from 'ava'
import { ComponentCompiler, Window } from '../index'
test('Window constructor', (t) => {
t.throws(() => {
new Window()
},
{
code: "GenericFailure",
message: "Window can only be created by using a Component."
}
);
})
test('Window show / hide', (t) => {
let compiler = new ComponentCompiler;
let definition = compiler.buildFromSource(`
export component App inherits Window {
width: 300px;
height: 300px;
}`, "");
t.not(definition, null);
let instance = definition!.create();
t.not(instance, null);
let window = instance!.window();
t.is(window.isVisible, false);
window.show();
t.is(window.isVisible, true);
window.hide();
t.is(window.isVisible, false);
})

View file

@ -15,3 +15,6 @@ pub use diagnostic::*;
mod value; mod value;
pub use value::*; pub use value::*;
mod window;
pub use window::*;

View file

@ -2,9 +2,12 @@
// 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 i_slint_compiler::langtype::Type; use i_slint_compiler::langtype::Type;
use i_slint_core::window::WindowInner;
use napi::{Env, Error, JsFunction, JsUnknown, NapiRaw, NapiValue, Ref, Result}; use napi::{Env, Error, JsFunction, JsUnknown, NapiRaw, NapiValue, Ref, Result};
use slint_interpreter::{ComponentHandle, ComponentInstance, Value}; use slint_interpreter::{ComponentHandle, ComponentInstance, Value};
use crate::JsWindow;
use super::JsComponentDefinition; use super::JsComponentDefinition;
#[napi(js_name = "ComponentInstance")] #[napi(js_name = "ComponentInstance")]
@ -329,6 +332,11 @@ impl JsComponentInstance {
.map_err(|_| napi::Error::from_reason("Cannot invoke callback."))?; .map_err(|_| napi::Error::from_reason("Cannot invoke callback."))?;
super::to_js_unknown(&env, &result) super::to_js_unknown(&env, &result)
} }
#[napi]
pub fn window(&self) -> Result<JsWindow> {
Ok(JsWindow { inner: WindowInner::from_pub(self.inner.window()).window_adapter() })
}
} }
// Wrapper around Ref<>, which requires manual ref-counting. // Wrapper around Ref<>, which requires manual ref-counting.

View file

@ -0,0 +1,103 @@
// 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 crate::types::{JsPoint, JsSize};
use i_slint_core::window::WindowAdapterRc;
use slint_interpreter::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize};
#[napi(js_name = "Window")]
pub struct JsWindow {
pub(crate) inner: WindowAdapterRc,
}
impl From<WindowAdapterRc> for JsWindow {
fn from(instance: WindowAdapterRc) -> Self {
Self { inner: instance }
}
}
#[napi]
impl JsWindow {
#[napi(constructor)]
pub fn new() -> napi::Result<Self> {
Err(napi::Error::from_reason(
"Window can only be created by using a Component.".to_string(),
))
}
#[napi]
pub fn show(&self) -> napi::Result<()> {
self.inner
.window()
.show()
.map_err(|_| napi::Error::from_reason("Cannot show window.".to_string()))
}
#[napi]
pub fn hide(&self) -> napi::Result<()> {
self.inner
.window()
.hide()
.map_err(|_| napi::Error::from_reason("Cannot hide window.".to_string()))
}
#[napi(getter)]
pub fn is_visible(&self) -> bool {
self.inner.window().is_visible()
}
#[napi(getter)]
pub fn get_logical_position(&self) -> JsPoint {
let pos = self.inner.window().position().to_logical(self.inner.window().scale_factor());
JsPoint { x: pos.x as f64, y: pos.y as f64 }
}
#[napi(setter)]
pub fn set_logical_position(&self, position: JsPoint) {
self.inner
.window()
.set_position(LogicalPosition { x: position.x as f32, y: position.y as f32 });
}
#[napi(getter)]
pub fn get_physical_position(&self) -> JsPoint {
let pos = self.inner.window().position();
JsPoint { x: pos.x as f64, y: pos.y as f64 }
}
#[napi(setter)]
pub fn set_physical_position(&self, position: JsPoint) {
self.inner.window().set_position(PhysicalPosition {
x: position.x.floor() as i32,
y: position.y.floor() as i32,
});
}
#[napi(getter)]
pub fn get_logical_size(&self) -> JsSize {
let size = self.inner.window().size().to_logical(self.inner.window().scale_factor());
JsSize { width: size.width as f64, height: size.height as f64 }
}
#[napi(setter)]
pub fn set_logical_size(&self, size: JsSize) {
self.inner.window().set_size(LogicalSize::from_physical(
PhysicalSize { width: size.width.floor() as u32, height: size.height.floor() as u32 },
self.inner.window().scale_factor(),
));
}
#[napi(getter)]
pub fn get_physical_size(&self) -> JsSize {
let size = self.inner.window().size();
JsSize { width: size.width as f64, height: size.height as f64 }
}
#[napi(setter)]
pub fn set_physical_size(&self, size: JsSize) {
self.inner.window().set_size(PhysicalSize {
width: size.width.floor() as u32,
height: size.height.floor() as u32,
});
}
}

View file

@ -9,3 +9,9 @@ pub use image_data::*;
mod model; mod model;
pub use model::*; pub use model::*;
mod point;
pub use point::*;
mod size;
pub use size::*;

View file

@ -0,0 +1,52 @@
// 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::{
bindgen_prelude::{FromNapiValue, Object},
JsUnknown,
};
#[napi(js_name = Point)]
pub struct JsPoint {
pub x: f64,
pub y: f64,
}
#[napi]
impl JsPoint {
#[napi(constructor)]
pub fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
}
impl FromNapiValue for JsPoint {
unsafe fn from_napi_value(
env: napi::sys::napi_env,
napi_val: napi::sys::napi_value,
) -> napi::Result<Self> {
let obj = unsafe { Object::from_napi_value(env, napi_val)? };
let x: f64 = obj
.get::<_, JsUnknown>("x")
.ok()
.flatten()
.and_then(|p| p.coerce_to_number().ok())
.and_then(|f64_num| f64_num.try_into().ok())
.ok_or_else(
|| napi::Error::from_reason(
format!("Cannot convert object to Point, because the provided object does not have an f64 x property")
))?;
let y: f64 = obj
.get::<_, JsUnknown>("y")
.ok()
.flatten()
.and_then(|p| p.coerce_to_number().ok())
.and_then(|f64_num| f64_num.try_into().ok())
.ok_or_else(
|| napi::Error::from_reason(
format!("Cannot convert object to Point, because the provided object does not have an f64 y property")
))?;
Ok(JsPoint { x, y })
}
}

View file

@ -0,0 +1,60 @@
// 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::{
bindgen_prelude::{FromNapiValue, Object},
JsUnknown, Result,
};
#[napi(js_name = Size)]
pub struct JsSize {
pub width: f64,
pub height: f64,
}
#[napi]
impl JsSize {
#[napi(constructor)]
pub fn new(width: f64, height: f64) -> Result<Self> {
if width < 0. {
return Err(napi::Error::from_reason("width cannot be negative".to_string()));
}
if height < 0. {
return Err(napi::Error::from_reason("height cannot be negative".to_string()));
}
Ok(Self { width, height })
}
}
impl FromNapiValue for JsSize {
unsafe fn from_napi_value(
env: napi::sys::napi_env,
napi_val: napi::sys::napi_value,
) -> napi::Result<Self> {
let obj = unsafe { Object::from_napi_value(env, napi_val)? };
let width: f64 = obj
.get::<_, JsUnknown>("width")
.ok()
.flatten()
.and_then(|p| p.coerce_to_number().ok())
.and_then(|f64_num| f64_num.try_into().ok())
.ok_or_else(
|| napi::Error::from_reason(
format!("Cannot convert object to Size, because the provided object does not have an f64 width property")
))?;
let height: f64 = obj
.get::<_, JsUnknown>("height")
.ok()
.flatten()
.and_then(|p| p.coerce_to_number().ok())
.and_then(|f64_num| f64_num.try_into().ok())
.ok_or_else(
|| napi::Error::from_reason(
format!("Cannot convert object to Size, because the provided object does not have an f64 height property")
))?;
Ok(JsSize { width, height })
}
}