Add the Dialog element

This commit is contained in:
Olivier Goffart 2021-09-29 14:34:10 +02:00 committed by Olivier Goffart
parent 3cf9089293
commit 7f05bfa309
15 changed files with 452 additions and 31 deletions

View file

@ -12,6 +12,7 @@ All notable changes to this project will be documented in this file.
- sixtyfps-compiler and sixtyfps-viewer can read the .60 file content from stdin by passing `-` - sixtyfps-compiler and sixtyfps-viewer can read the .60 file content from stdin by passing `-`
- sixtyfps-viewer gained ability to read or save the property values to a json file with `--save-data` and `--load-data` - sixtyfps-viewer gained ability to read or save the property values to a json file with `--save-data` and `--load-data`
- New `StandardButton` widget - New `StandardButton` widget
- New `Dialog` element
- `sixtyfps::Image` has now a `path()` accessor function in Rust and C++ to access the optional path - `sixtyfps::Image` has now a `path()` accessor function in Rust and C++ to access the optional path
of the file on disk that's backing the image. of the file on disk that's backing the image.

View file

@ -649,6 +649,34 @@ Example := Window {
} }
``` ```
## `Dialog`
Dialog is like a window, but it has buttons that are automatically laid out.
A Dialog should have one main element for the content, that is not a button.
And the window can have any number of `StandardButton` widget.
The button will be layed out in an order that depends on the platform.
The `kind` property of the `StandardButton`s needs to be set to a specific value. it cannot be a complex expression.
### Properties
* **`title`** (*string*): The window title that is shown in the title bar.
* **`icon`** (*image*): The window icon shown in the title bar or the task bar on window managers supporting it.
### Example
```60
import { StandardButton } from "sixtyfps_widgets.60";
Example := Dialog {
Text {
text: "This is a dialog box";
}
StandardButton { kind: ok; }
StandardButton { kind: cancel; }
}
```
# Builtin Structures # Builtin Structures
## `Point` ## `Point`

View file

