Skip to content

Commit

Permalink
Add support for the initial_focus synthetic property
Browse files Browse the repository at this point in the history
Setting it will translate to a set_focus_item call in the constructor.

This implements parts of slint-ui#55
  • Loading branch information
tronical committed Sep 30, 2020
1 parent 2050f08 commit 9ad8968
Show file tree
Hide file tree
Showing 22 changed files with 541 additions and 35 deletions.
5 changes: 5 additions & 0 deletions api/sixtyfps-cpp/include/sixtyfps.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,11 @@ struct ComponentWindow
&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:
cbindgen_private::ComponentWindowOpaque inner;
};
Expand Down
3 changes: 2 additions & 1 deletion api/sixtyfps-node/native/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,8 @@ fn to_eval_value<'cx>(
| Type::Signal { .. }
| Type::Easing
| 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"),
}
}

Expand Down
1 change: 1 addition & 0 deletions docs/builtin_elements.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
* **`horizontal_alignment`**, **`vertical_alignment`** (*FIXME: enum*): How is the text aligned
within the item
* **`has_focus`** (*bool*): Set to true when item is focused and receives keyboard events.


### Example
Expand Down
51 changes: 51 additions & 0 deletions docs/langref.md
Original file line number Diff line number Diff line change
Expand Up @@ -524,3 +524,54 @@ App := Rectangle {
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 {}
}
}
```
16 changes: 15 additions & 1 deletion sixtyfps_compiler/expression_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ impl Hash for NamedReference {
pub enum BuiltinFunction {
GetWindowScaleFactor,
Debug,
SetFocusItem,
}

impl BuiltinFunction {
Expand All @@ -55,6 +56,10 @@ impl BuiltinFunction {
BuiltinFunction::Debug => {
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],
},
}
}
}
Expand Down Expand Up @@ -183,6 +188,10 @@ pub enum Expression {
/// Reference to a function built into the run-time, implemented natively
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
///
/// Example: `idx` in `for xxx[idx] in ...`. The element is the reference to the
Expand Down Expand Up @@ -314,6 +323,7 @@ impl Expression {
element.upgrade().unwrap().borrow().lookup_property(name)
}
Expression::BuiltinFunctionReference(funcref) => funcref.ty(),
Expression::ElementReference(_) => Type::ElementReference,
Expression::RepeaterIndexReference { .. } => Type::Int32,
Expression::RepeaterModelReference { element } => {
if let Expression::Cast { from, .. } = element
Expand Down Expand Up @@ -399,6 +409,7 @@ impl Expression {
Expression::PropertyReference { .. } => {}
Expression::FunctionParameterReference { .. } => {}
Expression::BuiltinFunctionReference { .. } => {}
Expression::ElementReference(_) => {}
Expression::ObjectAccess { base, .. } => visitor(&**base),
Expression::RepeaterIndexReference { .. } => {}
Expression::RepeaterModelReference { .. } => {}
Expand Down Expand Up @@ -461,6 +472,7 @@ impl Expression {
Expression::PropertyReference { .. } => {}
Expression::FunctionParameterReference { .. } => {}
Expression::BuiltinFunctionReference { .. } => {}
Expression::ElementReference(_) => {}
Expression::ObjectAccess { base, .. } => visitor(&mut **base),
Expression::RepeaterIndexReference { .. } => {}
Expression::RepeaterModelReference { .. } => {}
Expand Down Expand Up @@ -522,6 +534,7 @@ impl Expression {
Expression::SignalReference { .. } => false,
Expression::PropertyReference { .. } => false,
Expression::BuiltinFunctionReference { .. } => false,
Expression::ElementReference(_) => false,
Expression::RepeaterIndexReference { .. } => false,
Expression::RepeaterModelReference { .. } => false,
Expression::FunctionParameterReference { .. } => false,
Expand Down Expand Up @@ -678,7 +691,8 @@ impl Expression {
| Type::Native(_)
| Type::Signal { .. }
| Type::Function { .. }
| Type::Void => Expression::Invalid,
| Type::Void
| Type::ElementReference => Expression::Invalid,
Type::Float32 => Expression::NumberLiteral(0., Unit::None),
Type::Int32 => Expression::NumberLiteral(0., Unit::None),
Type::String => Expression::StringLiteral(String::new()),
Expand Down
39 changes: 35 additions & 4 deletions sixtyfps_compiler/generator/cpp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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((
Access::Public,
Declaration::Function(Function {
Expand Down Expand Up @@ -1064,7 +1068,11 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com
"[](auto... args){ (std::cout << ... << args) << std::endl; return nullptr; }"
.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 } => {
let access = access_member(
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
Expand Down Expand Up @@ -1136,10 +1144,33 @@ fn compile_expression(e: &crate::expression_tree::Expression, component: &Rc<Com

format!("[&]{{ {} }}()", x.join(";"))
}
Expression::FunctionCall { function, arguments } => {
let mut args = arguments.iter().map(|e| compile_expression(e, component));
format!("{}({})", compile_expression(&function, component), args.join(", "))
}
Expression::FunctionCall { function, arguments } => match &**function {
Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem) => {
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 } => {
let rhs = compile_expression(&*rhs, &component);
compile_assignment(lhs, *op, rhs, component)
Expand Down
51 changes: 39 additions & 12 deletions sixtyfps_compiler/generator/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
#(#resource_symbols)*

Expand Down Expand Up @@ -674,6 +678,7 @@ fn generate_component(
let self_pinned = std::rc::Rc::pin(self_);
self_pinned.self_weak.set(PinWeak::downgrade(self_pinned.clone())).map_err(|_|())
.expect("Can only be pinned once");
let _self = self_pinned.as_ref();
#(#init)*
self_pinned
}
Expand Down Expand Up @@ -878,7 +883,9 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
quote!(#window_ref.scale_factor)
}
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 } => {
let access = access_member(
&element.upgrade().unwrap().borrow().base_type.as_component().root_element,
Expand Down Expand Up @@ -932,19 +939,39 @@ fn compile_expression(e: &Expression, component: &Rc<Component>) -> TokenStream
false,
),
Expression::FunctionCall { function, arguments } => {
let f = compile_expression(function, &component);
let a = arguments.iter().map(|a| compile_expression(a, &component));
if let Type::Signal { args } = function.ty() {
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()),*)}
match &**function {
Expression::BuiltinFunctionReference(BuiltinFunction::SetFocusItem) => {
if arguments.len() != 1 {
panic!("internal error: incorrect argument count to SetFocusItem call");
}
if let Expression::ElementReference(focus_item) = &arguments[0] {
let item = format_ident!("{}", focus_item.upgrade().unwrap().borrow().id);
let window_ref = window_ref_expression(component);
quote!(
#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 {
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 } => {
let rhs = compile_expression(&*rhs, &component);
Expand Down
2 changes: 2 additions & 0 deletions sixtyfps_compiler/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ mod passes {
pub mod collect_resources;
pub mod compile_paths;
pub mod deduplicate_property_read;
pub mod focus_item;
pub mod inlining;
pub mod lower_layout;
pub mod lower_states;
Expand Down Expand Up @@ -122,6 +123,7 @@ pub fn run_passes(
passes::inlining::inline(doc);
passes::compile_paths::compile_paths(&doc.root_component, &doc.local_registry, diag);
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::collect_resources::collect_resources(&doc.root_component);
doc.root_component.embed_file_resources.set(compiler_config.embed_resources);
Expand Down
19 changes: 18 additions & 1 deletion sixtyfps_compiler/object_tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ LICENSE END */
use crate::diagnostics::{FileDiagnostics, Spanned, SpannedWithSourceFile};
use crate::expression_tree::{Expression, ExpressionSpanned, NamedReference};
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::collections::HashMap;
use std::rc::{Rc, Weak};
Expand Down Expand Up @@ -100,6 +100,9 @@ pub struct Component {
/// When creating this component and inserting "children", append them to the children of
/// the element pointer to by this field.
pub child_insertion_point: RefCell<Option<ElementRc>>,

/// Code to be inserted into the constructor
pub setup_code: RefCell<Vec<Expression>>,
}

impl Component {
Expand Down Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 9ad8968

Please sign in to comment.