mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-03 07:04:34 +00:00
Add support for the initial_focus synthetic property
Setting it will translate to a set_focus_item call in the constructor. This implements parts of #55
This commit is contained in:
parent
2050f08f1e
commit
9ad8968529
22 changed files with 542 additions and 36 deletions
|
@ -95,6 +95,11 @@ struct ComponentWindow
|
||||||
&inner, VRef<ComponentVTable> { &Component::component_type, c });
|
&inner, VRef<ComponentVTable> { &Component::component_type, c });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void set_focus_item(Pin<VRef<ComponentVTable>> c, Pin<VRef<ItemVTable>> item)
|
||||||
|
{
|
||||||
|
cbindgen_private::sixtyfps_component_window_set_focus_item(&inner, c, item);
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
cbindgen_private::ComponentWindowOpaque inner;
|
cbindgen_private::ComponentWindowOpaque inner;
|
||||||
};
|
};
|
||||||
|
|
|
@ -184,7 +184,8 @@ fn to_eval_value<'cx>(
|
||||||
| Type::Signal { .. }
|
| Type::Signal { .. }
|
||||||
| Type::Easing
|
| Type::Easing
|
||||||
| Type::Component(_) // The struct case is handled before
|
| Type::Component(_) // The struct case is handled before
|
||||||
| Type::PathElements => cx.throw_error("Cannot convert to a Sixtyfps property value"),
|
| Type::PathElements
|
||||||
|
| Type::ElementReference => cx.throw_error("Cannot convert to a Sixtyfps property value"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -242,6 +242,7 @@ The `TextInput` is a lower-level item that shows text and allows entering text.
|
||||||
* **`color`** (*color*): The color of the text (default: transparent)
|
* **`color`** (*color*): The color of the text (default: transparent)
|
||||||
* **`horizontal_alignment`**, **`vertical_alignment`** (*FIXME: enum*): How is the text aligned
|
* **`horizontal_alignment`**, **`vertical_alignment`** (*FIXME: enum*): How is the text aligned
|
||||||
within the item
|
within the item
|
||||||
|
* **`has_focus`** (*bool*): Set to true when item is focused and receives keyboard events.
|
||||||
|
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
|
@ -524,3 +524,54 @@ App := Rectangle {
|
||||||
Button {} // from button.60
|
Button {} // from button.60
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Focus Handling
|
||||||
|
|
||||||
|
Certain elements such as ```TextInput``` accept not only input from the mouse/finger but
|
||||||
|
also key events originating from (virtual) keyboards. In order for an item to receive
|
||||||
|
these events, it must have the focus. This is visible through the `has_focus` property.
|
||||||
|
|
||||||
|
Items themselves may acquire the focus as a result of user input such as a mouse click.
|
||||||
|
In addition, developers can specify which items shall receive the focus when the application
|
||||||
|
window initially receives the focus. This is specified through the `initial_focus` property,
|
||||||
|
for which bindings may be declared at the root of components. For example in the following
|
||||||
|
scene with two ```TextInput``` elements, it is the second one that'll have the initial keyboard
|
||||||
|
focus when they're shown:
|
||||||
|
|
||||||
|
```60
|
||||||
|
App := Window {
|
||||||
|
initial_focus: second_input_field;
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
Row {
|
||||||
|
first_input_field := TextInput {}
|
||||||
|
}
|
||||||
|
Row {
|
||||||
|
second_input_field := TextInput {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
The initial focus may also be propagated through reusable components:
|
||||||
|
|
||||||
|
```60
|
||||||
|
LabeledInput := GridLayout {
|
||||||
|
initial_focus: input;
|
||||||
|
Row {
|
||||||
|
Text {
|
||||||
|
text: "Input Label:";
|
||||||
|
}
|
||||||
|
input := TextInput {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
App := Window {
|
||||||
|
initial_focus: label2;
|
||||||
|
|
||||||
|
GridLayout {
|
||||||
|
label1 := LabeledInput {}
|
||||||
|
label2 := LabeledInput {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
|
@ -44,6 +44,7 @@ impl Hash for NamedReference {
|
||||||
pub enum BuiltinFunction {
|
pub enum BuiltinFunction {
|
||||||
GetWindowScaleFactor,
|
GetWindowScaleFactor,
|
||||||
Debug,
|
Debug,
|
||||||
|
SetFocusItem,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BuiltinFunction {
|
impl BuiltinFunction {
|
||||||
|
@ -55,6 +56,10 @@ impl BuiltinFunction {
|
||||||
BuiltinFunction::Debug => {
|
BuiltinFunction::Debug => {
|
||||||
Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
|
Type::Function { return_type: Box::new(Type::Void), args: vec![Type::String] }
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::SetFocusItem => Type::Function {
|
||||||
|
return_type: Box::new(Type::Void),
|
||||||
|
args: vec![Type::ElementReference],
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,6 +188,10 @@ pub enum Expression {
|
||||||
/// Reference to a function built into the run-time, implemented natively
|
/// Reference to a function built into the run-time, implemented natively
|
||||||
BuiltinFunctionReference(BuiltinFunction),
|
BuiltinFunctionReference(BuiltinFunction),
|
||||||
|
|
||||||
|
/// A reference to a specific element. This isn't possible to create in .60 syntax itself, but intermediate passes may generate this
|
||||||
|
/// type of expression.
|
||||||
|
ElementReference(Weak<RefCell<Element>>),
|
||||||
|
|
||||||
/// Reference to the index variable of a repeater
|
/// Reference to the index variable of a repeater
|
||||||
///
|
///
|
||||||
/// Example: `idx` in `for xxx[idx] in ...`. The element is the reference to the
|
/// Example: `idx` in `for xxx[idx] in ...`. The element is the reference to the
|
||||||
|
@ -314,6 +323,7 @@ impl Expression {
|
||||||
element.upgrade().unwrap().borrow().lookup_property(name)
|
element.upgrade().unwrap().borrow().lookup_property(name)
|
||||||
}
|
}
|
||||||
Expression::BuiltinFunctionReference(funcref) => funcref.ty(),
|
Expression::BuiltinFunctionReference(funcref) => funcref.ty(),
|
||||||
|
Expression::ElementReference(_) => Type::ElementReference,
|
||||||
Expression::RepeaterIndexReference { .. } => Type::Int32,
|
Expression::RepeaterIndexReference { .. } => Type::Int32,
|
||||||
Expression::RepeaterModelReference { element } => {
|
Expression::RepeaterModelReference { element } => {
|
||||||
if let Expression::Cast { from, .. } = element
|
if let Expression::Cast { from, .. } = element
|
||||||
|
@ -399,6 +409,7 @@ impl Expression {
|
||||||
Expression::PropertyReference { .. } => {}
|
Expression::PropertyReference { .. } => {}
|
||||||
Expression::FunctionParameterReference { .. } => {}
|
Expression::FunctionParameterReference { .. } => {}
|
||||||
Expression::BuiltinFunctionReference { .. } => {}
|
Expression::BuiltinFunctionReference { .. } => {}
|
||||||
|
Expression::ElementReference(_) => {}
|
||||||
Expression::ObjectAccess { base, .. } => visitor(&**base),
|
Expression::ObjectAccess { base, .. } => visitor(&**base),
|
||||||
Expression::RepeaterIndexReference { .. } => {}
|
Expression::RepeaterIndexReference { .. } => {}
|
||||||
Expression::RepeaterModelReference { .. } => {}
|
Expression::RepeaterModelReference { .. } => {}
|
||||||
|
@ -461,6 +472,7 @@ impl Expression {
|
||||||
Expression::PropertyReference { .. } => {}
|
Expression::PropertyReference { .. } => {}
|
||||||
Expression::FunctionParameterReference { .. } => {}
|
Expression::FunctionParameterReference { .. } => {}
|
||||||
Expression::BuiltinFunctionReference { .. } => {}
|
Expression::BuiltinFunctionReference { .. } => {}
|
||||||
|
Expression::ElementReference(_) => {}
|
||||||
Expression::ObjectAccess { base, .. } => visitor(&mut **base),
|
Expression::ObjectAccess { base, .. } => visitor(&mut **base),
|
||||||
Expression::RepeaterIndexReference { .. } => {}
|
Expression::RepeaterIndexReference { .. } => {}
|
||||||
Expression::RepeaterModelReference { .. } => {}
|
Expression::RepeaterModelReference { .. } => {}
|
||||||
|
@ -522,6 +534,7 @@ impl Expression {
|
||||||
Expression::SignalReference { .. } => false,
|
Expression::SignalReference { .. } => false,
|
||||||
Expression::PropertyReference { .. } => false,
|
Expression::PropertyReference { .. } => false,
|
||||||
Expression::BuiltinFunctionReference { .. } => false,
|
Expression::BuiltinFunctionReference { .. } => false,
|
||||||
|
Expression::ElementReference(_) => false,
|
||||||
Expression::RepeaterIndexReference { .. } => false,
|
Expression::RepeaterIndexReference { .. } => false,
|
||||||
Expression::RepeaterModelReference { .. } => false,
|
Expression::RepeaterModelReference { .. } => false,
|
||||||
Expression::FunctionParameterReference { .. } => false,
|
Expression::FunctionParameterReference { .. } => false,
|
||||||
|
@ -678,7 +691,8 @@ impl Expression {
|
||||||
| Type::Native(_)
|
| Type::Native(_)
|
||||||
| Type::Signal { .. }
|
| Type::Signal { .. }
|
||||||
| Type::Function { .. }
|
| Type::Function { .. }
|
||||||
| Type::Void => Expression::Invalid,
|
| Type::Void
|
||||||
|
| Type::ElementReference => Expression::Invalid,
|
||||||
Type::Float32 => Expression::NumberLiteral(0., Unit::None),
|
Type::Float32 => Expression::NumberLiteral(0., Unit::None),
|
||||||
Type::Int32 => Expression::NumberLiteral(0., Unit::None),
|
Type::Int32 => Expression::NumberLiteral(0., Unit::None),
|
||||||
Type::String => Expression::StringLiteral(String::new()),
|
Type::String => Expression::StringLiteral(String::new()),
|
||||||
|
|
|
@ -775,6 +775,10 @@ fn generate_component(
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
for extra_init_code in component.setup_code.borrow().iter() {
|
||||||
|
init.push(compile_expression(extra_init_code, component));
|
||||||
|
}
|
||||||
|
|
||||||
component_struct.members.push((
|
component_struct.members.push((
|
||||||
Access::Public,
|
Access::Public,
|
||||||
Declaration::Function(Function {
|
Declaration::Function(Function {
|
||||||
|
@ -1064,7 +1068,11 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
|
||||||
"[](auto... args){ (std::cout << ... << args) << std::endl; return nullptr; }"
|
"[](auto... args){ (std::cout << ... << args) << std::endl; return nullptr; }"
|
||||||
.into()
|
.into()
|
||||||
}
|
}
|
||||||
|
BuiltinFunction::SetFocusItem => {
|
||||||
|
format!("{}.set_focus_item", window_ref_expression(component))
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
|
||||||
Expression::RepeaterIndexReference { element } => {
|
Expression::RepeaterIndexReference { element } => {
|
||||||
let access = access_member(
|
let access = access_member(
|
||||||
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
|
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
|
||||||
|
@ -1136,10 +1144,33 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
|
||||||
|
|
||||||
format!("[&]{{ {} }}()", x.join(";"))
|
format!("[&]{{ {} }}()", x.join(";"))
|
||||||
}
|
}
|
||||||
Expression::FunctionCall { function, arguments } => {
|
Expression::FunctionCall { function, arguments } => match &**function {
|
||||||
let mut args = arguments.iter().map(|e| compile_expression(e, component));
|
Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem) => {
|
||||||
format!("{}({})", compile_expression(&function, component), args.join(", "))
|
if arguments.len() != 1 {
|
||||||
}
|
panic!("internal error: incorrect argument count to SetFocusItem call");
|
||||||
|
}
|
||||||
|
if let Expression::ElementReference(focus_item) = &arguments[0] {
|
||||||
|
let focus_item = focus_item.upgrade().unwrap();
|
||||||
|
let focus_item = focus_item.borrow();
|
||||||
|
let window_ref = window_ref_expression(component);
|
||||||
|
let component =
|
||||||
|
format!("{{&{}::component_type, this}}", component_id(component));
|
||||||
|
let item = format!(
|
||||||
|
"{{&sixtyfps::private_api::{vt}, &{item}}}",
|
||||||
|
vt = focus_item.base_type.as_native().vtable_symbol,
|
||||||
|
item = focus_item.id
|
||||||
|
);
|
||||||
|
format!("{}.set_focus_item({}, {});", window_ref, component, item)
|
||||||
|
} else {
|
||||||
|
panic!("internal error: argument to SetFocusItem must be an element")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let mut args = arguments.iter().map(|e| compile_expression(e, component));
|
||||||
|
|
||||||
|
format!("{}({})", compile_expression(&function, component), args.join(", "))
|
||||||
|
}
|
||||||
|
},
|
||||||
Expression::SelfAssignment { lhs, rhs, op } => {
|
Expression::SelfAssignment { lhs, rhs, op } => {
|
||||||
let rhs = compile_expression(&*rhs, &component);
|
let rhs = compile_expression(&*rhs, &component);
|
||||||
compile_assignment(lhs, *op, rhs, component)
|
compile_assignment(lhs, *op, rhs, component)
|
||||||
|
|
|
@ -524,6 +524,10 @@ fn generate_component(
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
|
for extra_init_code in component.setup_code.borrow().iter() {
|
||||||
|
init.push(compile_expression(extra_init_code, component));
|
||||||
|
}
|
||||||
|
|
||||||
Some(quote!(
|
Some(quote!(
|
||||||
#(#resource_symbols)*
|
#(#resource_symbols)*
|
||||||
|
|
||||||
|
@ -674,6 +678,7 @@ fn generate_component(
|
||||||
let self_pinned = std::rc::Rc::pin(self_);
|
let self_pinned = std::rc::Rc::pin(self_);
|
||||||
self_pinned.self_weak.set(PinWeak::downgrade(self_pinned.clone())).map_err(|_|())
|
self_pinned.self_weak.set(PinWeak::downgrade(self_pinned.clone())).map_err(|_|())
|
||||||
.expect("Can only be pinned once");
|
.expect("Can only be pinned once");
|
||||||
|
let _self = self_pinned.as_ref();
|
||||||
#(#init)*
|
#(#init)*
|
||||||
self_pinned
|
self_pinned
|
||||||
}
|
}
|
||||||
|
@ -878,7 +883,9 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
|
||||||
quote!(#window_ref.scale_factor)
|
quote!(#window_ref.scale_factor)
|
||||||
}
|
}
|
||||||
BuiltinFunction::Debug => quote!((|x| println!("{:?}", x))),
|
BuiltinFunction::Debug => quote!((|x| println!("{:?}", x))),
|
||||||
|
BuiltinFunction::SetFocusItem => panic!("internal error: SetFocusItem is handled directly in CallFunction")
|
||||||
},
|
},
|
||||||
|
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
|
||||||
Expression::RepeaterIndexReference { element } => {
|
Expression::RepeaterIndexReference { element } => {
|
||||||
let access = access_member(
|
let access = access_member(
|
||||||
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
|
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
|
||||||
|
@ -932,19 +939,39 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
|
||||||
false,
|
false,
|
||||||
),
|
),
|
||||||
Expression::FunctionCall { function, arguments } => {
|
Expression::FunctionCall { function, arguments } => {
|
||||||
let f = compile_expression(function, &component);
|
match &**function {
|
||||||
let a = arguments.iter().map(|a| compile_expression(a, &component));
|
Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem) => {
|
||||||
if let Type::Signal { args } = function.ty() {
|
if arguments.len() != 1 {
|
||||||
let cast = args.iter().map(|ty| match ty {
|
panic!("internal error: incorrect argument count to SetFocusItem call");
|
||||||
Type::Bool => quote!(as bool),
|
}
|
||||||
Type::Int32 => quote!(as i32),
|
if let Expression::ElementReference(focus_item) = &arguments[0] {
|
||||||
Type::Float32 => quote!(as f32),
|
let item = format_ident!("{}", focus_item.upgrade().unwrap().borrow().id);
|
||||||
_ => quote!(.clone()),
|
let window_ref = window_ref_expression(component);
|
||||||
});
|
quote!(
|
||||||
quote! { #f.emit(&(#((#a)#cast,)*).into())}
|
#window_ref.set_focus_item(VRef::new_pin(self_pinned.as_ref()), VRef::new_pin(Self::FIELD_OFFSETS.#item.apply_pin(self_pinned.as_ref())));
|
||||||
} else {
|
)
|
||||||
quote! { #f(#(#a.clone()),*)}
|
} else {
|
||||||
|
panic!("internal error: argument to SetFocusItem must be an element")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let f = compile_expression(function, &component);
|
||||||
|
let a = arguments.iter().map(|a| compile_expression(a, &component));
|
||||||
|
let function_type = function.ty();
|
||||||
|
if let Type::Signal { args } = function_type {
|
||||||
|
let cast = args.iter().map(|ty| match ty {
|
||||||
|
Type::Bool => quote!(as bool),
|
||||||
|
Type::Int32 => quote!(as i32),
|
||||||
|
Type::Float32 => quote!(as f32),
|
||||||
|
_ => quote!(.clone()),
|
||||||
|
});
|
||||||
|
quote! { #f.emit(&(#((#a)#cast,)*).into())}
|
||||||
|
} else {
|
||||||
|
quote! { #f(#(#a.clone()),*)}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Expression::SelfAssignment { lhs, rhs, op } => {
|
Expression::SelfAssignment { lhs, rhs, op } => {
|
||||||
let rhs = compile_expression(&*rhs, &component);
|
let rhs = compile_expression(&*rhs, &component);
|
||||||
|
|
|
@ -47,6 +47,7 @@ mod passes {
|
||||||
pub mod collect_resources;
|
pub mod collect_resources;
|
||||||
pub mod compile_paths;
|
pub mod compile_paths;
|
||||||
pub mod deduplicate_property_read;
|
pub mod deduplicate_property_read;
|
||||||
|
pub mod focus_item;
|
||||||
pub mod inlining;
|
pub mod inlining;
|
||||||
pub mod lower_layout;
|
pub mod lower_layout;
|
||||||
pub mod lower_states;
|
pub mod lower_states;
|
||||||
|
@ -122,6 +123,7 @@ pub fn run_passes(
|
||||||
passes::inlining::inline(doc);
|
passes::inlining::inline(doc);
|
||||||
passes::compile_paths::compile_paths(&doc.root_component, &doc.local_registry, diag);
|
passes::compile_paths::compile_paths(&doc.root_component, &doc.local_registry, diag);
|
||||||
passes::unique_id::assign_unique_id(&doc.root_component);
|
passes::unique_id::assign_unique_id(&doc.root_component);
|
||||||
|
passes::focus_item::determine_initial_focus_item(&doc.root_component, diag);
|
||||||
passes::materialize_fake_properties::materialize_fake_properties(&doc.root_component);
|
passes::materialize_fake_properties::materialize_fake_properties(&doc.root_component);
|
||||||
passes::collect_resources::collect_resources(&doc.root_component);
|
passes::collect_resources::collect_resources(&doc.root_component);
|
||||||
doc.root_component.embed_file_resources.set(compiler_config.embed_resources);
|
doc.root_component.embed_file_resources.set(compiler_config.embed_resources);
|
||||||
|
|
|
@ -14,7 +14,7 @@ LICENSE END */
|
||||||
use crate::diagnostics::{FileDiagnostics, Spanned, SpannedWithSourceFile};
|
use crate::diagnostics::{FileDiagnostics, Spanned, SpannedWithSourceFile};
|
||||||
use crate::expression_tree::{Expression, ExpressionSpanned, NamedReference};
|
use crate::expression_tree::{Expression, ExpressionSpanned, NamedReference};
|
||||||
use crate::parser::{syntax_nodes, SyntaxKind, SyntaxNodeWithSourceFile};
|
use crate::parser::{syntax_nodes, SyntaxKind, SyntaxNodeWithSourceFile};
|
||||||
use crate::typeregister::{Type, TypeRegister};
|
use crate::typeregister::{NativeClass, Type, TypeRegister};
|
||||||
use std::cell::{Cell, RefCell};
|
use std::cell::{Cell, RefCell};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::{Rc, Weak};
|
use std::rc::{Rc, Weak};
|
||||||
|
@ -100,6 +100,9 @@ pub struct Component {
|
||||||
/// When creating this component and inserting "children", append them to the children of
|
/// When creating this component and inserting "children", append them to the children of
|
||||||
/// the element pointer to by this field.
|
/// the element pointer to by this field.
|
||||||
pub child_insertion_point: RefCell<Option<ElementRc>>,
|
pub child_insertion_point: RefCell<Option<ElementRc>>,
|
||||||
|
|
||||||
|
/// Code to be inserted into the constructor
|
||||||
|
pub setup_code: RefCell<Vec<Expression>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component {
|
impl Component {
|
||||||
|
@ -647,6 +650,20 @@ impl Element {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn native_class(&self) -> Option<Rc<NativeClass>> {
|
||||||
|
let mut base_type = self.base_type.clone();
|
||||||
|
loop {
|
||||||
|
match &base_type {
|
||||||
|
Type::Component(component) => {
|
||||||
|
base_type = component.root_element.clone().borrow().base_type.clone();
|
||||||
|
}
|
||||||
|
Type::Builtin(builtin) => break Some(builtin.native_class.clone()),
|
||||||
|
Type::Native(native) => break Some(native.clone()),
|
||||||
|
_ => break None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_from_node(node: syntax_nodes::Type, diag: &mut FileDiagnostics, tr: &TypeRegister) -> Type {
|
fn type_from_node(node: syntax_nodes::Type, diag: &mut FileDiagnostics, tr: &TypeRegister) -> Type {
|
||||||
|
|
71
sixtyfps_compiler/passes/focus_item.rs
Normal file
71
sixtyfps_compiler/passes/focus_item.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
This file is also available under commercial licensing terms.
|
||||||
|
Please contact info@sixtyfps.io for more information.
|
||||||
|
LICENSE END */
|
||||||
|
//! This pass follows the initial_focus property on the root element to determine the initial focus item
|
||||||
|
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
diagnostics::BuildDiagnostics,
|
||||||
|
expression_tree::{BuiltinFunction, Expression},
|
||||||
|
object_tree::*,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub fn determine_initial_focus_item(component: &Rc<Component>, diag: &mut BuildDiagnostics) {
|
||||||
|
let mut focus_item_candidate = component.root_element.clone();
|
||||||
|
if !focus_item_candidate.borrow().bindings.contains_key("initial_focus") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let focus_item = loop {
|
||||||
|
let (focus_target, binding_location) = {
|
||||||
|
let mut focus_item_mut = focus_item_candidate.borrow_mut();
|
||||||
|
let binding = focus_item_mut.bindings.remove("initial_focus").unwrap();
|
||||||
|
focus_item_mut.property_declarations.remove("initial_focus");
|
||||||
|
if let Expression::ElementReference(target) = &binding.expression {
|
||||||
|
(target.upgrade().unwrap(), binding.clone())
|
||||||
|
} else {
|
||||||
|
diag.push_error(
|
||||||
|
"internal error: initial_focus property is of type ElementReference but received non-element-reference binding".to_owned(),
|
||||||
|
&binding,
|
||||||
|
);
|
||||||
|
break None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if focus_target.borrow().bindings.contains_key("initial_focus") {
|
||||||
|
focus_item_candidate = focus_target;
|
||||||
|
} else {
|
||||||
|
if let Some(native_class) = focus_target.borrow().native_class() {
|
||||||
|
if native_class.lookup_property("has_focus").is_some() {
|
||||||
|
break Some(focus_target.clone());
|
||||||
|
} else {
|
||||||
|
diag.push_error("element is not focusable".to_owned(), &binding_location);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
diag.push_error("internal error: item targeted by initial_focus does not have an underlying native class".to_owned(), &binding_location);
|
||||||
|
}
|
||||||
|
break None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(focus_item) = focus_item {
|
||||||
|
let setup_code = Expression::FunctionCall {
|
||||||
|
function: Box::new(Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem)),
|
||||||
|
arguments: vec![Expression::ElementReference(Rc::downgrade(&focus_item))],
|
||||||
|
};
|
||||||
|
|
||||||
|
component.setup_code.borrow_mut().push(setup_code);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any stray bindings in other inlined elements to avoid materializing them.
|
||||||
|
recurse_elem(&component.root_element, &(), &mut |element_rc, _| {
|
||||||
|
element_rc.borrow_mut().bindings.remove("initial_focus");
|
||||||
|
});
|
||||||
|
}
|
|
@ -9,7 +9,11 @@
|
||||||
LICENSE END */
|
LICENSE END */
|
||||||
//! Inline each object_tree::Component within the main Component
|
//! Inline each object_tree::Component within the main Component
|
||||||
|
|
||||||
use crate::{expression_tree::NamedReference, object_tree::*, typeregister::Type};
|
use crate::{
|
||||||
|
expression_tree::{Expression, NamedReference},
|
||||||
|
object_tree::*,
|
||||||
|
typeregister::Type,
|
||||||
|
};
|
||||||
use by_address::ByAddress;
|
use by_address::ByAddress;
|
||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
@ -113,6 +117,7 @@ fn inline_element(
|
||||||
// Now fixup all binding and reference
|
// Now fixup all binding and reference
|
||||||
for (_, e) in &mapping {
|
for (_, e) in &mapping {
|
||||||
visit_all_named_references(e, |nr| fixup_reference(nr, &mapping));
|
visit_all_named_references(e, |nr| fixup_reference(nr, &mapping));
|
||||||
|
visit_element_expressions(e, |expr, _| fixup_element_references(expr, &mapping));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,6 +168,19 @@ fn fixup_reference(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn fixup_element_references(
|
||||||
|
expr: &mut Expression,
|
||||||
|
mapping: &HashMap<ByAddress<ElementRc>, ElementRc>,
|
||||||
|
) {
|
||||||
|
if let Expression::ElementReference(element) = expr {
|
||||||
|
if let Some(new_element) =
|
||||||
|
element.upgrade().and_then(|e| mapping.get(&element_key(e.clone())))
|
||||||
|
{
|
||||||
|
*element = Rc::downgrade(new_element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn duplicate_transition(
|
fn duplicate_transition(
|
||||||
t: &Transition,
|
t: &Transition,
|
||||||
mapping: &mut HashMap<ByAddress<ElementRc>, Rc<RefCell<Element>>>,
|
mapping: &mut HashMap<ByAddress<ElementRc>, Rc<RefCell<Element>>>,
|
||||||
|
|
|
@ -383,6 +383,8 @@ impl Expression {
|
||||||
if let Some(elem) = elem_opt {
|
if let Some(elem) = elem_opt {
|
||||||
let prop_name = if let Some(second) = it.next() {
|
let prop_name = if let Some(second) = it.next() {
|
||||||
second
|
second
|
||||||
|
} else if matches!(ctx.property_type, Type::ElementReference) {
|
||||||
|
return Self::ElementReference(Rc::downgrade(&elem));
|
||||||
} else {
|
} else {
|
||||||
ctx.diag.push_error("Cannot take reference of an element".into(), &node);
|
ctx.diag.push_error("Cannot take reference of an element".into(), &node);
|
||||||
return Self::Invalid;
|
return Self::Invalid;
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
This file is also available under commercial licensing terms.
|
||||||
|
Please contact info@sixtyfps.io for more information.
|
||||||
|
LICENSE END */
|
||||||
|
|
||||||
|
|
||||||
|
X := Rectangle {
|
||||||
|
initial_focus: someRect;
|
||||||
|
// ^error{element is not focusable}
|
||||||
|
|
||||||
|
|
||||||
|
someRect := Rectangle {}
|
||||||
|
}
|
|
@ -50,6 +50,8 @@ pub enum Type {
|
||||||
|
|
||||||
Enumeration(Rc<Enumeration>),
|
Enumeration(Rc<Enumeration>),
|
||||||
EnumerationValue(EnumerationValue),
|
EnumerationValue(EnumerationValue),
|
||||||
|
|
||||||
|
ElementReference,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl core::cmp::PartialEq for Type {
|
impl core::cmp::PartialEq for Type {
|
||||||
|
@ -80,6 +82,7 @@ impl core::cmp::PartialEq for Type {
|
||||||
(Type::PathElements, Type::PathElements) => true,
|
(Type::PathElements, Type::PathElements) => true,
|
||||||
(Type::Easing, Type::Easing) => true,
|
(Type::Easing, Type::Easing) => true,
|
||||||
(Type::Enumeration(lhs), Type::Enumeration(rhs)) => lhs == rhs,
|
(Type::Enumeration(lhs), Type::Enumeration(rhs)) => lhs == rhs,
|
||||||
|
(Type::ElementReference, Type::ElementReference) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,6 +144,7 @@ impl Display for Type {
|
||||||
Type::EnumerationValue(value) => {
|
Type::EnumerationValue(value) => {
|
||||||
write!(f, "enum {}::{}", value.enumeration.name, value.to_string())
|
write!(f, "enum {}::{}", value.enumeration.name, value.to_string())
|
||||||
}
|
}
|
||||||
|
Type::ElementReference => write!(f, "element ref"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -165,6 +169,7 @@ impl Type {
|
||||||
| Self::Model
|
| Self::Model
|
||||||
| Self::Easing
|
| Self::Easing
|
||||||
| Self::Enumeration(_)
|
| Self::Enumeration(_)
|
||||||
|
| Self::ElementReference
|
||||||
| Self::Object(_)
|
| Self::Object(_)
|
||||||
| Self::Array(_) => true,
|
| Self::Array(_) => true,
|
||||||
Self::Component(c) => c.root_element.borrow().base_type == Type::Void,
|
Self::Component(c) => c.root_element.borrow().base_type == Type::Void,
|
||||||
|
@ -468,6 +473,7 @@ pub fn reserved_property(name: &str) -> Type {
|
||||||
("row", Type::Int32),
|
("row", Type::Int32),
|
||||||
("colspan", Type::Int32),
|
("colspan", Type::Int32),
|
||||||
("rowspan", Type::Int32),
|
("rowspan", Type::Int32),
|
||||||
|
("initial_focus", Type::ElementReference),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
{
|
{
|
||||||
|
|
|
@ -191,7 +191,9 @@ impl ComponentWindow {
|
||||||
self.0.clone().process_key_input(event, component)
|
self.0.clone().process_key_input(event, component)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn set_focus_item(
|
/// Clears the focus on any previously focused item and makes the provided
|
||||||
|
/// item the focus item, in order to receive future key events.
|
||||||
|
pub fn set_focus_item(
|
||||||
&self,
|
&self,
|
||||||
component: core::pin::Pin<crate::component::ComponentRef>,
|
component: core::pin::Pin<crate::component::ComponentRef>,
|
||||||
item: Pin<VRef<crate::items::ItemVTable>>,
|
item: Pin<VRef<crate::items::ItemVTable>>,
|
||||||
|
@ -601,4 +603,15 @@ pub mod ffi {
|
||||||
let window = &*(handle as *const ComponentWindow);
|
let window = &*(handle as *const ComponentWindow);
|
||||||
window.free_graphics_resources(component)
|
window.free_graphics_resources(component)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the focus item.
|
||||||
|
#[no_mangle]
|
||||||
|
pub unsafe extern "C" fn sixtyfps_component_window_set_focus_item(
|
||||||
|
handle: *const ComponentWindowOpaque,
|
||||||
|
component: Pin<VRef<ComponentVTable>>,
|
||||||
|
item: Pin<VRef<ItemVTable>>,
|
||||||
|
) {
|
||||||
|
let window = &*(handle as *const ComponentWindow);
|
||||||
|
window.set_focus_item(component, item)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -840,6 +840,10 @@ pub fn instantiate<'id>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for extra_init_code in component_type.original.setup_code.borrow().iter() {
|
||||||
|
eval::eval_expression(extra_init_code, instance_ref, &mut Default::default());
|
||||||
|
}
|
||||||
|
|
||||||
component_box
|
component_box
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -198,6 +198,7 @@ pub fn eval_expression(
|
||||||
Expression::BuiltinFunctionReference(_) => panic!(
|
Expression::BuiltinFunctionReference(_) => panic!(
|
||||||
"naked builtin function reference not allowed, should be handled by function call"
|
"naked builtin function reference not allowed, should be handled by function call"
|
||||||
),
|
),
|
||||||
|
Expression::ElementReference(_) => todo!("Element references are only supported in the context of built-in function calls at the moment"),
|
||||||
Expression::PropertyReference(NamedReference { element, name }) => {
|
Expression::PropertyReference(NamedReference { element, name }) => {
|
||||||
load_property(component, &element.upgrade().unwrap(), name.as_ref()).unwrap()
|
load_property(component, &element.upgrade().unwrap(), name.as_ref()).unwrap()
|
||||||
}
|
}
|
||||||
|
@ -241,9 +242,9 @@ pub fn eval_expression(
|
||||||
}
|
}
|
||||||
v
|
v
|
||||||
}
|
}
|
||||||
Expression::FunctionCall { function, arguments } => {
|
Expression::FunctionCall { function, arguments } => match &**function {
|
||||||
let a = arguments.iter().map(|e| eval_expression(e, component, local_context));
|
Expression::SignalReference(NamedReference { element, name }) => {
|
||||||
if let Expression::SignalReference(NamedReference { element, name }) = &**function {
|
let a = arguments.iter().map(|e| eval_expression(e, component, local_context));
|
||||||
let element = element.upgrade().unwrap();
|
let element = element.upgrade().unwrap();
|
||||||
generativity::make_guard!(guard);
|
generativity::make_guard!(guard);
|
||||||
let enclosing_component =
|
let enclosing_component =
|
||||||
|
@ -266,19 +267,44 @@ pub fn eval_expression(
|
||||||
}
|
}
|
||||||
|
|
||||||
Value::Void
|
Value::Void
|
||||||
} else if let Expression::BuiltinFunctionReference(funcref) = &**function {
|
|
||||||
match funcref {
|
|
||||||
BuiltinFunction::GetWindowScaleFactor => {
|
|
||||||
Value::Number(window_ref(component).unwrap().scale_factor() as _)
|
|
||||||
}
|
|
||||||
BuiltinFunction::Debug => {
|
|
||||||
println!("{:?}", a);
|
|
||||||
Value::Void
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
panic!("call of something not a signal")
|
|
||||||
}
|
}
|
||||||
|
Expression::BuiltinFunctionReference(BuiltinFunction::GetWindowScaleFactor) => {
|
||||||
|
Value::Number(window_ref(component).unwrap().scale_factor() as _)
|
||||||
|
}
|
||||||
|
Expression::BuiltinFunctionReference(BuiltinFunction::Debug) => {
|
||||||
|
let a = arguments.iter().map(|e| eval_expression(e, component, local_context));
|
||||||
|
println!("{:?}", a);
|
||||||
|
Value::Void
|
||||||
|
}
|
||||||
|
Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem) => {
|
||||||
|
if arguments.len() != 1 {
|
||||||
|
panic!("internal error: incorrect argument count to SetFocusItem")
|
||||||
|
}
|
||||||
|
if let Expression::ElementReference(focus_item) = &arguments[0] {
|
||||||
|
generativity::make_guard!(guard);
|
||||||
|
let component_ref: Pin<vtable::VRef<corelib::component::ComponentVTable>> = unsafe {
|
||||||
|
Pin::new_unchecked(vtable::VRef::from_raw(
|
||||||
|
core::ptr::NonNull::from(&component.component_type.ct).cast(),
|
||||||
|
core::ptr::NonNull::from(&*component.as_ptr()),
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
let focus_item = focus_item.upgrade().unwrap();
|
||||||
|
let enclosing_component =
|
||||||
|
enclosing_component_for_element(&focus_item, component, guard);
|
||||||
|
let component_type = enclosing_component.component_type;
|
||||||
|
|
||||||
|
let item_info = &component_type.items[focus_item.borrow().id.as_str()];
|
||||||
|
let item =
|
||||||
|
unsafe { item_info.item_from_component(enclosing_component.as_ptr()) };
|
||||||
|
|
||||||
|
window_ref(component).unwrap().set_focus_item(component_ref, item);
|
||||||
|
Value::Void
|
||||||
|
} else {
|
||||||
|
panic!("internal error: argument to SetFocusItem must be an element")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => panic!("call of something not a signal"),
|
||||||
}
|
}
|
||||||
Expression::SelfAssignment { lhs, rhs, op } => {
|
Expression::SelfAssignment { lhs, rhs, op } => {
|
||||||
let rhs = eval_expression(&**rhs, component, local_context);
|
let rhs = eval_expression(&**rhs, component, local_context);
|
||||||
|
|
90
tests/cases/focus/initial_focus.60
Normal file
90
tests/cases/focus/initial_focus.60
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
This file is also available under commercial licensing terms.
|
||||||
|
Please contact info@sixtyfps.io for more information.
|
||||||
|
LICENSE END */
|
||||||
|
|
||||||
|
TestCase := Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
initial_focus: input1;
|
||||||
|
|
||||||
|
input1 := TextInput {
|
||||||
|
width: parent.width;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
input2 := TextInput {
|
||||||
|
y: 200px;
|
||||||
|
width: parent.width;
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
property<bool> input1_focused: input1.has_focus;
|
||||||
|
property<string> input1_text: input1.text;
|
||||||
|
property<bool> input2_focused: input2.has_focus;
|
||||||
|
property<string> input2_text: input2.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new();
|
||||||
|
let instance = instance.as_ref();
|
||||||
|
assert!(instance.get_input1_focused());
|
||||||
|
assert!(!instance.get_input2_focused());
|
||||||
|
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(instance, "Only for field 1");
|
||||||
|
assert_eq!(instance.get_input1_text(), "Only for field 1");
|
||||||
|
assert_eq!(instance.get_input2_text(), "");
|
||||||
|
|
||||||
|
sixtyfps::testing::send_mouse_click(instance, 150., 100.);
|
||||||
|
assert!(instance.get_input1_focused());
|
||||||
|
assert!(!instance.get_input2_focused());
|
||||||
|
|
||||||
|
sixtyfps::testing::send_mouse_click(instance, 150., 300.);
|
||||||
|
assert!(!instance.get_input1_focused());
|
||||||
|
assert!(instance.get_input2_focused());
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
TestCase instance;
|
||||||
|
assert(instance.get_input1_focused());
|
||||||
|
assert(!instance.get_input2_focused());
|
||||||
|
|
||||||
|
sixtyfps::testing::send_keyboard_string_sequence(instance, "Only for field 1");
|
||||||
|
assert_eq(instance.get_input1_text(), "Only for field 1");
|
||||||
|
assert_eq(instance.get_input2_text(), "");
|
||||||
|
|
||||||
|
sixtyfps::testing::send_mouse_click(instance, 150., 100.);
|
||||||
|
assert(instance.get_input1_focused());
|
||||||
|
assert(!instance.get_input2_focused());
|
||||||
|
|
||||||
|
sixtyfps::testing::send_mouse_click(instance, 150., 300.);
|
||||||
|
assert(!instance.get_input1_focused());
|
||||||
|
assert(instance.get_input2_focused());
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new sixtyfps.TestCase();
|
||||||
|
assert(instance.input1_focused);
|
||||||
|
assert(!instance.input2_focused);
|
||||||
|
|
||||||
|
instance.send_keyboard_string_sequence("Only for field 1");
|
||||||
|
assert.equal(instance.input1_text, "Only for field 1");
|
||||||
|
assert.equal(instance.input2_text, "");
|
||||||
|
|
||||||
|
instance.send_mouse_click(150., 100.);
|
||||||
|
assert(instance.input1_focused);
|
||||||
|
assert(!instance.input2_focused);
|
||||||
|
|
||||||
|
instance.send_mouse_click(150., 300.);
|
||||||
|
assert(!instance.input1_focused);
|
||||||
|
assert(instance.input2_focused);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
53
tests/cases/focus/initial_focus_through_component.60
Normal file
53
tests/cases/focus/initial_focus_through_component.60
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
This file is also available under commercial licensing terms.
|
||||||
|
Please contact info@sixtyfps.io for more information.
|
||||||
|
LICENSE END */
|
||||||
|
|
||||||
|
TextComponent := Rectangle {
|
||||||
|
property<bool> has_focus: my_text_input.has_focus;
|
||||||
|
initial_focus: my_text_input;
|
||||||
|
my_text_input := TextInput { }
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase := Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
initial_focus: input2;
|
||||||
|
|
||||||
|
input1 := TextComponent {
|
||||||
|
}
|
||||||
|
input2 := TextComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
property<bool> input1_focused: input1.has_focus;
|
||||||
|
property<bool> input2_focused: input2.has_focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new();
|
||||||
|
let instance = instance.as_ref();
|
||||||
|
assert!(!instance.get_input1_focused());
|
||||||
|
assert!(instance.get_input2_focused());
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
TestCase instance;
|
||||||
|
assert(!instance.get_input1_focused());
|
||||||
|
assert(instance.get_input2_focused());
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new sixtyfps.TestCase();
|
||||||
|
assert(!instance.input1_focused);
|
||||||
|
assert(instance.input2_focused);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
55
tests/cases/focus/initial_focus_through_layout.60
Normal file
55
tests/cases/focus/initial_focus_through_layout.60
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/* LICENSE BEGIN
|
||||||
|
This file is part of the SixtyFPS Project -- https://sixtyfps.io
|
||||||
|
Copyright (c) 2020 Olivier Goffart <olivier.goffart@sixtyfps.io>
|
||||||
|
Copyright (c) 2020 Simon Hausmann <simon.hausmann@sixtyfps.io>
|
||||||
|
|
||||||
|
SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
This file is also available under commercial licensing terms.
|
||||||
|
Please contact info@sixtyfps.io for more information.
|
||||||
|
LICENSE END */
|
||||||
|
|
||||||
|
TextComponent := GridLayout {
|
||||||
|
property<bool> has_focus: my_text_input.has_focus;
|
||||||
|
initial_focus: my_text_input;
|
||||||
|
Row {
|
||||||
|
my_text_input := TextInput { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TestCase := Rectangle {
|
||||||
|
width: 400px;
|
||||||
|
height: 400px;
|
||||||
|
initial_focus: input2;
|
||||||
|
|
||||||
|
input1 := TextComponent {
|
||||||
|
}
|
||||||
|
input2 := TextComponent {
|
||||||
|
}
|
||||||
|
|
||||||
|
property<bool> input1_focused: input1.has_focus;
|
||||||
|
property<bool> input2_focused: input2.has_focus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
```rust
|
||||||
|
let instance = TestCase::new();
|
||||||
|
let instance = instance.as_ref();
|
||||||
|
assert!(!instance.get_input1_focused());
|
||||||
|
assert!(instance.get_input2_focused());
|
||||||
|
```
|
||||||
|
|
||||||
|
```cpp
|
||||||
|
TestCase instance;
|
||||||
|
assert(!instance.get_input1_focused());
|
||||||
|
assert(instance.get_input2_focused());
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
var instance = new sixtyfps.TestCase();
|
||||||
|
assert(!instance.input1_focused);
|
||||||
|
assert(instance.input2_focused);
|
||||||
|
```
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -143,6 +143,7 @@ fn gen_corelib(include_dir: &Path) -> anyhow::Result<()> {
|
||||||
"sixtyfps_component_window_get_scale_factor",
|
"sixtyfps_component_window_get_scale_factor",
|
||||||
"sixtyfps_component_window_set_scale_factor",
|
"sixtyfps_component_window_set_scale_factor",
|
||||||
"sixtyfps_component_window_free_graphics_resources",
|
"sixtyfps_component_window_free_graphics_resources",
|
||||||
|
"sixtyfps_component_window_set_focus_item",
|
||||||
"sixtyfps_new_path_elements",
|
"sixtyfps_new_path_elements",
|
||||||
"sixtyfps_new_path_events",
|
"sixtyfps_new_path_events",
|
||||||
]
|
]
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue