├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── examples └── demo.rs └── src ├── apply_mutations.rs ├── colors.rs ├── deferred_system.rs ├── ecs_hooks.rs ├── elements.rs ├── events.rs ├── hot_reload.rs ├── lib.rs ├── parse_attributes.rs └── tick.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_dioxus" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bevy = "0.13" 8 | bevy_mod_picking = { version = "0.18", default-features = false, features = [ 9 | "backend_bevy_ui", 10 | ] } 11 | dioxus = { version = "0.5", default-features = false, features = [ 12 | "macro", 13 | "signals", 14 | "hooks", 15 | ] } 16 | dioxus-rsx = { version = "0.5", default-features = false, features = [ 17 | "hot_reload", 18 | ], optional = true } 19 | dioxus-hot-reload = { version = "0.5", default-features = false, features = [ 20 | "custom_file_watcher", 21 | ], optional = true } 22 | 23 | [features] 24 | hot_reload = ["dioxus/hot-reload", "dioxus-rsx", "dioxus-hot-reload"] 25 | 26 | [[example]] 27 | name = "demo" 28 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /examples/demo.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, reflect::TypeInfo}; 2 | use bevy_dioxus::{colors::*, prelude::*}; 3 | use bevy_mod_picking::DefaultPickingPlugins; 4 | 5 | fn main() { 6 | App::new() 7 | .add_plugins((DefaultPlugins, DioxusUiPlugin, DefaultPickingPlugins)) 8 | .add_systems(Startup, |mut commands: Commands| { 9 | commands.spawn(DioxusUiBundle { 10 | dioxus_ui_root: DioxusUiRoot(Editor), 11 | node_bundle: NodeBundle::default(), 12 | }); 13 | commands.spawn((Camera2dBundle::default(), Name::new("Camera"))); 14 | }) 15 | .run(); 16 | } 17 | 18 | #[component] 19 | fn Editor() -> Element { 20 | // TODO: When selected entity is despawned, need to reset this to None 21 | let selected_entity = use_signal_sync(|| Option::::None); 22 | 23 | rsx! { 24 | node { 25 | width: "100vw", 26 | height: "100vh", 27 | justify_content: "space_between", 28 | SceneTree { selected_entity } 29 | EntityInspector { selected_entity } 30 | } 31 | } 32 | } 33 | 34 | #[component] 35 | fn SceneTree(selected_entity: Signal, SyncStorage>) -> Element { 36 | let mut entities = use_query_filtered::<(Entity, DebugName), Without>(); 37 | let entities = entities.query(); 38 | let mut entities = entities.into_iter().collect::>(); 39 | entities.sort_by_key(|(entity, _)| *entity); 40 | 41 | let system_scheduler = use_system_scheduler(); 42 | 43 | rsx! { 44 | node { 45 | onclick: move |_| selected_entity.set(None), 46 | flex_direction: "column", 47 | if entities.is_empty() { 48 | "No entities exist" 49 | } else { 50 | for (entity, name) in entities { 51 | Button { 52 | onclick: move |event: DioxusEvent| if *event.data == PointerButton::Primary { 53 | if Some(entity) == selected_entity() { 54 | selected_entity.set(None); 55 | } else { 56 | selected_entity.set(Some(entity)); 57 | } 58 | event.stop_propagation(); 59 | }, 60 | base_color: if Some(entity) == selected_entity() { Some(VIOLET_700.to_owned()) } else { None }, 61 | click_color: if Some(entity) == selected_entity() { Some(VIOLET_400.to_owned()) } else { None }, 62 | hover_color: if Some(entity) == selected_entity() { Some(VIOLET_500.to_owned()) } else { None }, 63 | match name.name { 64 | Some(name) => format!("{name}"), 65 | _ => format!("Entity ({:?})", name.entity) 66 | } 67 | } 68 | } 69 | } 70 | Button { 71 | onclick: move |event: DioxusEvent| if *event.data == PointerButton::Primary { 72 | system_scheduler.schedule(move |world: &mut World| { 73 | let new_entity = world.spawn_empty(); 74 | selected_entity.set(Some(new_entity.id())); 75 | }); 76 | event.stop_propagation(); 77 | }, 78 | text { text: "Spawn Entity", text_size: "18" } 79 | } 80 | } 81 | } 82 | } 83 | 84 | #[component] 85 | fn EntityInspector(selected_entity: ReadOnlySignal, SyncStorage>) -> Element { 86 | let world = use_world(); 87 | let type_registry = use_resource::().read(); 88 | let components = selected_entity() 89 | .map(|selected_entity| { 90 | let entity_ref = world.get_entity(selected_entity).unwrap(); 91 | let mut components = entity_ref 92 | .archetype() 93 | .components() 94 | .map(|component_id| { 95 | let component_info = world.components().get_info(component_id).unwrap(); 96 | let type_info = component_info 97 | .type_id() 98 | .and_then(|type_id| type_registry.get_type_info(type_id)); 99 | let (_, name) = component_info.name().rsplit_once("::").unwrap(); 100 | let (crate_name, _) = component_info.name().split_once("::").unwrap(); 101 | (name, crate_name, type_info) 102 | }) 103 | .collect::>(); 104 | components.sort_by_key(|(name, _, _)| *name); 105 | components 106 | }) 107 | .unwrap_or_default(); 108 | 109 | rsx! { 110 | if selected_entity().is_none() { 111 | node { 112 | margin: "8", 113 | "Select an entity to view its components" 114 | } 115 | } else { 116 | node { 117 | flex_direction: "column", 118 | margin: "8", 119 | text { text: "Entity Inspector", text_size: "24" } 120 | for (name, crate_name, type_info) in components { 121 | node { 122 | flex_direction: "column", 123 | margin_bottom: "6", 124 | node { 125 | column_gap: "6", 126 | align_items: "baseline", 127 | text { text: name, text_size: "18" } 128 | text { text: crate_name, text_size: "14", text_color: NEUTRAL_400 } 129 | } 130 | if let Some(type_info) = type_info { 131 | { component_inspector(type_info) } 132 | } 133 | } 134 | } 135 | } 136 | } 137 | } 138 | } 139 | 140 | // TODO: Ideally this would be a component 141 | fn component_inspector<'a>(type_info: &'a TypeInfo) -> Element { 142 | rsx! { 143 | match type_info { 144 | TypeInfo::Struct(info) => rsx! { 145 | for field in info.iter() { 146 | { format!("{}: {}", field.name(), field.type_path()) } 147 | } 148 | }, 149 | TypeInfo::TupleStruct(_) => rsx! { "TODO" }, 150 | TypeInfo::Tuple(_) => rsx! { "TODO" }, 151 | TypeInfo::List(_) => rsx! { "TODO" }, 152 | TypeInfo::Array(_) => rsx! { "TODO" }, 153 | TypeInfo::Map(_) => rsx! { "TODO" }, 154 | TypeInfo::Enum(_) => rsx! { "TODO" }, 155 | TypeInfo::Value(_) => rsx! { "TODO" }, 156 | } 157 | } 158 | } 159 | 160 | #[allow(non_snake_case)] 161 | fn Button(props: ButtonProps) -> Element { 162 | let mut clicked = use_signal(|| false); 163 | let mut hovered = use_signal(|| false); 164 | let background_color = if clicked() { 165 | props.click_color.unwrap_or(NEUTRAL_500.to_owned()) 166 | } else if hovered() { 167 | props.hover_color.unwrap_or(NEUTRAL_600.to_owned()) 168 | } else { 169 | props.base_color.unwrap_or(NEUTRAL_800.to_owned()) 170 | }; 171 | 172 | rsx! { 173 | node { 174 | onclick: move |event| props.onclick.call(event), 175 | onclick_down: move |event| if *event.data == PointerButton::Primary { clicked.set(true) }, 176 | onclick_up: move |event| if *event.data == PointerButton::Primary { clicked.set(false) }, 177 | onmouse_enter: move |_| hovered.set(true), 178 | onmouse_exit: move |_| { hovered.set(false); clicked.set(false) }, 179 | padding: "8", 180 | background_color, 181 | { &props.children } 182 | } 183 | } 184 | } 185 | 186 | #[derive(Props, PartialEq, Clone)] 187 | struct ButtonProps { 188 | onclick: EventHandler>, 189 | base_color: Option, 190 | click_color: Option, 191 | hover_color: Option, 192 | children: Element, 193 | } 194 | -------------------------------------------------------------------------------- /src/apply_mutations.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | events::{insert_event_listener, remove_event_listener}, 3 | parse_attributes::set_attribute, 4 | }; 5 | use bevy::{ 6 | asset::AssetServer, 7 | ecs::{ 8 | entity::{Entity, EntityHashMap}, 9 | system::Command, 10 | world::World, 11 | }, 12 | hierarchy::{BuildWorldChildren, Children, DespawnRecursive, Parent}, 13 | prelude::default, 14 | render::{color::Color, view::Visibility}, 15 | text::{Text, TextLayoutInfo, TextStyle}, 16 | transform::components::Transform, 17 | ui::{ 18 | node_bundles::{ImageBundle, NodeBundle, TextBundle}, 19 | widget::TextFlags, 20 | *, 21 | }, 22 | utils::HashMap, 23 | }; 24 | use dioxus::dioxus_core::{ 25 | AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations, 26 | }; 27 | 28 | pub struct MutationApplier<'a> { 29 | element_id_to_bevy_ui_entity: &'a mut HashMap, 30 | bevy_ui_entity_to_element_id: &'a mut EntityHashMap, 31 | templates: &'a mut HashMap, 32 | world: &'a mut World, 33 | asset_server: &'a AssetServer, 34 | stack: Vec, 35 | } 36 | 37 | impl<'a> MutationApplier<'a> { 38 | pub fn new( 39 | element_id_to_bevy_ui_entity: &'a mut HashMap, 40 | bevy_ui_entity_to_element_id: &'a mut EntityHashMap, 41 | templates: &'a mut HashMap, 42 | root_entity: Entity, 43 | world: &'a mut World, 44 | asset_server: &'a AssetServer, 45 | ) -> Self { 46 | element_id_to_bevy_ui_entity.insert(ElementId(0), root_entity); 47 | bevy_ui_entity_to_element_id.insert(root_entity, ElementId(0)); 48 | 49 | Self { 50 | element_id_to_bevy_ui_entity, 51 | bevy_ui_entity_to_element_id, 52 | templates, 53 | world, 54 | asset_server, 55 | stack: vec![root_entity], 56 | } 57 | } 58 | } 59 | 60 | impl<'a> WriteMutations for MutationApplier<'a> { 61 | fn register_template(&mut self, template: Template) { 62 | self.templates.insert( 63 | template.name.to_owned(), 64 | BevyTemplate::from_dioxus(&template, self.asset_server), 65 | ); 66 | } 67 | 68 | fn append_children(&mut self, id: ElementId, m: usize) { 69 | let mut parent = self 70 | .world 71 | .entity_mut(self.element_id_to_bevy_ui_entity[&id]); 72 | for child in self.stack.drain((self.stack.len() - m)..) { 73 | parent.add_child(child); 74 | } 75 | } 76 | 77 | fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) { 78 | let mut entity = *self.stack.last().unwrap(); 79 | for index in path { 80 | entity = self.world.entity(entity).get::().unwrap()[*index as usize]; 81 | } 82 | self.element_id_to_bevy_ui_entity.insert(id, entity); 83 | self.bevy_ui_entity_to_element_id.insert(entity, id); 84 | } 85 | 86 | fn create_placeholder(&mut self, id: ElementId) { 87 | let entity = self.world.spawn(NodeBundle::default()).id(); 88 | self.element_id_to_bevy_ui_entity.insert(id, entity); 89 | self.bevy_ui_entity_to_element_id.insert(entity, id); 90 | self.stack.push(entity); 91 | } 92 | 93 | fn create_text_node(&mut self, value: &str, id: ElementId) { 94 | let entity = 95 | BevyTemplateNode::IntrinsicTextNode(Text::from_section(value, TextStyle::default())) 96 | .spawn(self.world); 97 | self.element_id_to_bevy_ui_entity.insert(id, entity); 98 | self.bevy_ui_entity_to_element_id.insert(entity, id); 99 | self.stack.push(entity); 100 | } 101 | 102 | fn hydrate_text_node(&mut self, path: &'static [u8], value: &str, id: ElementId) { 103 | let mut entity = *self.stack.last().unwrap(); 104 | for index in path { 105 | entity = self.world.entity(entity).get::().unwrap()[*index as usize]; 106 | } 107 | self.world.entity_mut(entity).insert(( 108 | Text::from_section(value, TextStyle::default()), 109 | TextLayoutInfo::default(), 110 | TextFlags::default(), 111 | ContentSize::default(), 112 | )); 113 | self.element_id_to_bevy_ui_entity.insert(id, entity); 114 | self.bevy_ui_entity_to_element_id.insert(entity, id); 115 | } 116 | 117 | fn load_template(&mut self, name: &'static str, index: usize, id: ElementId) { 118 | let entity = self.templates[name].roots[index].spawn(self.world); 119 | self.element_id_to_bevy_ui_entity.insert(id, entity); 120 | self.bevy_ui_entity_to_element_id.insert(entity, id); 121 | self.stack.push(entity); 122 | } 123 | 124 | fn replace_node_with(&mut self, id: ElementId, m: usize) { 125 | let existing = self.element_id_to_bevy_ui_entity[&id]; 126 | let existing_parent = self.world.entity(existing).get::().unwrap().get(); 127 | let mut existing_parent = self.world.entity_mut(existing_parent); 128 | 129 | let existing_index = existing_parent 130 | .get::() 131 | .unwrap() 132 | .iter() 133 | .position(|child| *child == existing) 134 | .unwrap(); 135 | existing_parent 136 | .insert_children(existing_index, &self.stack.split_off(self.stack.len() - m)); 137 | 138 | DespawnRecursive { entity: existing }.apply(self.world); 139 | // TODO: We're not removing child entities from the element maps 140 | if let Some(existing_element_id) = self.bevy_ui_entity_to_element_id.remove(&existing) { 141 | self.element_id_to_bevy_ui_entity 142 | .remove(&existing_element_id); 143 | } 144 | } 145 | 146 | fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) { 147 | let mut existing = self.stack[self.stack.len() - m - 1]; 148 | for index in path { 149 | existing = self.world.entity(existing).get::().unwrap()[*index as usize]; 150 | } 151 | let existing_parent = self.world.entity(existing).get::().unwrap().get(); 152 | let mut existing_parent = self.world.entity_mut(existing_parent); 153 | 154 | let existing_index = existing_parent 155 | .get::() 156 | .unwrap() 157 | .iter() 158 | .position(|child| *child == existing) 159 | .unwrap(); 160 | existing_parent 161 | .insert_children(existing_index, &self.stack.split_off(self.stack.len() - m)); 162 | 163 | DespawnRecursive { entity: existing }.apply(self.world); 164 | // TODO: We're not removing child entities from the element maps 165 | if let Some(existing_element_id) = self.bevy_ui_entity_to_element_id.remove(&existing) { 166 | self.element_id_to_bevy_ui_entity 167 | .remove(&existing_element_id); 168 | } 169 | } 170 | 171 | fn insert_nodes_after(&mut self, id: ElementId, m: usize) { 172 | let entity = self.element_id_to_bevy_ui_entity[&id]; 173 | let parent = self.world.entity(entity).get::().unwrap().get(); 174 | let mut parent = self.world.entity_mut(parent); 175 | let index = parent 176 | .get::() 177 | .unwrap() 178 | .iter() 179 | .position(|child| *child == entity) 180 | .unwrap(); 181 | parent.insert_children(index + 1, &self.stack.split_off(self.stack.len() - m)); 182 | } 183 | 184 | fn insert_nodes_before(&mut self, id: ElementId, m: usize) { 185 | let existing = self.element_id_to_bevy_ui_entity[&id]; 186 | let parent = self.world.entity(existing).get::().unwrap().get(); 187 | let mut parent = self.world.entity_mut(parent); 188 | let index = parent 189 | .get::() 190 | .unwrap() 191 | .iter() 192 | .position(|child| *child == existing) 193 | .unwrap(); 194 | parent.insert_children(index, &self.stack.split_off(self.stack.len() - m)); 195 | } 196 | 197 | fn set_attribute( 198 | &mut self, 199 | name: &'static str, 200 | _ns: Option<&'static str>, 201 | value: &AttributeValue, 202 | id: ElementId, 203 | ) { 204 | let value = match value { 205 | AttributeValue::Text(value) => value, 206 | AttributeValue::None => todo!("Remove the attribute"), 207 | value => { 208 | panic!("Encountered unsupported bevy_dioxus attribute `{name}: {value:?}`.") 209 | } 210 | }; 211 | 212 | let ( 213 | mut style, 214 | mut border_color, 215 | mut outline, 216 | mut background_color, 217 | mut transform, 218 | mut visibility, 219 | mut z_index, 220 | mut text, 221 | mut image, 222 | ) = self 223 | .world 224 | .query::<( 225 | &mut Style, 226 | &mut BorderColor, 227 | &mut Outline, 228 | &mut BackgroundColor, 229 | &mut Transform, 230 | &mut Visibility, 231 | &mut ZIndex, 232 | Option<&mut Text>, 233 | Option<&mut UiImage>, 234 | )>() 235 | .get_mut(self.world, self.element_id_to_bevy_ui_entity[&id]) 236 | .unwrap(); 237 | 238 | set_attribute( 239 | name, 240 | &value, 241 | &mut style, 242 | &mut border_color, 243 | &mut outline, 244 | &mut background_color, 245 | &mut transform, 246 | &mut visibility, 247 | &mut z_index, 248 | text.as_deref_mut(), 249 | image.as_deref_mut(), 250 | self.asset_server, 251 | ); 252 | } 253 | 254 | fn set_node_text(&mut self, value: &str, id: ElementId) { 255 | self.world 256 | .entity_mut(self.element_id_to_bevy_ui_entity[&id]) 257 | .insert(Text::from_section(value, TextStyle::default())); 258 | } 259 | 260 | fn create_event_listener(&mut self, name: &'static str, id: ElementId) { 261 | insert_event_listener( 262 | &name, 263 | self.world 264 | .entity_mut(self.element_id_to_bevy_ui_entity[&id]), 265 | ); 266 | } 267 | 268 | fn remove_event_listener(&mut self, name: &'static str, id: ElementId) { 269 | remove_event_listener( 270 | &name, 271 | self.world 272 | .entity_mut(self.element_id_to_bevy_ui_entity[&id]), 273 | ); 274 | } 275 | 276 | fn remove_node(&mut self, id: ElementId) { 277 | let entity = self.element_id_to_bevy_ui_entity[&id]; 278 | DespawnRecursive { entity }.apply(self.world); 279 | // TODO: We're not removing child entities from the element maps 280 | if let Some(existing_element_id) = self.bevy_ui_entity_to_element_id.remove(&entity) { 281 | self.element_id_to_bevy_ui_entity 282 | .remove(&existing_element_id); 283 | } 284 | } 285 | 286 | fn push_root(&mut self, id: ElementId) { 287 | self.stack.push(self.element_id_to_bevy_ui_entity[&id]); 288 | } 289 | } 290 | 291 | pub struct BevyTemplate { 292 | roots: Box<[BevyTemplateNode]>, 293 | } 294 | 295 | enum BevyTemplateNode { 296 | Node { 297 | style: StyleComponents, 298 | children: Box<[Self]>, 299 | }, 300 | TextNode { 301 | text: Text, 302 | style: StyleComponents, 303 | children: Box<[Self]>, 304 | }, 305 | ImageNode { 306 | image: UiImage, 307 | style: StyleComponents, 308 | children: Box<[Self]>, 309 | }, 310 | IntrinsicTextNode(Text), 311 | } 312 | 313 | impl BevyTemplate { 314 | fn from_dioxus(template: &Template, asset_server: &AssetServer) -> Self { 315 | Self { 316 | roots: template 317 | .roots 318 | .iter() 319 | .map(|node| BevyTemplateNode::from_dioxus(node, asset_server)) 320 | .collect(), 321 | } 322 | } 323 | } 324 | 325 | impl BevyTemplateNode { 326 | fn from_dioxus(node: &TemplateNode, asset_server: &AssetServer) -> Self { 327 | match node { 328 | TemplateNode::Element { 329 | tag: "node", 330 | namespace: Some("bevy_ui"), 331 | attrs, 332 | children, 333 | } => { 334 | let (style, _, _) = parse_template_attributes(attrs, Color::NONE, asset_server); 335 | Self::Node { 336 | style, 337 | children: children 338 | .iter() 339 | .map(|node| Self::from_dioxus(node, asset_server)) 340 | .collect(), 341 | } 342 | } 343 | TemplateNode::Element { 344 | tag: "text", 345 | namespace: Some("bevy_ui"), 346 | attrs, 347 | children, 348 | } => { 349 | let (style, text, _) = parse_template_attributes(attrs, Color::NONE, asset_server); 350 | Self::TextNode { 351 | text, 352 | style, 353 | children: children 354 | .iter() 355 | .map(|node| Self::from_dioxus(node, asset_server)) 356 | .collect(), 357 | } 358 | } 359 | TemplateNode::Element { 360 | tag: "image", 361 | namespace: Some("bevy_ui"), 362 | attrs, 363 | children, 364 | } => { 365 | let (style, _, image) = 366 | parse_template_attributes(attrs, Color::WHITE, asset_server); 367 | Self::ImageNode { 368 | image, 369 | style, 370 | children: children 371 | .iter() 372 | .map(|node| Self::from_dioxus(node, asset_server)) 373 | .collect(), 374 | } 375 | } 376 | TemplateNode::Text { text } => { 377 | Self::IntrinsicTextNode(Text::from_section(*text, TextStyle::default())) 378 | } 379 | TemplateNode::Dynamic { id: _ } => Self::Node { 380 | style: StyleComponents::default(), 381 | children: Box::new([]), 382 | }, 383 | TemplateNode::DynamicText { id: _ } => { 384 | Self::IntrinsicTextNode(Text::from_section("", TextStyle::default())) 385 | } 386 | TemplateNode::Element { 387 | tag, 388 | namespace: None, 389 | .. 390 | } => { 391 | panic!("Encountered unsupported bevy_dioxus tag `{tag}`.") 392 | } 393 | TemplateNode::Element { 394 | tag, 395 | namespace: Some(namespace), 396 | .. 397 | } => { 398 | panic!("Encountered unsupported bevy_dioxus tag `{namespace}::{tag}`.") 399 | } 400 | } 401 | } 402 | 403 | fn spawn(&self, world: &mut World) -> Entity { 404 | match self { 405 | BevyTemplateNode::Node { style, children } => { 406 | let children = children 407 | .iter() 408 | .map(|child| child.spawn(world)) 409 | .collect::>(); 410 | world 411 | .spawn(( 412 | NodeBundle { 413 | style: style.style.clone(), 414 | border_color: style.border_color, 415 | background_color: style.background_color, 416 | transform: style.transform, 417 | visibility: style.visibility, 418 | z_index: style.z_index, 419 | ..default() 420 | }, 421 | style.outline, 422 | )) 423 | .push_children(&children) 424 | .id() 425 | } 426 | BevyTemplateNode::TextNode { 427 | text, 428 | style, 429 | children, 430 | } => { 431 | let children = children 432 | .iter() 433 | .map(|child| child.spawn(world)) 434 | .collect::>(); 435 | world 436 | .spawn(NodeBundle { 437 | border_color: style.border_color, 438 | ..default() 439 | }) 440 | .insert(( 441 | TextBundle { 442 | text: text.clone(), 443 | style: style.style.clone(), 444 | background_color: style.background_color, 445 | transform: style.transform, 446 | visibility: style.visibility, 447 | z_index: style.z_index, 448 | ..default() 449 | }, 450 | style.outline, 451 | )) 452 | .push_children(&children) 453 | .id() 454 | } 455 | BevyTemplateNode::ImageNode { 456 | image, 457 | style, 458 | children, 459 | } => { 460 | let children = children 461 | .iter() 462 | .map(|child| child.spawn(world)) 463 | .collect::>(); 464 | world 465 | .spawn(NodeBundle { 466 | border_color: style.border_color, 467 | ..default() 468 | }) 469 | .insert(( 470 | ImageBundle { 471 | image: image.clone(), 472 | style: style.style.clone(), 473 | background_color: style.background_color, 474 | transform: style.transform, 475 | visibility: style.visibility, 476 | z_index: style.z_index, 477 | ..default() 478 | }, 479 | style.outline, 480 | )) 481 | .push_children(&children) 482 | .id() 483 | } 484 | Self::IntrinsicTextNode(text) => world 485 | .spawn(TextBundle { 486 | text: text.clone(), 487 | ..default() 488 | }) 489 | .id(), 490 | } 491 | } 492 | } 493 | 494 | fn parse_template_attributes( 495 | attributes: &[TemplateAttribute], 496 | background_color: Color, 497 | asset_server: &AssetServer, 498 | ) -> (StyleComponents, Text, UiImage) { 499 | let mut style = StyleComponents { 500 | background_color: BackgroundColor(background_color), 501 | ..default() 502 | }; 503 | let mut text = Text::from_section("", TextStyle::default()); 504 | let mut image = UiImage::default(); 505 | for attribute in attributes { 506 | if let TemplateAttribute::Static { 507 | name, 508 | value, 509 | namespace: _, 510 | } = attribute 511 | { 512 | set_attribute( 513 | name, 514 | value, 515 | &mut style.style, 516 | &mut style.border_color, 517 | &mut style.outline, 518 | &mut style.background_color, 519 | &mut style.transform, 520 | &mut style.visibility, 521 | &mut style.z_index, 522 | Some(&mut text), 523 | Some(&mut image), 524 | asset_server, 525 | ); 526 | } 527 | } 528 | (style, text, image) 529 | } 530 | 531 | #[derive(Default)] 532 | struct StyleComponents { 533 | style: Style, 534 | border_color: BorderColor, 535 | outline: Outline, 536 | background_color: BackgroundColor, 537 | transform: Transform, 538 | visibility: Visibility, 539 | z_index: ZIndex, 540 | } 541 | -------------------------------------------------------------------------------- /src/colors.rs: -------------------------------------------------------------------------------- 1 | // Tailwind v3.4.0 https://tailwindcss.com/docs/customizing-colors 2 | 3 | pub const BLACK: &str = "#000000"; 4 | pub const WHITE: &str = "#ffffff"; 5 | pub const TRANSPARENT: &str = "#00000000"; 6 | 7 | pub const SLATE_50: &str = "#f8fafc"; 8 | pub const SLATE_100: &str = "#f1f5f9"; 9 | pub const SLATE_200: &str = "#e2e8f0"; 10 | pub const SLATE_300: &str = "#cbd5e1"; 11 | pub const SLATE_400: &str = "#94a3b8"; 12 | pub const SLATE_500: &str = "#64748b"; 13 | pub const SLATE_600: &str = "#475569"; 14 | pub const SLATE_700: &str = "#334155"; 15 | pub const SLATE_800: &str = "#1e293b"; 16 | pub const SLATE_900: &str = "#0f172a"; 17 | pub const SLATE_950: &str = "#020617"; 18 | 19 | pub const GRAY_50: &str = "#f9fafb"; 20 | pub const GRAY_100: &str = "#f3f4f6"; 21 | pub const GRAY_200: &str = "#e5e7eb"; 22 | pub const GRAY_300: &str = "#d1d5db"; 23 | pub const GRAY_400: &str = "#9ca3af"; 24 | pub const GRAY_500: &str = "#6b7280"; 25 | pub const GRAY_600: &str = "#4b5563"; 26 | pub const GRAY_700: &str = "#374151"; 27 | pub const GRAY_800: &str = "#1f2937"; 28 | pub const GRAY_900: &str = "#111827"; 29 | pub const GRAY_950: &str = "#030712"; 30 | 31 | pub const ZINC_50: &str = "#fafafa"; 32 | pub const ZINC_100: &str = "#f4f4f5"; 33 | pub const ZINC_200: &str = "#e4e4e7"; 34 | pub const ZINC_300: &str = "#d4d4d8"; 35 | pub const ZINC_400: &str = "#a1a1aa"; 36 | pub const ZINC_500: &str = "#71717a"; 37 | pub const ZINC_600: &str = "#52525b"; 38 | pub const ZINC_700: &str = "#3f3f46"; 39 | pub const ZINC_800: &str = "#27272a"; 40 | pub const ZINC_900: &str = "#18181b"; 41 | pub const ZINC_950: &str = "#09090b"; 42 | 43 | pub const NEUTRAL_50: &str = "#fafafa"; 44 | pub const NEUTRAL_100: &str = "#f5f5f5"; 45 | pub const NEUTRAL_200: &str = "#e5e5e5"; 46 | pub const NEUTRAL_300: &str = "#d4d4d4"; 47 | pub const NEUTRAL_400: &str = "#a3a3a3"; 48 | pub const NEUTRAL_500: &str = "#737373"; 49 | pub const NEUTRAL_600: &str = "#525252"; 50 | pub const NEUTRAL_700: &str = "#404040"; 51 | pub const NEUTRAL_800: &str = "#262626"; 52 | pub const NEUTRAL_900: &str = "#171717"; 53 | pub const NEUTRAL_950: &str = "#0a0a0a"; 54 | 55 | pub const STONE_50: &str = "#fafaf9"; 56 | pub const STONE_100: &str = "#f5f5f4"; 57 | pub const STONE_200: &str = "#e7e5e4"; 58 | pub const STONE_300: &str = "#d6d3d1"; 59 | pub const STONE_400: &str = "#a8a29e"; 60 | pub const STONE_500: &str = "#78716c"; 61 | pub const STONE_600: &str = "#57534e"; 62 | pub const STONE_700: &str = "#44403c"; 63 | pub const STONE_800: &str = "#292524"; 64 | pub const STONE_900: &str = "#1c1917"; 65 | pub const STONE_950: &str = "#0c0a09"; 66 | 67 | pub const RED_50: &str = "#fef2f2"; 68 | pub const RED_100: &str = "#fee2e2"; 69 | pub const RED_200: &str = "#fecaca"; 70 | pub const RED_300: &str = "#fca5a5"; 71 | pub const RED_400: &str = "#f87171"; 72 | pub const RED_500: &str = "#ef4444"; 73 | pub const RED_600: &str = "#dc2626"; 74 | pub const RED_700: &str = "#b91c1c"; 75 | pub const RED_800: &str = "#991b1b"; 76 | pub const RED_900: &str = "#7f1d1d"; 77 | pub const RED_950: &str = "#450a0a"; 78 | 79 | pub const ORANGE_50: &str = "#fff7ed"; 80 | pub const ORANGE_100: &str = "#ffedd5"; 81 | pub const ORANGE_200: &str = "#fed7aa"; 82 | pub const ORANGE_300: &str = "#fdba74"; 83 | pub const ORANGE_400: &str = "#fb923c"; 84 | pub const ORANGE_500: &str = "#f97316"; 85 | pub const ORANGE_600: &str = "#ea580c"; 86 | pub const ORANGE_700: &str = "#c2410c"; 87 | pub const ORANGE_800: &str = "#9a3412"; 88 | pub const ORANGE_900: &str = "#7c2d12"; 89 | pub const ORANGE_950: &str = "#431407"; 90 | 91 | pub const AMBER_50: &str = "#fffbeb"; 92 | pub const AMBER_100: &str = "#fef3c7"; 93 | pub const AMBER_200: &str = "#fde68a"; 94 | pub const AMBER_300: &str = "#fcd34d"; 95 | pub const AMBER_400: &str = "#fbbf24"; 96 | pub const AMBER_500: &str = "#f59e0b"; 97 | pub const AMBER_600: &str = "#d97706"; 98 | pub const AMBER_700: &str = "#b45309"; 99 | pub const AMBER_800: &str = "#92400e"; 100 | pub const AMBER_900: &str = "#78350f"; 101 | pub const AMBER_950: &str = "#451a03"; 102 | 103 | pub const YELLOW_50: &str = "#fefce8"; 104 | pub const YELLOW_100: &str = "#fef9c3"; 105 | pub const YELLOW_200: &str = "#fef08a"; 106 | pub const YELLOW_300: &str = "#fde047"; 107 | pub const YELLOW_400: &str = "#facc15"; 108 | pub const YELLOW_500: &str = "#eab308"; 109 | pub const YELLOW_600: &str = "#ca8a04"; 110 | pub const YELLOW_700: &str = "#a16207"; 111 | pub const YELLOW_800: &str = "#854d0e"; 112 | pub const YELLOW_900: &str = "#713f12"; 113 | pub const YELLOW_950: &str = "#422006"; 114 | 115 | pub const LIME_50: &str = "#f7fee7"; 116 | pub const LIME_100: &str = "#ecfccb"; 117 | pub const LIME_200: &str = "#d9f99d"; 118 | pub const LIME_300: &str = "#bef264"; 119 | pub const LIME_400: &str = "#a3e635"; 120 | pub const LIME_500: &str = "#84cc16"; 121 | pub const LIME_600: &str = "#65a30d"; 122 | pub const LIME_700: &str = "#4d7c0f"; 123 | pub const LIME_800: &str = "#3f6212"; 124 | pub const LIME_900: &str = "#365314"; 125 | pub const LIME_950: &str = "#1a2e05"; 126 | 127 | pub const GREEN_50: &str = "#f0fdf4"; 128 | pub const GREEN_100: &str = "#dcfce7"; 129 | pub const GREEN_200: &str = "#bbf7d0"; 130 | pub const GREEN_300: &str = "#86efac"; 131 | pub const GREEN_400: &str = "#4ade80"; 132 | pub const GREEN_500: &str = "#22c55e"; 133 | pub const GREEN_600: &str = "#16a34a"; 134 | pub const GREEN_700: &str = "#15803d"; 135 | pub const GREEN_800: &str = "#166534"; 136 | pub const GREEN_900: &str = "#14532d"; 137 | pub const GREEN_950: &str = "#052e16"; 138 | 139 | pub const EMERALD_50: &str = "#ecfdf5"; 140 | pub const EMERALD_100: &str = "#d1fae5"; 141 | pub const EMERALD_200: &str = "#a7f3d0"; 142 | pub const EMERALD_300: &str = "#6ee7b7"; 143 | pub const EMERALD_400: &str = "#34d399"; 144 | pub const EMERALD_500: &str = "#10b981"; 145 | pub const EMERALD_600: &str = "#059669"; 146 | pub const EMERALD_700: &str = "#047857"; 147 | pub const EMERALD_800: &str = "#065f46"; 148 | pub const EMERALD_900: &str = "#064e3b"; 149 | pub const EMERALD_950: &str = "#022c22"; 150 | 151 | pub const TEAL_50: &str = "#f0fdfa"; 152 | pub const TEAL_100: &str = "#ccfbf1"; 153 | pub const TEAL_200: &str = "#99f6e4"; 154 | pub const TEAL_300: &str = "#5eead4"; 155 | pub const TEAL_400: &str = "#2dd4bf"; 156 | pub const TEAL_500: &str = "#14b8a6"; 157 | pub const TEAL_600: &str = "#0d9488"; 158 | pub const TEAL_700: &str = "#0f766e"; 159 | pub const TEAL_800: &str = "#115e59"; 160 | pub const TEAL_900: &str = "#134e4a"; 161 | pub const TEAL_950: &str = "#042f2e"; 162 | 163 | pub const CYAN_50: &str = "#ecfeff"; 164 | pub const CYAN_100: &str = "#cffafe"; 165 | pub const CYAN_200: &str = "#a5f3fc"; 166 | pub const CYAN_300: &str = "#67e8f9"; 167 | pub const CYAN_400: &str = "#22d3ee"; 168 | pub const CYAN_500: &str = "#06b6d4"; 169 | pub const CYAN_600: &str = "#0891b2"; 170 | pub const CYAN_700: &str = "#0e7490"; 171 | pub const CYAN_800: &str = "#155e75"; 172 | pub const CYAN_900: &str = "#164e63"; 173 | pub const CYAN_950: &str = "#083344"; 174 | 175 | pub const SKY_50: &str = "#f0f9ff"; 176 | pub const SKY_100: &str = "#e0f2fe"; 177 | pub const SKY_200: &str = "#bae6fd"; 178 | pub const SKY_300: &str = "#7dd3fc"; 179 | pub const SKY_400: &str = "#38bdf8"; 180 | pub const SKY_500: &str = "#0ea5e9"; 181 | pub const SKY_600: &str = "#0284c7"; 182 | pub const SKY_700: &str = "#0369a1"; 183 | pub const SKY_800: &str = "#075985"; 184 | pub const SKY_900: &str = "#0c4a6e"; 185 | pub const SKY_950: &str = "#082f49"; 186 | 187 | pub const BLUE_50: &str = "#eff6ff"; 188 | pub const BLUE_100: &str = "#dbeafe"; 189 | pub const BLUE_200: &str = "#bfdbfe"; 190 | pub const BLUE_300: &str = "#93c5fd"; 191 | pub const BLUE_400: &str = "#60a5fa"; 192 | pub const BLUE_500: &str = "#3b82f6"; 193 | pub const BLUE_600: &str = "#2563eb"; 194 | pub const BLUE_700: &str = "#1d4ed8"; 195 | pub const BLUE_800: &str = "#1e40af"; 196 | pub const BLUE_900: &str = "#1e3a8a"; 197 | pub const BLUE_950: &str = "#172554"; 198 | 199 | pub const INDIGO_50: &str = "#eef2ff"; 200 | pub const INDIGO_100: &str = "#e0e7ff"; 201 | pub const INDIGO_200: &str = "#c7d2fe"; 202 | pub const INDIGO_300: &str = "#a5b4fc"; 203 | pub const INDIGO_400: &str = "#818cf8"; 204 | pub const INDIGO_500: &str = "#6366f1"; 205 | pub const INDIGO_600: &str = "#4f46e5"; 206 | pub const INDIGO_700: &str = "#4338ca"; 207 | pub const INDIGO_800: &str = "#3730a3"; 208 | pub const INDIGO_900: &str = "#312e81"; 209 | pub const INDIGO_950: &str = "#1e1b4b"; 210 | 211 | pub const VIOLET_50: &str = "#f5f3ff"; 212 | pub const VIOLET_100: &str = "#ede9fe"; 213 | pub const VIOLET_200: &str = "#ddd6fe"; 214 | pub const VIOLET_300: &str = "#c4b5fd"; 215 | pub const VIOLET_400: &str = "#a78bfa"; 216 | pub const VIOLET_500: &str = "#8b5cf6"; 217 | pub const VIOLET_600: &str = "#7c3aed"; 218 | pub const VIOLET_700: &str = "#6d28d9"; 219 | pub const VIOLET_800: &str = "#5b21b6"; 220 | pub const VIOLET_900: &str = "#4c1d95"; 221 | pub const VIOLET_950: &str = "#2e1065"; 222 | 223 | pub const PURPLE_50: &str = "#faf5ff"; 224 | pub const PURPLE_100: &str = "#f3e8ff"; 225 | pub const PURPLE_200: &str = "#e9d5ff"; 226 | pub const PURPLE_300: &str = "#d8b4fe"; 227 | pub const PURPLE_400: &str = "#c084fc"; 228 | pub const PURPLE_500: &str = "#a855f7"; 229 | pub const PURPLE_600: &str = "#9333ea"; 230 | pub const PURPLE_700: &str = "#7e22ce"; 231 | pub const PURPLE_800: &str = "#6b21a8"; 232 | pub const PURPLE_900: &str = "#581c87"; 233 | pub const PURPLE_950: &str = "#3b0764"; 234 | 235 | pub const FUCHSIA_50: &str = "#fdf4ff"; 236 | pub const FUCHSIA_100: &str = "#fae8ff"; 237 | pub const FUCHSIA_200: &str = "#f5d0fe"; 238 | pub const FUCHSIA_300: &str = "#f0abfc"; 239 | pub const FUCHSIA_400: &str = "#e879f9"; 240 | pub const FUCHSIA_500: &str = "#d946ef"; 241 | pub const FUCHSIA_600: &str = "#c026d3"; 242 | pub const FUCHSIA_700: &str = "#a21caf"; 243 | pub const FUCHSIA_800: &str = "#86198f"; 244 | pub const FUCHSIA_900: &str = "#701a75"; 245 | pub const FUCHSIA_950: &str = "#4a044e"; 246 | 247 | pub const PINK_50: &str = "#fdf2f8"; 248 | pub const PINK_100: &str = "#fce7f3"; 249 | pub const PINK_200: &str = "#fbcfe8"; 250 | pub const PINK_300: &str = "#f9a8d4"; 251 | pub const PINK_400: &str = "#f472b6"; 252 | pub const PINK_500: &str = "#ec4899"; 253 | pub const PINK_600: &str = "#db2777"; 254 | pub const PINK_700: &str = "#be185d"; 255 | pub const PINK_800: &str = "#9d174d"; 256 | pub const PINK_900: &str = "#831843"; 257 | pub const PINK_950: &str = "#500724"; 258 | 259 | pub const ROSE_50: &str = "#fff1f2"; 260 | pub const ROSE_100: &str = "#ffe4e6"; 261 | pub const ROSE_200: &str = "#fecdd3"; 262 | pub const ROSE_300: &str = "#fda4af"; 263 | pub const ROSE_400: &str = "#fb7185"; 264 | pub const ROSE_500: &str = "#f43f5e"; 265 | pub const ROSE_600: &str = "#e11d48"; 266 | pub const ROSE_700: &str = "#be123c"; 267 | pub const ROSE_800: &str = "#9f1239"; 268 | pub const ROSE_900: &str = "#881337"; 269 | pub const ROSE_950: &str = "#4c0519"; 270 | -------------------------------------------------------------------------------- /src/deferred_system.rs: -------------------------------------------------------------------------------- 1 | use crate::ecs_hooks::EcsContext; 2 | use bevy::ecs::system::{IntoSystem, Resource, System}; 3 | 4 | #[derive(Resource, Default)] 5 | pub struct DeferredSystemRunQueue { 6 | pub run_queue: Box>>>, 7 | } 8 | 9 | #[derive(Clone, Copy)] 10 | pub struct DeferredSystemScheduler { 11 | run_queue: *mut Vec>>, 12 | } 13 | 14 | impl DeferredSystemScheduler { 15 | pub fn schedule(&self, system: S) 16 | where 17 | S: IntoSystem<(), (), M> + 'static, 18 | M: 'static, 19 | { 20 | unsafe { &mut *self.run_queue }.push(Box::new(S::into_system(system))); 21 | } 22 | } 23 | 24 | unsafe impl Send for DeferredSystemScheduler {} 25 | unsafe impl Sync for DeferredSystemScheduler {} 26 | 27 | pub fn use_system_scheduler() -> DeferredSystemScheduler { 28 | DeferredSystemScheduler { 29 | run_queue: Box::as_mut( 30 | &mut EcsContext::get_world() 31 | .resource_mut::() 32 | .run_queue, 33 | ), 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/ecs_hooks.rs: -------------------------------------------------------------------------------- 1 | use crate::UiContext; 2 | use bevy::{ 3 | ecs::{ 4 | component::ComponentId, 5 | // event::{Event, EventIterator, Events, ManualEventReader}, 6 | query::{QueryFilter, ReadOnlyQueryData}, 7 | system::{Query, Resource, SystemState}, 8 | world::World, 9 | }, 10 | utils::{HashMap, HashSet}, 11 | }; 12 | use dioxus::{ 13 | dioxus_core::{use_hook, ScopeId}, 14 | prelude::{consume_context, current_scope_id, use_drop}, 15 | }; 16 | use std::any::TypeId; 17 | 18 | #[derive(Default)] 19 | pub(crate) struct EcsSubscriptions { 20 | pub resources: Box>>, 21 | #[allow(clippy::type_complexity)] 22 | pub events: Box bool>, HashSet)>>, 23 | pub world_and_queries: Box>, 24 | } 25 | 26 | #[derive(Clone)] 27 | pub(crate) struct EcsContext { 28 | pub world: *mut World, 29 | } 30 | 31 | impl EcsContext { 32 | pub fn get_world<'a>() -> &'a mut World { 33 | unsafe { &mut *consume_context::().world } 34 | } 35 | } 36 | 37 | pub fn use_world<'a>() -> &'a World { 38 | let world = EcsContext::get_world(); 39 | 40 | let scope_id = current_scope_id().unwrap(); 41 | let subscription_manager = use_hook(|| { 42 | let subscription_manager = &mut world 43 | .non_send_resource_mut::() 44 | .subscriptions 45 | .world_and_queries; 46 | subscription_manager.insert(scope_id); 47 | Box::as_mut(subscription_manager) as *mut HashSet 48 | }); 49 | use_drop(move || { 50 | unsafe { &mut *subscription_manager }.remove(&scope_id); 51 | }); 52 | 53 | world 54 | } 55 | 56 | pub fn use_resource<'a, T: Resource>() -> &'a T { 57 | let world = EcsContext::get_world(); 58 | 59 | let resource_id = world.components().resource_id::().unwrap(); 60 | let scope_id = current_scope_id().unwrap(); 61 | let subscription_manager = use_hook(|| { 62 | let subscription_manager = &mut world 63 | .non_send_resource_mut::() 64 | .subscriptions 65 | .resources; 66 | subscription_manager 67 | .entry(resource_id) 68 | .or_default() 69 | .insert(scope_id); 70 | Box::as_mut(subscription_manager) as *mut HashMap> 71 | }); 72 | use_drop(move || { 73 | let subscription_manager = &mut unsafe { &mut *subscription_manager }; 74 | let resource_subscriptions = subscription_manager.get_mut(&resource_id).unwrap(); 75 | resource_subscriptions.remove(&scope_id); 76 | if resource_subscriptions.is_empty() { 77 | subscription_manager.remove(&resource_id); 78 | } 79 | }); 80 | 81 | world.resource() 82 | } 83 | 84 | pub fn use_query<'a, Q>() -> UseQuery<'a, Q, ()> 85 | where 86 | Q: ReadOnlyQueryData, 87 | { 88 | use_query_filtered() 89 | } 90 | 91 | pub fn use_query_filtered<'a, Q, F>() -> UseQuery<'a, Q, F> 92 | where 93 | Q: ReadOnlyQueryData, 94 | F: QueryFilter, 95 | { 96 | let world = EcsContext::get_world(); 97 | 98 | let scope_id = current_scope_id().unwrap(); 99 | let subscription_manager = use_hook(|| { 100 | let subscription_manager = &mut world 101 | .non_send_resource_mut::() 102 | .subscriptions 103 | .world_and_queries; 104 | subscription_manager.insert(scope_id); 105 | Box::as_mut(subscription_manager) as *mut HashSet 106 | }); 107 | use_drop(move || { 108 | unsafe { &mut *subscription_manager }.remove(&scope_id); 109 | }); 110 | 111 | UseQuery { 112 | system_state: SystemState::new(world), 113 | world_ref: world, 114 | } 115 | } 116 | 117 | // TODO 118 | // pub fn use_event_reader<'a, E: Event>() -> EventIterator<'a, E> { 119 | // // TODO: Register the subscription 120 | 121 | // let event_reader = use_hook(ManualEventReader::default); 122 | // let events = EcsContext::get_world().resource::>(); 123 | // event_reader.read(events) 124 | // } 125 | 126 | pub struct UseQuery<'a, Q: ReadOnlyQueryData + 'static, F: QueryFilter + 'static> { 127 | system_state: SystemState>, 128 | world_ref: &'a World, 129 | } 130 | 131 | impl<'a, Q, F> UseQuery<'a, Q, F> 132 | where 133 | Q: ReadOnlyQueryData, 134 | F: QueryFilter, 135 | { 136 | pub fn query(&mut self) -> Query { 137 | self.system_state.get(self.world_ref) 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/elements.rs: -------------------------------------------------------------------------------- 1 | macro_rules! node_attributes { 2 | () => { 3 | pub const animate: AttributeDescription = ("animate", None, false); 4 | pub const display: AttributeDescription = ("display", None, false); 5 | pub const position: AttributeDescription = ("position", None, false); 6 | pub const overflow: AttributeDescription = ("overflow", None, false); 7 | pub const overflow_x: AttributeDescription = ("overflow_x", None, false); 8 | pub const overflow_y: AttributeDescription = ("overflow_y", None, false); 9 | pub const left: AttributeDescription = ("left", None, false); 10 | pub const right: AttributeDescription = ("right", None, false); 11 | pub const top: AttributeDescription = ("top", None, false); 12 | pub const bottom: AttributeDescription = ("bottom", None, false); 13 | pub const width: AttributeDescription = ("width", None, false); 14 | pub const height: AttributeDescription = ("height", None, false); 15 | pub const min_width: AttributeDescription = ("min_width", None, false); 16 | pub const min_height: AttributeDescription = ("min_height", None, false); 17 | pub const aspect_ratio: AttributeDescription = ("aspect_ratio", None, false); 18 | pub const align_items: AttributeDescription = ("align_items", None, false); 19 | pub const justify_items: AttributeDescription = ("justify_items", None, false); 20 | pub const align_self: AttributeDescription = ("align_self", None, false); 21 | pub const justify_self: AttributeDescription = ("justify_self", None, false); 22 | pub const align_content: AttributeDescription = ("align_content", None, false); 23 | pub const justify_content: AttributeDescription = ("justify_content", None, false); 24 | pub const margin: AttributeDescription = ("margin", None, false); 25 | pub const margin_left: AttributeDescription = ("margin_left", None, false); 26 | pub const margin_right: AttributeDescription = ("margin_right", None, false); 27 | pub const margin_top: AttributeDescription = ("margin_top", None, false); 28 | pub const margin_bottom: AttributeDescription = ("margin_bottom", None, false); 29 | pub const padding: AttributeDescription = ("padding", None, false); 30 | pub const padding_left: AttributeDescription = ("padding_left", None, false); 31 | pub const padding_right: AttributeDescription = ("padding_right", None, false); 32 | pub const padding_top: AttributeDescription = ("padding_top", None, false); 33 | pub const padding_bottom: AttributeDescription = ("padding_bottom", None, false); 34 | pub const border_width: AttributeDescription = ("border_width", None, false); 35 | pub const border_width_left: AttributeDescription = ("border_width_left", None, false); 36 | pub const border_width_right: AttributeDescription = ("border_width_right", None, false); 37 | pub const border_width_top: AttributeDescription = ("border_width_top", None, false); 38 | pub const border_width_bottom: AttributeDescription = ("border_width_bottom", None, false); 39 | pub const border_color: AttributeDescription = ("border_color", None, false); 40 | pub const outline_width: AttributeDescription = ("outline_width", None, false); 41 | pub const outline_offset: AttributeDescription = ("outline_offset", None, false); 42 | pub const outline_color: AttributeDescription = ("outline_color", None, false); 43 | pub const flex_direction: AttributeDescription = ("flex_direction", None, false); 44 | pub const flex_wrap: AttributeDescription = ("flex_wrap", None, false); 45 | pub const flex_grow: AttributeDescription = ("flex_grow", None, false); 46 | pub const flex_shrink: AttributeDescription = ("flex_shrink", None, false); 47 | pub const flex_basis: AttributeDescription = ("flex_basis", None, false); 48 | pub const row_gap: AttributeDescription = ("row_gap", None, false); 49 | pub const column_gap: AttributeDescription = ("column_gap", None, false); 50 | pub const grid_auto_flow: AttributeDescription = ("grid_auto_flow", None, false); 51 | pub const grid_template_rows: AttributeDescription = ("grid_template_rows", None, false); 52 | pub const grid_template_columns: AttributeDescription = 53 | ("grid_template_columns", None, false); 54 | pub const grid_auto_rows: AttributeDescription = ("grid_auto_rows", None, false); 55 | pub const grid_auto_columns: AttributeDescription = ("grid_auto_columns", None, false); 56 | pub const grid_row: AttributeDescription = ("grid_row", None, false); 57 | pub const grid_column: AttributeDescription = ("grid_column", None, false); 58 | pub const background_color: AttributeDescription = ("background_color", None, false); 59 | pub const translation: AttributeDescription = ("translation", None, false); 60 | pub const translation_x: AttributeDescription = ("translation", None, false); 61 | pub const translation_y: AttributeDescription = ("translation", None, false); 62 | pub const rotation: AttributeDescription = ("rotation", None, false); 63 | pub const scale: AttributeDescription = ("scale", None, false); 64 | pub const scale_x: AttributeDescription = ("scale_x", None, false); 65 | pub const scale_y: AttributeDescription = ("scale_y", None, false); 66 | pub const visibility: AttributeDescription = ("visibility", None, false); 67 | pub const z_index: AttributeDescription = ("z_index", None, false); 68 | }; 69 | } 70 | 71 | #[allow(non_camel_case_types, non_upper_case_globals)] 72 | pub mod dioxus_elements { 73 | pub use crate::events::events; 74 | 75 | pub type AttributeDescription = (&'static str, Option<&'static str>, bool); 76 | const NAME_SPACE: Option<&'static str> = Some("bevy_ui"); 77 | 78 | pub struct node; 79 | impl node { 80 | pub const TAG_NAME: &'static str = "node"; 81 | pub const NAME_SPACE: Option<&'static str> = NAME_SPACE; 82 | node_attributes!(); 83 | } 84 | 85 | pub struct text; 86 | impl text { 87 | pub const TAG_NAME: &'static str = "text"; 88 | pub const NAME_SPACE: Option<&'static str> = NAME_SPACE; 89 | pub const text: AttributeDescription = ("text", None, false); 90 | pub const text_direction: AttributeDescription = ("text_direction", None, false); 91 | pub const text_multiline_justification: AttributeDescription = 92 | ("text_multiline_justification", None, false); 93 | pub const text_size: AttributeDescription = ("text_size", None, false); 94 | pub const text_color: AttributeDescription = ("text_color", None, false); 95 | node_attributes!(); 96 | } 97 | 98 | pub struct image; 99 | impl image { 100 | pub const TAG_NAME: &'static str = "image"; 101 | pub const NAME_SPACE: Option<&'static str> = NAME_SPACE; 102 | pub const image_asset_path: AttributeDescription = ("image_asset_path", None, false); 103 | node_attributes!(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | ecs::{ 3 | component::Component, 4 | entity::{Entity, EntityHashSet}, 5 | event::{Event, EventWriter, Events, ManualEventReader}, 6 | system::{Local, Query, Resource}, 7 | world::World, 8 | }, 9 | hierarchy::Parent, 10 | prelude::EntityWorldMut, 11 | ui::RelativeCursorPosition, 12 | }; 13 | use bevy_mod_picking::events::{Click, Down, Out, Over, Pointer, Up}; 14 | use std::{any::Any, mem, rc::Rc}; 15 | 16 | // TODO: Other events 17 | pub mod events { 18 | use bevy_mod_picking::pointer::PointerButton; 19 | 20 | super::impl_event! [ 21 | (); 22 | onmouse_over 23 | onmouse_out 24 | onmouse_enter 25 | onmouse_exit 26 | ]; 27 | 28 | super::impl_event! [ 29 | PointerButton; 30 | onclick 31 | onclick_down 32 | onclick_up 33 | ]; 34 | } 35 | 36 | #[derive(Resource, Default)] 37 | pub struct EventReaders { 38 | click: ManualEventReader>, 39 | click_down: ManualEventReader>, 40 | click_up: ManualEventReader>, 41 | mouse_over: ManualEventReader>, 42 | mouse_out: ManualEventReader>, 43 | mouse_enter: ManualEventReader, 44 | mouse_exit: ManualEventReader, 45 | } 46 | 47 | impl EventReaders { 48 | #[allow(clippy::too_many_arguments)] 49 | pub fn read_events( 50 | &mut self, 51 | click: &Events>, 52 | click_down: &Events>, 53 | click_up: &Events>, 54 | mouse_over: &Events>, 55 | mouse_out: &Events>, 56 | mouse_enter: &Events, 57 | mouse_exit: &Events, 58 | ) -> Vec<(Entity, &'static str, Rc, bool)> { 59 | let mut events: Vec<(Entity, &'static str, Rc, bool)> = Vec::new(); 60 | for event in self.click.read(click) { 61 | events.push((event.target, "click", Rc::new(event.button), true)); 62 | } 63 | for event in self.click_down.read(click_down) { 64 | events.push((event.target, "click_down", Rc::new(event.button), true)); 65 | } 66 | for event in self.click_up.read(click_up) { 67 | events.push((event.target, "click_up", Rc::new(event.button), true)); 68 | } 69 | for event in self.mouse_over.read(mouse_over) { 70 | events.push((event.target, "mouse_over", Rc::new(()), false)); 71 | } 72 | for event in self.mouse_out.read(mouse_out) { 73 | events.push((event.target, "mouse_out", Rc::new(()), false)); 74 | } 75 | for event in self.mouse_enter.read(mouse_enter) { 76 | events.push((event.target, "mouse_enter", Rc::new(()), false)); 77 | } 78 | for event in self.mouse_exit.read(mouse_exit) { 79 | events.push((event.target, "mouse_exit", Rc::new(()), false)); 80 | } 81 | events 82 | } 83 | } 84 | 85 | pub fn insert_event_listener(name: &str, mut entity: EntityWorldMut<'_>) { 86 | match name { 87 | "click" => entity.insert(HasClickEventListener), 88 | "click_down" => entity.insert(HasClickDownEventListener), 89 | "click_up" => entity.insert(HasClickUpEventListener), 90 | "mouse_over" => &mut entity, 91 | "mouse_out" => &mut entity, 92 | "mouse_enter" => entity.insert(( 93 | HasMouseEnterEventListener, 94 | RelativeCursorPosition::default(), 95 | )), 96 | "mouse_exit" => { 97 | entity.insert((HasMouseExitEventListener, RelativeCursorPosition::default())) 98 | } 99 | _ => panic!("Encountered unsupported bevy_dioxus event `{name}`."), 100 | }; 101 | } 102 | 103 | pub fn remove_event_listener(name: &str, mut entity: EntityWorldMut<'_>) { 104 | match name { 105 | "click" => entity.remove::(), 106 | "click_down" => entity.remove::(), 107 | "click_up" => entity.remove::(), 108 | "mouse_over" => &mut entity, 109 | "mouse_out" => &mut entity, 110 | "mouse_enter" => { 111 | entity.remove::(); 112 | if !entity.contains::() { 113 | entity.remove::(); 114 | } 115 | &mut entity 116 | } 117 | "mouse_exit" => { 118 | entity.remove::(); 119 | if !entity.contains::() { 120 | entity.remove::(); 121 | } 122 | &mut entity 123 | } 124 | _ => unreachable!(), 125 | }; 126 | } 127 | 128 | #[derive(Component)] 129 | pub struct HasClickEventListener; 130 | 131 | #[derive(Component)] 132 | pub struct HasClickDownEventListener; 133 | 134 | #[derive(Component)] 135 | pub struct HasClickUpEventListener; 136 | 137 | #[derive(Component)] 138 | pub struct HasMouseEnterEventListener; 139 | 140 | #[derive(Component)] 141 | pub struct HasMouseExitEventListener; 142 | 143 | // ---------------------------------------------------------------------------- 144 | 145 | pub fn bubble_event(event_name: &str, target_entity: &mut Entity, world: &World) { 146 | match event_name { 147 | "click" => bubble_event_helper::(target_entity, world), 148 | "click_down" => bubble_event_helper::(target_entity, world), 149 | "click_up" => bubble_event_helper::(target_entity, world), 150 | _ => unreachable!(), 151 | }; 152 | } 153 | 154 | fn bubble_event_helper(target_entity: &mut Entity, world: &World) { 155 | while !world.entity(*target_entity).contains::() { 156 | *target_entity = match world.entity(*target_entity).get::() { 157 | Some(parent) => parent.get(), 158 | None => return, 159 | }; 160 | } 161 | } 162 | 163 | // ---------------------------------------------------------------------------- 164 | 165 | pub fn generate_mouse_enter_leave_events( 166 | entities: Query<(Entity, &RelativeCursorPosition)>, 167 | mut previous_over: Local, 168 | mut over: Local, 169 | mut enter: EventWriter, 170 | mut leave: EventWriter, 171 | ) { 172 | mem::swap::(&mut previous_over, &mut over); 173 | 174 | over.clear(); 175 | for (entity, relative_cursor_position) in &entities { 176 | if relative_cursor_position.mouse_over() { 177 | over.insert(entity); 178 | } 179 | } 180 | 181 | enter.send_batch( 182 | over.iter() 183 | .copied() 184 | .filter(|entity| !previous_over.contains(entity)) 185 | .map(|target| MouseEnter { target }), 186 | ); 187 | 188 | leave.send_batch( 189 | previous_over 190 | .iter() 191 | .copied() 192 | .filter(|entity| !over.contains(entity)) 193 | .map(|target| MouseExit { target }), 194 | ); 195 | } 196 | 197 | #[derive(Event)] 198 | pub struct MouseEnter { 199 | target: Entity, 200 | } 201 | 202 | #[derive(Event)] 203 | pub struct MouseExit { 204 | target: Entity, 205 | } 206 | 207 | // ---------------------------------------------------------------------------- 208 | 209 | pub trait EventReturn

