Allow normal Element to be in the Dialog's button row with dialog-button-role

This commit is contained in:
Olivier Goffart 2021-10-04 18:35:36 +02:00 committed by Olivier Goffart
parent fcf55084ab
commit ae3e1b4680
13 changed files with 165 additions and 65 deletions

View file

@ -116,6 +116,7 @@ fn gen_corelib(root_dir: &Path, include_dir: &Path) -> anyhow::Result<()> {
"ImageFit",
"FillRule",
"StandardButtonKind",
"DialogButtonRole",
"PointerEventKind",
"PointerEventButton",
"PointerEvent",

View file

@ -17,6 +17,7 @@ These properties are valid on all visible items
children with transparency. 0 is fully transparent (invisible), and 1 is fully opaque. (default: 1)
* **`visible`** (*bool*): When set to `false`, the element and all his children will not be drawn
and not react to mouse input (default: `true`)
* **`dialog-button-role`** (*enum DialogButtonRole*): Specify that this is a button in a `Dialog`.
### Drop Shadows
@ -656,13 +657,15 @@ Example := Window {
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` widgets.
And the window can have any number of `StandardButton` widgets or other button
with the `dialog-button-role` property.
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,
and there cannot be several button of the same kind.
The `kind` property of the `StandardButton`s and the ``dialog-button-role` properties needs to be set to a specific value,
it cannot be a complex expression.
There cannot be several StandardButton of the same kind.
If A callback `<kind>_clicked` is automatically added for each button which does not have an explicit
If A callback `<kind>_clicked` is automatically added for each StandardButton which does not have an explicit
callback handler, so it can be handled from the native code. (e.g. if there is a button of kind `cancel`,
a `cancel_clicked` callback will be added)
When viewed with the `sixtyfps-viewer` program, the `ok`, `cancel`, and `close` button will cause the dialog to close.
@ -675,13 +678,17 @@ When viewed with the `sixtyfps-viewer` program, the `ok`, `cancel`, and `close`
### Example
```60
import { StandardButton } from "sixtyfps_widgets.60";
import { StandardButton, Button } from "sixtyfps_widgets.60";
Example := Dialog {
Text {
text: "This is a dialog box";
}
StandardButton { kind: ok; }
StandardButton { kind: cancel; }
Button {
text: "More Info";
dialog-button-role: action;
}
}
```
@ -789,3 +796,19 @@ This enum describes the different ways of deciding what the inside of a shape de
* **`FillRule.nonzero`**: The ["nonzero" fill rule as defined in SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule#nonzero).
* **`FillRule.evenodd`**: The ["evenodd" fill rule as defined in SVG](https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule#evenodd).
## `DialogButtonRole`
This enum represent the value of the `dialog-button-role` property which can be added to
any element within a `Dialog` to put that item in the button row, and its exact position
depends on the role and the platform.
### Values
* **`none`**: This is not a button means to go in the row of button of the dialog
* **`accept`**: This is the role of the main button to click to accept the dialog. e.g. "Ok" or "Yes"
* **`reject`**: This is the role of the main button to click to reject the dialog. e.g. "Cancel" or "No"
* **`apply`**: This is the role of the "Apply" button
* **`reset`**: This is the role of the "Reset" button
* **`help`**: This is the role of the "Help" button
* **`action`**: This is the role of any other button that perform another action.

View file

@ -396,7 +396,7 @@ pub struct GridLayout {
/// 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>>,
pub dialog_button_roles: Option<Vec<String>>,
}
impl GridLayout {

View file

@ -405,19 +405,24 @@ fn lower_dialog_layout(
let mut seen_buttons = HashSet::new();
let layout_children = std::mem::take(&mut dialog_element.borrow_mut().children);
for layout_child in &layout_children {
let is_button = layout_child.borrow().property_declarations.get("kind").map_or(false, |pd| {
let dialog_button_role_binding =
layout_child.borrow_mut().bindings.remove("dialog-button-role");
let is_button = if let Some(role_binding) = dialog_button_role_binding {
if let Expression::EnumerationValue(val) = &role_binding.expression {
let en = &val.enumeration;
debug_assert_eq!(en.name, "DialogButtonRole");
button_roles.push(en.values[val.value].clone());
if val.value == 0 {
diag.push_error("The `dialog-button-role` cannot be set explicitly to none".into(), &role_binding);
}
} else {
diag.push_error("The `dialog-button-role` property must be known at compile-time".into(), &role_binding);
}
true
} else if 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,
);
}) {
// layout_child is a StandardButton
match layout_child.borrow().bindings.get("kind") {
None => diag.push_error(
"The `kind` property of the StandardButton in a Dialog must be set".into(),
@ -429,20 +434,20 @@ fn lower_dialog_layout(
debug_assert_eq!(en.name, "StandardButtonKind");
let kind = &en.values[val.value];
let role = match kind.as_str() {
"ok" => "Accept",
"cancel" => "Reject",
"apply" => "Apply",
"close" => "Reject",
"reset" => "Reset",
"help" => "Help",
"yes" => "Accept",
"no" => "Reject",
"abort" => "Reject",
"retry" => "Accept",
"ignore" => "Accept",
"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);
button_roles.push(role.into());
if !seen_buttons.insert(val.value) {
diag.push_error("Duplicated `kind`: There are two StandardButton in this Dialog with the same kind".into(), binding);
} else if Rc::ptr_eq(
@ -487,6 +492,20 @@ fn lower_dialog_layout(
}
}
}
true
} else {
false
};
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,
);
} else if main_widget.is_some() {
diag.push_error(
"A Dialog can have only one child element that is not a StandardButton".into(),
@ -755,5 +774,8 @@ fn check_no_layout_properties(item: &ElementRc, diag: &mut BuildDiagnostics) {
if matches!(prop.as_ref(), "col" | "row" | "colspan" | "rowspan") {
diag.push_error(format!("{} used outside of a GridLayout", prop), expr);
}
if matches!(prop.as_ref(), "dialog-button-role") {
diag.push_error(format!("{} used outside of a Dialog", prop), expr);
}
}
}

View file

@ -40,12 +40,25 @@ MyDiag3 := Dialog {
}
MyDialog4 := Dialog {
StandardButton { kind: ok; }
Rectangle { dialog-button-role: accept; }
Rectangle { dialog-button-role: none; }
// ^error{The `dialog-button-role` cannot be set explicitly to none}
Rectangle { dialog-button-role: true ? accept : reject; }
// ^error{The `dialog-button-role` property must be known at compile-time}
Rectangle {
Rectangle { dialog-button-role: accept; }
// ^error{dialog-button-role used outside of a Dialog}
}
}
Test := Rectangle {
MyDiag1 {}
MyDiag2 {} // FIXME: not the best place for the error
// ^error{A Dialog must have a single child element that is not StandardButton}
MyDiag3 {}
MyDialog4 {}
}

View file

@ -43,6 +43,24 @@ const RESERVED_LAYOUT_PROPERTIES: &[(&str, Type)] = &[
("rowspan", Type::Int32),
];
thread_local! {
pub static DIALOG_BUTTON_ROLE_ENUM: Type =
Type::Enumeration(Rc::new(Enumeration {
name: "DialogButtonRole".into(),
values: IntoIterator::into_iter([
"none".to_owned(),
"accept".to_owned(),
"reject".to_owned(),
"apply".to_owned(),
"reset".to_owned(),
"action".to_owned(),
"help".to_owned(),
])
.collect(),
default_value: 0,
}));
}
const RESERVED_OTHER_PROPERTIES: &[(&str, Type)] = &[
("clip", Type::Bool),
("opacity", Type::Float32),
@ -67,6 +85,7 @@ pub fn reserved_properties() -> impl Iterator<Item = (&'static str, Type)> {
.chain(std::array::IntoIter::new([
("forward-focus", Type::ElementReference),
("focus", BuiltinFunction::SetFocusItem.ty()),
("dialog-button-role", DIALOG_BUTTON_ROLE_ENUM.with(|e| e.clone())),
]))
}
@ -183,6 +202,7 @@ impl TypeRegister {
);
declare_enum("PointerEventKind", &["cancel", "down", "up"]);
declare_enum("PointerEventButton", &["none", "left", "right", "middle"]);
register.insert_type(DIALOG_BUTTON_ROLE_ENUM.with(|x| x.clone()));
register.supported_property_animation_types.insert(Type::Float32.to_string());
register.supported_property_animation_types.insert(Type::Int32.to_string());

View file

@ -1297,6 +1297,27 @@ impl Default for StandardButtonKind {
}
}
#[derive(
Copy, Clone, Eq, PartialEq, Hash, Debug, strum_macros::EnumString, strum_macros::ToString,
)]
#[repr(C)]
#[allow(non_camel_case_types)]
pub enum DialogButtonRole {
none,
accept,
reject,
apply,
reset,
action,
help,
}
impl Default for DialogButtonRole {
fn default() -> Self {
Self::none
}
}
#[derive(Copy, Clone, Debug, PartialEq, strum_macros::EnumString, strum_macros::Display)]
#[repr(C)]
#[allow(non_camel_case_types)]

View file

@ -11,7 +11,7 @@ LICENSE END */
// cspell:ignore coord
use crate::{slice::Slice, SharedVector};
use crate::{items::DialogButtonRole, slice::Slice, SharedVector};
/// Vertical or Horizontal orientation
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
@ -719,18 +719,6 @@ 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`
@ -753,19 +741,21 @@ pub fn reorder_dialog_button_layout(cells: &mut [GridLayoutCellData], roles: &[D
let mut idx = 0;
if cfg!(windows) {
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
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);
add_buttons(cells, roles, &mut idx, DialogButtonRole::accept);
add_buttons(cells, roles, &mut idx, DialogButtonRole::action);
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);
add_buttons(cells, roles, &mut idx, DialogButtonRole::help);
add_buttons(cells, roles, &mut idx, DialogButtonRole::reset);
add_buttons(cells, roles, &mut idx, DialogButtonRole::apply);
add_buttons(cells, roles, &mut idx, DialogButtonRole::action);
idx += 1;
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reject);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Accept);
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")
@ -774,20 +764,22 @@ pub fn reorder_dialog_button_layout(cells: &mut [GridLayoutCellData], roles: &[D
.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);
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);
add_buttons(cells, roles, &mut idx, DialogButtonRole::action);
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 and fallback for WASM build
add_buttons(cells, roles, &mut idx, DialogButtonRole::Help);
add_buttons(cells, roles, &mut idx, DialogButtonRole::Reset);
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);
add_buttons(cells, roles, &mut idx, DialogButtonRole::action);
add_buttons(cells, roles, &mut idx, DialogButtonRole::apply);
add_buttons(cells, roles, &mut idx, DialogButtonRole::reject);
add_buttons(cells, roles, &mut idx, DialogButtonRole::accept);
}
}

View file

@ -47,6 +47,7 @@ declare_ValueType![
crate::items::EventResult,
crate::Brush,
crate::items::FillRule,
crate::items::DialogButtonRole,
crate::items::StandardButtonKind,
crate::graphics::Point,
crate::items::PointerEvent,

View file

@ -315,6 +315,7 @@ declare_value_enum_conversion!(sixtyfps_corelib::items::FillRule, FillRule);
declare_value_enum_conversion!(sixtyfps_corelib::items::StandardButtonKind, StandardButtonKind);
declare_value_enum_conversion!(sixtyfps_corelib::items::PointerEventKind, PointerEventKind);
declare_value_enum_conversion!(sixtyfps_corelib::items::PointerEventButton, PointerEventButton);
declare_value_enum_conversion!(sixtyfps_corelib::items::DialogButtonRole, DialogButtonRole);
impl From<sixtyfps_corelib::animations::Instant> for Value {
fn from(value: sixtyfps_corelib::animations::Instant) -> Self {

View file

@ -892,6 +892,7 @@ pub(crate) fn generate_component<'id>(
"StandardButtonKind" => {
property_info::<sixtyfps_corelib::items::StandardButtonKind>()
}
"DialogButtonRole" => property_info::<sixtyfps_corelib::items::DialogButtonRole>(),
"PointerEventButton" => {
property_info::<sixtyfps_corelib::items::PointerEventButton>()
}

View file

@ -16,6 +16,7 @@ use sixtyfps_compilerlib::langtype::Type;
use sixtyfps_compilerlib::layout::{Layout, LayoutConstraints, LayoutGeometry, Orientation};
use sixtyfps_compilerlib::namedreference::NamedReference;
use sixtyfps_compilerlib::object_tree::ElementRc;
use sixtyfps_corelib::items::DialogButtonRole;
use sixtyfps_corelib::layout::{self as core_layout};
use sixtyfps_corelib::model::RepeatedComponent;
use sixtyfps_corelib::slice::Slice;
@ -98,7 +99,7 @@ pub(crate) fn solve_layout(
{
let roles = buttons_roles
.iter()
.map(|r| core_layout::DialogButtonRole::from_str(r).unwrap())
.map(|r| DialogButtonRole::from_str(r).unwrap())
.collect::<Vec<_>>();
core_layout::reorder_dialog_button_layout(&mut cells, &roles);
}

View file

@ -8,7 +8,7 @@
Please contact info@sixtyfps.io for more information.
LICENSE END */
import { StandardButton, GridBox } from "sixtyfps_widgets.60";
import { StandardButton, Button, GridBox } from "sixtyfps_widgets.60";
TestCase := Dialog {
Rectangle {
@ -23,6 +23,10 @@ TestCase := Dialog {
StandardButton { kind: apply; }
StandardButton { kind: reset; }
StandardButton { kind: yes; }
Button {
text: "Action";
dialog-button-role: action;
}
}
/*