@ -399,6 +399,8 @@ export PopupWindow := _ {
//show() is hardcoded in typeregister.rs //show() is hardcoded in typeregister.rs
} }
export Dialog := WindowItem {}
PropertyAnimation := _ { PropertyAnimation := _ {
property <duration> duration; property <duration> duration;
property <easing> easing; property <easing> easing;

View file

@ -1893,16 +1893,25 @@ fn compile_expression(
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component); let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
let cells = grid_layout_cell_data(layout, *o, component); let cells = grid_layout_cell_data(layout, *o, component);
let size = layout_geometry_size(&layout.geometry.rect, *o, component); let size = layout_geometry_size(&layout.geometry.rect, *o, component);
format!("[&] {{ \ let dialog = if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, *o) {
format!("sixtyfps::cbindgen_private::DialogButtonRole roles[] = {{ {r} }};\
sixtyfps::cbindgen_private::sixtyfps_reorder_dialog_button_layout(cells,\
sixtyfps::Slice<sixtyfps::cbindgen_private::DialogButtonRole>{{ roles, std::size(roles) }});\
",
r = button_roles.iter().map(|r| format!("sixtyfps::cbindgen_private::DialogButtonRole::{}", r)).join(", ")
)
} else { String::new() };
format!("[&] {{\
const auto padding = {p};\ const auto padding = {p};\
sixtyfps::GridLayoutCellData cells[] = {{ {c} }}; \ sixtyfps::GridLayoutCellData cells[] = {{ {c} }};\
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}}; \ {dialog}
const sixtyfps::GridLayoutData grid {{ {sz}, {s}, &padding, slice }}; const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}};\
sixtyfps::SharedVector<float> result; const sixtyfps::GridLayoutData grid {{ {sz}, {s}, &padding, slice }};\
sixtyfps::SharedVector<float> result;\
sixtyfps::sixtyfps_solve_grid_layout(&grid, &result);\ sixtyfps::sixtyfps_solve_grid_layout(&grid, &result);\
return result; return result;\
}}()", }}()",
p = padding, c = cells, s = spacing, sz = size dialog = dialog, p = padding, c = cells, s = spacing, sz = size
) )
} }
Expression::SolveLayout(Layout::BoxLayout(layout), o) => { Expression::SolveLayout(Layout::BoxLayout(layout), o) => {

View file

@ -1598,12 +1598,26 @@ fn compile_expression(expr: &Expression, component: &Rc<Component>) -> TokenStre
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component); let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
let cells = grid_layout_cell_data(layout, *o, component); let cells = grid_layout_cell_data(layout, *o, component);
let size = layout_geometry_size(&layout.geometry.rect, *o, component); let size = layout_geometry_size(&layout.geometry.rect, *o, component);
quote!(solve_grid_layout(&GridLayoutData{ if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, *o) {
size: #size, let role = button_roles.iter().map(|x| format_ident!("{}", x));
spacing: #spacing, quote!({
padding: #padding, let mut cells = #cells;
cells: Slice::from_slice(&#cells), reorder_dialog_button_layout(&mut cells, &[ #(DialogButtonRole::#role),* ]);
})) solve_grid_layout(&GridLayoutData{
size: #size,
spacing: #spacing,
padding: #padding,
cells: Slice::from_slice(&cells),
})
})
} else {
quote!(solve_grid_layout(&GridLayoutData{
size: #size,
spacing: #spacing,
padding: #padding,
cells: Slice::from_slice(&#cells),
}))
}
} }
Expression::SolveLayout(Layout::BoxLayout(layout), o) => { Expression::SolveLayout(Layout::BoxLayout(layout), o) => {
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component); let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
@ -1626,8 +1640,8 @@ fn compile_expression(expr: &Expression, component: &Rc<Component>) -> TokenStre
}) })
} }
Expression::SolveLayout(Layout::PathLayout(layout), _) => { Expression::SolveLayout(Layout::PathLayout(layout), _) => {
let width = layout_geometry_size(&layout.rect, Orientation::Horizontal, component); let width = layout_geometry_size(&layout.rect, Orientation::Horizontal, component);
let height = layout_geometry_size(&layout.rect, Orientation::Vertical, component); let height = layout_geometry_size(&layout.rect, Orientation::Vertical, component);
let elements = compile_path(&layout.path, component); let elements = compile_path(&layout.path, component);
let get_prop = |nr: &Option<NamedReference>| { let get_prop = |nr: &Option<NamedReference>| {
nr.as_ref().map_or_else( nr.as_ref().map_or_else(

View file

@ -393,6 +393,10 @@ pub struct GridLayout {
pub elems: Vec<GridLayoutElement>, pub elems: Vec<GridLayoutElement>,
pub geometry: LayoutGeometry, pub geometry: LayoutGeometry,
/// When this GridLyout is actually the layout of a Dialog, then the cells start with all the buttons,
/// and this variable contains their roles. The string is actually one of the values from the sixtyfps_corelib::layout::DialogButtonRole
pub dialog_button_roles: Option<Vec<&'static str>>,
} }
impl GridLayout { impl GridLayout {

View file

@ -90,7 +90,7 @@ pub async fn run_passes(
lower_states::lower_states(root_component, &doc.local_registry, diag); lower_states::lower_states(root_component, &doc.local_registry, diag);
repeater_component::process_repeater_components(root_component); repeater_component::process_repeater_components(root_component);
lower_popups::lower_popups(root_component, &doc.local_registry, diag); lower_popups::lower_popups(root_component, &doc.local_registry, diag);
lower_layout::lower_layouts(root_component, &global_type_registry.borrow(), diag); lower_layout::lower_layouts(root_component, &mut type_loader, diag).await;
z_order::reorder_by_z_order(root_component, diag); z_order::reorder_by_z_order(root_component, diag);
lower_shadows::lower_shadow_properties(root_component, &doc.local_registry, diag); lower_shadows::lower_shadow_properties(root_component, &doc.local_registry, diag);
clip::handle_clip(root_component, &global_type_registry.borrow(), diag); clip::handle_clip(root_component, &global_type_registry.borrow(), diag);

View file

@ -69,7 +69,7 @@ pub async fn apply_default_properties_from_style(
.into() .into()
}); });
} }
"Window" | "WindowItem" => { "Dialog" | "Window" | "WindowItem" => {
elem.bindings.set_binding_if_not_set("background".into(), || { elem.bindings.set_binding_if_not_set("background".into(), || {
Expression::PropertyReference(NamedReference::new( Expression::PropertyReference(NamedReference::new(
&style_metrics.root_element, &style_metrics.root_element,

View file

@ -18,7 +18,8 @@ use std::collections::HashSet;
use std::rc::Rc; use std::rc::Rc;
pub fn ensure_window(component: &Rc<Component>, type_register: &TypeRegister) { pub fn ensure_window(component: &Rc<Component>, type_register: &TypeRegister) {
if component.root_element.borrow().base_type.to_string() == "Window" { if matches!(component.root_element.borrow().base_type.to_string().as_str(), "Window" | "Dialog")
{
return; // already a window, nothing to do return; // already a window, nothing to do
} }

View file

@ -17,20 +17,35 @@ use crate::expression_tree::*;
use crate::langtype::Type; use crate::langtype::Type;
use crate::layout::*; use crate::layout::*;
use crate::object_tree::*; use crate::object_tree::*;
use crate::typeloader::TypeLoader;
use crate::typeregister::TypeRegister; use crate::typeregister::TypeRegister;
use std::rc::Rc; use std::rc::Rc;
pub fn lower_layouts( pub async fn lower_layouts(
component: &Rc<Component>, component: &Rc<Component>,
type_register: &TypeRegister, type_loader: &mut TypeLoader<'_>,
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
) { ) {
// Ignore import errors
let mut build_diags_to_ignore = crate::diagnostics::BuildDiagnostics::default();
let style_metrics = type_loader
.import_type("sixtyfps_widgets.60", "StyleMetrics", &mut build_diags_to_ignore)
.await;
let style_metrics =
style_metrics.and_then(|sm| if let Type::Component(c) = sm { Some(c) } else { None });
*component.root_constraints.borrow_mut() = *component.root_constraints.borrow_mut() =
LayoutConstraints::new(&component.root_element, diag); LayoutConstraints::new(&component.root_element, diag);
recurse_elem_including_sub_components(component, &(), &mut |elem, _| { recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
let component = elem.borrow().enclosing_component.upgrade().unwrap(); let component = elem.borrow().enclosing_component.upgrade().unwrap();
lower_element_layout(&component, elem, type_register, diag); lower_element_layout(
&component,
elem,
&type_loader.global_type_registry.borrow(),
&style_metrics,
diag,
);
check_no_layout_properties(elem, diag); check_no_layout_properties(elem, diag);
}); });
} }
@ -39,6 +54,7 @@ fn lower_element_layout(
component: &Rc<Component>, component: &Rc<Component>,
elem: &ElementRc, elem: &ElementRc,
type_register: &TypeRegister, type_register: &TypeRegister,
style_metrics: &Option<Rc<Component>>,
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
) { ) {
let base_type = if let Type::Builtin(base_type) = &elem.borrow().base_type { let base_type = if let Type::Builtin(base_type) = &elem.borrow().base_type {
@ -49,9 +65,13 @@ fn lower_element_layout(
match base_type.name.as_str() { match base_type.name.as_str() {
"Row" => panic!("Error caught at element lookup time"), "Row" => panic!("Error caught at element lookup time"),
"GridLayout" => lower_grid_layout(component, elem, diag), "GridLayout" => lower_grid_layout(component, elem, diag),
"HorizontalLayout" => lower_box_layout(component, elem, diag, Orientation::Horizontal), "HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
"VerticalLayout" => lower_box_layout(component, elem, diag, Orientation::Vertical), "VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
"PathLayout" => lower_path_layout(component, elem, diag), "PathLayout" => lower_path_layout(elem, diag),
"Dialog" => {
lower_dialog_layout(elem, style_metrics, diag);
return; // the Dialog stays in the tree as a Dialog
}
_ => return, _ => return,
}; };
@ -81,6 +101,7 @@ fn lower_grid_layout(
let mut grid = GridLayout { let mut grid = GridLayout {
elems: Default::default(), elems: Default::default(),
geometry: LayoutGeometry::new(grid_layout_element), geometry: LayoutGeometry::new(grid_layout_element),
dialog_button_roles: None,
}; };
let layout_cache_prop_h = let layout_cache_prop_h =
@ -200,6 +221,25 @@ impl GridLayout {
*col = c; *col = c;
} }
self.add_element_with_coord(
item_element,
(*row, *col),
(rowspan, colspan),
layout_cache_prop_h,
layout_cache_prop_v,
diag,
)
}
fn add_element_with_coord(
&mut self,
item_element: &ElementRc,
(row, col): (u16, u16),
(rowspan, colspan): (u16, u16),
layout_cache_prop_h: &NamedReference,
layout_cache_prop_v: &NamedReference,
diag: &mut BuildDiagnostics,
) {
let index = self.elems.len(); let index = self.elems.len();
if let Some(layout_item) = create_layout_item(item_element, diag) { if let Some(layout_item) = create_layout_item(item_element, diag) {
if layout_item.repeater_index.is_some() { if layout_item.repeater_index.is_some() {
@ -222,8 +262,8 @@ impl GridLayout {
} }
self.elems.push(GridLayoutElement { self.elems.push(GridLayoutElement {
col: *col, col,
row: *row, row,
colspan, colspan,
rowspan, rowspan,
item: layout_item.item, item: layout_item.item,
@ -233,7 +273,6 @@ impl GridLayout {
} }
fn lower_box_layout( fn lower_box_layout(
_component: &Rc<Component>,
layout_element: &ElementRc, layout_element: &ElementRc,
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
orientation: Orientation, orientation: Orientation,
@ -337,11 +376,141 @@ fn lower_box_layout(
layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v)); layout_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
} }
fn lower_path_layout( fn lower_dialog_layout(
_component: &Rc<Component>, dialog_element: &ElementRc,
layout_element: &ElementRc, style_metrics: &Option<Rc<Component>>,
diag: &mut BuildDiagnostics, diag: &mut BuildDiagnostics,
) { ) {
let mut grid = GridLayout {
elems: Default::default(),
geometry: LayoutGeometry::new(dialog_element),
dialog_button_roles: None,
};
if let Some(metrics) = style_metrics.as_ref().map(|comp| &comp.root_element) {
grid.geometry.padding.bottom.get_or_insert(NamedReference::new(metrics, "layout-padding"));
grid.geometry.padding.top.get_or_insert(NamedReference::new(metrics, "layout-padding"));
grid.geometry.padding.left.get_or_insert(NamedReference::new(metrics, "layout-padding"));
grid.geometry.padding.right.get_or_insert(NamedReference::new(metrics, "layout-padding"));
grid.geometry.spacing.get_or_insert(NamedReference::new(metrics, "layout-spacing"));
}
let layout_cache_prop_h = create_new_prop(dialog_element, "layout-cache-h", Type::LayoutCache);
let layout_cache_prop_v = create_new_prop(dialog_element, "layout-cache-v", Type::LayoutCache);
let layout_info_prop_h = create_new_prop(dialog_element, "layoutinfo-h", layout_info_type());
let layout_info_prop_v = create_new_prop(dialog_element, "layoutinfo-v", layout_info_type());
let mut main_widget = None;
let mut button_roles = vec![];
for layout_child in &dialog_element.borrow().children {
let is_button = layout_child.borrow().property_declarations.get("kind").map_or(false, |pd| {
matches!(&pd.property_type, Type::Enumeration(e) if e.name == "StandardButtonKind")
});
if is_button {
grid.add_element_with_coord(
layout_child,
(1, button_roles.len() as u16),
(1, 1),
&layout_cache_prop_h,
&layout_cache_prop_v,
diag,
);
match layout_child.borrow().bindings.get("kind") {
None => diag.push_error(
"The `kind` property of the StandardButton in a Dialog must be set".into(),
&*layout_child.borrow(),
),
Some(binding) => {
if let Expression::EnumerationValue(val) = &binding.expression {
let en = &val.enumeration;
debug_assert_eq!(en.name, "StandardButtonKind");
let role = match en.values[val.value].as_str() {
"ok" => "Accept",
"cancel" => "Reject",
"apply" => "Apply",
"close" => "Reject",
"reset" => "Reset",
"help" => "Help",
"yes" => "Accept",
"no" => "Reject",
"abort" => "Reject",
"retry" => "Accept",
"ignore" => "Accept",
_ => unreachable!(),
};
button_roles.push(role);
} else {
diag.push_error(
"The `kind` property of the StandardButton in a Dialog must be known at compile-time"
.into(),
binding,
);
}
}
}
} else if main_widget.is_some() {
diag.push_error(
"A Dialog should have a single child element that is not StandardButton".into(),
&*layout_child.borrow(),
);
} else {
main_widget = Some(layout_child.clone())
}
}
if let Some(main_widget) = main_widget {
grid.add_element_with_coord(
&main_widget,
(0, 0),
(1, button_roles.len() as u16 + 1),
&layout_cache_prop_h,
&layout_cache_prop_v,
diag,
);
} else {
diag.push_error(
"A Dialog should have a single child element that is not StandardButton".into(),
&*dialog_element.borrow(),
);
}
grid.dialog_button_roles = Some(button_roles);
let span = dialog_element.borrow().to_source_location();
layout_cache_prop_h.element().borrow_mut().bindings.insert(
layout_cache_prop_h.name().into(),
BindingExpression::new_with_span(
Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Horizontal),
span.clone(),
),
);
layout_cache_prop_v.element().borrow_mut().bindings.insert(
layout_cache_prop_v.name().into(),
BindingExpression::new_with_span(
Expression::SolveLayout(Layout::GridLayout(grid.clone()), Orientation::Vertical),
span.clone(),
),
);
layout_info_prop_h.element().borrow_mut().bindings.insert(
layout_info_prop_h.name().into(),
BindingExpression::new_with_span(
Expression::ComputeLayoutInfo(
Layout::GridLayout(grid.clone()),
Orientation::Horizontal,
),
span.clone(),
),
);
layout_info_prop_v.element().borrow_mut().bindings.insert(
layout_info_prop_v.name().into(),
BindingExpression::new_with_span(
Expression::ComputeLayoutInfo(Layout::GridLayout(grid), Orientation::Vertical),
span,
),
);
dialog_element.borrow_mut().layout_info_prop = Some((layout_info_prop_h, layout_info_prop_v));
}
fn lower_path_layout(layout_element: &ElementRc, diag: &mut BuildDiagnostics) {
let layout_cache_prop = create_new_prop(layout_element, "layout-cache", Type::LayoutCache); let layout_cache_prop = create_new_prop(layout_element, "layout-cache", Type::LayoutCache);
let path_elements_expr = match layout_element.borrow_mut().bindings.remove("elements") { let path_elements_expr = match layout_element.borrow_mut().bindings.remove("elements") {

View file

@ -0,0 +1,48 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 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 */
import { StandardButton } from "sixtyfps_widgets.60";
MyDiag1 := Dialog {
Rectangle {}
StandardButton { kind: cancel; }
Rectangle {}
// ^error{A Dialog should have a single child element that is not StandardButton}
}
MyDiag2 := Dialog {
StandardButton { kind: cancel; }
StandardButton {
kind: cancel;
col: 42;
// ^error{col used outside of a GridLayout}
rowspan: 2;
// ^error{rowspan used outside of a GridLayout}
}
}
MyDiag3 := Dialog {
Rectangle { }
StandardButton { kind: ok; }
StandardButton { }
// ^error{The `kind` property of the StandardButton in a Dialog must be set}
StandardButton { kind: true ? ok : cancel; }
// ^error{The `kind` property of the StandardButton in a Dialog must be known at compile-time}
}
Test := Rectangle {
MyDiag1 {}
MyDiag2 {} // FIXME: not the best place for the error
// ^error{A Dialog should have a single child element that is not StandardButton}
MyDiag3 {}
}

View file

@ -0,0 +1,23 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 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 */
import { StandardButton } from "sixtyfps_widgets.60";
Test := Dialog {
// ^error{A Dialog should have a single child element that is not StandardButton}
StandardButton { kind: ok; }
StandardButton { }
// ^error{The `kind` property of the StandardButton in a Dialog must be set}
StandardButton { kind: true ? ok : cancel; }
// ^error{The `kind` property of the StandardButton in a Dialog must be known at compile-time}
}

View file

@ -719,6 +719,78 @@ pub fn solve_path_layout(data: &PathLayoutData, repeater_indexes: Slice<u32>) ->
result result
} }
#[derive(
Copy, Clone, Eq, PartialEq, Hash, Debug, strum_macros::EnumString, strum_macros::ToString,
)]
#[repr(u8)]
pub enum DialogButtonRole {
Accept,
Reject,
Apply,
Reset,
Help,
}
/// Given the cells of a layout of a Dialog, re-order the button according to the platform
///
/// This function assume that the `roles` contains the roles of the button which are the first `cells`
/// It will simply change the column field of the cell
pub fn reorder_dialog_button_layout(cells: &mut [GridLayoutCellData], roles: &[DialogButtonRole]) {
fn add_buttons(
cells: &mut [GridLayoutCellData],
roles: &[DialogButtonRole],
idx: &mut u16,
role: DialogButtonRole,
) {
for (cell, r) in cells.iter_mut().zip(roles.iter()) {
if *r == role {
cell.col_or_row = *idx;
*idx += 1;
}
}
}
let mut idx = 0;
if cfg!(windows) {
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
idx += 1;
add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
} else if cfg!(target_os = "macos") {
add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
idx += 1;
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
// assume some unix check if XDG_CURRENT_DESKTOP stats with K
} else if std::env::var("XDG_CURRENT_DESKTOP")
.ok()
.and_then(|v| v.as_bytes().get(0).copied())
.map_or(false, |x| x.to_ascii_uppercase() == b'K')
{
// KDE variant
add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
idx += 1;
add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
} else {
// GNOME variant
add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
idx += 1;
add_buttons(cells, roles, &mut idx, DialogButtonRole::Apply);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
}
}
#[cfg(feature = "ffi")] #[cfg(feature = "ffi")]
pub(crate) mod ffi { pub(crate) mod ffi {
#![allow(unsafe_code)] #![allow(unsafe_code)]
@ -779,4 +851,16 @@ pub(crate) mod ffi {
) { ) {
*result = super::solve_path_layout(data, repeater_indexes) *result = super::solve_path_layout(data, repeater_indexes)
} }
/// Calls [`reorder_dialog_button_layout`].
///
/// Safety: `cells` must be a pointer to a mutable array of cell data, the array must have at
/// least `roles.len()` elements.
#[no_mangle]
pub unsafe extern "C" fn sixtyfps_reorder_dialog_button_layout(
cells: *mut GridLayoutCellData,
roles: Slice<DialogButtonRole>,
) {
reorder_dialog_button_layout(core::slice::from_raw_parts_mut(cells, roles.len()), &roles);
}
} }

View file

@ -21,6 +21,7 @@ use sixtyfps_corelib::model::RepeatedComponent;
use sixtyfps_corelib::slice::Slice; use sixtyfps_corelib::slice::Slice;
use sixtyfps_corelib::window::WindowRc; use sixtyfps_corelib::window::WindowRc;
use std::convert::TryInto; use std::convert::TryInto;
use std::str::FromStr;
pub(crate) fn to_runtime(o: Orientation) -> core_layout::Orientation { pub(crate) fn to_runtime(o: Orientation) -> core_layout::Orientation {
match o { match o {
@ -91,7 +92,17 @@ pub(crate) fn solve_layout(
match lay { match lay {
Layout::GridLayout(grid_layout) => { Layout::GridLayout(grid_layout) => {
let cells = grid_layout_data(grid_layout, orientation, component, &expr_eval); let mut cells = grid_layout_data(grid_layout, orientation, component, &expr_eval);
if let (Some(buttons_roles), Orientation::Horizontal) =
(&grid_layout.dialog_button_roles, orientation)
{
let roles = buttons_roles
.iter()
.map(|r| core_layout::DialogButtonRole::from_str(r).unwrap())
.collect::<Vec<_>>();
core_layout::reorder_dialog_button_layout(&mut cells, &roles);
}
let (padding, spacing) = let (padding, spacing) =
padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval); padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);

View file

@ -0,0 +1,27 @@
/* LICENSE BEGIN
This file is part of the SixtyFPS Project -- https://sixtyfps.io
Copyright (c) 2021 Olivier Goffart <olivier.goffart@sixtyfps.io>
Copyright (c) 2021 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 */
import { StandardButton, GridBox } from "sixtyfps_widgets.60";
TestCase := Dialog {
Rectangle {
background: red;
preferred-width: 600px;
preferred-height: 600px;
}
StandardButton { kind: help; }
StandardButton { kind: ok; }
StandardButton { kind: cancel; }
StandardButton { kind: apply; }
StandardButton { kind: reset; }
StandardButton { kind: yes; }
}