Start a new sixtyfps::Window API for Rust, C++, the interpreters and JS

The generated component now provides access to a Window type
via the window() accessor function.

This is part of #333
This commit is contained in:
Simon Hausmann 2021-07-21 18:38:05 +02:00 committed by Simon Hausmann
parent 5fd63b63f1
commit 66891a299c
14 changed files with 200 additions and 50 deletions

View file

@ -13,6 +13,8 @@ This class will have the following public member functions:
and react to user input, it's still necessary to spin the event loop, by calling {cpp:func}`sixtyfps::run_event_loop()`
or using the convenience `fun` function in this class.
* A `hide` function, which de-registers the component from the windowing system.
* A `window` function that provides access to the {cpp:class}`sixtyfps::Window`, allow for further customization
towards the windowing system.
* A `run` convenience function, which will show the component and starts the event loop.
* for each properties:
* A getter `get_<property_name>` returning the property type.

View file

@ -82,6 +82,7 @@ using cbindgen_private::KeyEvent;
class WindowRc
{
public:
explicit WindowRc(cbindgen_private::WindowRcOpaque adopted_inner) : inner(adopted_inner) { }
WindowRc() { cbindgen_private::sixtyfps_windowrc_init(&inner); }
~WindowRc() { cbindgen_private::sixtyfps_windowrc_drop(&inner); }
WindowRc(const WindowRc &other)
@ -89,7 +90,15 @@ public:
cbindgen_private::sixtyfps_windowrc_clone(&other.inner, &inner);
}
WindowRc(WindowRc &&) = delete;
WindowRc &operator=(const WindowRc &) = delete;
WindowRc &operator=(WindowRc &&) = delete;
WindowRc &operator=(const WindowRc &other)
{
if (this != &other) {
cbindgen_private::sixtyfps_windowrc_drop(&inner);
cbindgen_private::sixtyfps_windowrc_clone(&other.inner, &inner);
}
return *this;
}
void show() const { sixtyfps_windowrc_show(&inner); }
void hide() const { sixtyfps_windowrc_hide(&inner); }
@ -257,6 +266,40 @@ public:
}
};
/// This class represents a window towards the windowing system, that's used to render the
/// scene of a component. It provides API to control windowing system specific aspects such
/// as the position on the screen.
class Window
{
public:
/// \private
/// Internal function used by the generated code to construct a new instance of this
/// public API wrapper.
explicit Window(const private_api::WindowRc &windowrc) : inner(windowrc) { }
/// Copy-constructs a new window from \a other. Window instances are explicitly
/// shared and reference counted. Creating a copy will not create a second window
/// on the screen.
Window(const Window &other) = default;
/// Copy-constructs the window \a other to this and returns a reference to this.
/// Window instances are explicitly shared and reference counted. Creating a copy
/// will not create a second window on the screen.
Window &operator=(const Window &other) = default;
Window(Window &&other) = delete;
Window &operator=(Window &&other) = delete;
/// Destroys this window. Window instances are explicitly shared and reference counted.
/// If this window instance is the last one referencing the window towards the windowing
/// system, then it will also become hidden and destroyed.
~Window() = default;
/// Registers the window with the windowing system in order to make it visible on the screen.
void show() const { inner.show(); }
/// De-registers the window from the windowing system, therefore hiding it.
void hide() const { inner.hide(); }
private:
private_api::WindowRc inner;
};
/// A Timer that can call a callback at repeated interval
///
/// Use the static single_shot function to make a single shot timer

View file

@ -585,6 +585,15 @@ public:
{
cbindgen_private::sixtyfps_interpreter_component_instance_show(inner(), false);
}
/// Returns the Window associated with this component. The window API can be used
/// to control different aspects of the integration into the windowing system,
/// such as the position on the screen.
sixtyfps::Window window() const
{
cbindgen_private::WindowRcOpaque win;
cbindgen_private::sixtyfps_interpreter_component_instance_window(inner(), &win);
return sixtyfps::Window(sixtyfps::private_api::WindowRc(win));
}
/// This is a convenience function that first calls show(), followed by
/// sixtyfps::run_event_loop() and hide().
void run() const

