mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Allow to give a name to for
and if
This commit is contained in:
parent
52e85933ed
commit
42c25248a8
8 changed files with 121 additions and 49 deletions
|
@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
|
||||||
- One can now omit the type of a two way binding property
|
- One can now omit the type of a two way binding property
|
||||||
- One can declare callback aliases
|
- One can declare callback aliases
|
||||||
- `abs()` function to get the absolute value
|
- `abs()` function to get the absolute value
|
||||||
|
- Ability to name the root element of an `if` or `for`
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|
|
@ -546,8 +546,19 @@ clicked => { ; }
|
||||||
|
|
||||||
## Repetition
|
## Repetition
|
||||||
|
|
||||||
The `for` syntax
|
The `for`-`in` syntax can be used to repeat an element.
|
||||||
|
|
||||||
|
The sytax look like this: `for name[index] in model : id := Element { ... }`
|
||||||
|
|
||||||
|
The *model* can be of the following type:
|
||||||
|
- an integer, in which case the element will be repeated that amount of time
|
||||||
|
- an array type or a model declared natively, in which case the element will be instantiated for each element in the array or model.
|
||||||
|
|
||||||
|
The *name* will be available for lookup within the element and is going to be like a pseudo-property set to the
|
||||||
|
value of the model. The *index* is optional and will be set to the index of this element in the model.
|
||||||
|
The *id* is also optional.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
```60
|
```60
|
||||||
Example := Window {
|
Example := Window {
|
||||||
|
@ -562,6 +573,37 @@ Example := Window {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
```60
|
||||||
|
Example := Window {
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
property <[{foo: string, col: color}]> model: [
|
||||||
|
{foo: "abc", col: #f00 },
|
||||||
|
{foo: "def", col: #00f },
|
||||||
|
];
|
||||||
|
VerticalLayout {
|
||||||
|
for data in root.model: my_repeated_text := Text {
|
||||||
|
color: data.col;
|
||||||
|
text: data.foo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Conditional element
|
||||||
|
|
||||||
|
Similar to `for`, the `if` construct can instentiate element only if a given condition is true.
|
||||||
|
The syntax is `if (condition) : id := Element { ... }`
|
||||||
|
|
||||||
|
```60
|
||||||
|
Example := Window {
|
||||||
|
height: 50px;
|
||||||
|
width: 50px;
|
||||||
|
if (true) : foo := Rectangle { background: blue; }
|
||||||
|
if (false) : Rectangle { background: red; }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Animations
|
## Animations
|
||||||
|
|
||||||
Simple animation that animates a property can be declared with `animate` like so:
|
Simple animation that animates a property can be declared with `animate` like so:
|
||||||
|
|
|
@ -728,26 +728,14 @@ impl Element {
|
||||||
|
|
||||||
for se in node.children() {
|
for se in node.children() {
|
||||||
if se.kind() == SyntaxKind::SubElement {
|
if se.kind() == SyntaxKind::SubElement {
|
||||||
let id = identifier_text(&se).unwrap_or_default();
|
|
||||||
if matches!(id.as_ref(), "parent" | "self" | "root") {
|
|
||||||
diag.push_error(
|
|
||||||
format!("'{}' is a reserved id", id),
|
|
||||||
&se.child_token(SyntaxKind::Identifier).unwrap(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if let Some(element_node) = se.child_node(SyntaxKind::Element) {
|
|
||||||
let parent_type = r.borrow().base_type.clone();
|
let parent_type = r.borrow().base_type.clone();
|
||||||
r.borrow_mut().children.push(Element::from_node(
|
r.borrow_mut().children.push(Element::from_sub_element_node(
|
||||||
element_node.into(),
|
se.into(),
|
||||||
id,
|
|
||||||
parent_type,
|
parent_type,
|
||||||
component_child_insertion_point,
|
component_child_insertion_point,
|
||||||
diag,
|
diag,
|
||||||
tr,
|
tr,
|
||||||
));
|
));
|
||||||
} else {
|
|
||||||
assert!(diag.has_error());
|
|
||||||
}
|
|
||||||
} else if se.kind() == SyntaxKind::RepeatedElement {
|
} else if se.kind() == SyntaxKind::RepeatedElement {
|
||||||
let rep = Element::from_repeated_node(
|
let rep = Element::from_repeated_node(
|
||||||
se.into(),
|
se.into(),
|
||||||
|
@ -832,6 +820,30 @@ impl Element {
|
||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn from_sub_element_node(
|
||||||
|
node: syntax_nodes::SubElement,
|
||||||
|
parent_type: Type,
|
||||||
|
component_child_insertion_point: &mut Option<ChildrenInsertionPoint>,
|
||||||
|
diag: &mut BuildDiagnostics,
|
||||||
|
tr: &TypeRegister,
|
||||||
|
) -> ElementRc {
|
||||||
|
let id = identifier_text(&node).unwrap_or_default();
|
||||||
|
if matches!(id.as_ref(), "parent" | "self" | "root") {
|
||||||
|
diag.push_error(
|
||||||
|
format!("'{}' is a reserved id", id),
|
||||||
|
&node.child_token(SyntaxKind::Identifier).unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Element::from_node(
|
||||||
|
node.Element(),
|
||||||
|
id,
|
||||||
|
parent_type,
|
||||||
|
component_child_insertion_point,
|
||||||
|
diag,
|
||||||
|
tr,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn from_repeated_node(
|
fn from_repeated_node(
|
||||||
node: syntax_nodes::RepeatedElement,
|
node: syntax_nodes::RepeatedElement,
|
||||||
parent: &ElementRc,
|
parent: &ElementRc,
|
||||||
|
@ -860,10 +872,9 @@ impl Element {
|
||||||
is_conditional_element: false,
|
is_conditional_element: false,
|
||||||
is_listview,
|
is_listview,
|
||||||
};
|
};
|
||||||
let e = Element::from_node(
|
let e = Element::from_sub_element_node(
|
||||||
node.Element(),
|
node.SubElement(),
|
||||||
String::new(),
|
parent.borrow().base_type.clone(),
|
||||||
parent.borrow().base_type.to_owned(),
|
|
||||||
component_child_insertion_point,
|
component_child_insertion_point,
|
||||||
diag,
|
diag,
|
||||||
tr,
|
tr,
|
||||||
|
@ -886,9 +897,8 @@ impl Element {
|
||||||
is_conditional_element: true,
|
is_conditional_element: true,
|
||||||
is_listview: None,
|
is_listview: None,
|
||||||
};
|
};
|
||||||
let e = Element::from_node(
|
let e = Element::from_sub_element_node(
|
||||||
node.Element(),
|
node.SubElement(),
|
||||||
String::new(),
|
|
||||||
parent_type,
|
parent_type,
|
||||||
component_child_insertion_point,
|
component_child_insertion_point,
|
||||||
diag,
|
diag,
|
||||||
|
|
|
@ -323,9 +323,9 @@ declare_syntax! {
|
||||||
Element -> [ ?QualifiedName, *PropertyDeclaration, *Binding, *CallbackConnection,
|
Element -> [ ?QualifiedName, *PropertyDeclaration, *Binding, *CallbackConnection,
|
||||||
*CallbackDeclaration, *SubElement, *RepeatedElement, *PropertyAnimation,
|
*CallbackDeclaration, *SubElement, *RepeatedElement, *PropertyAnimation,
|
||||||
*TwoWayBinding, *States, *Transitions, ?ChildrenPlaceholder ],
|
*TwoWayBinding, *States, *Transitions, ?ChildrenPlaceholder ],
|
||||||
RepeatedElement -> [ ?DeclaredIdentifier, ?RepeatedIndex, Expression , Element],
|
RepeatedElement -> [ ?DeclaredIdentifier, ?RepeatedIndex, Expression , SubElement],
|
||||||
RepeatedIndex -> [],
|
RepeatedIndex -> [],
|
||||||
ConditionalElement -> [ Expression , Element],
|
ConditionalElement -> [ Expression , SubElement],
|
||||||
CallbackDeclaration -> [ DeclaredIdentifier, *Type, ?ReturnType, ?TwoWayBinding ],
|
CallbackDeclaration -> [ DeclaredIdentifier, *Type, ?ReturnType, ?TwoWayBinding ],
|
||||||
/// `-> type` (but without the ->)
|
/// `-> type` (but without the ->)
|
||||||
ReturnType -> [Type],
|
ReturnType -> [Type],
|
||||||
|
|
|
@ -125,7 +125,7 @@ pub fn parse_element_content(p: &mut impl Parser) {
|
||||||
fn parse_sub_element(p: &mut impl Parser) {
|
fn parse_sub_element(p: &mut impl Parser) {
|
||||||
let mut p = p.start_node(SyntaxKind::SubElement);
|
let mut p = p.start_node(SyntaxKind::SubElement);
|
||||||
if p.nth(1).kind() == SyntaxKind::ColonEqual {
|
if p.nth(1).kind() == SyntaxKind::ColonEqual {
|
||||||
assert!(p.expect(SyntaxKind::Identifier));
|
p.expect(SyntaxKind::Identifier);
|
||||||
p.expect(SyntaxKind::ColonEqual);
|
p.expect(SyntaxKind::ColonEqual);
|
||||||
}
|
}
|
||||||
parse_element(&mut *p);
|
parse_element(&mut *p);
|
||||||
|
@ -136,6 +136,7 @@ fn parse_sub_element(p: &mut impl Parser) {
|
||||||
/// for xx in mm: Elem { }
|
/// for xx in mm: Elem { }
|
||||||
/// for [idx] in mm: Elem { }
|
/// for [idx] in mm: Elem { }
|
||||||
/// for xx [idx] in foo.bar: Elem { }
|
/// for xx [idx] in foo.bar: Elem { }
|
||||||
|
/// for _ in (xxx()): blah := Elem { Elem{} }
|
||||||
/// ```
|
/// ```
|
||||||
/// Must consume at least one token
|
/// Must consume at least one token
|
||||||
fn parse_repeated_element(p: &mut impl Parser) {
|
fn parse_repeated_element(p: &mut impl Parser) {
|
||||||
|
@ -155,19 +156,20 @@ fn parse_repeated_element(p: &mut impl Parser) {
|
||||||
if p.peek().as_str() != "in" {
|
if p.peek().as_str() != "in" {
|
||||||
p.error("Invalid 'for' syntax: there should be a 'in' token");
|
p.error("Invalid 'for' syntax: there should be a 'in' token");
|
||||||
drop(p.start_node(SyntaxKind::Expression));
|
drop(p.start_node(SyntaxKind::Expression));
|
||||||
drop(p.start_node(SyntaxKind::Element));
|
drop(p.start_node(SyntaxKind::SubElement).start_node(SyntaxKind::Element));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
p.consume(); // "in"
|
p.consume(); // "in"
|
||||||
parse_expression(&mut *p);
|
parse_expression(&mut *p);
|
||||||
p.expect(SyntaxKind::Colon);
|
p.expect(SyntaxKind::Colon);
|
||||||
parse_element(&mut *p);
|
parse_sub_element(&mut *p);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, parser_test)]
|
#[cfg_attr(test, parser_test)]
|
||||||
/// ```test,ConditionalElement
|
/// ```test,ConditionalElement
|
||||||
/// if (condition) : Elem { }
|
/// if (condition) : Elem { }
|
||||||
/// if (foo ? bar : xx) : Elem { foo:bar; Elem {}}
|
/// if (foo ? bar : xx) : Elem { foo:bar; Elem {}}
|
||||||
|
/// if (true) : foo := Elem {}
|
||||||
/// ```
|
/// ```
|
||||||
/// Must consume at least one token
|
/// Must consume at least one token
|
||||||
fn parse_if_element(p: &mut impl Parser) {
|
fn parse_if_element(p: &mut impl Parser) {
|
||||||
|
@ -176,15 +178,15 @@ fn parse_if_element(p: &mut impl Parser) {
|
||||||
p.consume(); // "if"
|
p.consume(); // "if"
|
||||||
if !p.expect(SyntaxKind::LParent) {
|
if !p.expect(SyntaxKind::LParent) {
|
||||||
drop(p.start_node(SyntaxKind::Expression));
|
drop(p.start_node(SyntaxKind::Expression));
|
||||||
drop(p.start_node(SyntaxKind::Element));
|
drop(p.start_node(SyntaxKind::SubElement).start_node(SyntaxKind::Element));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parse_expression(&mut *p);
|
parse_expression(&mut *p);
|
||||||
if !p.expect(SyntaxKind::RParent) || !p.expect(SyntaxKind::Colon) {
|
if !p.expect(SyntaxKind::RParent) || !p.expect(SyntaxKind::Colon) {
|
||||||
drop(p.start_node(SyntaxKind::Element));
|
drop(p.start_node(SyntaxKind::SubElement).start_node(SyntaxKind::Element));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
parse_element(&mut *p);
|
parse_sub_element(&mut *p);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(test, parser_test)]
|
#[cfg_attr(test, parser_test)]
|
||||||
|
|
|
@ -27,6 +27,13 @@ SubElements := Rectangle {
|
||||||
root := Rectangle {
|
root := Rectangle {
|
||||||
// ^error{'root' is a reserved id}
|
// ^error{'root' is a reserved id}
|
||||||
self := Rectangle {}
|
self := Rectangle {}
|
||||||
|
// ^error{'self' is a reserved id}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (true) : root := Rectangle {
|
||||||
|
// ^error{'root' is a reserved id}
|
||||||
|
|
||||||
|
for _ in 1 : self := Rectangle { }
|
||||||
// ^error{'self' is a reserved id}
|
// ^error{'self' is a reserved id}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,8 +31,16 @@ Hello := Rectangle {
|
||||||
// ^error{Cannot access id 'ccc'}
|
// ^error{Cannot access id 'ccc'}
|
||||||
}
|
}
|
||||||
|
|
||||||
Text { text: ccc.text; }
|
for plop in 0 : named_for := Rectangle {
|
||||||
|
Text { color: named_for.background; }
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: ccc.text;
|
||||||
// ^error{Cannot access id 'ccc'}
|
// ^error{Cannot access id 'ccc'}
|
||||||
|
color: named_for.background;
|
||||||
|
// ^error{Cannot access id 'named_for'}
|
||||||
|
}
|
||||||
|
|
||||||
for aaa in aaa.text: Rectangle {
|
for aaa in aaa.text: Rectangle {
|
||||||
// ^error{Cannot convert string to model}
|
// ^error{Cannot convert string to model}
|
||||||
|
|
|
@ -16,16 +16,18 @@ Extra2 := Rectangle {
|
||||||
property<int> top_level;
|
property<int> top_level;
|
||||||
property<int> value;
|
property<int> value;
|
||||||
callback update_value;
|
callback update_value;
|
||||||
for aaa[r] in [[10, top_level], [2, 3]] : Rectangle {
|
for aaa[r] in [[10, top_level], [2, 3]] : blah := Rectangle {
|
||||||
width: parent.width;
|
width: parent.width;
|
||||||
height: root.height;
|
height: root.height;
|
||||||
|
property <int> some_value: 1000;
|
||||||
for bb[l] in aaa : TouchArea {
|
for bb[l] in aaa : TouchArea {
|
||||||
|
property <int> some_value: 1515;
|
||||||
width: 10phx;
|
width: 10phx;
|
||||||
height: 10phx;
|
height: 10phx;
|
||||||
x: r*10phx;
|
x: r*10phx;
|
||||||
y: l*10phx;
|
y: l*10phx;
|
||||||
clicked => {
|
clicked => {
|
||||||
root.value += bb;
|
root.value += bb + blah.some_value;
|
||||||
update_value();
|
update_value();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -96,13 +98,13 @@ auto handle = TestCase::create();
|
||||||
const TestCase &instance = *handle;
|
const TestCase &instance = *handle;
|
||||||
|
|
||||||
sixtyfps::testing::send_mouse_click(&instance, 5., 5.);
|
sixtyfps::testing::send_mouse_click(&instance, 5., 5.);
|
||||||
assert_eq(instance.get_value(), 10);
|
assert_eq(instance.get_value(), 1010);
|
||||||
|
|
||||||
sixtyfps::testing::send_mouse_click(&instance, 15., 15.);
|
sixtyfps::testing::send_mouse_click(&instance, 15., 15.);
|
||||||
assert_eq(instance.get_value(), 13);
|
assert_eq(instance.get_value(), 2013);
|
||||||
|
|
||||||
sixtyfps::testing::send_mouse_click(&instance, 5., 15.);
|
sixtyfps::testing::send_mouse_click(&instance, 5., 15.);
|
||||||
assert_eq(instance.get_value(), 13+42);
|
assert_eq(instance.get_value(), 3000+13+42);
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -110,28 +112,28 @@ assert_eq(instance.get_value(), 13+42);
|
||||||
let instance = TestCase::new();
|
let instance = TestCase::new();
|
||||||
|
|
||||||
sixtyfps::testing::send_mouse_click(&instance, 5., 5.);
|
sixtyfps::testing::send_mouse_click(&instance, 5., 5.);
|
||||||
assert_eq!(instance.get_value(), 10);
|
assert_eq!(instance.get_value(), 1010);
|
||||||
|
|
||||||
sixtyfps::testing::send_mouse_click(&instance, 15., 15.);
|
sixtyfps::testing::send_mouse_click(&instance, 15., 15.);
|
||||||
assert_eq!(instance.get_value(), 13);
|
assert_eq!(instance.get_value(), 2013);
|
||||||
|
|
||||||
sixtyfps::testing::send_mouse_click(&instance, 5., 15.);
|
sixtyfps::testing::send_mouse_click(&instance, 5., 15.);
|
||||||
assert_eq!(instance.get_value(), 13+42);
|
assert_eq!(instance.get_value(), 3000+13+42);
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
```js
|
```js
|
||||||
var instance = new sixtyfps.TestCase();
|
var instance = new sixtyfps.TestCase();
|
||||||
instance.send_mouse_click(5., 5.);
|
instance.send_mouse_click(5., 5.);
|
||||||
assert.equal(instance.value, 10);
|
assert.equal(instance.value, 1010);
|
||||||
|
|
||||||
instance.cond1 = true;
|
instance.cond1 = true;
|
||||||
instance.send_mouse_click(15., 15.);
|
instance.send_mouse_click(15., 15.);
|
||||||
assert.equal(instance.value, 13);
|
assert.equal(instance.value, 2013);
|
||||||
|
|
||||||
instance.cond1 = false;
|
instance.cond1 = false;
|
||||||
instance.send_mouse_click(5., 15.);
|
instance.send_mouse_click(5., 15.);
|
||||||
assert.equal(instance.value, 13+42);
|
assert.equal(instance.value, 3000+13+42);
|
||||||
```
|
```
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue