mirror of
https://github.com/slint-ui/slint.git
synced 2025-10-01 14:21:16 +00:00
Add the Dialog element
This commit is contained in:
parent
3cf9089293
commit
7f05bfa309
15 changed files with 452 additions and 31 deletions
|
@ -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-viewer gained ability to read or save the property values to a json file with `--save-data` and `--load-data`
|
||||
- New `StandardButton` widget
|
||||
- New `Dialog` element
|
||||
- `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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
## `Point`
|
||||
|
|
|
@ -399,6 +399,8 @@ export PopupWindow := _ {
|
|||
//show() is hardcoded in typeregister.rs
|
||||
}
|
||||
|
||||
export Dialog := WindowItem {}
|
||||
|
||||
PropertyAnimation := _ {
|
||||
property <duration> duration;
|
||||
property <easing> easing;
|
||||
|
|
|
@ -1893,16 +1893,25 @@ fn compile_expression(
|
|||
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
|
||||
let cells = grid_layout_cell_data(layout, *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};\
|
||||
sixtyfps::GridLayoutCellData cells[] = {{ {c} }}; \
|
||||
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}}; \
|
||||
const sixtyfps::GridLayoutData grid {{ {sz}, {s}, &padding, slice }};
|
||||
sixtyfps::SharedVector<float> result;
|
||||
sixtyfps::GridLayoutCellData cells[] = {{ {c} }};\
|
||||
{dialog}
|
||||
const sixtyfps::Slice<sixtyfps::GridLayoutCellData> slice{{ cells, std::size(cells)}};\
|
||||
const sixtyfps::GridLayoutData grid {{ {sz}, {s}, &padding, slice }};\
|
||||
sixtyfps::SharedVector<float> 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) => {
|
||||
|
|
|
@ -1598,6 +1598,19 @@ fn compile_expression(expr: &Expression, component: &Rc<Component>) -> TokenStre
|
|||
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
|
||||
let cells = grid_layout_cell_data(layout, *o, component);
|
||||
let size = layout_geometry_size(&layout.geometry.rect, *o, component);
|
||||
if let (Some(button_roles), Orientation::Horizontal) = (&layout.dialog_button_roles, *o) {
|
||||
let role = button_roles.iter().map(|x| format_ident!("{}", x));
|
||||
quote!({
|
||||
let mut cells = #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,
|
||||
|
@ -1605,6 +1618,7 @@ fn compile_expression(expr: &Expression, component: &Rc<Component>) -> TokenStre
|
|||
cells: Slice::from_slice(&#cells),
|
||||
}))
|
||||
}
|
||||
}
|
||||
Expression::SolveLayout(Layout::BoxLayout(layout), o) => {
|
||||
let (padding, spacing) = generate_layout_padding_and_spacing(&layout.geometry, *o, component);
|
||||
let mut repeated_indices = Default::default();
|
||||
|
|
|
@ -393,6 +393,10 @@ pub struct GridLayout {
|
|||
pub elems: Vec<GridLayoutElement>,
|
||||
|
||||
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 {
|
||||
|
|
|
@ -90,7 +90,7 @@ pub async fn run_passes(
|
|||
lower_states::lower_states(root_component, &doc.local_registry, diag);
|
||||
repeater_component::process_repeater_components(root_component);
|
||||
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);
|
||||
lower_shadows::lower_shadow_properties(root_component, &doc.local_registry, diag);
|
||||
clip::handle_clip(root_component, &global_type_registry.borrow(), diag);
|
||||
|
|
|
@ -69,7 +69,7 @@ pub async fn apply_default_properties_from_style(
|
|||
.into()
|
||||
});
|
||||
}
|
||||
"Window" | "WindowItem" => {
|
||||
"Dialog" | "Window" | "WindowItem" => {
|
||||
elem.bindings.set_binding_if_not_set("background".into(), || {
|
||||
Expression::PropertyReference(NamedReference::new(
|
||||
&style_metrics.root_element,
|
||||
|
|
|
@ -18,7 +18,8 @@ use std::collections::HashSet;
|
|||
use std::rc::Rc;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -17,20 +17,35 @@ use crate::expression_tree::*;
|
|||
use crate::langtype::Type;
|
||||
use crate::layout::*;
|
||||
use crate::object_tree::*;
|
||||
use crate::typeloader::TypeLoader;
|
||||
use crate::typeregister::TypeRegister;
|
||||
use std::rc::Rc;
|
||||
|
||||
pub fn lower_layouts(
|
||||
pub async fn lower_layouts(
|
||||
component: &Rc<Component>,
|
||||
type_register: &TypeRegister,
|
||||
type_loader: &mut TypeLoader<'_>,
|
||||
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() =
|
||||
LayoutConstraints::new(&component.root_element, diag);
|
||||
|
||||
recurse_elem_including_sub_components(component, &(), &mut |elem, _| {
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
@ -39,6 +54,7 @@ fn lower_element_layout(
|
|||
component: &Rc<Component>,
|
||||
elem: &ElementRc,
|
||||
type_register: &TypeRegister,
|
||||
style_metrics: &Option<Rc<Component>>,
|
||||
diag: &mut BuildDiagnostics,
|
||||
) {
|
||||
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() {
|
||||
"Row" => panic!("Error caught at element lookup time"),
|
||||
"GridLayout" => lower_grid_layout(component, elem, diag),
|
||||
"HorizontalLayout" => lower_box_layout(component, elem, diag, Orientation::Horizontal),
|
||||
"VerticalLayout" => lower_box_layout(component, elem, diag, Orientation::Vertical),
|
||||
"PathLayout" => lower_path_layout(component, elem, diag),
|
||||
"HorizontalLayout" => lower_box_layout(elem, diag, Orientation::Horizontal),
|
||||
"VerticalLayout" => lower_box_layout(elem, diag, Orientation::Vertical),
|
||||
"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,
|
||||
};
|
||||
|
||||
|
@ -81,6 +101,7 @@ fn lower_grid_layout(
|
|||
let mut grid = GridLayout {
|
||||
elems: Default::default(),
|
||||
geometry: LayoutGeometry::new(grid_layout_element),
|
||||
dialog_button_roles: None,
|
||||
};
|
||||
|
||||
let layout_cache_prop_h =
|
||||
|
@ -200,6 +221,25 @@ impl GridLayout {
|
|||
*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();
|
||||
if let Some(layout_item) = create_layout_item(item_element, diag) {
|
||||
if layout_item.repeater_index.is_some() {
|
||||
|
@ -222,8 +262,8 @@ impl GridLayout {
|
|||
}
|
||||
|
||||
self.elems.push(GridLayoutElement {
|
||||
col: *col,
|
||||
row: *row,
|
||||
col,
|
||||
row,
|
||||
colspan,
|
||||
rowspan,
|
||||
item: layout_item.item,
|
||||
|
@ -233,7 +273,6 @@ impl GridLayout {
|
|||
}
|
||||
|
||||
fn lower_box_layout(
|
||||
_component: &Rc<Component>,
|
||||
layout_element: &ElementRc,
|
||||
diag: &mut BuildDiagnostics,
|
||||
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));
|
||||
}
|
||||
|
||||
fn lower_path_layout(
|
||||
_component: &Rc<Component>,
|
||||
layout_element: &ElementRc,
|
||||
fn lower_dialog_layout(
|
||||
dialog_element: &ElementRc,
|
||||
style_metrics: &Option<Rc<Component>>,
|
||||
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 path_elements_expr = match layout_element.borrow_mut().bindings.remove("elements") {
|
||||
|
|
48
sixtyfps_compiler/tests/syntax/basic/dialog.60
Normal file
48
sixtyfps_compiler/tests/syntax/basic/dialog.60
Normal 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 {}
|
||||
}
|
||||
|
||||
|
23
sixtyfps_compiler/tests/syntax/basic/dialog2.60
Normal file
23
sixtyfps_compiler/tests/syntax/basic/dialog2.60
Normal 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}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -719,6 +719,78 @@ pub fn solve_path_layout(data: &PathLayoutData, repeater_indexes: Slice<u32>) ->
|
|||
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")]
|
||||
pub(crate) mod ffi {
|
||||
#![allow(unsafe_code)]
|
||||
|
@ -779,4 +851,16 @@ pub(crate) mod ffi {
|
|||
) {
|
||||
*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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ use sixtyfps_corelib::model::RepeatedComponent;
|
|||
use sixtyfps_corelib::slice::Slice;
|
||||
use sixtyfps_corelib::window::WindowRc;
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
|
||||
pub(crate) fn to_runtime(o: Orientation) -> core_layout::Orientation {
|
||||
match o {
|
||||
|
@ -91,7 +92,17 @@ pub(crate) fn solve_layout(
|
|||
|
||||
match lay {
|
||||
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) =
|
||||
padding_and_spacing(&grid_layout.geometry, orientation, &expr_eval);
|
||||
|
||||
|
|
27
tests/cases/elements/dialog.60
Normal file
27
tests/cases/elements/dialog.60
Normal 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; }
|
||||
}
|
||||
|
Loading…
Add table
Add a link
Reference in a new issue