: Sized { 210 | fn spawn(self) {} 211 | } 212 | 213 | impl EventReturn<()> for () {} 214 | 215 | macro_rules! impl_event { 216 | ( 217 | $data:ty; 218 | $( 219 | $( #[$attr:meta] )* 220 | $name:ident $(: $js_name:literal)? 221 | )* 222 | ) => { 223 | $( 224 | $( #[$attr] )* 225 | #[inline] 226 | pub fn $name, T>(mut _f: impl FnMut(dioxus::dioxus_core::Event<$data>) -> E + 'static) -> dioxus::dioxus_core::Attribute { 227 | dioxus::dioxus_core::Attribute::new( 228 | crate::events::impl_event!(@name $name $($js_name)?), 229 | dioxus::dioxus_core::AttributeValue::listener(move |e: dioxus::dioxus_core::Event<$data>| { 230 | _f(e).spawn(); 231 | }), 232 | None, 233 | false, 234 | ).into() 235 | } 236 | )* 237 | }; 238 | 239 | (@name $name:ident $js_name:literal) => { 240 | $js_name 241 | }; 242 | (@name $name:ident) => { 243 | stringify!($name) 244 | }; 245 | } 246 | 247 | use impl_event; 248 | -------------------------------------------------------------------------------- /src/hot_reload.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::dioxus_elements; 2 | use bevy::ecs::world::World; 3 | use dioxus::dioxus_core::{Template, VirtualDom}; 4 | use dioxus_hot_reload::{connect, HotReloadMsg}; 5 | use dioxus_rsx::HotReloadingContext; 6 | use std::sync::mpsc::{channel, Receiver}; 7 | 8 | pub fn update_templates(world: &mut World, virtual_dom: &mut VirtualDom) { 9 | if !world.contains_non_send::>() { 10 | let (updated_templates_sender, updated_templates_receiver) = channel(); 11 | connect(move |msg| match msg { 12 | HotReloadMsg::UpdateTemplate(updated_templated) => { 13 | let _ = updated_templates_sender.send(updated_templated); 14 | } 15 | HotReloadMsg::Shutdown => {} 16 | HotReloadMsg::UpdateAsset(_) => { 17 | panic!("Dioxus assets are not supported by bevy_dioxus"); 18 | } 19 | }); 20 | world.insert_non_send_resource(updated_templates_receiver); 21 | } 22 | 23 | let updated_templates_receiver = world.non_send_resource_mut::>(); 24 | while let Ok(updated_templated) = updated_templates_receiver.try_recv() { 25 | virtual_dom.replace_template(updated_templated); 26 | } 27 | } 28 | 29 | pub struct HotReloadContext; 30 | 31 | impl HotReloadingContext for HotReloadContext { 32 | fn map_attribute( 33 | element_name_rust: &str, 34 | attribute_name_rust: &str, 35 | ) -> Option<(&'static str, Option<&'static str>)> { 36 | if element_name_rust == dioxus_elements::text::TAG_NAME { 37 | let attribute = match attribute_name_rust { 38 | "text" => Some(("text", None)), 39 | "text_direction" => Some(("text_direction", None)), 40 | "text_multiline_justification" => Some(("text_multiline_justification", None)), 41 | "text_size" => Some(("text_size", None)), 42 | "text_color" => Some(("text_color", None)), 43 | _ => None, 44 | }; 45 | if let Some(attribute) = attribute { 46 | return Some(attribute); 47 | } 48 | } 49 | if element_name_rust == dioxus_elements::image::TAG_NAME { 50 | let attribute = match attribute_name_rust { 51 | "image_asset_path" => Some(("image_asset_path", None)), 52 | _ => None, 53 | }; 54 | if let Some(attribute) = attribute { 55 | return Some(attribute); 56 | } 57 | } 58 | if let dioxus_elements::node::TAG_NAME | dioxus_elements::text::TAG_NAME = element_name_rust 59 | { 60 | match attribute_name_rust { 61 | "animate" => Some(("animate", None)), 62 | "display" => Some(("display", None)), 63 | "position" => Some(("position", None)), 64 | "overflow" => Some(("overflow", None)), 65 | "overflow_x" => Some(("overflow_x", None)), 66 | "overflow_y" => Some(("overflow_y", None)), 67 | "left" => Some(("left", None)), 68 | "right" => Some(("right", None)), 69 | "top" => Some(("top", None)), 70 | "bottom" => Some(("bottom", None)), 71 | "width" => Some(("width", None)), 72 | "height" => Some(("height", None)), 73 | "min_width" => Some(("min_width", None)), 74 | "min_height" => Some(("min_height", None)), 75 | "aspect_ratio" => Some(("aspect_ratio", None)), 76 | "align_items" => Some(("align_items", None)), 77 | "justify_items" => Some(("justify_items", None)), 78 | "align_self" => Some(("align_self", None)), 79 | "justify_self" => Some(("justify_self", None)), 80 | "align_content" => Some(("align_content", None)), 81 | "justify_content" => Some(("justify_content", None)), 82 | "margin" => Some(("margin", None)), 83 | "margin_left" => Some(("margin_left", None)), 84 | "margin_right" => Some(("margin_right", None)), 85 | "margin_top" => Some(("margin_top", None)), 86 | "margin_bottom" => Some(("margin_bottom", None)), 87 | "padding" => Some(("padding", None)), 88 | "padding_left" => Some(("padding_left", None)), 89 | "padding_right" => Some(("padding_right", None)), 90 | "padding_top" => Some(("padding_top", None)), 91 | "padding_bottom" => Some(("padding_bottom", None)), 92 | "border_width" => Some(("border_width", None)), 93 | "border_width_left" => Some(("border_width_left", None)), 94 | "border_width_right" => Some(("border_width_right", None)), 95 | "border_width_top" => Some(("border_width_top", None)), 96 | "border_width_bottom" => Some(("border_width_bottom", None)), 97 | "border_color" => Some(("border_color", None)), 98 | "outline_width" => Some(("outline_width", None)), 99 | "outline_offset" => Some(("outline_offset", None)), 100 | "outline_color" => Some(("outline_color", None)), 101 | "flex_direction" => Some(("flex_direction", None)), 102 | "flex_wrap" => Some(("flex_wrap", None)), 103 | "flex_grow" => Some(("flex_grow", None)), 104 | "flex_shrink" => Some(("flex_shrink", None)), 105 | "flex_basis" => Some(("flex_basis", None)), 106 | "row_gap" => Some(("row_gap", None)), 107 | "column_gap" => Some(("column_gap", None)), 108 | "grid_auto_flow" => Some(("grid_auto_flow", None)), 109 | "grid_template_rows" => Some(("grid_template_rows", None)), 110 | "grid_template_columns" => Some(("grid_template_columns", None)), 111 | "grid_auto_rows" => Some(("grid_auto_rows", None)), 112 | "grid_auto_columns" => Some(("grid_auto_columns", None)), 113 | "grid_row" => Some(("grid_row", None)), 114 | "grid_column" => Some(("grid_column", None)), 115 | "background_color" => Some(("background_color", None)), 116 | "translation" => Some(("translation", None)), 117 | "translation_x" => Some(("translation_x", None)), 118 | "translation_y" => Some(("translation_y", None)), 119 | "rotation" => Some(("rotation", None)), 120 | "scale" => Some(("scale", None)), 121 | "scale_x" => Some(("scale_x", None)), 122 | "scale_y" => Some(("scale_y", None)), 123 | "visibility" => Some(("visibility", None)), 124 | "z_index" => Some(("z_index", None)), 125 | _ => None, 126 | } 127 | } else { 128 | None 129 | } 130 | } 131 | 132 | fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> { 133 | match element_name_rust { 134 | dioxus_elements::node::TAG_NAME => Some(( 135 | dioxus_elements::node::TAG_NAME, 136 | dioxus_elements::node::NAME_SPACE, 137 | )), 138 | dioxus_elements::text::TAG_NAME => Some(( 139 | dioxus_elements::text::TAG_NAME, 140 | dioxus_elements::text::NAME_SPACE, 141 | )), 142 | dioxus_elements::image::TAG_NAME => Some(( 143 | dioxus_elements::image::TAG_NAME, 144 | dioxus_elements::image::NAME_SPACE, 145 | )), 146 | _ => None, 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | mod apply_mutations; 2 | pub mod colors; 3 | mod deferred_system; 4 | mod ecs_hooks; 5 | mod elements; 6 | #[macro_use] 7 | mod events; 8 | #[cfg(feature = "hot_reload")] 9 | mod hot_reload; 10 | mod parse_attributes; 11 | mod tick; 12 | 13 | use self::{ 14 | apply_mutations::BevyTemplate, 15 | deferred_system::DeferredSystemRunQueue, 16 | ecs_hooks::EcsSubscriptions, 17 | events::{generate_mouse_enter_leave_events, EventReaders, MouseEnter, MouseExit}, 18 | tick::tick_dioxus_ui, 19 | }; 20 | use bevy::{ 21 | app::{App, Last, Plugin, PreUpdate}, 22 | ecs::{ 23 | bundle::Bundle, 24 | component::Component, 25 | entity::{Entity, EntityHashMap}, 26 | schedule::IntoSystemConfigs, 27 | }, 28 | prelude::Deref, 29 | ui::{node_bundles::NodeBundle, ui_focus_system}, 30 | utils::HashMap, 31 | }; 32 | use dioxus::dioxus_core::{Element, ElementId, VirtualDom}; 33 | 34 | pub mod prelude { 35 | pub use super::deferred_system::use_system_scheduler; 36 | pub use super::ecs_hooks::{ 37 | use_query, 38 | use_query_filtered, 39 | use_resource, 40 | use_world, 41 | // use_event_reader, TODO 42 | }; 43 | pub use super::elements::*; 44 | pub use super::{DioxusUiBundle, DioxusUiPlugin, DioxusUiRoot}; 45 | pub use bevy_mod_picking::pointer::PointerButton; 46 | pub use dioxus; 47 | pub use dioxus::prelude::{Event as DioxusEvent, *}; 48 | } 49 | 50 | pub struct DioxusUiPlugin; 51 | 52 | impl Plugin for DioxusUiPlugin { 53 | fn build(&self, app: &mut App) { 54 | #[cfg(feature = "hot_reload")] 55 | dioxus_hot_reload::hot_reload_init!(dioxus_hot_reload::Config::< 56 | hot_reload::HotReloadContext, 57 | >::default()); 58 | 59 | app.init_non_send_resource::() 60 | .init_resource::() 61 | .init_resource::() 62 | .add_event::() 63 | .add_event::() 64 | .add_systems( 65 | PreUpdate, 66 | generate_mouse_enter_leave_events.after(ui_focus_system), 67 | ) 68 | .add_systems(Last, tick_dioxus_ui); 69 | } 70 | } 71 | 72 | #[derive(Bundle)] 73 | pub struct DioxusUiBundle { 74 | pub dioxus_ui_root: DioxusUiRoot, 75 | pub node_bundle: NodeBundle, 76 | } 77 | 78 | #[derive(Component, Deref, Hash, PartialEq, Eq, Clone, Copy)] 79 | pub struct DioxusUiRoot(pub fn() -> Element); 80 | 81 | #[derive(Default)] 82 | struct UiContext { 83 | roots: HashMap<(Entity, DioxusUiRoot), UiRoot>, 84 | subscriptions: EcsSubscriptions, 85 | } 86 | 87 | struct UiRoot { 88 | virtual_dom: VirtualDom, 89 | element_id_to_bevy_ui_entity: HashMap, 90 | bevy_ui_entity_to_element_id: EntityHashMap, 91 | templates: HashMap, 92 | needs_rebuild: bool, 93 | } 94 | 95 | impl UiRoot { 96 | fn new(root_component: DioxusUiRoot) -> Self { 97 | Self { 98 | virtual_dom: VirtualDom::new(root_component.0), 99 | element_id_to_bevy_ui_entity: HashMap::new(), 100 | bevy_ui_entity_to_element_id: EntityHashMap::default(), 101 | templates: HashMap::new(), 102 | needs_rebuild: true, 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/parse_attributes.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | asset::{AssetPath, AssetServer}, 3 | math::Quat, 4 | render::{color::Color, view::Visibility}, 5 | text::{JustifyText, Text}, 6 | transform::components::Transform, 7 | ui::*, 8 | }; 9 | use std::f32::consts::PI; 10 | 11 | #[allow(clippy::too_many_arguments)] 12 | pub fn set_attribute( 13 | name: &str, 14 | value: &str, 15 | style: &mut Style, 16 | border_color: &mut BorderColor, 17 | outline: &mut Outline, 18 | background_color: &mut BackgroundColor, 19 | transform: &mut Transform, 20 | visibility: &mut Visibility, 21 | z_index: &mut ZIndex, 22 | text: Option<&mut Text>, 23 | image: Option<&mut UiImage>, 24 | asset_server: &AssetServer, 25 | ) { 26 | #[allow(unused_variables, unreachable_code)] 27 | match (name, value) { 28 | ("animate", value) => todo!(), 29 | ("display", "flex") => style.display = Display::Flex, 30 | ("display", "grid") => style.display = Display::Grid, 31 | ("display", "none") => style.display = Display::None, 32 | ("position", "relative") => style.position_type = PositionType::Relative, 33 | ("position", "absolute") => style.position_type = PositionType::Absolute, 34 | ("overflow", "visible") => style.overflow = Overflow::visible(), 35 | ("overflow", "clip") => style.overflow = Overflow::clip(), 36 | ("overflow_x", "visible") => style.overflow.x = OverflowAxis::Visible, 37 | ("overflow_x", "clip") => style.overflow.x = OverflowAxis::Clip, 38 | ("overflow_y", "visible") => style.overflow.y = OverflowAxis::Visible, 39 | ("overflow_y", "clip") => style.overflow.y = OverflowAxis::Clip, 40 | ("left", value) => style.left = parse_val(value), 41 | ("right", value) => style.right = parse_val(value), 42 | ("top", value) => style.top = parse_val(value), 43 | ("bottom", value) => style.bottom = parse_val(value), 44 | ("width", value) => style.width = parse_val(value), 45 | ("height", value) => style.height = parse_val(value), 46 | ("min_width", value) => style.min_width = parse_val(value), 47 | ("min_height", value) => style.min_height = parse_val(value), 48 | ("max_width", value) => style.max_width = parse_val(value), 49 | ("max_height", value) => style.max_height = parse_val(value), 50 | ("aspect_ratio", "none") => style.aspect_ratio = None, 51 | ("aspect_ratio", value) => style.aspect_ratio = Some(parse_f32(value)), 52 | ("align_items", "default") => style.align_items = AlignItems::Default, 53 | ("align_items", "start") => style.align_items = AlignItems::Start, 54 | ("align_items", "end") => style.align_items = AlignItems::End, 55 | ("align_items", "flex_start") => style.align_items = AlignItems::FlexStart, 56 | ("align_items", "flex_end") => style.align_items = AlignItems::FlexEnd, 57 | ("align_items", "center") => style.align_items = AlignItems::Center, 58 | ("align_items", "baseline") => style.align_items = AlignItems::Baseline, 59 | ("align_items", "stretch") => style.align_items = AlignItems::Stretch, 60 | ("justify_items", "default") => style.justify_items = JustifyItems::Default, 61 | ("justify_items", "start") => style.justify_items = JustifyItems::Start, 62 | ("justify_items", "end") => style.justify_items = JustifyItems::End, 63 | ("justify_items", "center") => style.justify_items = JustifyItems::Center, 64 | ("justify_items", "baseline") => style.justify_items = JustifyItems::Baseline, 65 | ("justify_items", "stretch") => style.justify_items = JustifyItems::Stretch, 66 | ("align_self", "auto") => style.align_self = AlignSelf::Auto, 67 | ("align_self", "start") => style.align_self = AlignSelf::Start, 68 | ("align_self", "end") => style.align_self = AlignSelf::End, 69 | ("align_self", "flex_start") => style.align_self = AlignSelf::FlexStart, 70 | ("align_self", "flex_end") => style.align_self = AlignSelf::FlexEnd, 71 | ("align_self", "center") => style.align_self = AlignSelf::Center, 72 | ("align_self", "baseline") => style.align_self = AlignSelf::Baseline, 73 | ("align_self", "stretch") => style.align_self = AlignSelf::Stretch, 74 | ("justify_self", "auto") => style.justify_self = JustifySelf::Auto, 75 | ("justify_self", "start") => style.justify_self = JustifySelf::Start, 76 | ("justify_self", "end") => style.justify_self = JustifySelf::End, 77 | ("justify_self", "center") => style.justify_self = JustifySelf::Center, 78 | ("justify_self", "baseline") => style.justify_self = JustifySelf::Baseline, 79 | ("justify_self", "stretch") => style.justify_self = JustifySelf::Stretch, 80 | ("align_content", "default") => style.align_content = AlignContent::Default, 81 | ("align_content", "start") => style.align_content = AlignContent::Start, 82 | ("align_content", "end") => style.align_content = AlignContent::End, 83 | ("align_content", "flex_start") => style.align_content = AlignContent::FlexStart, 84 | ("align_content", "flex_end") => style.align_content = AlignContent::FlexEnd, 85 | ("align_content", "center") => style.align_content = AlignContent::Center, 86 | ("align_content", "stretch") => style.align_content = AlignContent::Stretch, 87 | ("align_content", "space_between") => style.align_content = AlignContent::SpaceBetween, 88 | ("align_content", "space_evenly") => style.align_content = AlignContent::SpaceEvenly, 89 | ("align_content", "space_around") => style.align_content = AlignContent::SpaceAround, 90 | ("justify_content", "default") => style.justify_content = JustifyContent::Default, 91 | ("justify_content", "start") => style.justify_content = JustifyContent::Start, 92 | ("justify_content", "end") => style.justify_content = JustifyContent::End, 93 | ("justify_content", "flex_start") => style.justify_content = JustifyContent::FlexStart, 94 | ("justify_content", "flex_end") => style.justify_content = JustifyContent::FlexEnd, 95 | ("justify_content", "center") => style.justify_content = JustifyContent::Center, 96 | ("justify_content", "stretch") => style.justify_content = JustifyContent::Stretch, 97 | ("justify_content", "space_between") => { 98 | style.justify_content = JustifyContent::SpaceBetween; 99 | } 100 | ("justify_content", "space_evenly") => style.justify_content = JustifyContent::SpaceEvenly, 101 | ("justify_content", "space_around") => style.justify_content = JustifyContent::SpaceAround, 102 | ("margin", value) => style.margin = UiRect::all(parse_val(value)), 103 | ("margin_left", value) => style.margin.left = parse_val(value), 104 | ("margin_right", value) => style.margin.right = parse_val(value), 105 | ("margin_top", value) => style.margin.top = parse_val(value), 106 | ("margin_bottom", value) => style.margin.bottom = parse_val(value), 107 | ("padding", value) => style.padding = UiRect::all(parse_val(value)), 108 | ("padding_left", value) => style.padding.left = parse_val(value), 109 | ("padding_right", value) => style.padding.right = parse_val(value), 110 | ("padding_top", value) => style.padding.top = parse_val(value), 111 | ("padding_bottom", value) => style.padding.bottom = parse_val(value), 112 | ("border_width", value) => style.border = UiRect::all(parse_val(value)), 113 | ("border_width_left", value) => style.border.left = parse_val(value), 114 | ("border_width_right", value) => style.border.right = parse_val(value), 115 | ("border_width_top", value) => style.border.top = parse_val(value), 116 | ("border_width_bottom", value) => style.border.bottom = parse_val(value), 117 | ("border_color", value) => border_color.0 = parse_color(value), 118 | ("outline_width", value) => outline.width = parse_val(value), 119 | ("outline_offset", value) => outline.offset = parse_val(value), 120 | ("outline_color", value) => outline.color = parse_color(value), 121 | ("flex_direction", "row") => style.flex_direction = FlexDirection::Row, 122 | ("flex_direction", "column") => style.flex_direction = FlexDirection::Column, 123 | ("flex_direction", "row_reverse") => style.flex_direction = FlexDirection::RowReverse, 124 | ("flex_direction", "column_reverse") => style.flex_direction = FlexDirection::ColumnReverse, 125 | ("flex_wrap", "no_wrap") => style.flex_wrap = FlexWrap::NoWrap, 126 | ("flex_wrap", "wrap") => style.flex_wrap = FlexWrap::Wrap, 127 | ("flex_wrap", "wrap_reverse") => style.flex_wrap = FlexWrap::WrapReverse, 128 | ("flex_grow", value) => style.flex_grow = parse_f32(value), 129 | ("flex_shrink", value) => style.flex_shrink = parse_f32(value), 130 | ("flex_basis", value) => style.flex_basis = parse_val(value), 131 | ("row_gap", value) => style.row_gap = parse_val(value), 132 | ("column_gap", value) => style.column_gap = parse_val(value), 133 | ("grid_auto_flow", "row") => style.grid_auto_flow = GridAutoFlow::Row, 134 | ("grid_auto_flow", "column") => style.grid_auto_flow = GridAutoFlow::Column, 135 | ("grid_auto_flow", "row_dense") => style.grid_auto_flow = GridAutoFlow::RowDense, 136 | ("grid_auto_flow", "column_dense") => style.grid_auto_flow = GridAutoFlow::ColumnDense, 137 | ("grid_template_rows", value) => { 138 | style.grid_template_rows = todo!(); 139 | } 140 | ("grid_template_columns", value) => { 141 | style.grid_template_columns = todo!(); 142 | } 143 | ("grid_auto_rows", value) => { 144 | style.grid_auto_rows = todo!(); 145 | } 146 | ("grid_auto_columns", value) => { 147 | style.grid_auto_columns = todo!(); 148 | } 149 | ("grid_row", value) => { 150 | style.grid_row = todo!(); 151 | } 152 | ("grid_column", value) => { 153 | style.grid_column = todo!(); 154 | } 155 | ("background_color", value) => background_color.0 = parse_color(value), 156 | ("translation", value) => { 157 | let value = parse_f32(value); 158 | transform.translation.x = value; 159 | transform.translation.y = value; 160 | } 161 | ("translation_x", value) => transform.translation.x = parse_f32(value), 162 | ("translation_y", value) => transform.translation.y = parse_f32(value), 163 | ("rotation", value) => { 164 | transform.rotation = Quat::from_rotation_y(parse_f32(value) * (180.0 / PI)); 165 | } 166 | ("scale", value) => { 167 | let value = parse_f32(value); 168 | transform.scale.x = value; 169 | transform.scale.y = value; 170 | } 171 | ("scale_x", value) => transform.scale.x = parse_f32(value), 172 | ("scale_y", value) => transform.scale.y = parse_f32(value), 173 | ("visibility", "inherited") => *visibility = Visibility::Inherited, 174 | ("visibility", "hidden") => *visibility = Visibility::Hidden, 175 | ("visibility", "visible") => *visibility = Visibility::Visible, 176 | ("z_index", value) => match value.split_once(':') { 177 | Some(("local", value)) => *z_index = ZIndex::Local(parse_i32(value)), 178 | Some(("global", value)) => *z_index = ZIndex::Global(parse_i32(value)), 179 | None => *z_index = ZIndex::Local(parse_i32(value)), 180 | _ => panic!("Encountered invalid bevy_dioxus ZIndex `{value}`."), 181 | }, 182 | ("text", value) if text.is_some() => text.unwrap().sections[0].value = value.to_owned(), 183 | ("text_direction", "inherit") if text.is_some() => style.direction = Direction::Inherit, 184 | ("text_direction", "left_to_right") if text.is_some() => { 185 | style.direction = Direction::LeftToRight; 186 | } 187 | ("text_direction", "right_to_left") if text.is_some() => { 188 | style.direction = Direction::RightToLeft; 189 | } 190 | ("text_multiline_justification", "left") if text.is_some() => { 191 | text.unwrap().justify = JustifyText::Left; 192 | } 193 | ("text_multiline_justification", "center") if text.is_some() => { 194 | text.unwrap().justify = JustifyText::Center; 195 | } 196 | ("text_multiline_justification", "right") if text.is_some() => { 197 | text.unwrap().justify = JustifyText::Right; 198 | } 199 | ("text_size", value) if text.is_some() => { 200 | text.unwrap().sections[0].style.font_size = parse_f32(value); 201 | } 202 | ("text_color", value) if text.is_some() => { 203 | text.unwrap().sections[0].style.color = parse_color(value); 204 | } 205 | ("image_asset_path", value) if image.is_some() => { 206 | image.unwrap().texture = asset_server.load(AssetPath::parse(value)); 207 | } 208 | _ => panic!("Encountered unsupported bevy_dioxus attribute `{name}: {value}`."), 209 | } 210 | } 211 | 212 | fn parse_color(hex: &str) -> Color { 213 | Color::hex(hex).unwrap_or_else(|_| panic!("Encountered invalid bevy_dioxus Color hex `{hex}`.")) 214 | } 215 | 216 | fn parse_f32(float: &str) -> f32 { 217 | float 218 | .parse::() 219 | .unwrap_or_else(|val| panic!("Encountered invalid bevy_dioxus f32 `{val}`.")) 220 | } 221 | 222 | fn parse_i32(int: &str) -> i32 { 223 | int.parse::() 224 | .unwrap_or_else(|val| panic!("Encountered invalid bevy_dioxus i32 `{val}`.")) 225 | } 226 | 227 | fn parse_val(val: &str) -> Val { 228 | if let Ok(val) = val.parse::() { 229 | return Val::Px(val); 230 | } 231 | if let Some((val, "")) = val.split_once("px") { 232 | if let Ok(val) = val.parse::() { 233 | return Val::Px(val); 234 | } 235 | } 236 | if let Some((val, "")) = val.split_once("vw") { 237 | if let Ok(val) = val.parse::() { 238 | return Val::Vw(val); 239 | } 240 | } 241 | if let Some((val, "")) = val.split_once("vh") { 242 | if let Ok(val) = val.parse::() { 243 | return Val::Vh(val); 244 | } 245 | } 246 | panic!("Encountered invalid bevy_dioxus Val `{val}`."); 247 | } 248 | -------------------------------------------------------------------------------- /src/tick.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | apply_mutations::MutationApplier, 3 | deferred_system::DeferredSystemRunQueue, 4 | ecs_hooks::EcsContext, 5 | events::{bubble_event, EventReaders}, 6 | DioxusUiRoot, UiContext, UiRoot, 7 | }; 8 | use bevy::{ 9 | asset::AssetServer, 10 | ecs::{ 11 | entity::Entity, 12 | world::{Mut, World}, 13 | }, 14 | utils::HashMap, 15 | }; 16 | use std::{any::Any, mem, rc::Rc}; 17 | 18 | pub fn tick_dioxus_ui(world: &mut World) { 19 | run_deferred_systems(world); 20 | 21 | let ui_events = world.resource_scope(|world, mut event_readers: Mut| { 22 | event_readers.read_events( 23 | world.resource(), 24 | world.resource(), 25 | world.resource(), 26 | world.resource(), 27 | world.resource(), 28 | world.resource(), 29 | world.resource(), 30 | ) 31 | }); 32 | 33 | let root_entities: HashMap = world 34 | .query::<(Entity, &DioxusUiRoot)>() 35 | .iter(world) 36 | .map(|(entity, ui_root)| (entity, *ui_root)) 37 | .collect(); 38 | let mut ui_roots = mem::take(&mut world.non_send_resource_mut::().roots); 39 | 40 | for (root_entity, dioxus_ui_root) in root_entities { 41 | let mut ui_root = ui_roots 42 | .remove(&(root_entity, dioxus_ui_root)) 43 | .unwrap_or_else(|| UiRoot::new(dioxus_ui_root)); 44 | 45 | dispatch_ui_events(&ui_events, &mut ui_root, world); 46 | 47 | schedule_ui_renders_from_ecs_subscriptions(&mut ui_root, world); 48 | 49 | render_ui(root_entity, &mut ui_root, world); 50 | 51 | world 52 | .non_send_resource_mut::() 53 | .roots 54 | .insert((root_entity, dioxus_ui_root), ui_root); 55 | } 56 | } 57 | 58 | fn run_deferred_systems(world: &mut World) { 59 | for mut system in mem::take(&mut *world.resource_mut::().run_queue) { 60 | system.initialize(world); 61 | system.run((), world); 62 | } 63 | } 64 | 65 | fn dispatch_ui_events( 66 | events: &Vec<(Entity, &str, Rc, bool)>, 67 | ui_root: &mut UiRoot, 68 | world: &World, 69 | ) { 70 | for (mut target, name, data, bubbles) in events { 71 | if *bubbles { 72 | bubble_event(name, &mut target, world); 73 | } 74 | if let Some(target_element_id) = ui_root.bevy_ui_entity_to_element_id.get(&target) { 75 | ui_root 76 | .virtual_dom 77 | .handle_event(name, Rc::clone(data), *target_element_id, *bubbles); 78 | } 79 | } 80 | } 81 | 82 | fn schedule_ui_renders_from_ecs_subscriptions(ui_root: &mut UiRoot, world: &World) { 83 | let ecs_subscriptions = &world.non_send_resource::().subscriptions; 84 | 85 | for scope_id in &*ecs_subscriptions.world_and_queries { 86 | ui_root.virtual_dom.mark_dirty(*scope_id); 87 | } 88 | 89 | for (resource_id, scope_ids) in &*ecs_subscriptions.resources { 90 | if world.is_resource_changed_by_id(*resource_id) { 91 | for scope_id in scope_ids { 92 | ui_root.virtual_dom.mark_dirty(*scope_id); 93 | } 94 | } 95 | } 96 | 97 | for (new_events_exist, scope_ids) in ecs_subscriptions.events.values() { 98 | if new_events_exist(world) { 99 | for scope_id in scope_ids { 100 | ui_root.virtual_dom.mark_dirty(*scope_id); 101 | } 102 | } 103 | } 104 | } 105 | 106 | fn render_ui(root_entity: Entity, ui_root: &mut UiRoot, world: &mut World) { 107 | ui_root 108 | .virtual_dom 109 | .provide_root_context(EcsContext { world }); 110 | 111 | #[cfg(feature = "hot_reload")] 112 | crate::hot_reload::update_templates(world, &mut ui_root.virtual_dom); 113 | 114 | if ui_root.needs_rebuild { 115 | world.resource_scope(|world, asset_server: Mut| { 116 | let mut mutation_applier = MutationApplier::new( 117 | &mut ui_root.element_id_to_bevy_ui_entity, 118 | &mut ui_root.bevy_ui_entity_to_element_id, 119 | &mut ui_root.templates, 120 | root_entity, 121 | world, 122 | &asset_server, 123 | ); 124 | ui_root.virtual_dom.rebuild(&mut mutation_applier); 125 | }); 126 | ui_root.needs_rebuild = false; 127 | } 128 | 129 | world.resource_scope(|world, asset_server: Mut| { 130 | let mut mutation_applier = MutationApplier::new( 131 | &mut ui_root.element_id_to_bevy_ui_entity, 132 | &mut ui_root.bevy_ui_entity_to_element_id, 133 | &mut ui_root.templates, 134 | root_entity, 135 | world, 136 | &asset_server, 137 | ); 138 | ui_root.virtual_dom.render_immediate(&mut mutation_applier); 139 | }); 140 | } 141 | --------------------------------------------------------------------------------