diff --git a/Cargo.toml b/Cargo.toml index c05a7f57c..315f39f6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,6 +79,7 @@ skia-safe = { workspace = true } tokio = { workspace = true, features = ["fs"]} dioxus = { workspace = true } freya = { workspace = true } +freya-hooks = { workspace = true } freya-core = { workspace = true } freya-testing = { workspace = true } reqwest = { version = "0.12.0", features = ["json"] } @@ -96,6 +97,7 @@ tree-sitter-rust = "0.23.0" rfd = "0.14.1" bytes = "1.5.0" dioxus-sdk = { workspace = true } +winit = { workspace = true } [profile.release] lto = true diff --git a/README.md b/README.md index 3b1cef692..1cb7d9d80 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ fn app() -> Element { let mut count = use_signal(|| 0); - render!( + rsx!( rect { height: "50%", width: "100%", @@ -70,7 +70,7 @@ fn app() -> Element { Thanks to my sponsors for supporting this project! 😄 - +高庆丰 ### Want to try it? 🤔 diff --git a/crates/components/src/input.rs b/crates/components/src/input.rs index f29017ab0..3443d18bc 100644 --- a/crates/components/src/input.rs +++ b/crates/components/src/input.rs @@ -217,7 +217,7 @@ pub fn Input( main_align: "center", cursor_reference, a11y_id, - a11y_role: "textInput", + a11y_role: "text-input", a11y_auto_focus: "{auto_focus}", onkeydown, onkeyup, diff --git a/crates/components/src/network_image.rs b/crates/components/src/network_image.rs index 45d096d47..54d60b776 100644 --- a/crates/components/src/network_image.rs +++ b/crates/components/src/network_image.rs @@ -124,7 +124,7 @@ pub fn NetworkImage(props: NetworkImageProps) -> Element { a11y_id, image_data, a11y_role: "image", - a11y_alt: alt + a11y_name: alt }) } else if *status.read() == ImageState::Loading { if let Some(loading_element) = &props.loading { diff --git a/crates/components/src/scroll_views/scroll_bar.rs b/crates/components/src/scroll_views/scroll_bar.rs index 80c0ba7da..e726ca9a2 100644 --- a/crates/components/src/scroll_views/scroll_bar.rs +++ b/crates/components/src/scroll_views/scroll_bar.rs @@ -56,7 +56,7 @@ pub fn ScrollBar( rsx!( rect { overflow: "clip", - a11y_role:"scrollBar", + a11y_role: "scroll-bar", width: "{width}", height: "{height}", offset_x: "{offset_x}", diff --git a/crates/components/src/scroll_views/scroll_view.rs b/crates/components/src/scroll_views/scroll_view.rs index d82d768d7..0f4ac98f9 100644 --- a/crates/components/src/scroll_views/scroll_view.rs +++ b/crates/components/src/scroll_views/scroll_view.rs @@ -372,7 +372,7 @@ pub fn ScrollView( rsx!( rect { - a11y_role:"scrollView", + a11y_role:"scroll-view", overflow: "clip", direction: "horizontal", width, diff --git a/crates/components/src/scroll_views/use_scroll_controller.rs b/crates/components/src/scroll_views/use_scroll_controller.rs index c20b3b5b1..8e5736b93 100644 --- a/crates/components/src/scroll_views/use_scroll_controller.rs +++ b/crates/components/src/scroll_views/use_scroll_controller.rs @@ -75,6 +75,14 @@ impl ScrollController { } } + pub fn x(&self) -> Signal { + self.x + } + + pub fn y(&self) -> Signal { + self.y + } + pub fn use_apply(&mut self, width: f32, height: f32) { let scope_id = current_scope_id().unwrap(); diff --git a/crates/components/src/scroll_views/virtual_scroll_view.rs b/crates/components/src/scroll_views/virtual_scroll_view.rs index 74f9d51ff..2365ed697 100644 --- a/crates/components/src/scroll_views/virtual_scroll_view.rs +++ b/crates/components/src/scroll_views/virtual_scroll_view.rs @@ -447,7 +447,7 @@ pub fn VirtualScrollView< rsx!( rect { - a11y_role:"scrollView", + a11y_role: "scroll-view", overflow: "clip", direction: "horizontal", width: "{width}", diff --git a/crates/core/src/accessibility/mod.rs b/crates/core/src/accessibility/mod.rs index 52f3cc600..e590e541e 100644 --- a/crates/core/src/accessibility/mod.rs +++ b/crates/core/src/accessibility/mod.rs @@ -4,6 +4,7 @@ use freya_native_core::{ real_dom::NodeImmutable, }; use freya_node_state::AccessibilityNodeState; +use itertools::Itertools; pub use tree::*; use crate::{ @@ -54,7 +55,9 @@ impl NodeAccessibility for DioxusNode<'_> { /// Collect all descendant accessibility node ids fn get_accessibility_children(&self) -> Vec { - let node_accessibility = &*self.get::().unwrap(); - node_accessibility.descencent_accessibility_ids.clone() + self.children() + .into_iter() + .filter_map(|child| child.get_accessibility_id()) + .collect_vec() } } diff --git a/crates/core/src/accessibility/tree.rs b/crates/core/src/accessibility/tree.rs index ef5b1bfd9..f54c61f18 100644 --- a/crates/core/src/accessibility/tree.rs +++ b/crates/core/src/accessibility/tree.rs @@ -152,19 +152,20 @@ impl AccessibilityTree { // Mark the ancestors as modified for node_id in added_or_updated_ids.clone() { let node_ref = rdom.get(node_id).unwrap(); - let node_accessibility_state = node_ref.get::().unwrap(); - added_or_updated_ids.insert( - node_accessibility_state - .closest_accessibility_node_id - .unwrap_or(rdom.root_id()), - ); + let node_ref_parent = node_ref.parent_id().unwrap_or(rdom.root_id()); + added_or_updated_ids.insert(node_ref_parent); self.map .insert(node_ref.get_accessibility_id().unwrap(), node_id); } // Mark the still existing ancenstors as modified - for (node_id, ancestor_node_id) in removed_ids { - added_or_updated_ids.insert(ancestor_node_id); + for (_, ancestor_node_id) in removed_ids.iter() { + added_or_updated_ids.insert(*ancestor_node_id); + } + + // Remove all the deleted noeds from the added_or_update list + for (node_id, _) in removed_ids { + added_or_updated_ids.remove(&node_id); self.map.retain(|_, id| *id != node_id); } @@ -180,7 +181,6 @@ impl AccessibilityTree { { let accessibility_node = Self::create_node(&node_ref, layout_node, node_accessibility_state); - let accessibility_id = node_ref.get_accessibility_id().unwrap(); nodes.push((accessibility_id, accessibility_node)); @@ -332,7 +332,18 @@ impl AccessibilityTree { let transform_state = &*node_ref.get::().unwrap(); let node_type = node_ref.node_type(); - let mut builder = NodeBuilder::new(Role::default()); + let mut builder = match node_type.tag() { + // Make the root accessibility node. + Some(&TagName::Root) => NodeBuilder::new(Role::Window), + + // All other node types will either don't have a builder (but don't support + // accessibility attributes like with `text`) or have their builder made for + // them already. + Some(_) => node_accessibility.builder.clone().unwrap(), + + // Tag-less nodes can't have accessibility state + None => unreachable!(), + }; // Set children let children = node_ref.get_accessibility_children(); @@ -347,6 +358,14 @@ impl AccessibilityTree { y1: area.max_y(), }); + if let NodeType::Element(node) = &*node_type { + if matches!(node.tag, TagName::Label | TagName::Paragraph) && builder.name().is_none() { + if let Some(inner_text) = node_ref.get_inner_texts() { + builder.set_name(inner_text); + } + } + } + // Set focusable action // This will cause assistive technology to offer the user an option // to focus the current element if it supports it. @@ -453,28 +472,6 @@ impl AccessibilityTree { )); } - // Set text value - if let Some(alt) = &node_accessibility.a11y_alt { - builder.set_value(alt.to_owned()); - } else if let Some(value) = node_ref.get_inner_texts() { - builder.set_value(value); - builder.set_role(Role::Label); - } - - // Set name - if let Some(name) = &node_accessibility.a11y_name { - builder.set_name(name.to_owned()); - } - - // Set role - if let Some(role) = node_accessibility.a11y_role { - builder.set_role(role); - } - // Set root role - if node_ref.id() == node_ref.real_dom().root_id() { - builder.set_role(Role::Window); - } - builder.build() } } diff --git a/crates/core/src/dom/mutations_writer.rs b/crates/core/src/dom/mutations_writer.rs index 8db949c0b..aeb1d59be 100644 --- a/crates/core/src/dom/mutations_writer.rs +++ b/crates/core/src/dom/mutations_writer.rs @@ -17,7 +17,6 @@ use freya_native_core::{ NodeId, }; use freya_node_state::{ - AccessibilityNodeState, CursorState, CustomAttributeValues, LayerState, @@ -91,9 +90,8 @@ impl<'a> MutationsWriter<'a> { // Remove from the accessibility tree if node.get_accessibility_id().is_some() { - let node_accessibility_state = node.get::().unwrap(); - let closed_accessibility_node_id = node_accessibility_state - .closest_accessibility_node_id + let closed_accessibility_node_id = node + .parent_id() .unwrap_or(self.native_writer.rdom.root_id()); self.accessibility_dirty_nodes .remove(node.id(), closed_accessibility_node_id); diff --git a/crates/core/src/render/utils/label.rs b/crates/core/src/render/utils/label.rs index a71eeb3eb..e1d1619b2 100644 --- a/crates/core/src/render/utils/label.rs +++ b/crates/core/src/render/utils/label.rs @@ -38,11 +38,13 @@ pub fn create_label( } let mut paragraph = paragraph_builder.build(); - paragraph.layout(if font_style.max_lines == Some(1) { - f32::MAX - } else { - area_size.width + 1.0 - }); + paragraph.layout( + if font_style.max_lines == Some(1) && font_style.text_align == TextAlign::default() { + f32::MAX + } else { + area_size.width + 1.0 + }, + ); paragraph } diff --git a/crates/core/src/render/utils/paragraph.rs b/crates/core/src/render/utils/paragraph.rs index 9fe6e89dc..6e8754a28 100644 --- a/crates/core/src/render/utils/paragraph.rs +++ b/crates/core/src/render/utils/paragraph.rs @@ -10,7 +10,6 @@ use freya_node_state::{ FontStyleState, HighlightMode, LayoutState, - TextOverflow, }; use torin::prelude::{ Alignment, @@ -36,8 +35,8 @@ pub fn create_paragraph( paragraph_style.set_max_lines(font_style.max_lines); paragraph_style.set_replace_tab_characters(true); - if font_style.text_overflow == TextOverflow::Ellipsis { - paragraph_style.set_ellipsis("…"); + if let Some(ellipsis) = font_style.text_overflow.get_ellipsis() { + paragraph_style.set_ellipsis(ellipsis); } let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection); @@ -69,11 +68,13 @@ pub fn create_paragraph( } let mut paragraph = paragraph_builder.build(); - paragraph.layout(if font_style.max_lines == Some(1) { - f32::MAX - } else { - area_size.width + 1.0 - }); + paragraph.layout( + if font_style.max_lines == Some(1) && font_style.text_align == TextAlign::default() { + f32::MAX + } else { + area_size.width + 1.0 + }, + ); paragraph } diff --git a/crates/devtools/src/lib.rs b/crates/devtools/src/lib.rs index 5c651e353..f79b7b510 100644 --- a/crates/devtools/src/lib.rs +++ b/crates/devtools/src/lib.rs @@ -12,6 +12,7 @@ use freya_components::*; use freya_core::prelude::EventMessage; use freya_elements::elements as dioxus_elements; use freya_hooks::{ + use_applied_theme, use_init_theme, use_platform, DARK_THEME, @@ -116,15 +117,15 @@ impl PartialEq for DevToolsProps { #[allow(non_snake_case)] pub fn DevTools(props: DevToolsProps) -> Element { - let theme = use_init_theme(|| DARK_THEME); + use_init_theme(|| DARK_THEME); use_init_radio_station::(|| DevtoolsState { hovered_node: props.hovered_node.clone(), devtools_receiver: props.devtools_receiver.clone(), devtools_tree: HashSet::default(), }); - let theme = theme.read(); - let color = &theme.body.color; + let theme = use_applied_theme!(None, body); + let color = &theme.color; rsx!( rect { diff --git a/crates/elements/src/definitions.rs b/crates/elements/src/definitions.rs index ad5c9bdcb..abeb5c600 100644 --- a/crates/elements/src/definitions.rs +++ b/crates/elements/src/definitions.rs @@ -232,18 +232,81 @@ builder_constructors! { #[doc = include_str!("_docs/attributes/spacing.md")] spacing: String, - a11y_auto_focus: String, - a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, - a11y_focusable: String, canvas_reference: String, layer: String, offset_y: String, offset_x: String, reference: Reference, cursor_reference: CursorReference, + + a11y_id: String, + a11y_focusable: String, + a11y_auto_focus: String, + a11y_name: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `label` simply let's you display some text. /// @@ -305,12 +368,75 @@ builder_constructors! { opacity: String, layer: String, + + a11y_id: String, a11y_auto_focus: String, - a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, a11y_focusable: String, + a11y_name: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `paragraph` element let's you build texts with different styles. /// @@ -384,15 +510,79 @@ builder_constructors! { cursor_color: String, cursor_mode: String, cursor_id: String, - a11y_auto_focus: String, - a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, - a11y_focusable: String, + highlights: String, highlight_color: String, highlight_mode: String, + + a11y_id: String, + a11y_focusable: String, + a11y_auto_focus: String, + a11y_name: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `text` element is simply a text span used for the `paragraph` element. text { @@ -400,9 +590,6 @@ builder_constructors! { color: String, #[doc = include_str!("_docs/attributes/font_size.md")] text_shadow: String, - #[doc = include_str!("_docs/attributes/width_height.md")] - height: String, - width: String, #[doc = include_str!("_docs/attributes/font_size.md")] font_size: String, #[doc = include_str!("_docs/attributes/font_family.md")] @@ -456,11 +643,75 @@ builder_constructors! { image_data: String, image_reference: String, + + a11y_id: String, a11y_auto_focus: String, + a11y_focusable: String, a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; /// `svg` element let's you display SVG code. /// @@ -495,12 +746,75 @@ builder_constructors! { svg_data: String, svg_content: String, + + a11y_id: String, + a11y_focusable: String, a11y_auto_focus: String, a11y_name: String, - a11y_role:String, - a11y_id: AccessibilityId, - a11y_alt: String, - a11y_focusable: String, + a11y_description: String, + a11y_value: String, + a11y_access_key: String, + a11y_author_id: String, + a11y_keyboard_shortcut: String, + a11y_language: String, + a11y_placeholder: String, + a11y_role_description: String, + a11y_state_description: String, + a11y_tooltip: String, + a11y_url: String, + a11y_row_index_text: String, + a11y_column_index_text: String, + a11y_scroll_x: String, + a11y_scroll_x_min: String, + a11y_scroll_x_max: String, + a11y_scroll_y: String, + a11y_scroll_y_min: String, + a11y_scroll_y_max: String, + a11y_numeric_value: String, + a11y_min_numeric_value: String, + a11y_max_numeric_value: String, + a11y_numeric_value_step: String, + a11y_numeric_value_jump: String, + a11y_row_count: String, + a11y_column_count: String, + a11y_row_index: String, + a11y_column_index: String, + a11y_row_span: String, + a11y_column_span: String, + a11y_level: String, + a11y_size_of_set: String, + a11y_position_in_set: String, + a11y_color_value: String, + a11y_expanded: String, + a11y_selected: String, + a11y_hovered: String, + a11y_hidden: String, + a11y_linked: String, + a11y_multiselectable: String, + a11y_required: String, + a11y_visited: String, + a11y_busy: String, + a11y_live_atomic: String, + a11y_modal: String, + a11y_touch_transparent: String, + a11y_read_only: String, + a11y_disabled: String, + a11y_is_spelling_error: String, + a11y_is_grammar_error: String, + a11y_is_search_match: String, + a11y_is_suggestion: String, + a11y_role: String, + a11y_invalid: String, + a11y_toggled: String, + a11y_live: String, + a11y_default_action_verb: String, + a11y_orientation: String, + a11y_sort_direction: String, + a11y_current: String, + a11y_auto_complete: String, + a11y_has_popup: String, + a11y_list_style: String, + a11y_vertical_offset: String, }; } diff --git a/crates/freya/src/plugins/performance_overlay.rs b/crates/freya/src/plugins/performance_overlay.rs index 11d257808..3d4b79852 100644 --- a/crates/freya/src/plugins/performance_overlay.rs +++ b/crates/freya/src/plugins/performance_overlay.rs @@ -82,7 +82,7 @@ impl FreyaPlugin for PerformanceOverlayPlugin { // FPS add_text( &mut paragraph_builder, - format!("{} \n", self.frames.len()), + format!("{} FPS\n", self.frames.len()), 30.0, ); diff --git a/crates/hooks/src/text_editor.rs b/crates/hooks/src/text_editor.rs index ad932c235..936c3f13f 100644 --- a/crates/hooks/src/text_editor.rs +++ b/crates/hooks/src/text_editor.rs @@ -459,9 +459,8 @@ pub trait TextEditor { // Remove selected text let selection = self.get_selection_range(); if let Some((start, end)) = selection { - let cursor_pos = self.cursor_pos(); - let removed_text_len = self.remove(start..end); - self.set_cursor_pos(cursor_pos - removed_text_len); + self.remove(start..end); + self.set_cursor_pos(start); event.insert(TextEvent::TEXT_CHANGED); } diff --git a/crates/hooks/src/theming/base.rs b/crates/hooks/src/theming/base.rs index d8ab036ee..6371c4eea 100644 --- a/crates/hooks/src/theming/base.rs +++ b/crates/hooks/src/theming/base.rs @@ -21,6 +21,7 @@ pub(crate) const BASE_THEME: Theme = Theme { solid: cow_borrowed!(""), color: cow_borrowed!(""), placeholder_color: cow_borrowed!(""), + highlight_color: cow_borrowed!(""), }, body: BodyTheme { background: cow_borrowed!("key(background)"), @@ -55,7 +56,7 @@ pub(crate) const BASE_THEME: Theme = Theme { color: cow_borrowed!("key(color)"), }, placeholder_font_theme: FontTheme { - color: cow_borrowed!("rgb(100, 100, 100)"), + color: cow_borrowed!("key(placeholder_color)"), }, border_fill: cow_borrowed!("key(surface)"), width: cow_borrowed!("150"), @@ -94,7 +95,7 @@ pub(crate) const BASE_THEME: Theme = Theme { color: cow_borrowed!("key(color)"), }, border_fill: cow_borrowed!("key(surface)"), - arrow_fill: cow_borrowed!("rgb(40, 40, 40)"), + arrow_fill: cow_borrowed!("key(solid)"), }, dropdown_item: DropdownItemTheme { background: cow_borrowed!("key(background)"), @@ -105,7 +106,7 @@ pub(crate) const BASE_THEME: Theme = Theme { }, }, accordion: AccordionTheme { - color: cow_borrowed!("black"), + color: cow_borrowed!("key(color)"), background: cow_borrowed!("key(neutral_surface)"), border_fill: cow_borrowed!("key(surface)"), }, @@ -113,7 +114,7 @@ pub(crate) const BASE_THEME: Theme = Theme { primary_color: cow_borrowed!("key(tertiary_opposite_surface)"), }, link: LinkTheme { - highlight_color: cow_borrowed!("rgb(43,106,208)"), + highlight_color: cow_borrowed!("key(highlight_color)"), }, progress_bar: ProgressBarTheme { color: cow_borrowed!("white"), @@ -124,10 +125,10 @@ pub(crate) const BASE_THEME: Theme = Theme { }, table: TableTheme { font_theme: FontTheme { - color: cow_borrowed!("black"), + color: cow_borrowed!("key(color)"), }, background: cow_borrowed!("key(background)"), - arrow_fill: cow_borrowed!("rgb(40, 40, 40)"), + arrow_fill: cow_borrowed!("key(solid)"), row_background: cow_borrowed!("transparent"), alternate_row_background: cow_borrowed!("key(neutral_surface)"), divider_fill: cow_borrowed!("key(secondary_surface)"), @@ -200,7 +201,7 @@ pub(crate) const BASE_THEME: Theme = Theme { }, popup: PopupTheme { background: cow_borrowed!("key(background)"), - color: cow_borrowed!("black"), + color: cow_borrowed!("key(color)"), cross_fill: cow_borrowed!("key(solid)"), width: cow_borrowed!("350"), height: cow_borrowed!("200"), diff --git a/crates/hooks/src/theming/mod.rs b/crates/hooks/src/theming/mod.rs index ae3f32f7d..10d9d23e3 100644 --- a/crates/hooks/src/theming/mod.rs +++ b/crates/hooks/src/theming/mod.rs @@ -545,6 +545,7 @@ pub struct ColorsSheet { pub solid: Cow<'static, str>, pub color: Cow<'static, str>, pub placeholder_color: Cow<'static, str>, + pub highlight_color: Cow<'static, str>, } impl ColorsSheet { @@ -567,6 +568,7 @@ impl ColorsSheet { "solid" => self.solid.clone(), "color" => self.color.clone(), "placeholder_color" => self.placeholder_color.clone(), + "highlight_color" => self.highlight_color.clone(), _ => self.primary.clone(), } } else { diff --git a/crates/hooks/src/theming/themes.rs b/crates/hooks/src/theming/themes.rs index 289bec04e..5afb9f2d4 100644 --- a/crates/hooks/src/theming/themes.rs +++ b/crates/hooks/src/theming/themes.rs @@ -14,14 +14,15 @@ pub const DARK_THEME: Theme = Theme { secondary_surface: cow_borrowed!("rgb(45, 45, 45)"), neutral_surface: cow_borrowed!("rgb(25, 25, 25)"), focused_surface: cow_borrowed!("rgb(15, 15, 15)"), - opposite_surface: cow_borrowed!("rgb(210, 210, 210)"), - secondary_opposite_surface: cow_borrowed!("rgb(225, 225, 225)"), - tertiary_opposite_surface: cow_borrowed!("rgb(235, 235, 235)"), + opposite_surface: cow_borrowed!("rgb(125, 125, 125)"), + secondary_opposite_surface: cow_borrowed!("rgb(150, 150, 150)"), + tertiary_opposite_surface: cow_borrowed!("rgb(170, 170, 170)"), background: cow_borrowed!("rgb(20, 20, 20)"), focused_border: cow_borrowed!("rgb(110, 110, 110)"), solid: cow_borrowed!("rgb(240, 240, 240)"), color: cow_borrowed!("rgb(250, 250, 250)"), placeholder_color: cow_borrowed!("rgb(210, 210, 210)"), + highlight_color: cow_borrowed!("rgb(96, 145, 224)"), }, ..BASE_THEME }; @@ -44,6 +45,7 @@ pub const LIGHT_THEME: Theme = Theme { focused_border: cow_borrowed!("rgb(180, 180, 180)"), color: cow_borrowed!("rgb(10, 10, 10)"), placeholder_color: cow_borrowed!("rgb(100, 100, 100)"), + highlight_color: cow_borrowed!("rgb(38, 89, 170)"), }, ..BASE_THEME }; @@ -66,6 +68,7 @@ pub const BANANA_THEME: Theme = Theme { focused_border: cow_borrowed!("rgb(255, 239, 151)"), color: cow_borrowed!("rgb(85, 60, 5)"), placeholder_color: cow_borrowed!("rgb(56, 44, 5)"), + highlight_color: cow_borrowed!("rgb(143, 114, 6)"), }, ..BASE_THEME }; diff --git a/crates/hooks/src/use_init_native_platform.rs b/crates/hooks/src/use_init_native_platform.rs index bb5dffd64..aa267f673 100644 --- a/crates/hooks/src/use_init_native_platform.rs +++ b/crates/hooks/src/use_init_native_platform.rs @@ -155,7 +155,6 @@ mod test { let focus = use_focus(); rsx!(rect { a11y_id: focus.attribute(), - a11y_role: "genericContainer", width: "100%", height: "50%", }) @@ -223,12 +222,10 @@ mod test { rsx!( rect { a11y_id: focus_1.attribute(), - a11y_role: "genericContainer", a11y_auto_focus: "true", } rect { a11y_id: focus_2.attribute(), - a11y_role: "genericContainer", a11y_auto_focus: "true", } ) diff --git a/crates/native-core/src/attributes.rs b/crates/native-core/src/attributes.rs index f91f42fdc..1e7f2713a 100644 --- a/crates/native-core/src/attributes.rs +++ b/crates/native-core/src/attributes.rs @@ -43,12 +43,6 @@ pub enum AttributeName { PositionLeft, Opacity, Content, - A11YAutoFocus, - A11YName, - A11YFocusable, - A11YRole, - A11YId, - A11YAlt, CanvasReference, Layer, OffsetY, @@ -67,6 +61,160 @@ pub enum AttributeName { SvgData, SvgContent, Spacing, + + // Focus + A11yId, + A11yFocusable, + A11yAutoFocus, + + // Some internal notes about these accessibility attributes: + // + // - These are mostly derived from AccessKit's [`Node`] struct, with minor + // modifications to fit Freya's needs. These modifications are documented. + // + // - Some properties are commented out, meaning they are yet to be implemented. + // This is typically due to it being unclear how to represent these in Freya's + // attribute system (such as the association types, which will likely need + // some kind of ID system). + // + // - Any AccessKit properties that can be automatically calculated from style + // attributes or measured from torin are not included here, and are instead + // added in Freya's [`AccessibilityManager`] struct. + + // Vec associations + // A11yControls, + // A11yDetails, + // A11yDescribedBy, + // A11yFlowTo, + // A11yLabelledBy, + // A11yOwns, + // A11yRadioGroup, + + // NodeId associations + // ActiveDescendant, + // A11yErrorMessage, + // A11yInPageLinkTarget, + // A11yMemberOf, + // A11yNextOnLine, + // A11yPreviousOnLine, + // A11yPopupFor, + + // String + A11yName, + A11yDescription, + A11yValue, + A11yAccessKey, + A11yAuthorId, + // These three attributes are intended for assistive tech that parse MathML, + // which we don't support at the moment anyways. Unlikely to be implemented. + // A11yClassName, + // A11yHtmlTag, + // A11yInnerHtml, + A11yKeyboardShortcut, + A11yLanguage, + A11yPlaceholder, + A11yRoleDescription, + A11yStateDescription, + A11yTooltip, + A11yUrl, + A11yRowIndexText, + A11yColumnIndexText, + + // f64 + A11yScrollX, + A11yScrollXMin, + A11yScrollXMax, + A11yScrollY, + A11yScrollYMin, + A11yScrollYMax, + A11yNumericValue, + A11yMinNumericValue, + A11yMaxNumericValue, + A11yNumericValueStep, + A11yNumericValueJump, + + // usize + A11yRowCount, + A11yColumnCount, + A11yRowIndex, + A11yColumnIndex, + A11yRowSpan, + A11yColumnSpan, + A11yLevel, + A11ySizeOfSet, + A11yPositionInSet, + + // Color + A11yColorValue, + + // TODO: The following two categories are for inline text. They should be implemented + // automatically in [`AccessibilityManager`] based on Skia text measurement on text. + // spans. These really shouldn't be here (they should never have to be manually provided + // as an attribute), but I've left them here as a reminder to implement inline text data. + // + // See AccessKit's documentation for inline text measurements here: + // - + // + // Chromium also has a good writeup on how it measures inline text spans: + // - + + // LengthSlice + // A11yCharacterLengths, + // A11yWordLengths, + + // CoordSlice + // A11yCharacterPositions, + // A11yCharacterWidths, + + // bool + A11yExpanded, + A11ySelected, + + // bitflag + // TODO: This might be able to be determined automatically, + // but i'm not sure what ARIA property it corresponds to + // or its actual purpose. + A11yHovered, + A11yHidden, + A11yLinked, + A11yMultiselectable, + A11yRequired, + A11yVisited, + A11yBusy, + A11yLiveAtomic, + A11yModal, + A11yTouchTransparent, + A11yReadOnly, + A11yDisabled, + A11yIsSpellingError, + A11yIsGrammarError, + A11yIsSearchMatch, + A11yIsSuggestion, + + // Unique enums + A11yRole, + A11yInvalid, + A11yToggled, + A11yLive, + A11yDefaultActionVerb, + A11yOrientation, + A11ySortDirection, + A11yCurrent, // called AriaCurrent in accesskit, but that's a pretty poor name + A11yAutoComplete, + A11yHasPopup, + // This one is kind of weird to include, given it's reflecting a CSS property + // not in Freya for the HTML
    /
  • tags, but it can maybe be useful for + // language-specific semantics. + A11yListStyle, + A11yVerticalOffset, + // Other + // This could probably be inferred from Freya's text editing hook, but it's also + // a little strange in the data it expects. + // A11yTextSelection, + // A11yCustomActions, // Needs a special syntax or custom attribute value' + + // TODO: Some way to specify builtin AccessKit actions, as well as a way to + // handle actions in the form of an event. } impl FromStr for AttributeName { @@ -115,12 +263,6 @@ impl FromStr for AttributeName { "position_left" => Ok(AttributeName::PositionLeft), "opacity" => Ok(AttributeName::Opacity), "content" => Ok(AttributeName::Content), - "a11y_auto_focus" => Ok(AttributeName::A11YAutoFocus), - "a11y_name" => Ok(AttributeName::A11YName), - "a11y_role" => Ok(AttributeName::A11YRole), - "a11y_id" => Ok(AttributeName::A11YId), - "a11y_alt" => Ok(AttributeName::A11YAlt), - "a11y_focusable" => Ok(AttributeName::A11YFocusable), "canvas_reference" => Ok(AttributeName::CanvasReference), "layer" => Ok(AttributeName::Layer), "offset_y" => Ok(AttributeName::OffsetY), @@ -139,6 +281,74 @@ impl FromStr for AttributeName { "svg_data" => Ok(AttributeName::SvgData), "svg_content" => Ok(AttributeName::SvgContent), "spacing" => Ok(AttributeName::Spacing), + "a11y_id" => Ok(AttributeName::A11yId), + "a11y_focusable" => Ok(AttributeName::A11yFocusable), + "a11y_auto_focus" => Ok(AttributeName::A11yAutoFocus), + "a11y_name" => Ok(AttributeName::A11yName), + "a11y_description" => Ok(AttributeName::A11yDescription), + "a11y_value" => Ok(AttributeName::A11yValue), + "a11y_access_key" => Ok(AttributeName::A11yAccessKey), + "a11y_author_id" => Ok(AttributeName::A11yAuthorId), + "a11y_keyboard_shortcut" => Ok(AttributeName::A11yKeyboardShortcut), + "a11y_language" => Ok(AttributeName::A11yLanguage), + "a11y_placeholder" => Ok(AttributeName::A11yPlaceholder), + "a11y_role_description" => Ok(AttributeName::A11yRoleDescription), + "a11y_state_description" => Ok(AttributeName::A11yStateDescription), + "a11y_tooltip" => Ok(AttributeName::A11yTooltip), + "a11y_url" => Ok(AttributeName::A11yUrl), + "a11y_row_index_text" => Ok(AttributeName::A11yRowIndexText), + "a11y_column_index_text" => Ok(AttributeName::A11yColumnIndexText), + "a11y_scroll_x" => Ok(AttributeName::A11yScrollX), + "a11y_scroll_x_min" => Ok(AttributeName::A11yScrollXMin), + "a11y_scroll_x_max" => Ok(AttributeName::A11yScrollXMax), + "a11y_scroll_y" => Ok(AttributeName::A11yScrollY), + "a11y_scroll_y_min" => Ok(AttributeName::A11yScrollYMin), + "a11y_scroll_y_max" => Ok(AttributeName::A11yScrollYMax), + "a11y_numeric_value" => Ok(AttributeName::A11yNumericValue), + "a11y_min_numeric_value" => Ok(AttributeName::A11yMinNumericValue), + "a11y_max_numeric_value" => Ok(AttributeName::A11yMaxNumericValue), + "a11y_numeric_value_step" => Ok(AttributeName::A11yNumericValueStep), + "a11y_numeric_value_jump" => Ok(AttributeName::A11yNumericValueJump), + "a11y_row_count" => Ok(AttributeName::A11yRowCount), + "a11y_column_count" => Ok(AttributeName::A11yColumnCount), + "a11y_row_index" => Ok(AttributeName::A11yRowIndex), + "a11y_column_index" => Ok(AttributeName::A11yColumnIndex), + "a11y_row_span" => Ok(AttributeName::A11yRowSpan), + "a11y_column_span" => Ok(AttributeName::A11yColumnSpan), + "a11y_level" => Ok(AttributeName::A11yLevel), + "a11y_size_of_set" => Ok(AttributeName::A11ySizeOfSet), + "a11y_position_in_set" => Ok(AttributeName::A11yPositionInSet), + "a11y_color_value" => Ok(AttributeName::A11yColorValue), + "a11y_expanded" => Ok(AttributeName::A11yExpanded), + "a11y_selected" => Ok(AttributeName::A11ySelected), + "a11y_hovered" => Ok(AttributeName::A11yHovered), + "a11y_hidden" => Ok(AttributeName::A11yHidden), + "a11y_linked" => Ok(AttributeName::A11yLinked), + "a11y_multiselectable" => Ok(AttributeName::A11yMultiselectable), + "a11y_required" => Ok(AttributeName::A11yRequired), + "a11y_visited" => Ok(AttributeName::A11yVisited), + "a11y_busy" => Ok(AttributeName::A11yBusy), + "a11y_live_atomic" => Ok(AttributeName::A11yLiveAtomic), + "a11y_modal" => Ok(AttributeName::A11yModal), + "a11y_touch_transparent" => Ok(AttributeName::A11yTouchTransparent), + "a11y_read_only" => Ok(AttributeName::A11yReadOnly), + "a11y_disabled" => Ok(AttributeName::A11yDisabled), + "a11y_is_spelling_error" => Ok(AttributeName::A11yIsSpellingError), + "a11y_is_grammar_error" => Ok(AttributeName::A11yIsGrammarError), + "a11y_is_search_match" => Ok(AttributeName::A11yIsSearchMatch), + "a11y_is_suggestion" => Ok(AttributeName::A11yIsSuggestion), + "a11y_role" => Ok(AttributeName::A11yRole), + "a11y_invalid" => Ok(AttributeName::A11yInvalid), + "a11y_toggled" => Ok(AttributeName::A11yToggled), + "a11y_live" => Ok(AttributeName::A11yLive), + "a11y_default_action_verb" => Ok(AttributeName::A11yDefaultActionVerb), + "a11y_orientation" => Ok(AttributeName::A11yOrientation), + "a11y_sort_direction" => Ok(AttributeName::A11ySortDirection), + "a11y_current" => Ok(AttributeName::A11yCurrent), + "a11y_auto_complete" => Ok(AttributeName::A11yAutoComplete), + "a11y_has_popup" => Ok(AttributeName::A11yHasPopup), + "a11y_list_style" => Ok(AttributeName::A11yListStyle), + "a11y_vertical_offset" => Ok(AttributeName::A11yVerticalOffset), _ => Err(format!("{attr} not supported.")), } } diff --git a/crates/renderer/src/config.rs b/crates/renderer/src/config.rs index 4466ac4a4..226cf6e4d 100644 --- a/crates/renderer/src/config.rs +++ b/crates/renderer/src/config.rs @@ -8,18 +8,23 @@ use freya_core::{ FreyaPlugin, PluginsManager, }, + prelude::EventMessage, style::default_fonts, }; use freya_engine::prelude::Color; use freya_node_state::Parse; use image::ImageReader; -use winit::window::{ - Icon, - Window, - WindowAttributes, +use winit::{ + event_loop::EventLoopBuilder, + window::{ + Icon, + Window, + WindowAttributes, + }, }; -pub type WindowBuilderHook = Box WindowAttributes>; +pub type EventLoopBuilderHook = Box)>; +pub type WindowBuilderHook = Box WindowAttributes>; pub type EmbeddedFonts<'a> = Vec<(&'a str, &'a [u8])>; /// Configuration for a Window. @@ -48,6 +53,8 @@ pub struct WindowConfig { pub on_exit: Option, /// Hook function called with the Window Attributes. pub window_attributes_hook: Option, + /// Hook function called with the Event Loop Builder. + pub event_loop_builder_hook: Option, } impl Default for WindowConfig { @@ -65,6 +72,7 @@ impl Default for WindowConfig { on_setup: None, on_exit: None, window_attributes_hook: None, + event_loop_builder_hook: None, } } } @@ -213,9 +221,18 @@ impl<'a, T: Clone> LaunchConfig<'a, T> { /// Register a Window Attributes hook. pub fn with_window_attributes( mut self, - window_attributes_hook: impl Fn(WindowAttributes) -> WindowAttributes + 'static, + window_attributes_hook: impl FnOnce(WindowAttributes) -> WindowAttributes + 'static, ) -> Self { self.window_config.window_attributes_hook = Some(Box::new(window_attributes_hook)); self } + + /// Register an Event Loop Builder hook. + pub fn with_event_loop_builder( + mut self, + event_loop_builder_hook: impl FnOnce(&mut EventLoopBuilder) + 'static, + ) -> Self { + self.window_config.event_loop_builder_hook = Some(Box::new(event_loop_builder_hook)); + self + } } diff --git a/crates/renderer/src/renderer.rs b/crates/renderer/src/renderer.rs index 49fd15fa3..d5796e5b7 100644 --- a/crates/renderer/src/renderer.rs +++ b/crates/renderer/src/renderer.rs @@ -71,11 +71,16 @@ impl<'a, State: Clone + 'static> DesktopRenderer<'a, State> { pub fn launch( vdom: VirtualDom, sdom: SafeDOM, - config: LaunchConfig, + mut config: LaunchConfig, devtools: Option, hovered_node: HoveredNode, ) { - let event_loop = EventLoop::::with_user_event() + let mut event_loop_builder = EventLoop::::with_user_event(); + let event_loop_builder_hook = config.window_config.event_loop_builder_hook.take(); + if let Some(event_loop_builder_hook) = event_loop_builder_hook { + event_loop_builder_hook(&mut event_loop_builder); + } + let event_loop = event_loop_builder .build() .expect("Failed to create event loop."); let proxy = event_loop.create_proxy(); diff --git a/crates/renderer/src/window_state.rs b/crates/renderer/src/window_state.rs index 57a136264..2027682dd 100644 --- a/crates/renderer/src/window_state.rs +++ b/crates/renderer/src/window_state.rs @@ -94,7 +94,7 @@ impl<'a, State: Clone + 'a> WindowState<'a, State> { window_attributes.with_max_inner_size(LogicalSize::::from(max_size)); } - if let Some(with_window_attributes) = &config.window_config.window_attributes_hook { + if let Some(with_window_attributes) = config.window_config.window_attributes_hook.take() { window_attributes = (with_window_attributes)(window_attributes); } diff --git a/crates/state/src/accessibility.rs b/crates/state/src/accessibility.rs index 1fb90b874..751ee14ca 100644 --- a/crates/state/src/accessibility.rs +++ b/crates/state/src/accessibility.rs @@ -4,13 +4,26 @@ use std::sync::{ }; use accesskit::{ + AriaCurrent, + AutoComplete, + DefaultActionVerb, + HasPopup, + Invalid, + ListStyle, + Live, + NodeBuilder, NodeId as AccessibilityId, + Orientation, Role, + SortDirection, + Toggled, + VerticalOffset, }; use freya_common::{ AccessibilityDirtyNodes, AccessibilityGenerator, }; +use freya_engine::prelude::Color; use freya_native_core::{ attributes::AttributeName, exports::shipyard::Component, @@ -22,6 +35,7 @@ use freya_native_core::{ NodeMaskBuilder, State, }, + tags::TagName, NodeId, SendAnyMap, }; @@ -35,17 +49,13 @@ use crate::{ ParseError, }; -#[derive(Clone, Debug, PartialEq, Eq, Default, Component)] +#[derive(Clone, Debug, PartialEq, Default, Component)] pub struct AccessibilityNodeState { - pub closest_accessibility_node_id: Option, - pub descencent_accessibility_ids: Vec, pub node_id: NodeId, pub a11y_id: Option, - pub a11y_role: Option, - pub a11y_alt: Option, - pub a11y_name: Option, pub a11y_auto_focus: bool, pub a11y_focusable: Focusable, + pub builder: Option, } impl ParseAttribute for AccessibilityNodeState { @@ -54,47 +64,248 @@ impl ParseAttribute for AccessibilityNodeState { attr: freya_native_core::prelude::OwnedAttributeView, ) -> Result<(), crate::ParseError> { match attr.attribute { - AttributeName::A11YId => { + AttributeName::A11yId => { if let OwnedAttributeValue::Custom(CustomAttributeValues::AccessibilityId(id)) = attr.value { self.a11y_id = Some(*id); - // Enable focus on nodes that pass a custom a11y id if self.a11y_focusable.is_unknown() { self.a11y_focusable = Focusable::Enabled; } } } - AttributeName::A11YRole => { + AttributeName::A11yFocusable => { if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_role = Some( - serde_json::from_str::(&format!("\"{attr}\"")) - .map_err(|_| ParseError)?, - ) - } - } - AttributeName::A11YAlt => { - if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_alt = Some(attr.to_owned()) - } - } - AttributeName::A11YName => { - if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_name = Some(attr.to_owned()) + self.a11y_focusable = Focusable::parse(attr)?; } } - AttributeName::A11YAutoFocus => { + AttributeName::A11yAutoFocus => { if let OwnedAttributeValue::Text(attr) = attr.value { self.a11y_auto_focus = attr.parse().unwrap_or_default() } } - AttributeName::A11YFocusable => { + a11y_attr => { if let OwnedAttributeValue::Text(attr) = attr.value { - self.a11y_focusable = Focusable::parse(attr)?; + if let Some(builder) = self.builder.as_mut() { + match a11y_attr { + AttributeName::A11yName => builder.set_name(attr.clone()), + AttributeName::A11yDescription => builder.set_description(attr.clone()), + AttributeName::A11yValue => builder.set_value(attr.clone()), + AttributeName::A11yAccessKey => builder.set_access_key(attr.clone()), + AttributeName::A11yAuthorId => builder.set_author_id(attr.clone()), + AttributeName::A11yKeyboardShortcut => { + builder.set_keyboard_shortcut(attr.clone()) + } + AttributeName::A11yLanguage => builder.set_language(attr.clone()), + AttributeName::A11yPlaceholder => builder.set_placeholder(attr.clone()), + AttributeName::A11yRoleDescription => { + builder.set_role_description(attr.clone()) + } + AttributeName::A11yStateDescription => { + builder.set_state_description(attr.clone()) + } + AttributeName::A11yTooltip => builder.set_tooltip(attr.clone()), + AttributeName::A11yUrl => builder.set_url(attr.clone()), + AttributeName::A11yRowIndexText => { + builder.set_row_index_text(attr.clone()) + } + AttributeName::A11yColumnIndexText => { + builder.set_column_index_text(attr.clone()) + } + AttributeName::A11yScrollX => { + builder.set_scroll_x(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollXMin => { + builder.set_scroll_x_min(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollXMax => { + builder.set_scroll_x_max(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollY => { + builder.set_scroll_y(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollYMin => { + builder.set_scroll_y_min(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yScrollYMax => { + builder.set_scroll_y_max(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yNumericValue => { + builder.set_numeric_value(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yMinNumericValue => { + builder.set_min_numeric_value(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yMaxNumericValue => { + builder.set_max_numeric_value(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yNumericValueStep => builder + .set_numeric_value_step(attr.parse().map_err(|_| ParseError)?), + AttributeName::A11yNumericValueJump => builder + .set_numeric_value_jump(attr.parse().map_err(|_| ParseError)?), + AttributeName::A11yRowCount => { + builder.set_row_count(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColumnCount => { + builder.set_column_count(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yRowIndex => { + builder.set_row_index(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColumnIndex => { + builder.set_column_index(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yRowSpan => { + builder.set_row_span(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColumnSpan => { + builder.set_column_span(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yLevel => { + builder.set_level(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11ySizeOfSet => { + builder.set_size_of_set(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yPositionInSet => { + builder.set_position_in_set(attr.parse().map_err(|_| ParseError)?) + } + AttributeName::A11yColorValue => { + let color = Color::parse(attr)?; + builder.set_color_value( + ((color.a() as u32) << 24) + | ((color.b() as u32) << 16) + | (((color.g() as u32) << 8) + (color.r() as u32)), + ); + } + AttributeName::A11yExpanded => { + builder.set_expanded(attr.parse::().map_err(|_| ParseError)?); + } + AttributeName::A11ySelected => { + builder.set_selected(attr.parse::().map_err(|_| ParseError)?); + } + AttributeName::A11yHovered => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_hovered(); + } + } + AttributeName::A11yHidden => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_hidden(); + } + } + AttributeName::A11yLinked => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_linked(); + } + } + AttributeName::A11yMultiselectable => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_multiselectable(); + } + } + AttributeName::A11yRequired => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_required(); + } + } + AttributeName::A11yVisited => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_visited(); + } + } + AttributeName::A11yBusy => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_busy(); + } + } + AttributeName::A11yLiveAtomic => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_live_atomic(); + } + } + AttributeName::A11yModal => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_modal(); + } + } + AttributeName::A11yTouchTransparent => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_touch_transparent(); + } + } + AttributeName::A11yReadOnly => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_read_only(); + } + } + AttributeName::A11yDisabled => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_disabled(); + } + } + AttributeName::A11yIsSpellingError => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_spelling_error(); + } + } + AttributeName::A11yIsGrammarError => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_grammar_error(); + } + } + AttributeName::A11yIsSearchMatch => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_search_match(); + } + } + AttributeName::A11yIsSuggestion => { + if attr.parse::().map_err(|_| ParseError)? { + builder.set_is_suggestion(); + } + } + AttributeName::A11yRole => { + builder.set_role(Role::parse(attr)?); + } + AttributeName::A11yInvalid => { + builder.set_invalid(Invalid::parse(attr)?); + } + AttributeName::A11yToggled => { + builder.set_toggled(Toggled::parse(attr)?); + } + AttributeName::A11yLive => { + builder.set_live(Live::parse(attr)?); + } + AttributeName::A11yDefaultActionVerb => { + builder.set_default_action_verb(DefaultActionVerb::parse(attr)?); + } + AttributeName::A11yOrientation => { + builder.set_orientation(Orientation::parse(attr)?); + } + AttributeName::A11ySortDirection => { + builder.set_sort_direction(SortDirection::parse(attr)?); + } + AttributeName::A11yCurrent => { + builder.set_aria_current(AriaCurrent::parse(attr)?); + } + AttributeName::A11yAutoComplete => { + builder.set_auto_complete(AutoComplete::parse(attr)?); + } + AttributeName::A11yHasPopup => { + builder.set_has_popup(HasPopup::parse(attr)?); + } + AttributeName::A11yListStyle => { + builder.set_list_style(ListStyle::parse(attr)?); + } + AttributeName::A11yVerticalOffset => { + builder.set_vertical_offset(VerticalOffset::parse(attr)?); + } + _ => {} + } + } } } - _ => {} } Ok(()) @@ -103,27 +314,91 @@ impl ParseAttribute for AccessibilityNodeState { #[partial_derive_state] impl State for AccessibilityNodeState { - type ParentDependencies = (Self,); + type ParentDependencies = (); - type ChildDependencies = (Self,); + type ChildDependencies = (); type NodeDependencies = (); - const NODE_MASK: NodeMaskBuilder<'static> = - NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&[ - AttributeName::A11YId, - AttributeName::A11YRole, - AttributeName::A11YAlt, - AttributeName::A11YName, - AttributeName::A11YAutoFocus, - ])); + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + .with_attrs(AttributeMaskBuilder::Some(&[ + AttributeName::A11yId, + AttributeName::A11yFocusable, + AttributeName::A11yAutoFocus, + AttributeName::A11yName, + AttributeName::A11yDescription, + AttributeName::A11yValue, + AttributeName::A11yAccessKey, + AttributeName::A11yAuthorId, + AttributeName::A11yKeyboardShortcut, + AttributeName::A11yLanguage, + AttributeName::A11yPlaceholder, + AttributeName::A11yRoleDescription, + AttributeName::A11yStateDescription, + AttributeName::A11yTooltip, + AttributeName::A11yUrl, + AttributeName::A11yRowIndexText, + AttributeName::A11yColumnIndexText, + AttributeName::A11yScrollX, + AttributeName::A11yScrollXMin, + AttributeName::A11yScrollXMax, + AttributeName::A11yScrollY, + AttributeName::A11yScrollYMin, + AttributeName::A11yScrollYMax, + AttributeName::A11yNumericValue, + AttributeName::A11yMinNumericValue, + AttributeName::A11yMaxNumericValue, + AttributeName::A11yNumericValueStep, + AttributeName::A11yNumericValueJump, + AttributeName::A11yRowCount, + AttributeName::A11yColumnCount, + AttributeName::A11yRowIndex, + AttributeName::A11yColumnIndex, + AttributeName::A11yRowSpan, + AttributeName::A11yColumnSpan, + AttributeName::A11yLevel, + AttributeName::A11ySizeOfSet, + AttributeName::A11yPositionInSet, + AttributeName::A11yColorValue, + AttributeName::A11yExpanded, + AttributeName::A11ySelected, + AttributeName::A11yHovered, + AttributeName::A11yHidden, + AttributeName::A11yLinked, + AttributeName::A11yMultiselectable, + AttributeName::A11yRequired, + AttributeName::A11yVisited, + AttributeName::A11yBusy, + AttributeName::A11yLiveAtomic, + AttributeName::A11yModal, + AttributeName::A11yTouchTransparent, + AttributeName::A11yReadOnly, + AttributeName::A11yDisabled, + AttributeName::A11yIsSpellingError, + AttributeName::A11yIsGrammarError, + AttributeName::A11yIsSearchMatch, + AttributeName::A11yIsSuggestion, + AttributeName::A11yRole, + AttributeName::A11yInvalid, + AttributeName::A11yToggled, + AttributeName::A11yLive, + AttributeName::A11yDefaultActionVerb, + AttributeName::A11yOrientation, + AttributeName::A11ySortDirection, + AttributeName::A11yCurrent, + AttributeName::A11yAutoComplete, + AttributeName::A11yHasPopup, + AttributeName::A11yListStyle, + AttributeName::A11yVerticalOffset, + ])) + .with_tag(); fn update<'a>( &mut self, node_view: NodeView, _node: ::ElementBorrowed<'a>, - parent: Option<::ElementBorrowed<'a>>, - children: Vec<::ElementBorrowed<'a>>, + _parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> bool { let root_id = context.get::().unwrap(); @@ -134,6 +409,18 @@ impl State for AccessibilityNodeState { let mut accessibility = AccessibilityNodeState { node_id: node_view.node_id(), a11y_id: self.a11y_id, + builder: node_view.tag().and_then(|tag| { + match tag { + TagName::Image => Some(NodeBuilder::new(Role::Image)), + TagName::Label => Some(NodeBuilder::new(Role::Label)), + TagName::Paragraph => Some(NodeBuilder::new(Role::Paragraph)), + TagName::Rect => Some(NodeBuilder::new(Role::GenericContainer)), + TagName::Svg => Some(NodeBuilder::new(Role::GraphicsObject)), + TagName::Root => Some(NodeBuilder::new(Role::Window)), + // TODO: make this InlineTextBox and supply computed text span properties + TagName::Text => None, + } + }), ..Default::default() }; @@ -143,36 +430,19 @@ impl State for AccessibilityNodeState { } } - for (child,) in children { - if let Some(child_id) = child.a11y_id { - // Mark this child as descendent if it has an ID - accessibility.descencent_accessibility_ids.push(child_id) - } else { - // If it doesn't have an ID then use its descencent accessibility IDs - accessibility - .descencent_accessibility_ids - .extend(child.descencent_accessibility_ids.iter()); - } - } - - if let Some(parent) = parent { - // Mark the parent accessibility ID as the closest to this node or - // fallback to its closest ID. - accessibility.closest_accessibility_node_id = parent - .0 - .a11y_id - .map(|_| parent.0.node_id) - .or(parent.0.closest_accessibility_node_id); - } - let changed = &accessibility != self; let had_id = self.a11y_id.is_some(); *self = accessibility; - if changed { - // Assign an accessibility ID if none was passed but the node has a role - if self.a11y_id.is_none() && self.a11y_role.is_some() { + let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id; + + if changed && !is_orphan { + // Assign an accessibility ID if none was passed but the node has a valid builder + // + // In our case, builder will be `None` if the node's tag cannot be added to accessibility + // tree. + if self.a11y_id.is_none() && self.builder.is_some() { let id = AccessibilityId(accessibility_generator.new_id()); #[cfg(debug_assertions)] tracing::info!("Assigned {id:?} to {:?}", node_view.node_id()); diff --git a/crates/state/src/cursor.rs b/crates/state/src/cursor.rs index 6dcb5bbc2..f629507a0 100644 --- a/crates/state/src/cursor.rs +++ b/crates/state/src/cursor.rs @@ -21,6 +21,7 @@ use freya_native_core::{ State, }, tags::TagName, + NodeId, SendAnyMap, }; use freya_native_core_macro::partial_derive_state; @@ -151,6 +152,7 @@ impl State for CursorState { _children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> bool { + let root_id = context.get::().unwrap(); let paragraphs = context.get::>>().unwrap(); let compositor_dirty_nodes = context.get::>>().unwrap(); let mut cursor = parent.map(|(p,)| p.clone()).unwrap_or_default(); @@ -162,7 +164,9 @@ impl State for CursorState { } let changed = &cursor != self; - if changed && CursorMode::Editable == cursor.mode { + let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id; + + if changed && CursorMode::Editable == cursor.mode && !is_orphan { if let Some((tag, cursor_ref)) = node_view.tag().zip(cursor.cursor_ref.as_ref()) { if *tag == TagName::Paragraph { paragraphs diff --git a/crates/state/src/font_style.rs b/crates/state/src/font_style.rs index 54f215a0a..81005d484 100644 --- a/crates/state/src/font_style.rs +++ b/crates/state/src/font_style.rs @@ -277,6 +277,7 @@ impl State for FontStyleState { _children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> bool { + let root_id = context.get::().unwrap(); let torin_layout = context.get::>>>().unwrap(); let compositor_dirty_nodes = context.get::>>().unwrap(); @@ -290,7 +291,9 @@ impl State for FontStyleState { let changed = &font_style != self; - if changed { + let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id; + + if changed && !is_orphan { torin_layout.lock().unwrap().invalidate(node_view.node_id()); compositor_dirty_nodes .lock() diff --git a/crates/state/src/layer.rs b/crates/state/src/layer.rs index fc5d7c616..24bed3c99 100644 --- a/crates/state/src/layer.rs +++ b/crates/state/src/layer.rs @@ -14,6 +14,7 @@ use freya_native_core::{ NodeMaskBuilder, State, }, + NodeId, SendAnyMap, }; use freya_native_core_macro::partial_derive_state; @@ -75,6 +76,7 @@ impl State for LayerState { return false; } + let root_id = context.get::().unwrap(); let layers = context.get::>>().unwrap(); let inherited_layer = parent.map(|(p,)| p.layer_for_children).unwrap_or(0i16); @@ -91,7 +93,9 @@ impl State for LayerState { let changed = &layer_state != self; - if changed { + let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id; + + if changed && !is_orphan { layers .lock() .unwrap() diff --git a/crates/state/src/layout.rs b/crates/state/src/layout.rs index 939ebcd01..753123534 100644 --- a/crates/state/src/layout.rs +++ b/crates/state/src/layout.rs @@ -221,6 +221,7 @@ impl State for LayoutState { _children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> bool { + let root_id = context.get::().unwrap(); let torin_layout = context.get::>>>().unwrap(); let compositor_dirty_nodes = context.get::>>().unwrap(); @@ -237,7 +238,9 @@ impl State for LayoutState { let changed = layout != *self; - if changed { + let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id; + + if changed && !is_orphan { torin_layout.lock().unwrap().invalidate(node_view.node_id()); compositor_dirty_nodes .lock() diff --git a/crates/state/src/transform.rs b/crates/state/src/transform.rs index d7dfc634d..2d0569722 100644 --- a/crates/state/src/transform.rs +++ b/crates/state/src/transform.rs @@ -85,6 +85,7 @@ impl State for TransformState { _children: Vec<::ElementBorrowed<'a>>, context: &SendAnyMap, ) -> bool { + let root_id = context.get::().unwrap(); let compositor_dirty_nodes = context.get::>>().unwrap(); let inherited_transform = parent.map(|(p,)| p.clone()).unwrap_or_default(); @@ -101,7 +102,9 @@ impl State for TransformState { let changed = transform_state != *self; - if changed { + let is_orphan = node_view.height() == 0 && node_view.node_id() != *root_id; + + if changed && !is_orphan { compositor_dirty_nodes .lock() .unwrap() diff --git a/crates/state/src/values/accessibility.rs b/crates/state/src/values/accessibility.rs new file mode 100644 index 000000000..35101fffb --- /dev/null +++ b/crates/state/src/values/accessibility.rs @@ -0,0 +1,349 @@ +use accesskit::{ + AriaCurrent, + AutoComplete, + DefaultActionVerb, + HasPopup, + Invalid, + ListStyle, + Live, + Orientation, + Role, + SortDirection, + Toggled, + VerticalOffset, +}; + +use crate::{ + Parse, + ParseError, +}; + +impl Parse for Role { + fn parse(value: &str) -> Result { + Ok(match value { + "unknown" => Self::Unknown, + "inline-text-box" => Self::InlineTextBox, + "cell" => Self::Cell, + "label" => Self::Label, + "image" => Self::Image, + "link" => Self::Link, + "row" => Self::Row, + "list-item" => Self::ListItem, + "list-marker" => Self::ListMarker, + "tree-item" => Self::TreeItem, + "list-box-option" => Self::ListBoxOption, + "menu-item" => Self::MenuItem, + "menu-list-option" => Self::MenuListOption, + "paragraph" => Self::Paragraph, + "generic-container" => Self::GenericContainer, + "check-box" => Self::CheckBox, + "radio-button" => Self::RadioButton, + "text-input" => Self::TextInput, + "button" => Self::Button, + "default-button" => Self::DefaultButton, + "pane" => Self::Pane, + "row-header" => Self::RowHeader, + "column-header" => Self::ColumnHeader, + "row-group" => Self::RowGroup, + "list" => Self::List, + "table" => Self::Table, + "layout-table-cell" => Self::LayoutTableCell, + "layout-table-row" => Self::LayoutTableRow, + "layout-table" => Self::LayoutTable, + "switch" => Self::Switch, + "menu" => Self::Menu, + "multiline-text-input" => Self::MultilineTextInput, + "search-input" => Self::SearchInput, + "date-input" => Self::DateInput, + "date-time-input" => Self::DateTimeInput, + "week-input" => Self::WeekInput, + "month-input" => Self::MonthInput, + "time-input" => Self::TimeInput, + "email-input" => Self::EmailInput, + "number-input" => Self::NumberInput, + "password-input" => Self::PasswordInput, + "phone-number-input" => Self::PhoneNumberInput, + "url-input" => Self::UrlInput, + "abbr" => Self::Abbr, + "alert" => Self::Alert, + "alert-dialog" => Self::AlertDialog, + "application" => Self::Application, + "article" => Self::Article, + "audio" => Self::Audio, + "banner" => Self::Banner, + "blockquote" => Self::Blockquote, + "canvas" => Self::Canvas, + "caption" => Self::Caption, + "caret" => Self::Caret, + "code" => Self::Code, + "color-well" => Self::ColorWell, + "combo-box" => Self::ComboBox, + "editable-combo-box" => Self::EditableComboBox, + "complementary" => Self::Complementary, + "comment" => Self::Comment, + "content-deletion" => Self::ContentDeletion, + "content-insertion" => Self::ContentInsertion, + "content-info" => Self::ContentInfo, + "definition" => Self::Definition, + "description-list" => Self::DescriptionList, + "description-list-detail" => Self::DescriptionListDetail, + "description-list-term" => Self::DescriptionListTerm, + "details" => Self::Details, + "dialog" => Self::Dialog, + "directory" => Self::Directory, + "disclosure-triangle" => Self::DisclosureTriangle, + "document" => Self::Document, + "embedded-object" => Self::EmbeddedObject, + "emphasis" => Self::Emphasis, + "feed" => Self::Feed, + "figure-caption" => Self::FigureCaption, + "figure" => Self::Figure, + "footer" => Self::Footer, + "footer-as-non-landmark" => Self::FooterAsNonLandmark, + "form" => Self::Form, + "grid" => Self::Grid, + "group" => Self::Group, + "header" => Self::Header, + "header-as-non-landmark" => Self::HeaderAsNonLandmark, + "heading" => Self::Heading, + "iframe" => Self::Iframe, + "iframe-presentational" => Self::IframePresentational, + "ime-candidate" => Self::ImeCandidate, + "keyboard" => Self::Keyboard, + "legend" => Self::Legend, + "line-break" => Self::LineBreak, + "list-box" => Self::ListBox, + "log" => Self::Log, + "main" => Self::Main, + "mark" => Self::Mark, + "marquee" => Self::Marquee, + "math" => Self::Math, + "menu-bar" => Self::MenuBar, + "menu-item-check-box" => Self::MenuItemCheckBox, + "menu-item-radio" => Self::MenuItemRadio, + "menu-list-popup" => Self::MenuListPopup, + "meter" => Self::Meter, + "navigation" => Self::Navigation, + "note" => Self::Note, + "plugin-object" => Self::PluginObject, + "portal" => Self::Portal, + "pre" => Self::Pre, + "progress-indicator" => Self::ProgressIndicator, + "radio-group" => Self::RadioGroup, + "region" => Self::Region, + "root-web-area" => Self::RootWebArea, + "ruby" => Self::Ruby, + "ruby-annotation" => Self::RubyAnnotation, + "scroll-bar" => Self::ScrollBar, + "scroll-view" => Self::ScrollView, + "search" => Self::Search, + "section" => Self::Section, + "slider" => Self::Slider, + "spin-button" => Self::SpinButton, + "splitter" => Self::Splitter, + "status" => Self::Status, + "strong" => Self::Strong, + "suggestion" => Self::Suggestion, + "svg-root" => Self::SvgRoot, + "tab" => Self::Tab, + "tab-list" => Self::TabList, + "tab-panel" => Self::TabPanel, + "term" => Self::Term, + "time" => Self::Time, + "timer" => Self::Timer, + "title-bar" => Self::TitleBar, + "toolbar" => Self::Toolbar, + "tooltip" => Self::Tooltip, + "tree" => Self::Tree, + "tree-grid" => Self::TreeGrid, + "video" => Self::Video, + "web-view" => Self::WebView, + "window" => Self::Window, + "pdf-actionable-highlight" => Self::PdfActionableHighlight, + "pdf-root" => Self::PdfRoot, + "graphics-document" => Self::GraphicsDocument, + "graphics-object" => Self::GraphicsObject, + "graphics-symbol" => Self::GraphicsSymbol, + "doc-abstract" => Self::DocAbstract, + "doc-acknowledgements" => Self::DocAcknowledgements, + "doc-afterword" => Self::DocAfterword, + "doc-appendix" => Self::DocAppendix, + "doc-back-link" => Self::DocBackLink, + "doc-biblio-entry" => Self::DocBiblioEntry, + "doc-bibliography" => Self::DocBibliography, + "doc-biblio-ref" => Self::DocBiblioRef, + "doc-chapter" => Self::DocChapter, + "doc-colophon" => Self::DocColophon, + "doc-conclusion" => Self::DocConclusion, + "doc-cover" => Self::DocCover, + "doc-credit" => Self::DocCredit, + "doc-credits" => Self::DocCredits, + "doc-dedication" => Self::DocDedication, + "doc-endnote" => Self::DocEndnote, + "doc-endnotes" => Self::DocEndnotes, + "doc-epigraph" => Self::DocEpigraph, + "doc-epilogue" => Self::DocEpilogue, + "doc-errata" => Self::DocErrata, + "doc-example" => Self::DocExample, + "doc-footnote" => Self::DocFootnote, + "doc-foreword" => Self::DocForeword, + "doc-glossary" => Self::DocGlossary, + "doc-gloss-ref" => Self::DocGlossRef, + "doc-index" => Self::DocIndex, + "doc-introduction" => Self::DocIntroduction, + "doc-note-ref" => Self::DocNoteRef, + "doc-notice" => Self::DocNotice, + "doc-page-break" => Self::DocPageBreak, + "doc-page-footer" => Self::DocPageFooter, + "doc-page-header" => Self::DocPageHeader, + "doc-page-list" => Self::DocPageList, + "doc-part" => Self::DocPart, + "doc-preface" => Self::DocPreface, + "doc-prologue" => Self::DocPrologue, + "doc-pullquote" => Self::DocPullquote, + "doc-qna" => Self::DocQna, + "doc-subtitle" => Self::DocSubtitle, + "doc-tip" => Self::DocTip, + "doc-toc" => Self::DocToc, + "list-grid" => Self::ListGrid, + "terminal" => Self::Terminal, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Invalid { + fn parse(value: &str) -> Result { + Ok(match value { + "true" => Invalid::True, + "grammar" => Invalid::Grammar, + "spelling" => Invalid::Spelling, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Toggled { + fn parse(value: &str) -> Result { + Ok(match value { + "true" => Toggled::True, + "false" => Toggled::False, + "mixed" => Toggled::Mixed, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Live { + fn parse(value: &str) -> Result { + Ok(match value { + "assertive" => Live::Assertive, + "off" => Live::Off, + "polite" => Live::Polite, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for DefaultActionVerb { + fn parse(value: &str) -> Result { + Ok(match value { + "click" => DefaultActionVerb::Click, + "focus" => DefaultActionVerb::Focus, + "check" => DefaultActionVerb::Check, + "uncheck" => DefaultActionVerb::Uncheck, + "click-ancestor" => DefaultActionVerb::ClickAncestor, + "jump" => DefaultActionVerb::Jump, + "open" => DefaultActionVerb::Open, + "press" => DefaultActionVerb::Press, + "select" => DefaultActionVerb::Select, + "unselect" => DefaultActionVerb::Unselect, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for Orientation { + fn parse(value: &str) -> Result { + Ok(match value { + "horizontal" => Orientation::Horizontal, + "vertical" => Orientation::Vertical, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for SortDirection { + fn parse(value: &str) -> Result { + Ok(match value { + "ascending" => SortDirection::Ascending, + "descending" => SortDirection::Descending, + "other" => SortDirection::Other, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for AriaCurrent { + fn parse(value: &str) -> Result { + Ok(match value { + "false" => AriaCurrent::False, + "true" => AriaCurrent::True, + "page" => AriaCurrent::Page, + "step" => AriaCurrent::Step, + "location" => AriaCurrent::Location, + "date" => AriaCurrent::Date, + "time" => AriaCurrent::Time, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for AutoComplete { + fn parse(value: &str) -> Result { + Ok(match value { + "inline" => AutoComplete::Inline, + "list" => AutoComplete::List, + "both" => AutoComplete::Both, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for HasPopup { + fn parse(value: &str) -> Result { + Ok(match value { + "true" => HasPopup::True, + "menu" => HasPopup::Menu, + "listbox" => HasPopup::Listbox, + "tree" => HasPopup::Tree, + "grid" => HasPopup::Grid, + "dialog" => HasPopup::Dialog, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for ListStyle { + fn parse(value: &str) -> Result { + Ok(match value { + "circle" => ListStyle::Circle, + "disc" => ListStyle::Disc, + "image" => ListStyle::Image, + "numeric" => ListStyle::Numeric, + "square" => ListStyle::Square, + "other" => ListStyle::Other, + _ => Err(ParseError)?, + }) + } +} + +impl Parse for VerticalOffset { + fn parse(value: &str) -> Result { + Ok(match value { + "subscript" => VerticalOffset::Subscript, + "superscript" => VerticalOffset::Superscript, + _ => Err(ParseError)?, + }) + } +} diff --git a/crates/state/src/values/gradient.rs b/crates/state/src/values/gradient.rs index 5295a0e38..b3202ae43 100644 --- a/crates/state/src/values/gradient.rs +++ b/crates/state/src/values/gradient.rs @@ -148,7 +148,7 @@ impl RadialGradient { Shader::radial_gradient( Point::new(center.x, center.y), - bounds.width().max(bounds.height()), + bounds.width().max(bounds.height()) / 2.0, GradientShaderColors::Colors(&colors[..]), Some(&offsets[..]), TileMode::Clamp, diff --git a/crates/state/src/values/mod.rs b/crates/state/src/values/mod.rs index 4f2b435e4..7cabda902 100644 --- a/crates/state/src/values/mod.rs +++ b/crates/state/src/values/mod.rs @@ -1,3 +1,4 @@ +mod accessibility; mod alignment; mod border; mod color; diff --git a/examples/accessibility.rs b/examples/accessibility.rs index 56f07101c..27aa9d0cc 100644 --- a/examples/accessibility.rs +++ b/examples/accessibility.rs @@ -55,7 +55,7 @@ fn app() -> Element { width: "100%", height: "50%", a11y_role:"label", - a11y_alt: "This is a rectangle", + a11y_name: "This is a rectangle", onclick: move |_| { focus_b.focus(); }, diff --git a/examples/cloned_editor.rs b/examples/cloned_editor.rs index e611f8833..6fb3edda7 100644 --- a/examples/cloned_editor.rs +++ b/examples/cloned_editor.rs @@ -21,8 +21,7 @@ fn app() -> Element { #[allow(non_snake_case)] fn Body() -> Element { - let theme = use_theme(); - let theme = theme.read(); + let theme = use_applied_theme!(None, body); let mut editable = use_editable( || { @@ -55,7 +54,7 @@ fn Body() -> Element { cursor_reference, direction: "horizontal", onglobalclick: onclick, - background: "{theme.body.background}", + background: "{theme.background}", VirtualScrollView { width: "50%", length: editor.len_lines(), diff --git a/examples/input.rs b/examples/input.rs index 79bfb88b5..09f2381e3 100644 --- a/examples/input.rs +++ b/examples/input.rs @@ -18,7 +18,6 @@ fn app() -> Element { padding: "7", width: "100%", height: "100%", - font_size: "10", label { color: "black", "Your name:" @@ -39,13 +38,6 @@ fn app() -> Element { values.write().1 = txt; } }, - rect { - background: "red", - label { - color: "black", - "You are {values.read().0} and you are {values.read().1} years old." - } - } } ) } diff --git a/examples/switch_theme.rs b/examples/switch_theme.rs index befb14012..b3283ce6c 100644 --- a/examples/switch_theme.rs +++ b/examples/switch_theme.rs @@ -6,7 +6,7 @@ use freya::prelude::*; fn main() { - launch(app); + launch_with_props(app, "Switch Theme", (600., 700.)); } #[component] @@ -57,7 +57,12 @@ fn ThemeChanger() -> Element { label { "Light" } } } - + Link { + to: "https://freyaui.dev", + label { + "https://freyaui.dev" + } + } ) } diff --git a/examples/website.rs b/examples/website.rs index b2413be4c..6796ddcc1 100644 --- a/examples/website.rs +++ b/examples/website.rs @@ -272,7 +272,7 @@ fn Counter() -> Element { const CODE: &str = r#"fn app() -> Element { let mut count = use_signal(|| 0); - render!( + rsx!( rect { height: "50%", width: "100%", diff --git a/website/src/pages/index.astro b/website/src/pages/index.astro index ee38c08a3..7fb494534 100644 --- a/website/src/pages/index.astro +++ b/website/src/pages/index.astro @@ -6,7 +6,7 @@ const EXAMPLE_CODE = ` fn app() -> Element { let mut count = use_signal(|| 0); - render!( + rsx!( rect { height: "50%", width: "100%",