View file

@ -13,7 +13,8 @@ LICENSE END */
namespace sixtyfps::testing {
inline void init() {
inline void init()
{
cbindgen_private::sixtyfps_testing_init_backend();
}
@ -25,7 +26,7 @@ template<typename Component>
inline void send_mouse_click(const Component *component, float x, float y)
{
auto crc = *component->self_weak.into_dyn().lock();
cbindgen_private::sixtyfps_send_mouse_click(&crc, x, y, &component->window);
cbindgen_private::sixtyfps_send_mouse_click(&crc, x, y, &component->window_);
}
template<typename Component>
@ -33,7 +34,7 @@ inline void send_keyboard_string_sequence(const Component *component,
const sixtyfps::SharedString &str,
cbindgen_private::KeyboardModifiers modifiers = {})
{
cbindgen_private::send_keyboard_string_sequence(&str, modifiers, &component->window);
cbindgen_private::send_keyboard_string_sequence(&str, modifiers, &component->window_);
}
#define assert_eq(A, B) \

View file

@ -41,11 +41,15 @@ class Component {
}
show() {
this.comp.show();
this.window.show();
}
hide() {
this.comp.hide();
this.window.hide()
}
get window(): Window {
return this.comp.window();
}
send_mouse_click(x: number, y: number) {
@ -57,6 +61,11 @@ class Component {
}
}
interface Window {
show(): void;
hide(): void;
}
/**
* @hidden
*/

View file

@ -18,6 +18,7 @@ mod persistent_context;
struct WrappedComponentType(Option<sixtyfps_interpreter::ComponentDefinition>);
struct WrappedComponentRc(Option<sixtyfps_interpreter::ComponentInstance>);
struct WrappedWindow(Option<sixtyfps_interpreter::Window>);
/// We need to do some gymnastic with closures to pass the ExecuteContext with the right lifetime
type GlobalContextCallback<'c> =
@ -344,25 +345,14 @@ declare_types! {
})?;
Ok(JsUndefined::new().as_value(&mut cx))
}
method show(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"))?;
run_scoped(&mut cx,this.downcast().unwrap(), || {
component.show();
Ok(())
})?;
Ok(JsUndefined::new().as_value(&mut cx))
}
method hide(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.hide();
Ok(())
})?;
Ok(JsUndefined::new().as_value(&mut cx))
let window = component.window();
let mut obj = SixtyFpsWindow::new::<_, JsValue, _>(&mut cx, std::iter::empty())?;
cx.borrow_mut(&mut obj, |mut obj| obj.0 = Some(window));
Ok(obj.as_value(&mut cx))
}
method get_property(mut cx) {
let prop_name = cx.argument::<JsString>(0)?.value();
@ -485,6 +475,28 @@ declare_types! {
Ok(JsUndefined::new().as_value(&mut cx))
}
}
class SixtyFpsWindow for WrappedWindow {
init(_) {
Ok(WrappedWindow(None))
}
method show(mut cx) {
let this = cx.this();
let window = cx.borrow(&this, |x| x.0.as_ref().map(|c| c.clone()));
let window = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
window.show();
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().map(|c| c.clone()));
let window = window.ok_or(()).or_else(|()| cx.throw_error("Invalid type"))?;
window.hide();
Ok(JsUndefined::new().as_value(&mut cx))
}
}
}
fn singleshot_timer_property(id: u32) -> String {

View file

@ -56,6 +56,7 @@ pub mod generated_code {
use crate::re_exports;
use crate::ComponentHandle;
use crate::Weak;
use crate::Window;
/// This an example of the API that is generated for a component in `.60` design markup. This may help you understand
/// what functions you can call and how you can pass data in and out.
@ -146,6 +147,13 @@ pub mod generated_code {
unimplemented!();
}
/// Returns the Window associated with this component. The window API can be used
/// to control different aspects of the integration into the windowing system,
/// such as the position on the screen.
fn window(&self) -> Window {
unimplemented!()
}
/// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`]
/// and [`Self::hide`].
fn run(&self) {

View file

@ -450,6 +450,11 @@ pub trait ComponentHandle {
/// the window from the windowing system and it will not receive any further events.
fn hide(&self);
/// Returns the Window associated with this component. The window API can be used
/// to control different aspects of the integration into the windowing system,
/// such as the position on the screen.
fn window(&self) -> Window;
/// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`]
/// and [`Self::hide`].
fn run(&self);
@ -554,6 +559,8 @@ mod weak_handle {
pub use weak_handle::*;
pub use sixtyfps_corelib::window::api::Window;
/// This module contains functions useful for unit tests
pub mod testing {
use core::cell::Cell;

View file

@ -90,6 +90,7 @@
"untracked",
"vtable",
"wasm",
"windowrc", // used in sixtyfps_windowrc_foo FFI functions
"xtask"
],
"ignorePaths": [

View file

@ -901,7 +901,7 @@ fn generate_component(
Access::Private,
Declaration::Var(Var {
ty: "sixtyfps::private_api::WindowRc".into(),
name: "window".into(),
name: "window_".into(),
..Var::default()
}),
));
@ -910,7 +910,7 @@ fn generate_component(
Access::Public, // FIXME: many of the different component bindings need to access this
Declaration::Var(Var {
ty: "sixtyfps::private_api::WindowRc".into(),
name: "window".into(),
name: "window_".into(),
..Var::default()
}),
));
@ -932,7 +932,7 @@ fn generate_component(
Declaration::Function(Function {
name: "show".into(),
signature: "() const".into(),
statements: Some(vec!["window.show();".into()]),
statements: Some(vec!["window_.show();".into()]),
..Default::default()
}),
));
@ -942,7 +942,17 @@ fn generate_component(
Declaration::Function(Function {
name: "hide".into(),
signature: "() const".into(),
statements: Some(vec!["window.hide();".into()]),
statements: Some(vec!["window_.hide();".into()]),
..Default::default()
}),
));
component_struct.members.push((
Access::Public,
Declaration::Function(Function {
name: "window".into(),
signature: "() const -> sixtyfps::Window".into(),
statements: Some(vec!["return sixtyfps::Window(window_);".into()]),
..Default::default()
}),
));
@ -961,7 +971,7 @@ fn generate_component(
}),
));
init.push("self->window.init_items(this, item_tree());".into());
init.push("self->window_.init_items(this, item_tree());".into());
component_struct.friends.push("sixtyfps::private_api::WindowRc".into());
}
@ -976,7 +986,7 @@ fn generate_component(
];
if component.parent_element.upgrade().is_none() {
create_code.push("self->window.set_component(**self->self_weak.lock());".into());
create_code.push("self->window_.set_component(**self->self_weak.lock());".into());
}
create_code.extend(
@ -1088,7 +1098,7 @@ fn generate_component(
is_constructor_or_destructor: true,
statements: Some(init),
constructor_member_initializers: if !component.is_global() && !is_root {
vec!["window(parent->window)".into()]
vec!["window_(parent->window_)".into()]
} else {
vec![]
},
@ -1117,7 +1127,7 @@ fn generate_component(
.join(","),
);
destructor.push("};".into());
destructor.push("window.free_graphics_resources(sixtyfps::Slice<sixtyfps::private_api::ItemRef>{items, std::size(items)});".into());
destructor.push("window_.free_graphics_resources(sixtyfps::Slice<sixtyfps::private_api::ItemRef>{items, std::size(items)});".into());
}
component_struct.members.push((
@ -1404,7 +1414,7 @@ fn compile_expression(
),
Expression::BuiltinFunctionReference(funcref, _) => match funcref {
BuiltinFunction::GetWindowScaleFactor => {
"self->window.scale_factor".into()
"self->window_.scale_factor".into()
}
BuiltinFunction::Debug => {
"[](auto... args){ (std::cout << ... << args) << std::endl; return nullptr; }"
@ -1423,10 +1433,10 @@ fn compile_expression(
BuiltinFunction::ACos => format!("[](float a){{ return std::acos(a) / {}; }}", std::f32::consts::PI / 180.),
BuiltinFunction::ATan => format!("[](float a){{ return std::atan(a) / {}; }}", std::f32::consts::PI / 180.),
BuiltinFunction::SetFocusItem => {
"self->window.set_focus_item".into()
"self->window_.set_focus_item".into()
}
BuiltinFunction::ShowPopupWindow => {
"self->window.show_popup".into()
"self->window_.show_popup".into()
}
/* std::from_chars is unfortunately not yet implemented in gcc
@ -1562,7 +1572,7 @@ fn compile_expression(
if let Expression::ElementReference(focus_item) = &arguments[0] {
let focus_item = focus_item.upgrade().unwrap();
let focus_item = focus_item.borrow();
format!("self->window.set_focus_item(self->self_weak.lock()->into_dyn(), {});", focus_item.item_index.get().unwrap())
format!("self->window_.set_focus_item(self->self_weak.lock()->into_dyn(), {});", focus_item.item_index.get().unwrap())
} else {
panic!("internal error: argument to SetFocusItem must be an element")
}
@ -1580,7 +1590,7 @@ fn compile_expression(
let popup = popup_list.iter().find(|p| Rc::ptr_eq(&p.component, &pop_comp)).unwrap();
let x = access_named_reference(&popup.x, component, "self");
let y = access_named_reference(&popup.y, component, "self");
format!("self->window.show_popup<{}>(self, {{ {}.get(), {}.get() }} );", popup_window_id, x, y)
format!("self->window_.show_popup<{}>(self, {{ {}.get(), {}.get() }} );", popup_window_id, x, y)
} else {
panic!("internal error: argument to SetFocusItem must be an element")
}
@ -1593,7 +1603,7 @@ fn compile_expression(
let item = item.upgrade().unwrap();
let item = item.borrow();
let native_item = item.base_type.as_native();
format!("{vt}->layouting_info({{{vt}, const_cast<sixtyfps::cbindgen_private::{ty}*>(&self->{id})}}, {o}, &window)",
format!("{vt}->layouting_info({{{vt}, const_cast<sixtyfps::cbindgen_private::{ty}*>(&self->{id})}}, {o}, &window_)",
vt = native_item.cpp_vtable_getter,
ty = native_item.class_name,
id = item.id,
@ -2038,7 +2048,7 @@ fn get_layout_info(
format!("{}.get()", access_named_reference(layout_info_prop, component, "self"))
} else {
format!(
"{vt}->layouting_info({{{vt}, const_cast<sixtyfps::cbindgen_private::{ty}*>(&self->{id})}}, {o}, &self->window)",
"{vt}->layouting_info({{{vt}, const_cast<sixtyfps::cbindgen_private::{ty}*>(&self->{id})}}, {o}, &self->window_)",
vt = elem.borrow().base_type.as_native().cpp_vtable_getter,
ty = elem.borrow().base_type.as_native().class_name,
id = elem.borrow().id,

View file

@ -849,11 +849,15 @@ fn generate_component(
}
fn show(&self) {
vtable::VRc::as_pin_ref(&self.0).window.window_handle().show();
self.window().show();
}
fn hide(&self) {
vtable::VRc::as_pin_ref(&self.0).window.window_handle().hide();
self.window().hide()
}
fn window(&self) -> sixtyfps::Window {
vtable::VRc::as_pin_ref(&self.0).window.clone().into()
}
}
))

View file

@ -339,6 +339,44 @@ impl WindowHandleAccess for WindowRc {
}
}
/// Internal module to define the public Window API, for re-export in the regular Rust crate
/// and the interpreter crate.
pub mod api {
/// This type represents a window towards the windowing system, that's used to render the
/// scene of a component. It provides API to control windowing system specific aspects such
/// as the position on the screen.
#[derive(Clone)]
pub struct Window(super::WindowRc);
#[doc(hidden)]
impl From<super::WindowRc> for Window {
fn from(window: super::WindowRc) -> Self {
Self(window)
}
}
impl Window {
/// Registers the window with the windowing system in order to make it visible on the screen.
pub fn show(&self) {
use super::WindowHandleAccess;
self.0.window_handle().show();
}
/// De-registers the window from the windowing system, therefore hiding it.
pub fn hide(&self) {
use super::WindowHandleAccess;
self.0.window_handle().hide();
}
}
#[doc(hidden)]
impl super::WindowHandleAccess for Window {
fn window_handle(&self) -> &std::rc::Rc<super::Window> {
self.0.window_handle()
}
}
}
/// This module contains the functions needed to interface with the event loop and window traits
/// from outside the Rust language.
#[cfg(feature = "ffi")]

View file

@ -11,6 +11,7 @@ LICENSE END */
use core::convert::TryInto;
use sixtyfps_compilerlib::langtype::Type as LangType;
use sixtyfps_corelib::graphics::Image;
use sixtyfps_corelib::window::WindowHandleAccess;
use sixtyfps_corelib::{Brush, PathData, SharedString, SharedVector};
use std::collections::HashMap;
use std::iter::FromIterator;
@ -20,6 +21,8 @@ use std::rc::Rc;
#[doc(inline)]
pub use sixtyfps_compilerlib::diagnostics::{Diagnostic, DiagnosticLevel};
pub use sixtyfps_corelib::window::api::Window;
/// This enum represents the different public variants of the [`Value`] enum, without
/// the contained values.
// NOTE: The docs for ValueType are duplicated in sixtyfps_interpreter.h, for extraction by
@ -578,13 +581,14 @@ impl ComponentDefinition {
/// Instantiate the component using an existing window.
/// This method is internal because the Window is not a public type
#[doc(hidden)]
pub fn create_with_existing_window(
&self,
window: Rc<sixtyfps_corelib::window::Window>,
) -> ComponentInstance {
pub fn create_with_existing_window(&self, window: Window) -> ComponentInstance {
generativity::make_guard!(guard);
ComponentInstance {
inner: self.inner.unerase(guard).clone().create_with_existing_window(window),
inner: self
.inner
.unerase(guard)
.clone()
.create_with_existing_window(window.window_handle().clone()),
}
}
@ -781,6 +785,7 @@ impl ComponentInstance {
let comp = self.inner.unerase(guard);
comp.window().hide();
}
/// This is a convenience function that first calls [`Self::show`], followed by [`crate::run_event_loop()`]
/// and [`Self::hide`].
pub fn run(&self) {
@ -807,13 +812,14 @@ impl ComponentInstance {
WeakComponentInstance { inner: vtable::VRc::downgrade(&self.inner) }
}
/// Return the window.
/// This method is internal because the Window is not a public type
#[doc(hidden)]
pub fn window(&self) -> Rc<sixtyfps_corelib::window::Window> {
/// Returns the Window associated with this component. The window API can be used
/// to control different aspects of the integration into the windowing system,
/// such as the position on the screen.
pub fn window(&self) -> Window {
generativity::make_guard!(guard);
let comp = self.inner.unerase(guard);
comp.window()
let window_rc: sixtyfps_corelib::window::WindowRc = comp.window().into();
window_rc.into()
}
}

View file

@ -53,7 +53,7 @@ assert(instance.get_test_zero());
assert(instance.get_test());
ratio = 2.;
instance.window.set_scale_factor(ratio);
instance.window_.set_scale_factor(ratio);
assert_eq(instance.get_l1(), 12.);
assert_eq(instance.get_l2(), 12. * ratio);
assert_eq(instance.get_l3(), 100. + 12. * ratio);