├── .gitignore ├── Cargo.toml ├── LICENSE-MIT ├── README-zh-Hans.md ├── README.md ├── assets ├── counter.gif ├── game_menu.gif └── items │ ├── 0.png │ ├── 1.png │ ├── 2.png │ ├── 3.png │ └── 4.png ├── crates ├── hooked_collection │ ├── Cargo.toml │ └── src │ │ ├── hooked_map.rs │ │ ├── hooked_vec.rs │ │ ├── lib.rs │ │ ├── map_operation.rs │ │ ├── operation_record.rs │ │ ├── sender.rs │ │ └── vec_operation.rs ├── rxy_bevy │ ├── Cargo.toml │ └── src │ │ ├── cmd.rs │ │ ├── command.rs │ │ ├── entity_extra_data.rs │ │ ├── event.rs │ │ ├── focusable.rs │ │ ├── lib.rs │ │ ├── navigation │ │ └── mod.rs │ │ ├── nest.rs │ │ ├── plugin.rs │ │ ├── renderer │ │ ├── attr_parse.rs │ │ ├── attr_value.rs │ │ ├── attrs.rs │ │ ├── common_renderer.rs │ │ ├── composite_attrs.rs │ │ ├── elements │ │ │ ├── div.rs │ │ │ ├── img.rs │ │ │ ├── mod.rs │ │ │ └── span.rs │ │ ├── event.rs │ │ ├── mod.rs │ │ ├── node_tree.rs │ │ ├── style │ │ │ ├── attr_iter.rs │ │ │ ├── attr_syncer.rs │ │ │ ├── element_view_ext.rs │ │ │ ├── entity_world_ref.rs │ │ │ ├── focus_style.rs │ │ │ ├── interaction_style.rs │ │ │ ├── mod.rs │ │ │ ├── node_style_state.rs │ │ │ ├── node_tree.rs │ │ │ ├── plugin.rs │ │ │ ├── res_style_sheets.rs │ │ │ ├── shared_style_sheets.rs │ │ │ ├── shared_style_view.rs │ │ │ └── style_state_owner.rs │ │ ├── tailwind_attrs.rs │ │ ├── text_styled_element.rs │ │ ├── view_builder_ext.rs │ │ └── view_key.rs │ │ ├── res.rs │ │ ├── res_change_observe.rs │ │ ├── vec_data_source.rs │ │ ├── view │ │ ├── mod.rs │ │ ├── res.rs │ │ ├── system.rs │ │ └── system_once.rs │ │ ├── view_member │ │ ├── bundle.rs │ │ ├── event.rs │ │ └── mod.rs │ │ └── world_ext.rs ├── rxy_bevy_ecs │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ └── renderer │ │ ├── mod.rs │ │ ├── node_tree.rs │ │ └── view_key.rs ├── rxy_bevy_macro │ ├── Cargo.toml │ └── src │ │ ├── force_dynamic_view.rs │ │ ├── into_view.rs │ │ ├── lib.rs │ │ └── schema.rs ├── rxy_bevy_ui_components │ ├── Cargo.toml │ └── src │ │ ├── checkbox.rs │ │ ├── lib.rs │ │ ├── select.rs │ │ └── slider.rs ├── rxy_core │ ├── Cargo.toml │ └── src │ │ ├── build_info.rs │ │ ├── common_renderer │ │ ├── composite_attrs.rs │ │ ├── mod.rs │ │ └── tailwind_attrs.rs │ │ ├── diff.rs │ │ ├── either.rs │ │ ├── element │ │ ├── attr_value.rs │ │ ├── attrs.rs │ │ ├── dynamic_element.rs │ │ ├── element.rs │ │ ├── element_attr_type.rs │ │ ├── element_children.rs │ │ ├── element_type.rs │ │ ├── mod.rs │ │ └── view_member.rs │ │ ├── element_view.rs │ │ ├── impl │ │ ├── build_configure.rs │ │ ├── builder.rs │ │ ├── context.rs │ │ ├── dynamic.rs │ │ ├── either.rs │ │ ├── erasure.rs │ │ ├── future.rs │ │ ├── iter_data_keyed.rs │ │ ├── mod.rs │ │ ├── option.rs │ │ ├── reactive.rs │ │ ├── rebuild_fn_receiver.rs │ │ ├── recyclable.rs │ │ ├── reflect.rs │ │ ├── result.rs │ │ ├── static.rs │ │ ├── stream.rs │ │ ├── stream_with_default_value.rs │ │ ├── to_mutable.rs │ │ ├── virtual_container.rs │ │ ├── x_if.rs │ │ ├── x_if2.rs │ │ ├── x_iter.rs │ │ ├── x_iter_source.rs │ │ └── x_world.rs │ │ ├── into_view.rs │ │ ├── lib.rs │ │ ├── maybe_traits.rs │ │ ├── member_after_children.rs │ │ ├── member_owner.rs │ │ ├── mutable_view.rs │ │ ├── nest.rs │ │ ├── rebuild.rs │ │ ├── remove_on_drop.rs │ │ ├── renderer.rs │ │ ├── renderers │ │ ├── bevy.rs │ │ ├── mod.rs │ │ └── native.rs │ │ ├── schema │ │ ├── context.rs │ │ ├── controlled_state.rs │ │ ├── ctx.rs │ │ ├── element.rs │ │ ├── event.rs │ │ ├── fn.rs │ │ ├── mod.rs │ │ ├── param.rs │ │ ├── prop.rs │ │ ├── prop_state.rs │ │ ├── prop_value_wrapper.rs │ │ ├── props.rs │ │ ├── reactive.rs │ │ ├── required_param.rs │ │ ├── schema_with_element_view_bound.rs │ │ ├── slot.rs │ │ ├── view.rs │ │ └── wrapper.rs │ │ ├── smallbox │ │ ├── mod.rs │ │ ├── small_box.rs │ │ └── space.rs │ │ ├── static.rs │ │ ├── style │ │ ├── attr_style_owner.rs │ │ ├── mod.rs │ │ ├── style_sheet_definition.rs │ │ ├── style_sheet_items.rs │ │ └── view_member.rs │ │ ├── test.rs │ │ ├── utils │ │ ├── mod.rs │ │ ├── on_drop.rs │ │ └── synccell.rs │ │ ├── view.rs │ │ └── view_member.rs ├── rxy_macro │ ├── Cargo.toml │ └── src │ │ ├── all_tuples.rs │ │ ├── force_dynamic_view.rs │ │ ├── ident_count.rs │ │ ├── into_view.rs │ │ └── lib.rs ├── rxy_native │ ├── Cargo.toml │ └── src │ │ ├── app.rs │ │ ├── draw.rs │ │ ├── draw_text.rs │ │ ├── layout.rs │ │ ├── lib.rs │ │ ├── renderer │ │ ├── attr_values.rs │ │ ├── attrs.rs │ │ ├── common_renderer.rs │ │ ├── composite_attrs.rs │ │ ├── elements │ │ │ ├── div.rs │ │ │ ├── img.rs │ │ │ ├── mod.rs │ │ │ └── span.rs │ │ ├── layout │ │ │ ├── alignment.rs │ │ │ ├── convert.rs │ │ │ ├── flex.rs │ │ │ ├── geometry.rs │ │ │ ├── grid.rs │ │ │ ├── mod.rs │ │ │ ├── style.rs │ │ │ └── text.rs │ │ ├── mod.rs │ │ ├── node_bundles.rs │ │ ├── node_tree.rs │ │ ├── taffy.rs │ │ ├── tailwind_attrs.rs │ │ ├── transform │ │ │ └── mod.rs │ │ ├── ui_node.rs │ │ ├── view_key.rs │ │ └── visibility.rs │ │ ├── running_app.rs │ │ ├── user_event.rs │ │ ├── window.rs │ │ └── world_ext.rs ├── rxy_web_dom │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── lib.rs │ │ └── renderer │ │ ├── attr_values.rs │ │ ├── attrs.rs │ │ ├── common_renderer.rs │ │ ├── elements │ │ └── mod.rs │ │ ├── event.rs │ │ └── mod.rs └── xy_reactive │ ├── .gitignore │ ├── Cargo.toml │ ├── README.md │ ├── examples │ └── playground.rs │ ├── src │ ├── arena.rs │ ├── async_signal │ │ ├── derived.rs │ │ ├── mod.rs │ │ └── resource.rs │ ├── context.rs │ ├── effect.rs │ ├── lib.rs │ ├── memo.rs │ ├── notify.rs │ ├── render_effect.rs │ ├── selector.rs │ ├── serde.rs │ ├── serialization.rs │ ├── shared_context │ │ ├── hydrate.rs │ │ ├── islands.rs │ │ ├── mod.rs │ │ └── ssr.rs │ ├── signal │ │ ├── arc_signal.rs │ │ ├── mod.rs │ │ ├── read.rs │ │ ├── trigger.rs │ │ └── write.rs │ ├── signal_get_ext.rs │ ├── signal_traits.rs │ ├── source.rs │ ├── spawn.rs │ └── store │ │ ├── indexed.rs │ │ ├── keyed.rs │ │ ├── mod.rs │ │ ├── path.rs │ │ └── stored.rs │ └── tests │ ├── effect.rs │ ├── memo.rs │ └── signal.rs ├── examples ├── counter.rs ├── counter_by_channel.rs ├── game_ui_challenges │ ├── components │ │ ├── checkbox.rs │ │ ├── mod.rs │ │ ├── select.rs │ │ └── slider.rs │ ├── game_menu.rs │ └── inventory.rs ├── if_else.rs ├── iter_source.rs ├── native_playground │ ├── Cargo.toml │ └── src │ │ └── main.rs ├── playground.rs ├── style.rs └── web_playground │ ├── Cargo.toml │ ├── README.md │ ├── index.html │ └── src │ └── lib.rs ├── rustfmt.toml └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | crates/*/target 3 | examples/*/dist 4 | examples/*/node_modules 5 | Cargo.lock 6 | .cargo/config 7 | .cargo/config.toml 8 | /.idea 9 | /.vscode 10 | /benches/target 11 | examples/foo.rs -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Ycy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /assets/counter.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/counter.gif -------------------------------------------------------------------------------- /assets/game_menu.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/game_menu.gif -------------------------------------------------------------------------------- /assets/items/0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/items/0.png -------------------------------------------------------------------------------- /assets/items/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/items/1.png -------------------------------------------------------------------------------- /assets/items/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/items/2.png -------------------------------------------------------------------------------- /assets/items/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/items/3.png -------------------------------------------------------------------------------- /assets/items/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/assets/items/4.png -------------------------------------------------------------------------------- /crates/hooked_collection/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "hooked_collection" 4 | version = "0.1.0" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ahash.workspace = true 10 | async-channel.workspace = true 11 | hashbrown.workspace = true 12 | serde = { workspace = true, optional = true } 13 | smallvec.workspace = true 14 | 15 | [features] 16 | default = ["std"] 17 | serde = ["dep:serde"] 18 | std = ["async-channel/std"] 19 | -------------------------------------------------------------------------------- /crates/hooked_collection/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(dead_code)] 3 | #![no_std] 4 | extern crate alloc; 5 | 6 | #[cfg(feature = "std")] 7 | extern crate std; 8 | 9 | mod hooked_map; 10 | mod hooked_vec; 11 | mod map_operation; 12 | mod operation_record; 13 | mod sender; 14 | mod vec_operation; 15 | 16 | pub use hooked_map::*; 17 | pub use hooked_vec::*; 18 | pub use map_operation::*; 19 | pub use operation_record::*; 20 | pub use vec_operation::*; 21 | -------------------------------------------------------------------------------- /crates/hooked_collection/src/map_operation.rs: -------------------------------------------------------------------------------- 1 | use crate::hooked_map::{HookMap, HookedHashMap}; 2 | use core::cmp::Eq; 3 | use core::hash::Hash; 4 | use core::iter::IntoIterator; 5 | use hashbrown::HashMap; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq)] 8 | #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] 9 | pub enum MapOperation { 10 | Insert { key: K, value: V }, 11 | Update { key: K, value: V }, 12 | Patch { key: K }, 13 | Remove { key: K }, 14 | Clear {}, 15 | } 16 | 17 | pub trait ApplyMapOperation { 18 | fn apply_map_op(&mut self, diff: MapOperation); 19 | fn apply_map_ops(&mut self, diff: impl IntoIterator>) { 20 | for diff in diff { 21 | self.apply_map_op(diff); 22 | } 23 | } 24 | } 25 | 26 | impl ApplyMapOperation for HashMap 27 | where 28 | K: Hash + Eq, 29 | { 30 | fn apply_map_op(&mut self, diff: MapOperation) { 31 | match diff { 32 | MapOperation::Insert { key, value } => { 33 | self.insert(key, value); 34 | } 35 | MapOperation::Update { key, value } => { 36 | self.insert(key, value); 37 | } 38 | MapOperation::Remove { key } => { 39 | self.remove(&key); 40 | } 41 | MapOperation::Clear {} => { 42 | self.clear(); 43 | } 44 | MapOperation::Patch { key: _ } => { 45 | unimplemented!("Patch not implemented for HashMap") 46 | } 47 | } 48 | } 49 | } 50 | 51 | impl ApplyMapOperation for HookedHashMap 52 | where 53 | K: Hash + Eq, 54 | O: HookMap, 55 | { 56 | fn apply_map_op(&mut self, diff: MapOperation) { 57 | match diff { 58 | MapOperation::Insert { key, value } => { 59 | self.insert(key, value); 60 | } 61 | MapOperation::Update { key, value } => { 62 | self.insert(key, value); 63 | } 64 | MapOperation::Remove { key } => { 65 | self.remove(&key); 66 | } 67 | MapOperation::Clear {} => { 68 | self.clear(); 69 | } 70 | MapOperation::Patch { key: _ } => { 71 | unimplemented!("Patch not implemented for HashMap") 72 | } 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/rxy_bevy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "rxy_bevy" 4 | version = "0.1.0" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | #rxy_bevy_element.workspace = true 10 | rxy_bevy_macro.workspace = true 11 | rxy_core = { workspace = true, features = ["common_renderer", "async-channel", "bevy", "xy_reactive", "bevy_reflect", "x_iter_source"] } 12 | xy_reactive = { workspace = true, optional = true, features = ["bevy"] } 13 | bevy_color = { workspace = true } 14 | rxy_bevy_ecs = { path = "../rxy_bevy_ecs" } 15 | bevy_async_x = { git = "https://github.com/ycysdf/bevy_async_x", branch = "main", optional = true } 16 | rxy_macro.workspace = true 17 | hooked_collection.workspace = true 18 | 19 | glam.workspace = true 20 | serde.workspace = true 21 | 22 | count-macro.workspace = true 23 | paste.workspace = true 24 | 25 | async-channel.workspace = true 26 | async-broadcast.workspace = true 27 | 28 | smallvec.workspace = true 29 | 30 | bevy_app.workspace = true 31 | bevy_asset = { workspace = true, optional = false } 32 | bevy_text = { workspace = true, optional = false } 33 | bevy_transform = { workspace = true, optional = false } 34 | bevy_render.workspace = true 35 | bevy_ui.workspace = true 36 | bevy_core.workspace = true 37 | bevy_derive.workspace = true 38 | bevy_ecs.workspace = true 39 | bevy_hierarchy.workspace = true 40 | bevy_mod_picking.workspace = true 41 | bevy_reflect = { workspace = true, features = ["bevy"] } 42 | #bevy_sprite.workspace = true 43 | bevy_tasks.workspace = true 44 | bevy_utils.workspace = true 45 | #bevy_a11y.workspace = true 46 | bevy_input.workspace = true 47 | bevy_window.workspace = true 48 | 49 | futures-lite.workspace = true 50 | 51 | # cosmic-text.workspace = true 52 | 53 | oneshot.workspace = true 54 | 55 | [features] 56 | default = ["xy_reactive", "tailwind_aliases", "dynamic_element", "flexbox", "grid"] 57 | tailwind_aliases = [] 58 | async_x = ["bevy_async_x"] 59 | dynamic_element = [] 60 | style = ["rxy_core/style"] 61 | grid = [] 62 | flexbox = [] -------------------------------------------------------------------------------- /crates/rxy_bevy/src/cmd.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::{App, Plugin, Update}; 2 | use bevy_derive::{Deref, DerefMut}; 3 | use bevy_ecs::change_detection::Res; 4 | use bevy_ecs::prelude::{Deferred, World}; 5 | use bevy_ecs::schedule::IntoSystemConfigs; 6 | use bevy_ecs::system::{Resource, SystemBuffer, SystemMeta}; 7 | use bevy_ecs::world::{Command, CommandQueue}; 8 | 9 | #[derive(Resource, Clone, Deref, DerefMut)] 10 | pub struct CmdReceiver(pub async_channel::Receiver); 11 | 12 | #[derive(Resource, Clone, Deref, DerefMut)] 13 | pub struct CmdSender(pub async_channel::Sender); 14 | 15 | pub struct CommandChannelPlugin; 16 | 17 | impl Plugin for CommandChannelPlugin { 18 | fn build(&self, app: &mut App) { 19 | let (cmd_sender, cmd_receiver) = async_channel::unbounded::<_>(); 20 | 21 | app.insert_resource(CmdReceiver(cmd_receiver)) 22 | .insert_resource(CmdSender(cmd_sender)) 23 | .add_systems(Update, (recv_cmds.run_if(has_cmd),)); 24 | } 25 | } 26 | 27 | impl CmdSender { 28 | pub fn add(&self, cmd: C) { 29 | let mut command_queue = CommandQueue::default(); 30 | command_queue.push(cmd); 31 | self.send_blocking(command_queue).unwrap(); 32 | } 33 | } 34 | 35 | #[derive(Resource, Default)] 36 | pub struct CommandQueues { 37 | pub queues: Vec, 38 | } 39 | 40 | impl SystemBuffer for CommandQueues { 41 | fn apply(&mut self, _system_meta: &SystemMeta, world: &mut World) { 42 | for mut queue in self.queues.drain(..) { 43 | queue.apply(world); 44 | } 45 | } 46 | } 47 | 48 | pub fn recv_cmds(vdom_receiver: Res, mut commands: Deferred) { 49 | while let Ok(command_queue) = vdom_receiver.try_recv() { 50 | commands.queues.push(command_queue); 51 | } 52 | } 53 | 54 | pub fn has_cmd(vdom_receiver: Res) -> bool { 55 | !vdom_receiver.is_empty() 56 | } 57 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/command.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::{Commands, World}; 2 | use bevy_ecs::world::Command; 3 | 4 | use rxy_core::{IntoView, RendererNodeId, RendererWorld, View, ViewCtx}; 5 | 6 | use crate::{BevyRenderer, RxyRootEntity}; 7 | 8 | pub struct RxyUiSpawnCommand 9 | where 10 | V: View, 11 | { 12 | view: V, 13 | } 14 | 15 | impl Command for RxyUiSpawnCommand 16 | where 17 | V: View, 18 | { 19 | fn apply(self, world: &mut World) { 20 | let root = world.resource::().0; 21 | let _ = self.view.build( 22 | ViewCtx { 23 | world, 24 | parent: root, 25 | }, 26 | None, 27 | false, 28 | ); 29 | } 30 | } 31 | 32 | pub trait RxyViewSpawner 33 | where 34 | IV: IntoView, 35 | { 36 | type Output; 37 | fn spawn_view_on_root(&mut self, into_view: IV) -> Self::Output { 38 | self.spawn_view(into_view, |world| world.resource::().0) 39 | } 40 | fn spawn_view( 41 | &mut self, 42 | into_view: IV, 43 | parent: impl FnOnce(&mut RendererWorld) -> RendererNodeId 44 | + Send 45 | + 'static, 46 | ) -> Self::Output; 47 | } 48 | 49 | impl<'w, 's, IV> RxyViewSpawner for Commands<'w, 's> 50 | where 51 | IV: IntoView, 52 | { 53 | type Output = oneshot::Receiver<>::Key>; 54 | 55 | fn spawn_view( 56 | &mut self, 57 | into_view: IV, 58 | parent_f: impl FnOnce(&mut RendererWorld) -> RendererNodeId 59 | + Send 60 | + 'static, 61 | ) -> Self::Output 62 | where 63 | IV: IntoView, 64 | { 65 | let view = into_view.into_view(); 66 | let (sender, receiver) = oneshot::channel(); 67 | self.add(move |world: &mut World| { 68 | let parent = parent_f(world); 69 | let view_key = view.build(ViewCtx { world, parent }, None, false); 70 | let _ = sender.send(view_key); 71 | }); 72 | receiver 73 | } 74 | } 75 | 76 | impl RxyViewSpawner for World 77 | where 78 | IV: IntoView, 79 | { 80 | type Output = >::Key; 81 | 82 | fn spawn_view( 83 | &mut self, 84 | into_view: IV, 85 | parent_f: impl FnOnce(&mut RendererWorld) -> RendererNodeId 86 | + Send 87 | + 'static, 88 | ) -> Self::Output 89 | where 90 | IV: IntoView, 91 | { 92 | let parent = parent_f(self); 93 | into_view.into_view().build( 94 | ViewCtx { 95 | world: self, 96 | parent, 97 | }, 98 | None, 99 | false, 100 | ) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/entity_extra_data.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::Component; 2 | 3 | use rxy_core::{AttrIndex, ElementTypeUnTyped}; 4 | 5 | use crate::BevyRenderer; 6 | 7 | pub type AttrSetBits = u128; 8 | pub type AttrInitBits = u128; 9 | 10 | #[derive(Component, Clone)] 11 | pub struct ElementEntityExtraData { 12 | pub element_type: &'static dyn ElementTypeUnTyped, 13 | pub attr_is_set: AttrSetBits, 14 | pub attr_is_init: AttrSetBits, 15 | } 16 | 17 | impl ElementEntityExtraData { 18 | pub fn new(element_type: &'static dyn ElementTypeUnTyped) -> Self { 19 | Self { 20 | element_type, 21 | attr_is_set: 0, 22 | attr_is_init: 0, 23 | } 24 | } 25 | 26 | pub fn set_attr(&mut self, attr_index: AttrIndex, is_set: bool) { 27 | if attr_index == 0 { 28 | return; 29 | } 30 | if is_set { 31 | self.attr_is_set |= 1 << attr_index; 32 | } else { 33 | self.attr_is_set &= !(1 << attr_index); 34 | } 35 | } 36 | pub fn init_attr(&mut self, attr_index: AttrIndex, is_init: bool) { 37 | if attr_index == 0 { 38 | return; 39 | } 40 | if is_init { 41 | self.attr_is_init |= 1 << attr_index; 42 | } else { 43 | self.attr_is_init &= !(1 << attr_index); 44 | } 45 | } 46 | 47 | pub fn is_set_attr(&self, attr_index: AttrIndex) -> bool { 48 | Self::static_is_set_attr(self.attr_is_set, attr_index) 49 | } 50 | 51 | pub fn is_init_attr(&self, attr_index: AttrIndex) -> bool { 52 | (self.attr_is_init >> attr_index) & 1 == 1 53 | } 54 | 55 | pub fn static_is_set_attr(attr_is_set: AttrSetBits, attr_index: AttrIndex) -> bool { 56 | (attr_is_set >> attr_index) & 1 == 1 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | 3 | pub use cmd::*; 4 | pub use command::*; 5 | pub use entity_extra_data::*; 6 | pub use focusable::*; 7 | pub use plugin::*; 8 | pub use renderer::*; 9 | pub use res::*; 10 | pub use res_change_observe::*; 11 | use rxy_core::{ 12 | CloneableSchemaSlot, FnSchema, IntoViewSchemaFnWrapper, RebuildFnReceiver, RenderSchemaCtx, 13 | RendererSchemaView, SchemaSlot, 14 | }; 15 | pub use view::*; 16 | pub use view_member::*; 17 | pub use world_ext::*; 18 | 19 | mod cmd; 20 | mod command; 21 | mod entity_extra_data; 22 | pub mod event; 23 | mod focusable; 24 | pub mod navigation; 25 | mod nest; 26 | mod plugin; 27 | mod renderer; 28 | mod res; 29 | mod res_change_observe; 30 | pub mod vec_data_source; 31 | mod view; 32 | mod view_member; 33 | mod world_ext; 34 | 35 | pub type FnSchemaView = 36 | RendererSchemaView, P>, (), ()>; 37 | 38 | pub type SchemaCtx = RenderSchemaCtx; 39 | 40 | pub type ReceiverProp = RebuildFnReceiver; 41 | 42 | pub type Slot = SchemaSlot; 43 | pub type CloneableSlot = CloneableSchemaSlot; 44 | 45 | pub mod all_attrs { 46 | pub use crate::attrs::*; 47 | } 48 | 49 | pub mod prelude { 50 | pub use bevy_ui::prelude::Val; 51 | 52 | pub use rxy_bevy_macro::{ElementSchema, Schema}; 53 | 54 | pub use crate::elements::prelude::*; 55 | pub use crate::renderer::common_renderer::*; 56 | #[cfg(feature = "style")] 57 | pub use crate::renderer::style::ElementViewStyleExt; 58 | pub use crate::renderer::BevyElement; 59 | pub use crate::x_res_once; 60 | 61 | pub use super::all_attrs::{CommonAttrsElementViewBuilder, CommonAttrsViewBuilder}; 62 | pub use super::renderer::event::*; 63 | pub use super::renderer::view_builder_ext::*; 64 | #[cfg(feature = "tailwind_aliases")] 65 | pub use super::renderer::{ElementViewTailwindAttrs, MemberOwnerTailwindAttrs}; 66 | #[cfg(feature = "style")] 67 | pub use super::style::prelude::StyleError; 68 | #[cfg(feature = "style")] 69 | pub use super::style::prelude::*; 70 | pub use super::{ 71 | event::*, system_once, x_res, BevyRenderer, CloneableSlot, CmdReceiver, CmdSender, 72 | FnSchemaView, Focusable, ReceiverProp, ResChangeWorldExt, RxyPlugin, RxyViewSpawner, 73 | SchemaCtx, Slot, 74 | }; 75 | pub use super::{ElementViewCompositeAttrs, MemberOwnerCompositeAttrs}; 76 | } 77 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/nest.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::{Bundle, IntoSystem, Resource}; 2 | 3 | use rxy_core::{InnerIvmToVm, MaybeSend, XNest, XNestMapper}; 4 | 5 | use crate::prelude::ElementEventIds; 6 | use crate::{x_res, EventViewMember, XBundle, XRes}; 7 | 8 | impl XNest for XBundle 9 | where 10 | T: Bundle, 11 | { 12 | type Inner = Self; 13 | type MapInner = Self; 14 | 15 | fn map_inner(self) -> Self::MapInner { 16 | self 17 | } 18 | 19 | fn is_static() -> bool { 20 | true 21 | } 22 | } 23 | 24 | impl XNestMapper for XBundle 25 | where 26 | U: 'static, 27 | T: Bundle, 28 | { 29 | type MapInnerTo = U; 30 | 31 | fn map_inner_to( 32 | self, 33 | f: impl FnOnce(Self::Inner) -> U + Send + Clone + 'static, 34 | ) -> Self::MapInnerTo { 35 | f(self) 36 | } 37 | } 38 | 39 | impl XNest for EventViewMember 40 | where 41 | T: ElementEventIds, 42 | S: IntoSystem<(), (), TM> + Send + 'static, 43 | TM: Send + 'static, 44 | { 45 | type Inner = Self; 46 | type MapInner = Self; 47 | 48 | fn map_inner(self) -> Self::MapInner { 49 | self 50 | } 51 | 52 | fn is_static() -> bool { 53 | true 54 | } 55 | } 56 | 57 | impl XNestMapper for EventViewMember 58 | where 59 | U: 'static, 60 | T: ElementEventIds, 61 | S: IntoSystem<(), (), TM> + Send + 'static, 62 | TM: Send + 'static, 63 | { 64 | type MapInnerTo = U; 65 | 66 | fn map_inner_to( 67 | self, 68 | f: impl FnOnce(Self::Inner) -> U + Send + Clone + 'static, 69 | ) -> Self::MapInnerTo { 70 | f(self) 71 | } 72 | } 73 | 74 | impl XNest for XRes 75 | where 76 | T: Resource, 77 | F: Fn(&T) -> X + Send + 'static, 78 | X: XNest + MaybeSend + 'static, 79 | { 80 | type Inner = X::Inner; 81 | type MapInner = InnerIvmToVm; 82 | 83 | fn map_inner(self) -> Self::MapInner { 84 | InnerIvmToVm::new(self) 85 | } 86 | 87 | fn is_static() -> bool { 88 | X::is_static() 89 | } 90 | } 91 | 92 | impl XNestMapper for XRes 93 | where 94 | T: Resource, 95 | F: Fn(&T) -> X + Send + 'static, 96 | U: 'static, 97 | X: XNestMapper + MaybeSend + 'static, 98 | X::MapInnerTo: Send, 99 | { 100 | type MapInnerTo = XRes X::MapInnerTo + Send>>; 101 | 102 | fn map_inner_to( 103 | self, 104 | f: impl FnOnce(Self::Inner) -> U + Send + Clone + 'static, 105 | ) -> Self::MapInnerTo { 106 | x_res(Box::new(move |r: &T| { 107 | let x = (self.f)(r); 108 | x.map_inner_to(f.clone()) 109 | })) 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/plugin.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::prelude::*; 2 | use bevy_core::Name; 3 | use bevy_ecs::prelude::{Entity, FromWorld, IntoSystemConfigs, Res, Resource, World}; 4 | use bevy_mod_picking::prelude::*; 5 | use bevy_render::prelude::Visibility; 6 | use bevy_ui::prelude::NodeBundle; 7 | use bevy_ui::widget::TextFlags; 8 | use bevy_ui::Style; 9 | 10 | use crate::elements::ElementTypeRegisterAppExt; 11 | use crate::{handle_schedule_event, CommandChannelPlugin, FocusablePlugin, ScheduleSystemAdds}; 12 | 13 | #[derive(Resource)] 14 | pub struct RxyContainerEntity { 15 | pub entity: Entity, 16 | } 17 | 18 | impl FromWorld for RxyContainerEntity { 19 | fn from_world(world: &mut World) -> Self { 20 | let world_mut = world.spawn(( 21 | NodeBundle { 22 | visibility: Visibility::Hidden, 23 | ..Default::default() 24 | }, 25 | Name::new("[Rxy Ui Slots Container]"), 26 | )); 27 | Self { 28 | entity: world_mut.id(), 29 | } 30 | } 31 | } 32 | 33 | #[derive(Default)] 34 | pub struct RxyPlugin { 35 | root_entity: Option, 36 | } 37 | 38 | #[derive(Resource)] 39 | pub struct RxyRootEntity(pub Entity); 40 | 41 | impl Plugin for RxyPlugin { 42 | fn build(&self, app: &mut App) { 43 | let root_entity = RxyRootEntity(self.root_entity.unwrap_or_else(|| { 44 | app.world_mut() 45 | .spawn(( 46 | NodeBundle { 47 | style: Style { 48 | ..Default::default() 49 | }, 50 | ..Default::default() 51 | }, 52 | Name::new("[Rxy Ui Root]"), 53 | )) 54 | .id() 55 | })); 56 | 57 | app.add_plugins((DefaultPickingPlugins, CommandChannelPlugin, FocusablePlugin)) 58 | .insert_resource(root_entity) 59 | .register_type::() 60 | .register_type::() 61 | .init_resource::() 62 | // todo: 63 | // .register_attr_values() 64 | .register_element_types() 65 | .add_systems( 66 | First, 67 | handle_schedule_event 68 | .run_if(|systems: Res| !systems.systems.is_empty()), 69 | ); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/common_renderer.rs: -------------------------------------------------------------------------------- 1 | use bevy_ui::prelude::Button; 2 | use bevy_ui::{FocusPolicy, Interaction}; 3 | 4 | use rxy_core::common_renderer::CommonRenderer; 5 | use rxy_core::{define_common_view_fns, ElementAttrMember, ElementView, MapToAttrMarker, XNest}; 6 | 7 | use crate::elements::{element_div, element_img, element_span, element_span_attrs}; 8 | #[cfg(feature = "dynamic_element")] 9 | use crate::DynamicBevyElement; 10 | use crate::{x_bundle, BevyRenderer, Focusable}; 11 | 12 | define_common_view_fns!(BevyRenderer); 13 | 14 | #[cfg(not(feature = "dynamic_element"))] 15 | use crate::BevyElement; 16 | 17 | #[cfg(not(feature = "dynamic_element"))] 18 | impl CommonRenderer for BevyRenderer { 19 | type DivView = BevyElement; 20 | type TextView> = BevyElement; 21 | type ButtonView = 22 | BevyElement,)>; 23 | type ImgView = BevyElement; 24 | type TextContentEA = element_span_attrs::content; 25 | 26 | fn crate_text( 27 | str: impl XNest> = T>, 28 | ) -> Self::TextView 29 | where 30 | T: ElementAttrMember, 31 | { 32 | BevyElement::default().members(str.map_inner::>()) 33 | } 34 | 35 | fn crate_div() -> Self::DivView { 36 | BevyElement::default() 37 | } 38 | 39 | fn crate_button() -> Self::ButtonView { 40 | BevyElement::default().members(x_bundle(( 41 | FocusPolicy::default(), 42 | Interaction::default(), 43 | Button, 44 | Focusable::default(), 45 | ))) 46 | } 47 | 48 | fn crate_img() -> Self::ImgView { 49 | BevyElement::default() 50 | } 51 | } 52 | 53 | #[cfg(feature = "dynamic_element")] 54 | impl CommonRenderer for BevyRenderer { 55 | type DivView = DynamicBevyElement; 56 | type TextView> = 57 | DynamicBevyElement; 58 | type ButtonView = DynamicBevyElement; 59 | type ImgView = DynamicBevyElement; 60 | type TextContentEA = element_span_attrs::content; 61 | 62 | fn crate_text( 63 | str: impl XNest> = T>, 64 | ) -> Self::TextView 65 | where 66 | T: ElementAttrMember, 67 | { 68 | DynamicBevyElement::default().members(str.map_inner::>()) 69 | } 70 | 71 | fn crate_div() -> Self::DivView { 72 | DynamicBevyElement::default() 73 | } 74 | 75 | fn crate_button() -> Self::ButtonView { 76 | DynamicBevyElement::default().members(x_bundle(( 77 | FocusPolicy::default(), 78 | Interaction::default(), 79 | Button, 80 | Focusable::default(), 81 | ))) 82 | } 83 | 84 | fn crate_img() -> Self::ImgView { 85 | DynamicBevyElement::default() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/composite_attrs.rs: -------------------------------------------------------------------------------- 1 | use rxy_core::{impl_composite_attrs, impl_composite_attrs_use}; 2 | use crate::BevyRenderer; 3 | 4 | impl_composite_attrs_use!(); 5 | impl_composite_attrs!(BevyRenderer;MemberOwnerCompositeAttrs;MemberOwner); 6 | impl_composite_attrs!(BevyRenderer;ElementViewCompositeAttrs;ElementView); 7 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/elements/div.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | 4 | use bevy_reflect::Reflect; 5 | use bevy_ui::prelude::NodeBundle; 6 | 7 | use rxy_core::{ElementType, ElementTypeUnTyped, RendererNodeId, RendererWorld}; 8 | 9 | use crate::{BevyRenderer, BevyWorldExt}; 10 | 11 | #[derive(Reflect, Default, Debug, Clone, Copy)] 12 | pub struct element_div; 13 | 14 | impl ElementType for element_div { 15 | const TAG_NAME: &'static str = "div"; 16 | 17 | fn get() -> &'static dyn ElementTypeUnTyped { 18 | &element_div 19 | } 20 | 21 | fn spawn( 22 | world: &mut RendererWorld, 23 | parent: Option<&RendererNodeId>, 24 | reserve_node_id: Option>, 25 | ) -> RendererNodeId { 26 | let mut entity_world_mut = world.get_or_spawn_empty(parent, reserve_node_id); 27 | entity_world_mut.insert(NodeBundle::default()); 28 | entity_world_mut.id() 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/elements/img.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | 4 | use bevy_reflect::Reflect; 5 | use bevy_ui::node_bundles::ImageBundle; 6 | 7 | use rxy_core::{ElementType, ElementTypeUnTyped, RendererNodeId, RendererWorld}; 8 | 9 | use crate::{BevyRenderer, BevyWorldExt}; 10 | 11 | #[derive(Reflect, Default, Debug, Clone, Copy)] 12 | pub struct element_img; 13 | 14 | impl ElementType for element_img { 15 | const TAG_NAME: &'static str = "img"; 16 | 17 | fn get() -> &'static dyn ElementTypeUnTyped { 18 | &element_img 19 | } 20 | 21 | fn spawn( 22 | world: &mut RendererWorld, 23 | parent: Option<&RendererNodeId>, 24 | reserve_node_id: Option>, 25 | ) -> RendererNodeId { 26 | let mut entity_world_mut = world.get_or_spawn_empty(parent, reserve_node_id); 27 | entity_world_mut.insert(ImageBundle::default()); 28 | entity_world_mut.id() 29 | } 30 | } 31 | 32 | pub mod element_img_attrs { 33 | use bevy_asset::Handle; 34 | use bevy_render::texture::Image; 35 | use bevy_ui::UiImage; 36 | 37 | use rxy_core::{ElementAttrType, RendererNodeId, RendererWorld}; 38 | 39 | use crate::BevyRenderer; 40 | 41 | #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] 42 | pub struct src; 43 | 44 | impl ElementAttrType for src { 45 | type Value = Handle; 46 | 47 | const NAME: &'static str = stringify!(src); 48 | 49 | fn update_value( 50 | world: &mut RendererWorld, 51 | node_id: RendererNodeId, 52 | value: impl Into, 53 | ) { 54 | if let Some(mut ui_image) = world.get_mut::(node_id) { 55 | ui_image.texture = value.into(); 56 | } 57 | } 58 | } 59 | 60 | #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] 61 | pub struct flip_x; 62 | 63 | impl ElementAttrType for flip_x { 64 | type Value = bool; 65 | 66 | const NAME: &'static str = stringify!(flip_x); 67 | 68 | fn update_value( 69 | world: &mut RendererWorld, 70 | node_id: RendererNodeId, 71 | value: impl Into, 72 | ) { 73 | if let Some(mut ui_image) = world.get_mut::(node_id) { 74 | ui_image.flip_x = value.into(); 75 | } 76 | } 77 | } 78 | 79 | #[derive(Copy, Clone, Debug, PartialOrd, PartialEq)] 80 | pub struct flip_y; 81 | 82 | impl ElementAttrType for flip_y { 83 | type Value = bool; 84 | 85 | const NAME: &'static str = stringify!(flip_y); 86 | 87 | fn update_value( 88 | world: &mut RendererWorld, 89 | node_id: RendererNodeId, 90 | value: impl Into, 91 | ) { 92 | if let Some(mut ui_image) = world.get_mut::(node_id) { 93 | ui_image.flip_y = value.into(); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/elements/mod.rs: -------------------------------------------------------------------------------- 1 | use bevy_app::App; 2 | 3 | pub use div::*; 4 | pub use img::*; 5 | pub use span::*; 6 | 7 | mod div; 8 | mod img; 9 | mod span; 10 | 11 | pub mod prelude { 12 | use rxy_core::AttrIndex; 13 | use rxy_core::{attrs_fn_define, impl_attrs_for_element_type, impl_index_for_tys}; 14 | 15 | use crate::all_attrs::COMMON_ATTRS; 16 | use crate::element_attrs_fn_define; 17 | use crate::BevyRenderer; 18 | 19 | use super::*; 20 | 21 | element_attrs_fn_define! { 22 | [element_div] 23 | attrs = [] 24 | 25 | [element_span] 26 | attrs = [ 27 | content 28 | ] 29 | 30 | [element_img] 31 | attrs = [ 32 | src 33 | flip_x 34 | flip_y 35 | ] 36 | } 37 | } 38 | 39 | pub trait ElementTypeRegisterAppExt { 40 | fn register_element_types(&mut self) -> &mut Self; 41 | } 42 | 43 | impl ElementTypeRegisterAppExt for App { 44 | fn register_element_types(&mut self) -> &mut Self { 45 | self 46 | .register_type::() 47 | .register_type::() 48 | .register_type::(); 49 | 50 | #[cfg(feature = "dynamic_element")] 51 | use rxy_core::ElementTypeTypeInfo; 52 | #[cfg(feature = "dynamic_element")] 53 | self 54 | .register_type_data::>() 55 | .register_type_data::>() 56 | .register_type_data::>(); 57 | self 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/style/attr_syncer.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::world::EntityWorldMut; 2 | 3 | use rxy_core::prelude::Either; 4 | use rxy_core::style::{NodeInterStyleItemId, NodeStyleAttrInfo, NodeStyleItemId}; 5 | 6 | use crate::attrs::get_attr_by_index; 7 | 8 | use super::{Result, StyleStateOwner}; 9 | 10 | pub trait EntityAttrSyncer { 11 | fn sync_attr_value_to_element(self, entity_world_mut: &mut EntityWorldMut) -> Result; 12 | } 13 | 14 | impl EntityAttrSyncer for Either 15 | where 16 | L: EntityAttrSyncer, 17 | R: EntityAttrSyncer, 18 | { 19 | fn sync_attr_value_to_element(self, entity_world_mut: &mut EntityWorldMut) -> Result { 20 | match self { 21 | Either::Left(n) => n.sync_attr_value_to_element(entity_world_mut), 22 | Either::Right(n) => n.sync_attr_value_to_element(entity_world_mut), 23 | } 24 | } 25 | } 26 | 27 | impl<'a> EntityAttrSyncer for &'a NodeStyleAttrInfo { 28 | fn sync_attr_value_to_element(self, entity_world_mut: &mut EntityWorldMut) -> Result { 29 | self 30 | .top_item_id() 31 | .sync_attr_value_to_element(entity_world_mut) 32 | } 33 | } 34 | 35 | impl EntityAttrSyncer for NodeStyleItemId { 36 | fn sync_attr_value_to_element(self, entity_world_mut: &mut EntityWorldMut) -> Result { 37 | let node_id = entity_world_mut.id(); 38 | let attr_index = entity_world_mut 39 | .world() 40 | .get_style_item_attr_id(node_id, self)?; 41 | let value = entity_world_mut 42 | .world() 43 | .get_style_item_value(node_id, self) 44 | .map(|n| n.clone().value)?; 45 | entity_world_mut.world_scope(|world| { 46 | get_attr_by_index(attr_index).set_value(world, node_id, Some(value)); 47 | }); 48 | Ok(()) 49 | } 50 | } 51 | 52 | impl EntityAttrSyncer for NodeInterStyleItemId { 53 | #[inline] 54 | fn sync_attr_value_to_element(self, entity_world_mut: &mut EntityWorldMut) -> Result { 55 | self 56 | .style_item_id 57 | .sync_attr_value_to_element(entity_world_mut) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/style/element_view_ext.rs: -------------------------------------------------------------------------------- 1 | use rxy_core::style::{ElementStyleMember, StyleSheets}; 2 | use rxy_core::{rx, ElementView, MapToStyleSheetsMarker, MaybeSend, Reactive, XNest}; 3 | 4 | use crate::BevyRenderer; 5 | 6 | pub trait ElementViewStyleExt: ElementView { 7 | #[inline] 8 | fn style( 9 | self, 10 | style_sheets: impl XNest> = VM>, 11 | ) -> Self::AddMember 12 | where 13 | VM: ElementStyleMember, 14 | SS: StyleSheets, 15 | { 16 | self.member(style_sheets.map_inner::>()) 17 | } 18 | 19 | #[inline] 20 | fn rx_style( 21 | self, 22 | f: F, 23 | ) -> Self::AddMember VM + MaybeSend + 'static, VM>> 24 | where 25 | F: Fn() -> X + MaybeSend + 'static, 26 | X: XNest> = VM>, 27 | VM: ElementStyleMember, 28 | SS: StyleSheets, 29 | { 30 | self.member(rx(move || f().map_inner::>())) 31 | } 32 | } 33 | 34 | impl ElementViewStyleExt for T where T: ElementView {} 35 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/style/mod.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | 3 | pub use attr_iter::EntityStyleAttrInfoIterArgs; 4 | pub(crate) use style_state_owner::StyleStateOwner; 5 | pub use attr_syncer::EntityAttrSyncer; 6 | pub use element_view_ext::*; 7 | pub use entity_world_ref::*; 8 | pub use interaction_style::interaction_to_style_interaction; 9 | pub use plugin::{Previous, RxyStyleSheetPlugin}; 10 | use rxy_bevy_crate::BevyRenderer; 11 | use rxy_core::style::{AppliedStyleSheet, StyleSheetCtx, StyleSheetsInfo}; 12 | pub use shared_style_sheets::SharedStyleState; 13 | pub use shared_style_view::*; 14 | 15 | pub use crate as rxy_bevy_crate; 16 | 17 | mod attr_iter; 18 | mod attr_syncer; 19 | mod element_view_ext; 20 | mod entity_world_ref; 21 | mod focus_style; 22 | mod interaction_style; 23 | mod node_style_state; 24 | mod node_tree; 25 | mod plugin; 26 | mod shared_style_sheets; 27 | mod shared_style_view; 28 | mod res_style_sheets; 29 | mod style_state_owner; 30 | 31 | pub type Result = rxy_core::style::Result; 32 | pub type StyleError = rxy_core::style::StyleError; 33 | 34 | pub mod prelude { 35 | pub use super::{ 36 | typed_shared_style_sheets, RxyStyleSheetPlugin, SchemaCtxExt, StyleError, 37 | TypedStyleLabel, 38 | }; 39 | } 40 | 41 | pub fn typed_shared_style_sheets( 42 | type_id: TypeId, 43 | ctx: StyleSheetCtx, 44 | ) -> ( 45 | impl Iterator> + Send + 'static, 46 | StyleSheetsInfo, 47 | ) { 48 | let entity = ctx.world.get_typed_entity(type_id).unwrap(); 49 | { 50 | let mut entity_world_mut = ctx.world.entity_mut(entity); 51 | let shared_style_sheets = entity_world_mut.get_shared_style_state().unwrap(); 52 | shared_style_sheets.add_subscriber(ctx.node_id); 53 | } 54 | let mut entity_world_mut = ctx.world.entity_mut(entity); 55 | 56 | let style_sheets_state = entity_world_mut.get_style_sheets_state().unwrap(); 57 | ( 58 | style_sheets_state.apply_as_shared(entity, ctx.shared_style_sheet_index), 59 | style_sheets_state.style_sheets_info(), 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/style/plugin.rs: -------------------------------------------------------------------------------- 1 | use core::any::TypeId; 2 | use core::fmt::Debug; 3 | 4 | use bevy_app::{App, Plugin, Update}; 5 | use bevy_derive::{Deref, DerefMut}; 6 | use bevy_ecs::component::Component; 7 | use bevy_ecs::entity::Entity; 8 | use bevy_ecs::prelude::{DetectChanges, IntoSystemConfigs, Res, Resource, World}; 9 | use bevy_ecs::world::FromWorld; 10 | use bevy_utils::HashMap; 11 | 12 | use super::focus_style::update_focus_style; 13 | use super::interaction_style::update_interaction_styles; 14 | use super::rxy_bevy_crate::FocusedEntity; 15 | 16 | #[derive(Resource, Default, Deref, DerefMut)] 17 | pub struct TypedEntities(HashMap); 18 | 19 | #[derive(Resource)] 20 | pub struct RxySharedStyleContainer(pub Entity); 21 | 22 | impl FromWorld for RxySharedStyleContainer { 23 | fn from_world(world: &mut World) -> Self { 24 | Self( 25 | world 26 | .spawn((bevy_core::Name::new("[Rxy Shared Style Container]"),)) 27 | .id(), 28 | ) 29 | } 30 | } 31 | 32 | #[derive(Default)] 33 | pub struct RxyStyleSheetPlugin {} 34 | 35 | impl Plugin for RxyStyleSheetPlugin { 36 | fn build(&self, app: &mut App) { 37 | app.init_resource::() 38 | .init_resource::() 39 | .init_resource::>() 40 | .add_systems( 41 | Update, 42 | ( 43 | update_interaction_styles.after(update_focus_style), 44 | update_focus_style.run_if(|res: Res| res.is_changed()), 45 | ), 46 | ); 47 | } 48 | } 49 | 50 | #[derive(Default, Component, Resource, Clone, Debug, Deref, DerefMut)] 51 | pub struct Previous(pub T); 52 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/style/res_style_sheets.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::system::Resource; 2 | use bevy_ecs::world::FromWorld; 3 | use rxy_core::Renderer; 4 | 5 | use rxy_core::style::{AppliedStyleSheet, StyleSheetCtx, StyleSheets, StyleSheetsInfo}; 6 | 7 | use crate::XRes; 8 | 9 | impl StyleSheets for XRes 10 | where 11 | R: Renderer, 12 | F: Fn(&Res) -> T + Send + 'static, 13 | Res: Resource + FromWorld, 14 | T: StyleSheets, 15 | { 16 | fn style_sheets( 17 | self, 18 | ctx: StyleSheetCtx, 19 | ) -> ( 20 | impl Iterator> + Send + 'static, 21 | StyleSheetsInfo, 22 | ) { 23 | if !ctx.world.contains_resource::() { 24 | let res = Res::from_world(ctx.world); 25 | ctx.world.insert_resource(res); 26 | } 27 | let res = ctx.world.resource::(); 28 | (self.f)(res).style_sheets(ctx) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/style/shared_style_sheets.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::entity::Entity; 2 | use bevy_utils::HashSet; 3 | 4 | #[derive(Default, Clone)] 5 | pub struct SharedStyleState { 6 | pub subscribers: HashSet, 7 | } 8 | 9 | impl SharedStyleState { 10 | pub fn add_subscriber(&mut self, entity: Entity) { 11 | self.subscribers.insert(entity); 12 | } 13 | 14 | pub fn remove_subscriber(&mut self, entity: Entity) { 15 | self.subscribers.remove(&entity); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/tailwind_attrs.rs: -------------------------------------------------------------------------------- 1 | use bevy_render::view::Visibility; 2 | use bevy_text::{BreakLineOn, JustifyText}; 3 | use bevy_ui::{AlignItems, Display, FlexDirection, FlexWrap, JustifyContent, PositionType, Val}; 4 | use rxy_core::{impl_tailwind_attrs, impl_tailwind_attrs_use, StaticElementAttr}; 5 | 6 | use crate::BevyRenderer; 7 | 8 | impl_tailwind_attrs_use!(); 9 | impl_tailwind_attrs!(BevyRenderer;MemberOwnerTailwindAttrs;MemberOwner;include_text_and_z_index); 10 | impl_tailwind_attrs!(BevyRenderer;ElementViewTailwindAttrs;ElementView;include_text_and_z_index); 11 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/text_styled_element.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::*; 2 | use bevy_reflect::prelude::*; 3 | 4 | use rxy_core::ElementAttrType; 5 | 6 | use crate::{all_attrs, BevyRenderer, ElementStyleEntityExt}; 7 | 8 | pub trait TextStyledElementEntityWorldMutExt { 9 | fn scoped_text_styled_element_type( 10 | &mut self, 11 | f: impl FnMut(&'static dyn TextStyledElementType, &mut EntityWorldMut<'_>), 12 | ); 13 | } 14 | 15 | impl TextStyledElementEntityWorldMutExt for EntityWorldMut<'_> { 16 | fn scoped_text_styled_element_type( 17 | &mut self, 18 | mut f: impl FnMut(&'static dyn TextStyledElementType, &mut EntityWorldMut<'_>), 19 | ) { 20 | let element_type = self 21 | .get_element_extra_data_mut() 22 | .map(|n| n.element_type) 23 | .unwrap(); 24 | let type_registry = self.world().resource::().clone(); 25 | let type_registry = type_registry.read(); 26 | let option = type_registry 27 | .get_type_data::(element_type.type_id()) 28 | .and_then(|n| n.get(element_type.as_reflect())); 29 | f(option.unwrap(), self) 30 | } 31 | } 32 | 33 | #[reflect_trait] 34 | pub trait TextStyledElementType { 35 | fn set_font( 36 | &self, 37 | entity_ref: &mut EntityWorldMut<'_>, 38 | value: >::Value, 39 | ); 40 | fn set_font_size( 41 | &self, 42 | entity_ref: &mut EntityWorldMut<'_>, 43 | value: >::Value, 44 | ); 45 | fn set_text_color( 46 | &self, 47 | entity_ref: &mut EntityWorldMut<'_>, 48 | value: >::Value, 49 | ); 50 | fn set_text_linebreak( 51 | &self, 52 | entity_ref: &mut EntityWorldMut<'_>, 53 | value: >::Value, 54 | ); 55 | fn set_text_align( 56 | &self, 57 | entity_ref: &mut EntityWorldMut<'_>, 58 | value: >::Value, 59 | ); 60 | } 61 | // 62 | // pub fn context_children_scope( 63 | // context: &mut SetAttrValueContext, 64 | // mut f: impl FnMut(Entity, &mut SetAttrValueContext), 65 | // ) { 66 | // let Some(children) = context.entity_mut.get_mut::() else { 67 | // return; 68 | // }; 69 | // let children: Vec = children.into_iter().copied().collect(); 70 | // for entity in children { 71 | // f(entity, context); 72 | // } 73 | // } 74 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/view_builder_ext.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::bundle::Bundle; 2 | 3 | use rxy_core::{ElementView, MemberOwner}; 4 | 5 | use crate::{BevyRenderer, XBundle}; 6 | 7 | macro_rules! impl_view_builder_ext { 8 | ($name:ident;$ty:ident) => { 9 | pub trait $name: $ty + Sized { 10 | #[inline] 11 | fn bundle(self, bundle: T) -> Self::AddMember> 12 | where 13 | Self: Sized, 14 | { 15 | self.member(XBundle(bundle)) 16 | } 17 | } 18 | 19 | impl $name for T where T: $ty + Sized {} 20 | }; 21 | } 22 | impl_view_builder_ext!(MemberOwnerViewBuilderExt;MemberOwner); 23 | impl_view_builder_ext!(ElementViewViewBuilderExt;ElementView); 24 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/renderer/view_key.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::Entity; 2 | use bevy_hierarchy::DespawnRecursiveExt; 3 | 4 | use rxy_core::{NodeTree, RendererNodeId, RendererWorld, ViewKey}; 5 | 6 | use crate::BevyRenderer; 7 | 8 | impl ViewKey for Entity { 9 | fn remove(self, world: &mut RendererWorld) { 10 | world.entity_mut(self).despawn_recursive(); 11 | } 12 | 13 | #[inline] 14 | fn insert_before( 15 | &self, 16 | world: &mut RendererWorld, 17 | parent: Option<&RendererNodeId>, 18 | before_node_id: Option<&RendererNodeId>, 19 | ) { 20 | world.insert_before(parent, before_node_id, std::slice::from_ref(self)); 21 | } 22 | #[inline] 23 | fn set_visibility(&self, world: &mut RendererWorld, hidden: bool) { 24 | world.set_visibility(hidden, self) 25 | } 26 | 27 | #[inline] 28 | fn state_node_id(&self) -> Option> { 29 | Some(*self) 30 | } 31 | 32 | #[inline] 33 | fn reserve_key( 34 | world: &mut RendererWorld, 35 | _will_rebuild: bool, 36 | parent: RendererNodeId, 37 | spawn: bool, 38 | ) -> Self { 39 | world.reserve_node_id_or_spawn(parent, spawn) 40 | } 41 | 42 | fn first_node_id( 43 | &self, 44 | _world: &RendererWorld, 45 | ) -> Option> { 46 | Some(*self) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/res.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::Resource; 2 | 3 | use rxy_core::{x_world, MaybeSend, RendererWorld, XWorld}; 4 | 5 | use crate::BevyRenderer; 6 | 7 | #[inline] 8 | pub fn x_res_once( 9 | f: F, 10 | ) -> XWorld) -> T + MaybeSend + 'static> 11 | where 12 | RES: Resource, 13 | F: FnOnce(&RES) -> T + MaybeSend + 'static, 14 | { 15 | x_world(|world: &mut RendererWorld| f(world.resource::())) 16 | } 17 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/res_change_observe.rs: -------------------------------------------------------------------------------- 1 | use core::marker::PhantomData; 2 | 3 | use bevy_app::PreUpdate; 4 | use bevy_ecs::prelude::*; 5 | 6 | use crate::add_system; 7 | 8 | #[derive(Resource)] 9 | pub struct ResChangeObserve { 10 | sender: async_broadcast::Sender<()>, 11 | receiver: async_broadcast::Receiver<()>, 12 | _marker: PhantomData, 13 | } 14 | 15 | impl Default for ResChangeObserve 16 | where 17 | T: Resource, 18 | { 19 | fn default() -> Self { 20 | let (sender, receiver) = async_broadcast::broadcast(1024); 21 | Self { 22 | sender, 23 | receiver, 24 | _marker: Default::default(), 25 | } 26 | } 27 | } 28 | 29 | impl ResChangeObserve { 30 | pub fn new() -> Self { 31 | Self::default() 32 | } 33 | } 34 | 35 | pub trait ResChangeWorldExt { 36 | fn get_res_change_receiver(&mut self) -> async_broadcast::Receiver<()> 37 | where 38 | T: Resource; 39 | } 40 | 41 | impl ResChangeWorldExt for World { 42 | fn get_res_change_receiver(&mut self) -> async_broadcast::Receiver<()> 43 | where 44 | T: Resource, 45 | { 46 | let is_add_system = self.contains_resource::>(); 47 | let res_change_observe = self.get_resource_or_insert_with(ResChangeObserve::::new); 48 | 49 | let receiver = res_change_observe.receiver.clone(); 50 | fn res_observe(res_change: Res>) { 51 | let _ = res_change.sender.try_broadcast(()); 52 | } 53 | 54 | if !is_add_system { 55 | add_system( 56 | self, 57 | PreUpdate, 58 | res_observe::.run_if(|res_change: Res>, res: Res| { 59 | res_change.receiver.receiver_count() > 0 && res.is_changed() 60 | }), 61 | ); 62 | } 63 | receiver 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/vec_data_source.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::ops::Deref; 3 | 4 | use async_channel::Receiver; 5 | use bevy_ecs::prelude::Mut; 6 | use bevy_ecs::system::Resource; 7 | 8 | use hooked_collection::{HookVec, HookedVec, VecOperation}; 9 | use rxy_core::{DataOrPlaceholderNodeId, MaybeSend, RendererWorld, VecDataSource}; 10 | 11 | use crate::BevyRenderer; 12 | 13 | pub trait GetHookedVec { 14 | type HookVec: HookVec; 15 | fn get_hooked_vec(&self) -> &HookedVec<::Item, Self::HookVec>; 16 | } 17 | 18 | impl GetHookedVec for T 19 | where 20 | T: Deref>, 21 | O: HookVec, 22 | { 23 | type HookVec = O; 24 | 25 | fn get_hooked_vec(&self) -> &HookedVec { 26 | self.deref() 27 | } 28 | } 29 | 30 | pub fn use_hooked_vec_resource_source( 31 | receiver: Receiver::Item>>, 32 | ) -> ResourceHookedVecSource 33 | where 34 | T: Resource + GetHookedVec, 35 | ::Item: MaybeSend + Clone + 'static, 36 | T::HookVec: MaybeSend + 'static, 37 | { 38 | ResourceHookedVecSource { 39 | receiver, 40 | _marker: std::marker::PhantomData, 41 | } 42 | } 43 | 44 | pub struct ResourceHookedVecSource 45 | where 46 | T: Resource + GetHookedVec, 47 | { 48 | receiver: Receiver::Item>>, 49 | _marker: std::marker::PhantomData, 50 | } 51 | 52 | impl VecDataSource for ResourceHookedVecSource 53 | where 54 | T: Resource + GetHookedVec, 55 | ::Item: MaybeSend + Clone + 'static, 56 | T::HookVec: MaybeSend + 'static, 57 | { 58 | type Item = ::Item; 59 | type InitState = (); 60 | type State = (); 61 | type Op = VecOperation; 62 | 63 | fn map_and_init_state( 64 | self, 65 | world: &mut RendererWorld, 66 | mut map_f: impl FnMut(&Self::Item, &mut RendererWorld, usize) -> U, 67 | ) -> (Vec, Option<(Self::InitState, Receiver)>) { 68 | let vec = world.resource_scope(|world, hooked_vec_getter: Mut| { 69 | hooked_vec_getter 70 | .get_hooked_vec() 71 | .iter() 72 | .enumerate() 73 | .map(|(i, item)| map_f(item, world, i)) 74 | .collect::>() 75 | }); 76 | (vec, Some(((), self.receiver))) 77 | } 78 | 79 | fn ready_state(_state: &mut Self::InitState) -> Self::State {} 80 | 81 | fn apply_ops( 82 | _state: Self::State, 83 | ops: Vec, 84 | world: &mut RendererWorld, 85 | _state_node_id: DataOrPlaceholderNodeId, 86 | mut f: impl FnMut(VecOperation>, &[Self::Item], &mut RendererWorld), 87 | ) { 88 | world.resource_scope(|world, hooked_vec_getter: Mut| { 89 | let hooked_vec = hooked_vec_getter.get_hooked_vec(); 90 | for op in ops { 91 | f( 92 | op.as_ref().map(|n| Cow::Borrowed(n)), 93 | hooked_vec.as_slice(), 94 | world, 95 | ); 96 | } 97 | }); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/view/mod.rs: -------------------------------------------------------------------------------- 1 | pub use res::*; 2 | pub use system::*; 3 | pub use system_once::*; 4 | 5 | mod res; 6 | mod system; 7 | mod system_once; 8 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/view/system_once.rs: -------------------------------------------------------------------------------- 1 | use std::marker::PhantomData; 2 | 3 | use bevy_ecs::prelude::{IntoSystem, System}; 4 | use bevy_utils::default; 5 | 6 | use rxy_bevy_macro::{bevy_force_dynamic_view, BevyIntoView}; 7 | use rxy_core::{IntoView, View, ViewCtx}; 8 | 9 | use crate::BevyRenderer; 10 | 11 | #[derive(BevyIntoView)] 12 | pub struct SystemOnce(pub S, pub PhantomData<(M, IV)>) 13 | where 14 | M: Send + 'static, 15 | IV: IntoView + Send + 'static, 16 | S: IntoSystem<(), IV, M> + Send + 'static; 17 | 18 | #[bevy_force_dynamic_view] 19 | pub fn system_once(system: S) -> SystemOnce 20 | where 21 | M: Send + 'static, 22 | IV: IntoView + Send + 'static, 23 | S: IntoSystem<(), IV, M> + Send + 'static, 24 | { 25 | SystemOnce(system, default()) 26 | } 27 | 28 | impl View for SystemOnce 29 | where 30 | M: Send + 'static, 31 | IV: IntoView + Send + 'static, 32 | S: IntoSystem<(), IV, M> + Send + 'static, 33 | { 34 | type Key = >::Key; 35 | 36 | fn build( 37 | self, 38 | ViewCtx { world, parent }: ViewCtx, 39 | reserve_key: Option, 40 | will_rebuild: bool, 41 | ) -> Self::Key { 42 | let mut system: S::System = IntoSystem::into_system(self.0); 43 | system.initialize(world); 44 | let view: IV::View = system.run((), world).into_view(); 45 | system.apply_deferred(world); 46 | view.build(ViewCtx { world, parent }, reserve_key, will_rebuild) 47 | } 48 | 49 | fn rebuild(self, ctx: ViewCtx, key: Self::Key) { 50 | let mut system = IntoSystem::into_system(self.0); 51 | system.initialize(ctx.world); 52 | let view: IV::View = system.run((), ctx.world).into_view(); 53 | system.apply_deferred(ctx.world); 54 | view.rebuild(ctx, key) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/view_member/bundle.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::bundle::Bundle; 2 | 3 | use rxy_core::{ViewMember, ViewMemberCtx, ViewMemberIndex, ViewMemberOrigin}; 4 | 5 | use crate::BevyRenderer; 6 | 7 | pub struct XBundle(pub T); 8 | 9 | pub fn x_bundle(bundle: T) -> XBundle { 10 | XBundle(bundle) 11 | } 12 | 13 | impl ViewMemberOrigin for XBundle 14 | where 15 | T: Bundle, 16 | { 17 | type Origin = Self; 18 | } 19 | 20 | impl ViewMember for XBundle 21 | where 22 | T: Bundle, 23 | { 24 | fn count() -> ViewMemberIndex { 25 | 1 26 | } 27 | 28 | fn unbuild(ctx: ViewMemberCtx, view_removed: bool) { 29 | if view_removed { 30 | return; 31 | } 32 | let entity = ctx.node_id; 33 | ctx.world.entity_mut(entity).remove::(); 34 | } 35 | 36 | fn build(self, ctx: ViewMemberCtx, _will_rebuild: bool) { 37 | let entity = ctx.node_id; 38 | ctx.world.entity_mut(entity).insert(self.0); 39 | } 40 | 41 | fn rebuild(self, ctx: ViewMemberCtx) { 42 | self.build(ctx, true); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/rxy_bevy/src/view_member/mod.rs: -------------------------------------------------------------------------------- 1 | pub use bundle::*; 2 | pub use event::*; 3 | 4 | mod bundle; 5 | mod event; 6 | -------------------------------------------------------------------------------- /crates/rxy_bevy_ecs/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rxy_bevy_ecs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rxy_core = { workspace = true, default-features = false, features = ["common_renderer", "send_sync"] } 8 | bevy_ecs = { workspace=true, default-features = false } 9 | #bevy_reflect = { path = "../../../bevy/crates/bevy_reflect", optional = true, default-features = false, features = ["glam"] } 10 | bevy_hierarchy = { workspace=true, default-features = false } 11 | 12 | #[features] 13 | #reflect = ["bevy_ecs/bevy_reflect", "rxy_core/bevy_reflect"] -------------------------------------------------------------------------------- /crates/rxy_bevy_ecs/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod renderer; 2 | pub use renderer::*; 3 | -------------------------------------------------------------------------------- /crates/rxy_bevy_ecs/src/renderer/view_key.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! code_view_key { 3 | ($renderer:ident) => { 4 | const _: () = { 5 | use bevy_ecs::prelude::{Entity, World}; 6 | use bevy_hierarchy::DespawnRecursiveExt; 7 | use rxy_core::{NodeTree, RendererNodeId, RendererWorld, ViewKey}; 8 | 9 | impl ViewKey<$renderer> for Entity 10 | { 11 | fn remove(self, world: &mut RendererWorld<$renderer>) { 12 | world.entity_mut(self).despawn_recursive(); 13 | } 14 | 15 | #[inline] 16 | fn insert_before( 17 | &self, 18 | world: &mut RendererWorld<$renderer>, 19 | parent: Option<&RendererNodeId<$renderer>>, 20 | before_node_id: Option<&RendererNodeId<$renderer>>, 21 | ) { 22 | world.insert_before( 23 | parent, 24 | before_node_id, 25 | std::slice::from_ref(self), 26 | ); 27 | } 28 | #[inline] 29 | fn set_visibility( 30 | &self, 31 | world: &mut RendererWorld<$renderer>, 32 | hidden: bool, 33 | ) { 34 | >::set_visibility(world, hidden, self) 35 | } 36 | 37 | #[inline] 38 | fn state_node_id(&self) -> Option> { 39 | Some(*self) 40 | } 41 | 42 | fn reserve_key( 43 | world: &mut RendererWorld<$renderer>, 44 | _will_rebuild: bool, 45 | parent: RendererNodeId<$renderer>, 46 | spawn: bool, 47 | ) -> Self { 48 | world.reserve_node_id_or_spawn(parent, spawn) 49 | } 50 | 51 | fn first_node_id( 52 | &self, 53 | _world: &RendererWorld<$renderer>, 54 | ) -> Option> { 55 | Some(*self) 56 | } 57 | } 58 | }; 59 | }; 60 | } 61 | -------------------------------------------------------------------------------- /crates/rxy_bevy_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "rxy_bevy_macro" 4 | version = "0.1.0" 5 | [lib] 6 | proc-macro = true 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | convert_case.workspace = true 12 | proc-macro2.workspace = true 13 | quote.workspace = true 14 | syn.workspace = true 15 | -------------------------------------------------------------------------------- /crates/rxy_bevy_macro/src/force_dynamic_view.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse_macro_input, parse_quote, ItemFn, ReturnType}; 4 | 5 | pub fn common_impl_force_dynamic_view( 6 | is_current_crate: bool, 7 | is_into_view: bool, 8 | render: impl ToTokens, 9 | _input: TokenStream, 10 | item: TokenStream, 11 | ) -> TokenStream { 12 | let mut ast = parse_macro_input!(item as ItemFn); 13 | let path = if is_current_crate { 14 | quote!(crate) 15 | } else { 16 | quote!(rxy_core) 17 | }; 18 | let block = ast.block; 19 | 20 | let ReturnType::Type(_, ty) = &ast.sig.output else { 21 | panic!("require return type") 22 | }; 23 | ast.block = if is_into_view { 24 | parse_quote! { 25 | { 26 | use #path::IntoDynamicView; 27 | let r: #ty = #block; 28 | r.into_view().into_dynamic().into_view() 29 | } 30 | } 31 | } else { 32 | parse_quote! { 33 | { 34 | use #path::IntoDynamicView; 35 | let r: #ty = #block; 36 | r.into_dynamic().into_view() 37 | } 38 | } 39 | }; 40 | 41 | ast.sig.output = parse_quote!(-> #path::BoxedDynamicViewView<#render>); 42 | 43 | ast.into_token_stream().into() 44 | } 45 | -------------------------------------------------------------------------------- /crates/rxy_bevy_macro/src/into_view.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::quote; 3 | use syn::{parse_macro_input, DeriveInput}; 4 | 5 | pub fn bevy_into_view(input: TokenStream) -> TokenStream { 6 | let ast = parse_macro_input!(input as DeriveInput); 7 | 8 | let struct_name = &ast.ident; 9 | let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); 10 | 11 | TokenStream::from(quote! { 12 | impl #impl_generics rxy_core::IntoView for #struct_name #type_generics #where_clause { 13 | type View = #struct_name #type_generics; 14 | 15 | fn into_view(self) -> Self::View{ 16 | self 17 | } 18 | } 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /crates/rxy_bevy_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(unused_variables)] 3 | 4 | use proc_macro::TokenStream; 5 | 6 | use convert_case::Casing; 7 | use quote::{quote, ToTokens}; 8 | 9 | use schema::*; 10 | 11 | use crate::force_dynamic_view::common_impl_force_dynamic_view; 12 | 13 | mod force_dynamic_view; 14 | mod into_view; 15 | mod schema; 16 | 17 | #[proc_macro_derive(BevyIntoView)] 18 | pub fn bevy_into_view(input: TokenStream) -> TokenStream { 19 | into_view::bevy_into_view(input) 20 | } 21 | 22 | #[proc_macro_derive(Schema)] 23 | pub fn bevy_struct_schema(input: TokenStream) -> TokenStream { 24 | struct_schema(input, quote!(BevyRenderer), false) 25 | } 26 | 27 | #[proc_macro_derive(ElementSchema)] 28 | pub fn bevy_struct_element_schema(input: TokenStream) -> TokenStream { 29 | struct_schema(input, quote!(BevyRenderer), true) 30 | } 31 | 32 | #[proc_macro_attribute] 33 | pub fn schema(_input: TokenStream, item: TokenStream) -> TokenStream { 34 | fn_schema(_input, item) 35 | } 36 | 37 | #[proc_macro_attribute] 38 | pub fn bevy_force_dynamic_view(input: TokenStream, item: TokenStream) -> TokenStream { 39 | common_impl_force_dynamic_view(false, false, quote!(BevyRenderer), input, item) 40 | } 41 | 42 | #[proc_macro_attribute] 43 | pub fn bevy_force_into_dynamic_view(input: TokenStream, item: TokenStream) -> TokenStream { 44 | common_impl_force_dynamic_view(false, true, quote!(BevyRenderer), input, item) 45 | } 46 | -------------------------------------------------------------------------------- /crates/rxy_bevy_ui_components/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rxy_bevy_ui_components" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rxy_ui = { path = "../..", default-features = false, features = ["bevy", "signal"] } 8 | #rxy_bevy_macro.wrokspace =true 9 | bevy_ui.workspace = true 10 | bevy_render.workspace = true 11 | bevy_ecs.workspace = true 12 | bevy_reflect.workspace = true -------------------------------------------------------------------------------- /crates/rxy_bevy_ui_components/src/checkbox.rs: -------------------------------------------------------------------------------- 1 | use bevy_render::prelude::Color; 2 | 3 | use rxy_ui::prelude::*; 4 | 5 | // use bevy::prelude::*; 6 | use crate::{x_theme_once, x_ui_setting_once}; 7 | 8 | #[derive(TypedStyle)] 9 | pub struct CheckboxStyle; 10 | 11 | #[schema] 12 | pub fn schema_checkbox( 13 | mut ctx: SchemaCtx, 14 | value: ReadSignal, 15 | readonly: ReadSignal, 16 | onchange: Sender, 17 | ) -> impl IntoElementView { 18 | let is_checked = ctx.use_controlled_state(value, onchange); 19 | ctx.default_typed_style(CheckboxStyle, || { 20 | let size = 20; 21 | ( 22 | x().center() 23 | .size(size) 24 | .border(1) 25 | .border_color(tailwind::GRAY_600), 26 | x_hover().bg_color(tailwind::GRAY_600), 27 | // FocusStyle, 28 | ) 29 | }); 30 | 31 | button() 32 | .name("checkbox") 33 | .style(CheckboxStyle) 34 | .bg_color(rx(move || { 35 | is_checked 36 | .get() 37 | .then_some(x_theme_once(|n| n.primary_color)) 38 | })) 39 | .rx_member(move || { 40 | readonly.not_then_some(x_ui_setting_once(move |n| { 41 | ().on(n.confirm_event_ids.clone(), move || { 42 | is_checked.update(|is_checked| *is_checked = !*is_checked); 43 | }) 44 | })) 45 | }) 46 | } 47 | -------------------------------------------------------------------------------- /crates/rxy_bevy_ui_components/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::Resource; 2 | use bevy_reflect::Reflect; 3 | use bevy_render::prelude::Color; 4 | use rxy_ui::bevy::{x_res_once, BevyRenderer}; 5 | use rxy_ui::prelude::ElementEventId; 6 | use rxy_ui::{x_world, MaybeReflect, MaybeSend, RendererWorld, XWorld}; 7 | use std::sync::Arc; 8 | 9 | mod checkbox; 10 | // mod select; 11 | // mod slider; 12 | 13 | #[derive(Resource, Reflect)] 14 | pub struct UiGlobalSetting { 15 | confirm_event_ids: Arc>, 16 | } 17 | 18 | #[derive(Resource, Reflect)] 19 | pub struct UiThemeSetting { 20 | primary_color: Color, 21 | } 22 | 23 | macro_rules! define_x_res_fn { 24 | ($ident:ident,$ty:ty) => { 25 | #[inline] 26 | pub fn $ident( 27 | f: F, 28 | ) -> XWorld< 29 | BevyRenderer, 30 | impl FnOnce(&mut RendererWorld) -> T + MaybeSend + 'static, 31 | > 32 | where 33 | F: FnOnce(&$ty) -> T + MaybeSend + 'static, 34 | { 35 | x_res_once(|n: &$ty| f(n)) 36 | } 37 | }; 38 | } 39 | 40 | define_x_res_fn!(x_theme_once, UiThemeSetting); 41 | define_x_res_fn!(x_ui_setting_once, UiGlobalSetting); 42 | -------------------------------------------------------------------------------- /crates/rxy_core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "rxy_core" 4 | version = "0.1.0" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | smallvec.workspace = true 10 | rxy_macro.workspace = true 11 | xy_reactive = { workspace = true, optional = true } 12 | bevy_reflect = { workspace = true, optional = true } 13 | pin-project = { workspace = true, optional = true } 14 | 15 | ahash.workspace = true 16 | hashbrown.workspace = true 17 | 18 | # bevy 19 | bevy_utils = { workspace = true, optional = true } 20 | glam = { workspace = true, optional = true } 21 | bevy_asset = { workspace = true, optional = true } 22 | bevy_text = { workspace = true, optional = true } 23 | bevy_ui = { workspace = true, optional = true } 24 | bevy_color = { workspace = true } 25 | #bevy_transform = { workspace = true, optional = true } 26 | bevy_render = { workspace = true, optional = true } 27 | 28 | # native 29 | vello = { version = "0.1.0", default-features = false, optional = true } 30 | 31 | async-channel = { workspace = true, optional = true } 32 | hooked_collection = { workspace = true, optional = true } 33 | drain_filter_polyfill.workspace = true 34 | futures-lite.workspace = true 35 | indexmap.workspace = true 36 | once_cell.workspace = true 37 | oneshot.workspace = true 38 | bitflags = { workspace = true, optional = true } 39 | # serde.workspace = true 40 | 41 | #derive_more.workspace = true 42 | count-macro.workspace = true 43 | paste.workspace = true 44 | 45 | [features] 46 | default = ["std", "dynamic_element", "async-channel", "either_future", "send_sync"] 47 | send_sync = [] 48 | std = [] 49 | web_dom = [] 50 | native = ["vello","dep:glam"] 51 | bevy = [ 52 | "std", "send_sync", "bevy_utils", 53 | "dep:glam", 54 | "dep:bevy_asset", 55 | "dep:bevy_text", 56 | "dep:bevy_ui", 57 | #bevy_transform 58 | "dep:bevy_render", 59 | ] 60 | style = ["dep:bitflags"] 61 | view_children_erasure = [] 62 | xy_reactive = ["dep:xy_reactive", "std", "send_sync"] 63 | bevy_reflect = ["dep:bevy_reflect", "std", "send_sync"] 64 | x_iter_source = ["hooked_collection", "async-channel"] 65 | either_future = ["pin-project"] 66 | common_renderer = [] 67 | attr_index_u16 = [] 68 | dynamic_element = [] 69 | 70 | [dev-dependencies] 71 | static_assertions = "1.1" -------------------------------------------------------------------------------- /crates/rxy_core/src/build_info.rs: -------------------------------------------------------------------------------- 1 | use crate::{NodeTree, Renderer, RendererNodeId, RendererWorld, ViewMember, ViewMemberCtx}; 2 | 3 | pub struct BuildInfo { 4 | // > 0 5 | pub build_times: usize, 6 | } 7 | 8 | pub fn node_build_times_increment( 9 | world: &mut RendererWorld, 10 | state_node_id: RendererNodeId, 11 | ) -> BuildStatus 12 | where 13 | R: Renderer, 14 | { 15 | if let Some(build_info) = world.get_node_state_mut::(&state_node_id) { 16 | build_info.build_times += 1; 17 | BuildStatus::AlreadyBuild 18 | } else { 19 | world.set_node_state(&state_node_id, BuildInfo { build_times: 1 }); 20 | BuildStatus::NoBuild 21 | } 22 | } 23 | 24 | pub fn node_build_status( 25 | world: &RendererWorld, 26 | state_node_id: &RendererNodeId, 27 | ) -> BuildStatus 28 | where 29 | R: Renderer, 30 | { 31 | match world 32 | .get_node_state_ref::(state_node_id) 33 | .is_some() 34 | { 35 | true => BuildStatus::AlreadyBuild, 36 | false => BuildStatus::NoBuild, 37 | } 38 | } 39 | 40 | pub enum BuildStatus { 41 | AlreadyBuild, 42 | NoBuild, 43 | } 44 | 45 | impl BuildStatus { 46 | pub fn is_no_build(&self) -> bool { 47 | match self { 48 | BuildStatus::AlreadyBuild => false, 49 | BuildStatus::NoBuild => true, 50 | } 51 | } 52 | } 53 | 54 | impl<'a, R: Renderer> ViewMemberCtx<'a, R> { 55 | pub fn build_times_increment(&mut self) -> BuildStatus { 56 | if let Some(build_info) = self.indexed_view_member_state_mut::() { 57 | build_info.build_times += 1; 58 | BuildStatus::AlreadyBuild 59 | } else { 60 | self.set_indexed_view_member_state(BuildInfo { build_times: 1 }); 61 | BuildStatus::NoBuild 62 | } 63 | } 64 | 65 | pub fn build_status(&mut self) -> BuildStatus { 66 | match self.indexed_view_member_state_mut::().is_some() { 67 | true => BuildStatus::AlreadyBuild, 68 | false => BuildStatus::NoBuild, 69 | } 70 | } 71 | } 72 | 73 | pub trait ViewMemberBuildExt 74 | where 75 | R: Renderer, 76 | { 77 | fn build_or_rebuild_by(self, ctx: ViewMemberCtx, build_status: BuildStatus); 78 | #[inline] 79 | fn build_or_rebuild(self, mut ctx: ViewMemberCtx) 80 | where 81 | Self: Sized, 82 | { 83 | let build_status = ctx.build_times_increment(); 84 | self.build_or_rebuild_by(ctx, build_status); 85 | } 86 | } 87 | 88 | impl ViewMemberBuildExt for T 89 | where 90 | R: Renderer, 91 | T: ViewMember, 92 | { 93 | #[inline] 94 | fn build_or_rebuild_by(self, ctx: ViewMemberCtx, build_status: BuildStatus) { 95 | if build_status.is_no_build() { 96 | self.build(ctx, true); 97 | } else { 98 | self.rebuild(ctx); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /crates/rxy_core/src/element/element_attr_type.rs: -------------------------------------------------------------------------------- 1 | use crate::element::attr_value::AttrValue; 2 | use crate::smallbox::{SmallBox, S1}; 3 | use crate::{ 4 | MaybeFromReflect, MaybeSend, MaybeSync, MaybeTypePath, NodeTree, Renderer, RendererNodeId, 5 | RendererWorld, 6 | }; 7 | 8 | #[cfg(feature = "attr_index_u16")] 9 | pub type AttrIndex = u8; 10 | #[cfg(not(feature = "attr_index_u16"))] 11 | pub type AttrIndex = u8; 12 | 13 | pub trait HasIndex { 14 | const INDEX: AttrIndex; 15 | } 16 | 17 | pub trait ElementAttrType: HasIndex + MaybeSend + MaybeSync + 'static 18 | where 19 | R: Renderer, 20 | { 21 | type Value: AttrValue + Clone + Sized + MaybeFromReflect + MaybeTypePath; 22 | 23 | const NAME: &'static str; 24 | 25 | fn first_set_value( 26 | world: &mut RendererWorld, 27 | node_id: RendererNodeId, 28 | value: impl Into, 29 | ) { 30 | Self::update_value(world, node_id, value) 31 | } 32 | 33 | fn update_value( 34 | world: &mut RendererWorld, 35 | node_id: RendererNodeId, 36 | value: impl Into, 37 | ); 38 | 39 | #[inline] 40 | fn set_value( 41 | world: &mut RendererWorld, 42 | node_id: RendererNodeId, 43 | value: Option>, 44 | ) { 45 | let value = value 46 | .map(|n| n.into()) 47 | .unwrap_or_else(|| Self::Value::default_value()); 48 | if world.prepare_set_attr_and_get_is_init(&node_id, Self::INDEX) { 49 | Self::update_value(world, node_id, value); 50 | } else { 51 | Self::first_set_value(world, node_id, value); 52 | } 53 | } 54 | 55 | #[inline] 56 | fn set_dyn_value( 57 | world: &mut RendererWorld, 58 | node_id: RendererNodeId, 59 | value: Option>, 60 | ) { 61 | Self::set_value( 62 | world, 63 | node_id, 64 | value.map(|n| n.downcast::().unwrap().into_inner()), 65 | ); 66 | } 67 | 68 | fn set_default_value(world: &mut RendererWorld, node_id: RendererNodeId) { 69 | Self::update_value(world, node_id, Self::Value::default_value()) 70 | } 71 | } 72 | 73 | pub trait ElementAttrUntyped: MaybeSend + MaybeSync + 'static 74 | where 75 | R: Renderer, 76 | { 77 | fn attr_name(&self) -> &'static str; 78 | 79 | fn index(&self) -> u8; 80 | 81 | fn default_value(&self) -> SmallBox; 82 | 83 | fn set_value( 84 | &self, 85 | world: &mut RendererWorld, 86 | node_id: RendererNodeId, 87 | value: Option>, 88 | ); 89 | } 90 | 91 | impl ElementAttrUntyped for T 92 | where 93 | R: Renderer, 94 | T: ElementAttrType, 95 | { 96 | #[inline] 97 | fn attr_name(&self) -> &'static str { 98 | T::NAME 99 | } 100 | 101 | #[inline] 102 | fn index(&self) -> u8 { 103 | T::INDEX 104 | } 105 | 106 | fn default_value(&self) -> SmallBox { 107 | crate::smallbox!(T::Value::default_value()) 108 | } 109 | 110 | #[inline] 111 | fn set_value( 112 | &self, 113 | world: &mut RendererWorld, 114 | node_id: RendererNodeId, 115 | value: Option>, 116 | ) { 117 | T::set_dyn_value(world, node_id, value); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /crates/rxy_core/src/element/element_type.rs: -------------------------------------------------------------------------------- 1 | use crate::element::ElementAttrUntyped; 2 | use crate::{MaybeReflect, MaybeSend, MaybeSync, Renderer, RendererNodeId, RendererWorld}; 3 | 4 | pub trait ElementTypeUnTyped: MaybeReflect + MaybeSend + MaybeSync 5 | where 6 | R: Renderer, 7 | { 8 | fn tag_name(&self) -> &'static str; 9 | 10 | fn spawn( 11 | &self, 12 | world: &mut RendererWorld, 13 | parent: Option<&RendererNodeId>, 14 | reserve_node_id: Option>, 15 | ) -> RendererNodeId; 16 | } 17 | 18 | impl> ElementTypeUnTyped for T 19 | where 20 | R: Renderer, 21 | { 22 | #[inline] 23 | fn tag_name(&self) -> &'static str { 24 | T::TAG_NAME 25 | } 26 | 27 | #[inline] 28 | fn spawn( 29 | &self, 30 | world: &mut RendererWorld, 31 | parent: Option<&RendererNodeId>, 32 | reserve_node_id: Option>, 33 | ) -> RendererNodeId { 34 | T::spawn(world, parent, reserve_node_id) 35 | } 36 | } 37 | 38 | pub trait ElementType: MaybeReflect + MaybeSend + MaybeSync + 'static 39 | where 40 | R: Renderer, 41 | { 42 | const TAG_NAME: &'static str; 43 | 44 | fn get() -> &'static dyn ElementTypeUnTyped; 45 | 46 | fn spawn( 47 | world: &mut RendererWorld, 48 | parent: Option<&RendererNodeId>, 49 | reserve_node_id: Option>, 50 | ) -> RendererNodeId; 51 | } 52 | 53 | pub trait ElementTypeAttrs: ElementType 54 | where 55 | R: Renderer, 56 | { 57 | const ATTRS: &'static [&'static dyn ElementAttrUntyped]; 58 | } 59 | -------------------------------------------------------------------------------- /crates/rxy_core/src/element/mod.rs: -------------------------------------------------------------------------------- 1 | pub use attr_value::*; 2 | #[cfg(feature = "dynamic_element")] 3 | pub use dynamic_element::*; 4 | pub use element::*; 5 | pub use element_attr_type::*; 6 | pub use element_children::*; 7 | pub use element_type::*; 8 | pub use view_member::*; 9 | 10 | mod attr_value; 11 | pub mod attrs; 12 | #[cfg(feature = "dynamic_element")] 13 | mod dynamic_element; 14 | mod element_attr_type; 15 | mod element_children; 16 | mod element_type; 17 | mod view_member; 18 | 19 | mod element; 20 | -------------------------------------------------------------------------------- /crates/rxy_core/src/element_view.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | IntoView, MaybeSend, MemberOwner, Renderer, RendererNodeId, SoloView, View, ViewMember, 3 | ViewMemberIndex, 4 | }; 5 | 6 | /* 7 | pub trait ElementSoloView: ElementView + SoloView 8 | where 9 | R: Renderer, 10 | { 11 | } 12 | 13 | impl ElementSoloView for A 14 | where 15 | R: Renderer, 16 | A: ElementView + SoloView, 17 | { 18 | } 19 | impl ElementView for A 20 | where 21 | R: Renderer, 22 | A: MemberOwner + SoloView, 23 | { 24 | #[inline] 25 | fn element_node_id(key: &Self::Key) -> &RendererNodeId { 26 | A::node_id(key) 27 | } 28 | }*/ 29 | 30 | pub trait ElementView: SoloView + View 31 | where 32 | R: Renderer, 33 | { 34 | fn element_node_id(key: &Self::Key) -> &RendererNodeId { 35 | Self::node_id(key) 36 | } 37 | 38 | type E: MaybeSend + 'static; 39 | type AddMember>: ElementView; 40 | type SetMembers + MemberOwner>: ElementView; 41 | fn member_count(&self) -> ViewMemberIndex; 42 | fn member(self, member: VM) -> Self::AddMember 43 | where 44 | VM: ViewMember; 45 | fn members>(self, members: VM) -> Self::SetMembers<(VM,)> 46 | where 47 | VM: ViewMember; 48 | } 49 | 50 | pub trait IntoElementView: 'static 51 | where 52 | R: Renderer, 53 | { 54 | type View: ElementView; 55 | fn into_element_view(self) -> Self::View; 56 | } 57 | 58 | impl IntoElementView for T 59 | where 60 | R: Renderer, 61 | T: IntoView, 62 | T::View: ElementView, 63 | { 64 | type View = T::View; 65 | 66 | #[inline] 67 | fn into_element_view(self) -> Self::View { 68 | self.into_view() 69 | } 70 | } 71 | /* 72 | 73 | impl ElementView for ElementViewExtraMembers 74 | where 75 | R: Renderer, 76 | EV: ElementView, 77 | VM: ViewMember, 78 | { 79 | fn element_node_id(key: &Self::Key) -> &RendererNodeId { 80 | EV::element_node_id(key) 81 | } 82 | } 83 | */ 84 | -------------------------------------------------------------------------------- /crates/rxy_core/src/impl/build_configure.rs: -------------------------------------------------------------------------------- 1 | use crate::{IntoView, Renderer, View, ViewCtx}; 2 | 3 | pub fn build_configure(t: T, config: BuildConfig) -> BuildConfigure { 4 | BuildConfigure(t, config) 5 | } 6 | 7 | #[derive(Debug, Clone, PartialOrd, PartialEq, Copy)] 8 | pub enum WillRebuildOverride { 9 | Nop, 10 | Value(bool), 11 | } 12 | 13 | #[derive(Debug, Clone)] 14 | pub struct BuildConfig { 15 | pub override_will_rebuild: WillRebuildOverride, 16 | } 17 | 18 | #[derive(Debug, Clone)] 19 | pub struct BuildConfigure(T, BuildConfig); 20 | 21 | impl> View for BuildConfigure { 22 | type Key = V::Key; 23 | 24 | fn build( 25 | self, 26 | ctx: ViewCtx, 27 | reserve_key: Option, 28 | will_rebuild: bool, 29 | ) -> Self::Key { 30 | self.0.build( 31 | ctx, 32 | reserve_key, 33 | match self.1.override_will_rebuild { 34 | WillRebuildOverride::Nop => will_rebuild, 35 | WillRebuildOverride::Value(v) => v, 36 | }, 37 | ) 38 | } 39 | 40 | fn rebuild(self, ctx: ViewCtx, key: Self::Key) { 41 | self.0.rebuild(ctx, key) 42 | } 43 | } 44 | 45 | impl IntoView for BuildConfigure 46 | where 47 | R: Renderer, 48 | IV: IntoView, 49 | { 50 | type View = IV::View; 51 | 52 | fn into_view(self) -> Self::View { 53 | self.0.into_view() 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /crates/rxy_core/src/impl/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | 3 | pub use build_configure::*; 4 | pub use builder::*; 5 | pub use context::*; 6 | pub use dynamic::*; 7 | pub use either::*; 8 | pub use erasure::*; 9 | pub use future::*; 10 | pub use option::*; 11 | #[cfg(all(feature = "xy_reactive", feature = "send_sync"))] 12 | pub use reactive::*; 13 | pub use rebuild_fn_receiver::*; 14 | pub use stream::*; 15 | pub use to_mutable::*; 16 | pub use virtual_container::*; 17 | pub use x_if::*; 18 | // pub use r#static::*; 19 | pub use x_iter::*; 20 | // pub use reflect::*; 21 | #[cfg(feature = "x_iter_source")] 22 | pub use x_iter_source::*; 23 | pub use x_world::*; 24 | 25 | // pub use stream_with_default_value::*; 26 | 27 | mod build_configure; 28 | mod builder; 29 | mod either; 30 | mod option; 31 | mod rebuild_fn_receiver; 32 | mod stream; 33 | mod to_mutable; 34 | mod virtual_container; 35 | mod x_if; 36 | mod x_iter; 37 | // mod r#static; 38 | mod context; 39 | mod dynamic; 40 | mod erasure; 41 | mod future; 42 | mod reflect; 43 | mod result; 44 | #[cfg(feature = "x_iter_source")] 45 | mod x_iter_source; 46 | // mod stream_with_default_value; 47 | 48 | #[cfg(all(feature = "xy_reactive", feature = "send_sync"))] 49 | mod reactive; 50 | mod recyclable; 51 | mod x_world; 52 | // mod x_if2; 53 | -------------------------------------------------------------------------------- /crates/rxy_core/src/impl/result.rs: -------------------------------------------------------------------------------- 1 | use crate::{Either, IntoView, Renderer}; 2 | 3 | impl IntoView for Result 4 | where 5 | R: Renderer, 6 | T: IntoView, 7 | E: IntoView, 8 | { 9 | type View = as IntoView>::View; 10 | 11 | fn into_view(self) -> Self::View { 12 | let either: Either = self.into(); 13 | IntoView::into_view(either) 14 | } 15 | } 16 | 17 | // todo: 18 | // impl XNest for Result 19 | // where 20 | // R: Renderer, 21 | // LVM: ViewMember, 22 | // RVM: ViewMember, 23 | // LVMO: XNest, 24 | // RVMO: XNest, 25 | // { 26 | // type MapInner = Either; 27 | // fn into_member(self) -> Either { 28 | // match self { 29 | // Ok(n) => Either::Left(n.into_member()), 30 | // Err(n) => Either::Right(n.into_member()), 31 | // } 32 | // } 33 | // } 34 | 35 | /* 36 | 37 | impl ViewMember for Result 38 | where 39 | R: Renderer, 40 | T: ViewMember, 41 | E: ViewMember, 42 | { 43 | type Origin = (); 44 | 45 | fn count() -> ViewMemberIndex { 46 | T::count() + E::count() 47 | } 48 | 49 | fn unbuild(ctx: ViewMemberCtx, view_removed: bool) { 50 | T::unbuild( 51 | ViewMemberCtx { 52 | index: ctx.index, 53 | world: &mut *ctx.world, 54 | node_id: ctx.node_id.clone(), 55 | }, 56 | view_removed, 57 | ); 58 | E::unbuild( 59 | ViewMemberCtx { 60 | index: ctx.index + T::count(), 61 | world: ctx.world, 62 | node_id: ctx.node_id, 63 | }, 64 | view_removed, 65 | ); 66 | } 67 | 68 | fn build(self, ctx: ViewMemberCtx, will_rebuild: bool) { 69 | let either: Either = self.into(); 70 | either.build(ctx, will_rebuild); 71 | } 72 | 73 | fn rebuild(self, ctx: ViewMemberCtx) { 74 | let either: Either = self.into(); 75 | either.rebuild(ctx); 76 | } 77 | } 78 | */ 79 | -------------------------------------------------------------------------------- /crates/rxy_core/src/impl/static.rs: -------------------------------------------------------------------------------- 1 | /*use crate::{IntoView, Renderer, View, ViewCtx}; 2 | 3 | pub fn static_fn(f: fn() -> T) -> Static { 4 | Static(f) 5 | } 6 | 7 | pub struct Static(fn() -> T); 8 | 9 | impl View for Static 10 | where 11 | R: Renderer, 12 | IV: IntoView, 13 | { 14 | type Key = >::Key; 15 | fn build( 16 | self, 17 | ctx: ViewCtx, 18 | reserve_key: Option, 19 | _will_rebuild: bool, 20 | ) -> Self::Key { 21 | self.0().into_view().build(ctx, reserve_key, false) 22 | } 23 | fn rebuild(self, _ctx: ViewCtx, _key: Self::Key) {} 24 | } 25 | 26 | impl IntoView for Static 27 | where 28 | R: Renderer, 29 | IV: IntoView, 30 | { 31 | type View = Self; 32 | 33 | fn into_view(self) -> Self::View { 34 | self 35 | } 36 | } 37 | */ 38 | -------------------------------------------------------------------------------- /crates/rxy_core/src/impl/stream_with_default_value.rs: -------------------------------------------------------------------------------- 1 | /* use crate::{mutable_view_rebuild, IntoView, MutableView, Renderer, RendererNodeId, View, ViewCtx, ViewReBuilder, stream}; 2 | use use crate::utils::SyncCell;::futures::now_or_never; 3 | use futures_lite::{Stream, StreamExt}; 4 | 5 | pub struct StreamWithDefaultValue 6 | where 7 | S: Stream + MaybeSend + 'static, 8 | { 9 | stream: S, 10 | default_value: S::Item, 11 | } 12 | 13 | // impl IntoView for StreamWithDefaultValue 14 | // where 15 | // R: Renderer, 16 | // S: Stream + MaybeSend + 'static, 17 | // S::Item: IntoView, 18 | // { 19 | // type View = futures_lite::stream::Boxed<>::View>; 20 | // 21 | // fn into_view(self) -> Self::View { 22 | // self.0.map(|n| n.into_view()).boxed() 23 | // } 24 | // } 25 | 26 | pub fn stream_with_default_value( 27 | stream: S, 28 | default_value: S::Item, 29 | ) -> StreamWithDefaultValue 30 | where 31 | R: Renderer, 32 | S: Stream + MaybeSend + 'static, 33 | S::Item: IntoView, 34 | { 35 | StreamWithDefaultValue { 36 | stream, 37 | default_value, 38 | } 39 | } 40 | 41 | impl View for StreamWithDefaultValue 42 | where 43 | R: Renderer, 44 | S: Stream + MaybeSend + 'static, 45 | S::Item: IntoView, 46 | { 47 | type Key = (); 48 | 49 | fn build( 50 | self, 51 | ctx: ViewCtx, 52 | reserve_key: Option, 53 | will_rebuild: bool, 54 | ) -> Self::Key { 55 | StreamWithDefaultValue { 56 | stream, 57 | default_value, 58 | } = self; 59 | if let Some(item) = now_or_never(stream.next()) { 60 | let Some(v) = item else { 61 | return; 62 | }; 63 | mutable_view_rebuild( 64 | v, 65 | ViewCtx { 66 | world: &mut *ctx.world, 67 | parent: ctx.parent.clone(), 68 | }, 69 | state_node_id.clone(), 70 | ); 71 | } 72 | 73 | let mut re_builder = R::get_view_re_builder(ctx); 74 | 75 | R::spawn_and_detach({ 76 | async move { 77 | while let Some(v) = stream.next().await { 78 | re_builder.mutable_rebuild(v, &state_node_id); 79 | } 80 | } 81 | }); 82 | } 83 | 84 | fn rebuild(self, ctx: ViewCtx, key: Self::Key) { 85 | } 86 | } 87 | */ -------------------------------------------------------------------------------- /crates/rxy_core/src/impl/to_mutable.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | MutableView, MutableViewKey, Renderer, RendererNodeId, RendererWorld, View, ViewCtx, ViewKey, 3 | }; 4 | 5 | pub fn to_mutable(t: T) -> ToMutableWrapper { 6 | ToMutableWrapper(t) 7 | } 8 | 9 | #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] 10 | #[derive(Hash, Clone, Debug, PartialEq)] 11 | pub struct ToMutableWrapper(pub T); 12 | 13 | impl, R: Renderer> MutableViewKey for ToMutableWrapper { 14 | fn remove(self, world: &mut RendererWorld) { 15 | self.0.remove(world) 16 | } 17 | 18 | fn insert_before( 19 | &self, 20 | world: &mut RendererWorld, 21 | parent: Option<&RendererNodeId>, 22 | before_node_id: Option<&RendererNodeId>, 23 | ) { 24 | self.0.insert_before(world, parent, before_node_id) 25 | } 26 | 27 | fn set_visibility(&self, world: &mut RendererWorld, hidden: bool) { 28 | self.0.set_visibility(world, hidden) 29 | } 30 | 31 | fn first_node_id(&self, world: &RendererWorld) -> Option> { 32 | self.0.first_node_id(world) 33 | } 34 | 35 | fn state_node_id(&self) -> Option> { 36 | self.0.state_node_id() 37 | } 38 | } 39 | 40 | impl MutableView for ToMutableWrapper 41 | where 42 | R: Renderer, 43 | V: View, 44 | { 45 | type Key = ToMutableWrapper; 46 | 47 | fn no_placeholder_when_no_rebuild() -> bool { 48 | true 49 | } 50 | 51 | fn build(self, ctx: ViewCtx, placeholder_node_id: Option>) -> Self::Key { 52 | ToMutableWrapper(self.0.build(ctx, None, placeholder_node_id.is_some())) 53 | } 54 | 55 | fn rebuild( 56 | self, 57 | ctx: ViewCtx, 58 | key: Self::Key, 59 | _placeholder_node_id: RendererNodeId, 60 | ) -> Option { 61 | self.0.rebuild(ctx, key.0); 62 | None 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /crates/rxy_core/src/into_view.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::all_tuples; 2 | 3 | use rxy_macro::impl_into_view; 4 | 5 | use crate::{Renderer, View}; 6 | 7 | pub trait IntoView: 'static 8 | where 9 | R: Renderer, 10 | { 11 | type View: View; 12 | fn into_view(self) -> Self::View; 13 | } 14 | 15 | macro_rules! impl_into_view_for_tuples { 16 | ($first:ident) => { 17 | impl_into_view_for_tuples!($first,); 18 | }; 19 | ($first:ident,$($ty:ident),*$(,)?) => { 20 | impl $crate::IntoView for ($first,$($ty,)*) 21 | where 22 | R: $crate::Renderer, 23 | $first: $crate::IntoView, 24 | $($ty: $crate::IntoView),* 25 | { 26 | type View = ($first::View, $($ty::View,)*); 27 | 28 | fn into_view(self) -> Self::View { 29 | paste::paste! { 30 | let ([<$first:lower>], $([<$ty:lower>],)*) = self; 31 | ( 32 | [<$first:lower>].into_view(), 33 | $([<$ty:lower>].into_view(),)* 34 | ) 35 | } 36 | } 37 | } 38 | } 39 | } 40 | 41 | impl_into_view!(()); 42 | 43 | all_tuples!(impl_into_view_for_tuples, 1, 12, T); 44 | 45 | pub struct ToIntoView(pub T); 46 | 47 | impl IntoView for ToIntoView 48 | where 49 | R: Renderer, 50 | T: View, 51 | { 52 | type View = T; 53 | 54 | #[inline] 55 | fn into_view(self) -> Self::View { 56 | self.0 57 | } 58 | } 59 | 60 | #[inline] 61 | pub fn into_view>(view: V) -> ToIntoView { 62 | ToIntoView(view) 63 | } 64 | 65 | pub trait IntoCloneableView: 'static 66 | where 67 | R: Renderer, 68 | { 69 | type View: View + Clone; 70 | fn into_cloneable_view(self) -> Self::View; 71 | } 72 | 73 | impl IntoCloneableView for T 74 | where 75 | R: Renderer, 76 | T: IntoView, 77 | T::View: Clone, 78 | { 79 | type View = T::View; 80 | 81 | #[inline] 82 | fn into_cloneable_view(self) -> Self::View { 83 | self.into_view() 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/rxy_core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::type_complexity)] 2 | #![no_std] 3 | 4 | #[cfg(feature = "std")] 5 | extern crate std; 6 | 7 | extern crate alloc; 8 | // pub use clone_to::*; 9 | pub use count_macro; 10 | pub use paste::paste; 11 | 12 | pub use build_info::ViewMemberBuildExt; 13 | pub use either::*; 14 | pub use element::*; 15 | pub use element_view::*; 16 | pub use into_view::*; 17 | pub use maybe_traits::*; 18 | pub use member_owner::*; 19 | pub use mutable_view::*; 20 | pub use nest::*; 21 | pub use r#impl::*; 22 | pub use r#static::*; 23 | pub use rebuild::*; 24 | pub use renderer::*; 25 | pub use schema::*; 26 | pub use smallbox::*; 27 | pub use view::*; 28 | pub use view_member::*; 29 | 30 | mod either; 31 | mod r#impl; 32 | mod into_view; 33 | mod mutable_view; 34 | mod nest; 35 | mod rebuild; 36 | mod renderer; 37 | mod smallbox; 38 | mod view; 39 | mod view_member; 40 | 41 | // pub use nest::*; 42 | 43 | #[cfg(test)] 44 | pub mod test; 45 | 46 | pub mod prelude { 47 | #[cfg(feature = "async-channel")] 48 | pub use async_channel::Sender; 49 | 50 | #[cfg(feature = "style")] 51 | pub use crate::style::prelude::*; 52 | #[cfg(feature = "hooked_collection")] 53 | pub use crate::ListOperator; 54 | pub use crate::OnBuildExt; 55 | pub use crate::{ 56 | build_configure, fn_schema_view, into_view, member_builder, provide_context, style_builder, 57 | view_builder, x_future, x_if, x_if_else, x_iter, x_iter_keyed, x_stream, 58 | BoxedCloneableDynamicView, BoxedDynamicView, BoxedErasureView, Context, 59 | DeferredNodeTreeScoped, DynamicView, Either, EitherExt, ElementView, ErasureView, 60 | IntoDynamicView, IntoElementView, IntoView, IntoViewErasureExt, Keyed, MemberOwner, Renderer, 61 | Required, SchemaIntoViewFn, SoloView, Static, View, ViewCtx, ViewKey, ViewMember, 62 | ViewMemberCtx, 63 | }; 64 | #[cfg(feature = "xy_reactive")] 65 | pub use crate::{rx, ElementViewRxExt, MemberOwnerRxExt}; 66 | #[cfg(feature = "x_iter_source")] 67 | pub use crate::{use_list, x_iter_source}; 68 | pub use crate::{ElementAttrType, ElementAttrUntyped, ElementType, ElementTypeUnTyped}; 69 | pub use crate::{SchemaElementView, SchemaView}; 70 | 71 | pub use super::member_after_children::MemberAfterChildrenExt; 72 | } 73 | 74 | mod element_view; 75 | mod maybe_traits; 76 | mod schema; 77 | // mod styled; 78 | pub mod build_info; 79 | 80 | #[cfg(feature = "common_renderer")] 81 | pub mod common_renderer; 82 | pub mod diff; 83 | mod element; 84 | pub mod member_after_children; 85 | mod member_owner; 86 | pub mod remove_on_drop; 87 | mod renderers; 88 | mod r#static; 89 | #[cfg(feature = "style")] 90 | pub mod style; 91 | pub mod utils; 92 | -------------------------------------------------------------------------------- /crates/rxy_core/src/member_owner.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::all_tuples; 2 | use crate::{MaybeSend, Renderer, ViewMember}; 3 | 4 | #[macro_export] 5 | macro_rules! define_member_owner { 6 | ($ty:ident) => { 7 | pub trait $ty 8 | where 9 | R: Renderer, 10 | { 11 | type E: MaybeSend + 'static; 12 | type VM: ViewMember; 13 | type AddMember>: $ty; 14 | type SetMembers + MemberOwner>: $ty; 15 | fn member(self, member: VM) -> Self::AddMember 16 | where 17 | (Self::VM, VM): ViewMember, 18 | VM: ViewMember; 19 | fn members>(self, members: VM) -> Self::SetMembers<(VM,)> 20 | where 21 | VM: ViewMember; 22 | } 23 | }; 24 | } 25 | 26 | define_member_owner!(MemberOwner); 27 | // define_member_owner!(ElementMemberOwner); 28 | 29 | macro_rules! impl_member_owner_for_tuple { 30 | () => { 31 | #[allow(non_snake_case)] 32 | impl MemberOwner for () 33 | where 34 | R: Renderer, 35 | { 36 | type E = (); 37 | type VM = Self; 38 | type AddMember> = (T,); 39 | type SetMembers + MemberOwner> = T; 40 | 41 | fn member( 42 | self, 43 | member: T, 44 | ) -> Self::AddMember 45 | where 46 | (Self::VM, T): ViewMember, 47 | T: ViewMember, 48 | { 49 | (member,) 50 | } 51 | 52 | fn members( 53 | self, 54 | members: T, 55 | ) -> Self::SetMembers<(T,)> 56 | where 57 | T: ViewMember, 58 | { 59 | (members,) 60 | } 61 | } 62 | }; 63 | ($($t:ident),*) => { 64 | #[allow(non_snake_case)] 65 | impl MemberOwner for ($($t,)*) 66 | where 67 | R: Renderer, 68 | $($t: ViewMember),* 69 | { 70 | type E = (); 71 | type VM = Self; 72 | type AddMember> = (Self, T); 73 | type SetMembers + MemberOwner> = T; 74 | 75 | fn member(self, member: T) -> Self::AddMember 76 | where 77 | (Self::VM, T): ViewMember, 78 | T: ViewMember, 79 | { 80 | // let ($($t,)*) = self; 81 | // ($($t,)* member,) 82 | (self,member) 83 | } 84 | 85 | fn members(self, members: T) -> Self::SetMembers<(T,)> 86 | where 87 | T: ViewMember 88 | { 89 | (members,) 90 | } 91 | } 92 | }; 93 | } 94 | 95 | all_tuples!(impl_member_owner_for_tuple, 0, 4, M); 96 | -------------------------------------------------------------------------------- /crates/rxy_core/src/rebuild.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | use crate::utils::SyncCell; 4 | 5 | use crate::{rebuild_fn, MaybeSend, RebuildFn, RebuildFnReceiver, Renderer}; 6 | 7 | #[cfg_attr( 8 | feature = "bevy_reflect", 9 | derive(bevy_reflect::Reflect), 10 | reflect(type_path = false) 11 | )] 12 | pub struct ReBuildFn( 13 | #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] 14 | #[cfg(feature = "send_sync")] 15 | Option::NodeTree, T) + MaybeSend + 'static>>, 16 | #[cfg(not(feature = "send_sync"))] 17 | Option::NodeTree, T) + 'static>>, 18 | ) 19 | where 20 | R: Renderer; 21 | 22 | #[cfg(feature = "bevy_reflect")] 23 | impl bevy_reflect::TypePath for ReBuildFn { 24 | fn type_path() -> &'static str { 25 | "rxy_core::ReBuildFn" 26 | } 27 | 28 | fn short_type_path() -> &'static str { 29 | "ReBuildFn" 30 | } 31 | } 32 | 33 | impl ReBuildFn 34 | where 35 | R: Renderer, 36 | { 37 | pub fn new(f: impl FnMut(&mut ::NodeTree, T) + MaybeSend + 'static) -> Self { 38 | Self(Some(Box::new(f))) 39 | } 40 | 41 | pub fn call(&mut self, world: &mut ::NodeTree, vp: T) { 42 | self.0.as_mut().unwrap()(world, vp); 43 | } 44 | } 45 | 46 | pub fn rebuild_fn_channel() -> (ReBuildFn, oneshot::Sender>) 47 | where 48 | R: Renderer, 49 | T: 'static, 50 | { 51 | let (sender, receiver) = oneshot::channel(); 52 | let mut receiver = SyncCell::new(receiver); 53 | let mut cell = core::cell::OnceCell::new(); 54 | 55 | // let mut once_lock = core::sync::OnceLock::new(); 56 | let build_fn = ReBuildFn::new(move |world, n| { 57 | cell.get_or_init(|| receiver.get().try_recv().unwrap()); 58 | let rebuild_fn: &mut RebuildFn = cell.get_mut().unwrap(); 59 | (*rebuild_fn)(n, world); 60 | }); 61 | (build_fn, sender) 62 | } 63 | 64 | pub type TargetRebuildFnChannel = (ReBuildFn, RebuildFnReceiver); 65 | 66 | pub fn target_rebuild_fn_channel(target: Option) -> TargetRebuildFnChannel 67 | where 68 | R: Renderer, 69 | T: 'static, 70 | { 71 | let (rebuild_fn1, sender1) = rebuild_fn_channel::(); 72 | let receiver1 = rebuild_fn( 73 | target, 74 | Box::new(move |f| { 75 | let _ = sender1.send(f); 76 | }), 77 | ); 78 | (rebuild_fn1, receiver1) 79 | } 80 | -------------------------------------------------------------------------------- /crates/rxy_core/src/remove_on_drop.rs: -------------------------------------------------------------------------------- 1 | use crate::renderer::DeferredNodeTreeScoped; 2 | use crate::utils::OnDrop; 3 | use crate::utils::SyncCell; 4 | use crate::{NodeTree, Renderer, RendererWorld, ViewKey}; 5 | use alloc::boxed::Box; 6 | 7 | #[allow(dead_code)] 8 | #[cfg(feature = "send_sync")] 9 | pub struct ViewRemoveOnDrop(SyncCell>>); 10 | 11 | #[allow(dead_code)] 12 | #[cfg(not(feature = "send_sync"))] 13 | pub struct ViewRemoveOnDrop(SyncCell>>); 14 | 15 | pub trait RemoveOnDropWorldExt 16 | where 17 | R: Renderer, 18 | { 19 | fn remove_on_drop(&mut self, view_key: K) -> ViewRemoveOnDrop 20 | where 21 | K: ViewKey; 22 | } 23 | 24 | impl RemoveOnDropWorldExt for RendererWorld 25 | where 26 | R: Renderer, 27 | { 28 | fn remove_on_drop(&mut self, view_key: K) -> ViewRemoveOnDrop 29 | where 30 | K: ViewKey, 31 | { 32 | let deferred_world = self.world_scoped(); 33 | ViewRemoveOnDrop(SyncCell::new(OnDrop::new(Box::new(move || { 34 | deferred_world.scoped(|world| view_key.remove(world)) 35 | })))) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/rxy_core/src/renderers/mod.rs: -------------------------------------------------------------------------------- 1 | // orphan rule 2 | 3 | #[cfg(feature = "bevy")] 4 | mod bevy; 5 | 6 | #[cfg(feature = "native")] 7 | mod native; 8 | -------------------------------------------------------------------------------- /crates/rxy_core/src/renderers/native.rs: -------------------------------------------------------------------------------- 1 | use vello::kurbo::{Point, Rect, Shape};use crate::{ 2 | impl_attr_value, impl_x_value_wrappers, smallbox, AttrValue, 3 | SmallBox, XValueWrapper, S1, 4 | }; 5 | 6 | use crate::{Either, EitherExt, impl_attr_value_and_wrapper}; 7 | 8 | impl Shape for Either 9 | where 10 | T1: Shape, 11 | T2: Shape, 12 | { 13 | type PathElementsIter<'iter> 14 | where 15 | Self: 'iter, 16 | = Either, T2::PathElementsIter<'iter>>; 17 | 18 | fn path_elements(&self, tolerance: f64) -> Self::PathElementsIter<'_> { 19 | match self { 20 | Either::Left(n) => n.path_elements(tolerance).either_left(), 21 | Either::Right(n) => n.path_elements(tolerance).either_right(), 22 | } 23 | } 24 | 25 | fn area(&self) -> f64 { 26 | match self { 27 | Either::Left(n) => n.area(), 28 | Either::Right(n) => n.area(), 29 | } 30 | } 31 | 32 | fn perimeter(&self, accuracy: f64) -> f64 { 33 | match self { 34 | Either::Left(n) => n.perimeter(accuracy), 35 | Either::Right(n) => n.perimeter(accuracy), 36 | } 37 | } 38 | 39 | fn winding(&self, pt: Point) -> i32 { 40 | match self { 41 | Either::Left(n) => n.winding(pt), 42 | Either::Right(n) => n.winding(pt), 43 | } 44 | } 45 | 46 | fn bounding_box(&self) -> Rect { 47 | match self { 48 | Either::Left(n) => n.bounding_box(), 49 | Either::Right(n) => n.bounding_box(), 50 | } 51 | } 52 | } 53 | 54 | use vello::peniko::Color; 55 | 56 | impl_attr_value_and_wrapper! { 57 | Color => Color::rgba8(0, 0, 0, 0), 58 | // bevy_transform::prelude::Transform, 59 | glam::Affine2, 60 | glam::Vec2 61 | } 62 | 63 | 64 | 65 | impl Into> for f32 { 66 | fn into(self) -> XValueWrapper { 67 | XValueWrapper(glam::Vec2::new(self, self)) 68 | } 69 | } 70 | 71 | 72 | 73 | impl Into> for f32 { 74 | fn into(self) -> XValueWrapper { 75 | XValueWrapper(self as _) 76 | } 77 | } 78 | 79 | // impl Into> for i32 { 80 | // fn into(self) -> XValueWrapper { 81 | // XValueWrapper(self as _) 82 | // } 83 | // } -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{Context, InnerSchemaCtx, MaybeSend, MaybeSync, Renderer, SchemaParam, ViewCtx}; 2 | 3 | impl SchemaParam for Context 4 | where 5 | R: Renderer, 6 | T: MaybeSend + MaybeSync + Clone + 'static, 7 | { 8 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 9 | Context( 10 | ViewCtx:: { 11 | world: &mut *ctx.world, 12 | parent: ctx.parent.clone(), 13 | } 14 | .context::(), 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/controlled_state.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Debug; 2 | 3 | use async_channel::Sender; 4 | 5 | use xy_reactive::prelude::{ 6 | create_effect, use_rw_signal, ReadSignal, RwSignal, SignalGet, SignalGetUntracked, SignalSet, 7 | }; 8 | 9 | use crate::{InnerSchemaCtx, MaybeSend, MaybeSync, RenderSchemaCtx, Renderer}; 10 | 11 | impl RenderSchemaCtx 12 | where 13 | R: Renderer, 14 | { 15 | pub fn use_controlled_state( 16 | &mut self, 17 | value: ReadSignal, 18 | onchange: Sender, 19 | ) -> RwSignal 20 | where 21 | T: Debug + MaybeSend + MaybeSync + PartialEq + Clone + 'static, 22 | { 23 | self.mut_scoped(|ctx| ctx.use_controlled_state(value, onchange)) 24 | } 25 | } 26 | 27 | impl<'a, R, U> InnerSchemaCtx<'a, R, U> 28 | where 29 | R: Renderer, 30 | { 31 | pub fn use_controlled_state( 32 | &mut self, 33 | value: ReadSignal, 34 | onchange: Sender, 35 | ) -> RwSignal 36 | where 37 | T: Debug + MaybeSend + MaybeSync + PartialEq + Clone + 'static, 38 | { 39 | let signal = use_rw_signal(value.get_untracked()); 40 | let (read_signal, write_signal) = signal.split(); 41 | let event_effect = create_effect(move |_| { 42 | let value = read_signal.get(); 43 | onchange.send_blocking(value).unwrap(); 44 | }); 45 | let control_effect = create_effect(move |_| { 46 | let value = value.get(); 47 | if value != read_signal.get_untracked() { 48 | write_signal.set(value); 49 | } 50 | }); 51 | 52 | self 53 | .effect_state() 54 | .extend([event_effect.erase(), control_effect.erase()]); 55 | 56 | signal 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/fn.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::all_tuples; 2 | use crate::MaybeSend; 3 | 4 | pub trait SchemaFn

: MaybeSend + 'static { 5 | type View; 6 | fn call(self, param: P) -> Self::View; 7 | } 8 | 9 | impl SchemaFn<()> for F 10 | where 11 | F: FnOnce() -> V + MaybeSend + 'static, 12 | { 13 | type View = V; 14 | fn call(self, _p: ()) -> Self::View { 15 | self() 16 | } 17 | } 18 | 19 | macro_rules! impl_schema_fn { 20 | ($($P:ident),*) => { 21 | #[allow(non_snake_case)] 22 | impl SchemaFn<($($P,)*)> for F 23 | where 24 | F: FnOnce($($P),*) -> V + MaybeSend + 'static, 25 | { 26 | type View = V; 27 | fn call(self, ($($P,)*): ($($P,)*)) -> Self::View { 28 | self($($P,)*) 29 | } 30 | } 31 | }; 32 | () => {}; 33 | } 34 | 35 | all_tuples!(impl_schema_fn, 1, 16, P); 36 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/prop_state.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use core::any::Any; 3 | 4 | use crate::{BoxedPropValue, MaybeSend, ReBuildFn, Renderer}; 5 | 6 | // option 7 | pub trait PropState: MaybeSend 8 | where 9 | R: Renderer, 10 | { 11 | fn apply(&mut self, new_value: BoxedPropValue, world: &mut R::NodeTree); 12 | #[cfg(feature = "send_sync")] 13 | fn as_any_mut(&mut self) -> &mut (dyn Any + MaybeSend); 14 | #[cfg(not(feature = "send_sync"))] 15 | fn as_any_mut(&mut self) -> &mut (dyn Any); 16 | } 17 | 18 | #[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))] 19 | pub struct ReceiverPropState 20 | where 21 | R: Renderer, 22 | T: Clone + PartialEq + MaybeSend + 'static, 23 | { 24 | pub re_build_fns: Vec>, 25 | pub value: Option, 26 | // #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] 27 | // eq_fn: Option bool>, 28 | // #[cfg_attr(feature = "bevy_reflect", reflect(ignore))] 29 | // pub clone_fn: Option BoxedPropValue>, 30 | } 31 | 32 | impl ReceiverPropState 33 | where 34 | R: Renderer, 35 | T: Clone + PartialEq + MaybeSend + 'static, 36 | { 37 | pub fn new() -> Self { 38 | Self { 39 | value: None, 40 | re_build_fns: Default::default(), 41 | } 42 | } 43 | } 44 | 45 | impl Default for ReceiverPropState 46 | where 47 | R: Renderer, 48 | T: Clone + PartialEq + MaybeSend + 'static, 49 | { 50 | fn default() -> Self { 51 | Self::new() 52 | } 53 | } 54 | 55 | impl PropState for ReceiverPropState 56 | where 57 | R: Renderer, 58 | T: Clone + PartialEq + MaybeSend + 'static, 59 | { 60 | fn apply(&mut self, new_value: BoxedPropValue, world: &mut R::NodeTree) { 61 | let Ok(new_value) = new_value.downcast::().map(|n| *n) else { 62 | return; 63 | }; 64 | if self.value.as_ref().is_some_and(|n| &new_value == n) { 65 | return; 66 | } 67 | for f in self.re_build_fns.iter_mut() { 68 | f.call(world, new_value.clone()); 69 | } 70 | self.value = Some(new_value); 71 | } 72 | 73 | #[cfg(feature = "send_sync")] 74 | fn as_any_mut(&mut self) -> &mut (dyn Any + MaybeSend) { 75 | self 76 | } 77 | 78 | #[cfg(not(feature = "send_sync"))] 79 | fn as_any_mut(&mut self) -> &mut (dyn Any) { 80 | self 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/required_param.rs: -------------------------------------------------------------------------------- 1 | use core::any::TypeId; 2 | use core::ops::{Deref, DerefMut}; 3 | 4 | use crate::{ 5 | CloneableSchemaSlot, ConstIndex, InnerSchemaCtx, MaybeReflect, MaybeSend, RebuildFnReceiver, 6 | Renderer, SchemaParam, SchemaSlot, Static, 7 | }; 8 | 9 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 10 | pub struct Required(pub T); 11 | 12 | impl Required { 13 | pub fn into_inner(self) -> T { 14 | self.0 15 | } 16 | } 17 | 18 | impl From for Required { 19 | fn from(value: T) -> Self { 20 | Required(value) 21 | } 22 | } 23 | 24 | impl Deref for Required { 25 | type Target = T; 26 | 27 | fn deref(&self) -> &Self::Target { 28 | &self.0 29 | } 30 | } 31 | 32 | impl DerefMut for Required { 33 | fn deref_mut(&mut self) -> &mut Self::Target { 34 | &mut self.0 35 | } 36 | } 37 | 38 | impl SchemaParam for Required> 39 | where 40 | R: Renderer, 41 | T: MaybeSend + 'static, 42 | { 43 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 44 | let prop_type_id = TypeId::of::>(); 45 | let value: T = ctx.get_init_value::(prop_type_id).unwrap(); 46 | Required(Static(value)) 47 | } 48 | } 49 | 50 | impl SchemaParam for Required> 51 | where 52 | R: Renderer, 53 | T: MaybeReflect + Clone + PartialEq + MaybeSend + 'static, 54 | { 55 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 56 | Required( as SchemaParam>::from::(ctx)) 57 | } 58 | } 59 | 60 | impl SchemaParam for Required> 61 | where 62 | R: Renderer, 63 | { 64 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 65 | Required( as SchemaParam>::from::(ctx)) 66 | } 67 | } 68 | 69 | impl SchemaParam for Required> 70 | where 71 | R: Renderer, 72 | { 73 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 74 | Required( as SchemaParam>::from::(ctx)) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/schema_with_element_view_bound.rs: -------------------------------------------------------------------------------- 1 | use crate::{ElementView, InnerSchemaCtx, MaybeSend, Renderer, Schema}; 2 | 3 | #[derive(Clone, Debug, PartialEq, Eq)] 4 | pub struct ElementSchemaBoundWrapper(pub T); 5 | 6 | pub trait SchemaWithElementViewBound: MaybeSend + 'static { 7 | type View: ElementView; 8 | fn view(self, ctx: InnerSchemaCtx) -> Self::View; 9 | } 10 | 11 | impl Schema for ElementSchemaBoundWrapper 12 | where 13 | R: Renderer, 14 | T: SchemaWithElementViewBound, 15 | { 16 | type View = T::View; 17 | 18 | #[inline] 19 | fn view(self, ctx: InnerSchemaCtx) -> Self::View { 20 | self.0.view(ctx.cast()) 21 | } 22 | } 23 | 24 | impl SchemaWithElementViewBound for T 25 | where 26 | R: Renderer, 27 | T: Schema, 28 | T::View: ElementView, 29 | { 30 | type View = T::View; 31 | 32 | #[inline] 33 | fn view(self, ctx: InnerSchemaCtx) -> Self::View { 34 | self.view(ctx) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/slot.rs: -------------------------------------------------------------------------------- 1 | use core::any::TypeId; 2 | use core::fmt; 3 | 4 | use rxy_macro::IntoView; 5 | 6 | use crate::{BoxedCloneableErasureView, BoxedErasureView, ErasureViewKey, Renderer, View, ViewCtx}; 7 | use crate::{ConstIndex, InnerSchemaCtx, SchemaParam}; 8 | 9 | #[derive(IntoView)] 10 | pub struct SchemaSlot 11 | where 12 | R: Renderer, 13 | { 14 | view: Option>, 15 | } 16 | 17 | impl fmt::Debug for SchemaSlot 18 | where 19 | R: Renderer, 20 | { 21 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 22 | f.debug_struct("SchemaSlot").finish() 23 | } 24 | } 25 | 26 | impl SchemaSlot 27 | where 28 | R: Renderer, 29 | { 30 | pub fn new(view: Option>) -> Self { 31 | Self { view } 32 | } 33 | } 34 | 35 | impl View for SchemaSlot 36 | where 37 | R: Renderer, 38 | { 39 | type Key = Option>; 40 | 41 | fn build( 42 | self, 43 | ctx: ViewCtx, 44 | reserve_key: Option, 45 | _will_rebuild: bool, 46 | ) -> Self::Key { 47 | let view = self.view?; 48 | 49 | let key = view.build( 50 | ctx, 51 | reserve_key.map(|key| key.expect("reserve_key must not be None")), 52 | false, 53 | ); 54 | 55 | Some(key) 56 | } 57 | 58 | fn rebuild(self, _ctx: ViewCtx, _key: Self::Key) {} 59 | } 60 | 61 | #[derive(IntoView, Clone)] 62 | pub struct CloneableSchemaSlot 63 | where 64 | R: Renderer, 65 | { 66 | view: Option>, 67 | } 68 | 69 | impl fmt::Debug for CloneableSchemaSlot 70 | where 71 | R: Renderer, 72 | { 73 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 74 | f.debug_struct("CloneableSchemaSlot").finish() 75 | } 76 | } 77 | 78 | impl CloneableSchemaSlot 79 | where 80 | R: Renderer, 81 | { 82 | pub fn new(view: Option>) -> Self { 83 | Self { view } 84 | } 85 | } 86 | 87 | impl View for CloneableSchemaSlot 88 | where 89 | R: Renderer, 90 | { 91 | type Key = Option>; 92 | 93 | fn build( 94 | self, 95 | ctx: ViewCtx, 96 | reserve_key: Option, 97 | _will_rebuild: bool, 98 | ) -> Self::Key { 99 | let view = self.view?; 100 | 101 | let key = view.build( 102 | ctx, 103 | reserve_key.map(|key| key.expect("reserve_key must not be None")), 104 | false, 105 | ); 106 | 107 | Some(key) 108 | } 109 | 110 | fn rebuild(self, _ctx: ViewCtx, _key: Self::Key) {} 111 | } 112 | 113 | impl SchemaParam for SchemaSlot 114 | where 115 | R: Renderer, 116 | { 117 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 118 | let prop_type_id = TypeId::of::>(); 119 | SchemaSlot::new(ctx.slots.remove(&prop_type_id)) 120 | } 121 | } 122 | 123 | impl SchemaParam for CloneableSchemaSlot 124 | where 125 | R: Renderer, 126 | { 127 | fn from(ctx: &mut InnerSchemaCtx) -> Self { 128 | let prop_type_id = TypeId::of::>(); 129 | CloneableSchemaSlot::new(ctx.cloneable_slots.remove(&prop_type_id)) 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /crates/rxy_core/src/schema/wrapper.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Debug, Formatter}; 2 | use core::marker::PhantomData; 3 | 4 | use crate::{FnSchema, IntoView, MaybeSend, Renderer, RendererSchemaView, SchemaFn, SchemaParams}; 5 | 6 | pub struct IntoViewSchemaFnWrapper(pub T, PhantomData); 7 | 8 | impl Debug for IntoViewSchemaFnWrapper 9 | where 10 | T: Debug, 11 | { 12 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 13 | f.debug_tuple("IntoViewSchemaFnWrapper") 14 | .field(&self.0) 15 | .finish() 16 | } 17 | } 18 | 19 | impl Clone for IntoViewSchemaFnWrapper 20 | where 21 | T: Clone, 22 | { 23 | fn clone(&self) -> Self { 24 | IntoViewSchemaFnWrapper(self.0.clone(), Default::default()) 25 | } 26 | } 27 | 28 | impl PartialEq for IntoViewSchemaFnWrapper 29 | where 30 | T: PartialEq, 31 | { 32 | fn eq(&self, other: &Self) -> bool { 33 | self.0 == other.0 34 | } 35 | } 36 | 37 | impl Eq for IntoViewSchemaFnWrapper where T: Eq {} 38 | 39 | impl IntoViewSchemaFnWrapper { 40 | pub fn new(t: T) -> Self { 41 | IntoViewSchemaFnWrapper::(t, Default::default()) 42 | } 43 | } 44 | 45 | pub trait SchemaIntoViewFn: MaybeSend + 'static 46 | where 47 | R: Renderer, 48 | { 49 | type View: IntoView; 50 | fn call(self, param: P) -> Self::View; 51 | } 52 | 53 | impl SchemaFn

for IntoViewSchemaFnWrapper 54 | where 55 | R: Renderer, 56 | P: MaybeSend + 'static, 57 | T: SchemaIntoViewFn, 58 | { 59 | type View = T::View; 60 | 61 | fn call(self, param: P) -> Self::View { 62 | self.0.call(param) 63 | } 64 | } 65 | 66 | impl SchemaIntoViewFn for T 67 | where 68 | R: Renderer, 69 | T: SchemaFn

, 70 | T::View: IntoView, 71 | { 72 | type View = T::View; 73 | 74 | fn call(self, param: P) -> Self::View { 75 | self.call(param) 76 | } 77 | } 78 | 79 | pub type FnSchemaView = 80 | RendererSchemaView, P>, (), ()>; 81 | 82 | pub fn fn_schema_view(f: F) -> FnSchemaView 83 | where 84 | R: Renderer, 85 | F: SchemaIntoViewFn, 86 | P: SchemaParams, 87 | { 88 | RendererSchemaView::new(FnSchema::new(IntoViewSchemaFnWrapper::::new(f))) 89 | } 90 | -------------------------------------------------------------------------------- /crates/rxy_core/src/smallbox/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(clippy::all)] 3 | 4 | pub use small_box::*; 5 | pub use space::*; 6 | 7 | mod small_box; 8 | mod space; 9 | -------------------------------------------------------------------------------- /crates/rxy_core/src/smallbox/space.rs: -------------------------------------------------------------------------------- 1 | //! Space types that is used to define capacity 2 | 3 | /// Represent 1 * usize space 4 | pub struct S1 { 5 | _inner: [usize; 1], 6 | } 7 | 8 | /// Represent 2 * usize space 9 | pub struct S2 { 10 | _inner: [usize; 2], 11 | } 12 | 13 | /// Represent 4 * usize space 14 | pub struct S4 { 15 | _inner: [usize; 4], 16 | } 17 | 18 | /// Represent 8 * usize space 19 | pub struct S8 { 20 | _inner: [usize; 8], 21 | } 22 | 23 | /// Represent 16 * usize space 24 | pub struct S16 { 25 | _inner: [usize; 16], 26 | } 27 | 28 | /// Represent 32 * usize space 29 | pub struct S32 { 30 | _inner: [usize; 32], 31 | } 32 | 33 | /// Represent 64 * usize space 34 | pub struct S64 { 35 | _inner: [usize; 64], 36 | } 37 | -------------------------------------------------------------------------------- /crates/rxy_core/src/static.rs: -------------------------------------------------------------------------------- 1 | use core::ops::{Deref, DerefMut}; 2 | 3 | #[derive(Clone, Debug, Hash, PartialEq, Eq)] 4 | pub struct Static(pub T); 5 | 6 | impl Static { 7 | pub fn into_inner(self) -> T { 8 | self.0 9 | } 10 | } 11 | 12 | impl From for Static { 13 | fn from(value: T) -> Self { 14 | Static(value) 15 | } 16 | } 17 | 18 | impl Deref for Static { 19 | type Target = T; 20 | 21 | fn deref(&self) -> &Self::Target { 22 | &self.0 23 | } 24 | } 25 | 26 | impl DerefMut for Static { 27 | fn deref_mut(&mut self) -> &mut Self::Target { 28 | &mut self.0 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /crates/rxy_core/src/style/style_sheet_definition.rs: -------------------------------------------------------------------------------- 1 | use bitflags::bitflags; 2 | 3 | use crate::style::attr_style_owner::AttrStyleOwner; 4 | use crate::style::view_member::{StyleSheetIndex, StyleSheetLocation}; 5 | use crate::style::StyleItemValue; 6 | use crate::style::{NodeAttrStyleItemId, NodeStyleItemId, NodeStyleSheetId}; 7 | use crate::{EitherExt, Renderer, RendererNodeId, RendererWorld}; 8 | 9 | use super::Result; 10 | use alloc::vec::Vec; 11 | bitflags! { 12 | #[repr(transparent)] 13 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] 14 | pub struct StyleInteraction: u8 { 15 | const Focus = 0b00000001; 16 | const Hover = 0b00000010; 17 | const Active = 0b00000110; 18 | } 19 | } 20 | 21 | impl StyleInteraction { 22 | pub fn is_match(self, interaction: StyleInteraction, strict: bool) -> bool { 23 | if strict { 24 | self == interaction 25 | } else { 26 | self.contains(interaction) 27 | } 28 | } 29 | 30 | pub fn priority_iter() -> impl Iterator { 31 | [Self::Active, Self::Hover, Self::Focus].into_iter() 32 | } 33 | 34 | pub fn match_iter(self, strict: bool) -> impl Iterator { 35 | if strict { 36 | Some(self).into_iter().either_left() 37 | } else { 38 | Self::priority_iter() 39 | .filter(move |n| self.contains(*n)) 40 | .either_right() 41 | } 42 | } 43 | } 44 | 45 | #[derive(Clone, Default, Debug)] 46 | pub struct StyleSheetDefinition { 47 | pub interaction: Option, 48 | pub items: Vec, 49 | } 50 | 51 | impl StyleSheetDefinition { 52 | pub fn iter_attr_style_item_ids( 53 | &self, 54 | style_sheet_location: StyleSheetLocation, 55 | style_sheet_index: StyleSheetIndex, 56 | ) -> impl Iterator + '_ { 57 | self 58 | .items 59 | .iter() 60 | .enumerate() 61 | .map(move |(item_index, item)| NodeAttrStyleItemId { 62 | attr_id: item.attr_id, 63 | item_id: NodeStyleItemId { 64 | item_index: item_index as _, 65 | 66 | sheet_id: NodeStyleSheetId { 67 | index: style_sheet_index, 68 | location: style_sheet_location, 69 | }, 70 | }, 71 | }) 72 | } 73 | pub fn add_to( 74 | &self, 75 | attr_style_owner: &mut T, 76 | style_sheet_location: StyleSheetLocation, 77 | style_sheet_index: StyleSheetIndex, 78 | world: &RendererWorld, 79 | node_id: RendererNodeId, 80 | ) -> Result 81 | where 82 | R: Renderer, 83 | T: AttrStyleOwner, 84 | { 85 | attr_style_owner.add_attr_style_items( 86 | self 87 | .iter_attr_style_item_ids(style_sheet_location, style_sheet_index) 88 | .map(|n| T::from_definition_to_item_id(self, n).unwrap()), 89 | world, 90 | node_id, 91 | )?; 92 | Ok(()) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/rxy_core/src/style/style_sheet_items.rs: -------------------------------------------------------------------------------- 1 | use std::iter::once; 2 | 3 | use crate::style::{StyleItemValue, StyleSheetCtx}; 4 | use crate::utils::all_tuples; 5 | use crate::{smallbox, ElementAttr, StaticElementAttr, ElementAttrType, Renderer}; 6 | 7 | pub trait StyleSheetItems: Send + 'static 8 | where 9 | R: Renderer, 10 | { 11 | fn iter(self, ctx: StyleSheetCtx) -> impl Iterator + 'static; 12 | } 13 | 14 | impl StyleSheetItems for ElementAttr 15 | where 16 | R: Renderer, 17 | EA: ElementAttrType, 18 | { 19 | #[inline] 20 | fn iter(self, _ctx: StyleSheetCtx) -> impl Iterator + 'static { 21 | once(StyleItemValue { 22 | attr_id: EA::INDEX, 23 | value: smallbox!(self.0), 24 | }) 25 | } 26 | } 27 | impl StyleSheetItems for StaticElementAttr 28 | where 29 | R: Renderer, 30 | EA: ElementAttrType, 31 | { 32 | #[inline] 33 | fn iter(self, _ctx: StyleSheetCtx) -> impl Iterator + 'static { 34 | once(StyleItemValue { 35 | attr_id: EA::INDEX, 36 | value: smallbox!(self.0), 37 | }) 38 | } 39 | } 40 | 41 | macro_rules! impl_style_sheet_items_for_tuple { 42 | ($($t:ident),*) => { 43 | #[allow(non_snake_case)] 44 | impl StyleSheetItems for ($($t,)*) 45 | where 46 | R: Renderer, 47 | $($t: StyleSheetItems),* 48 | { 49 | #[inline] 50 | fn iter( 51 | self, 52 | _ctx: StyleSheetCtx, 53 | ) -> impl Iterator + 'static { 54 | let ($($t,)*) = self; 55 | core::iter::empty() 56 | $( 57 | .chain($t.iter(StyleSheetCtx{ 58 | inline_style_sheet_index: _ctx.inline_style_sheet_index, 59 | shared_style_sheet_index: _ctx.shared_style_sheet_index, 60 | world: unsafe {&mut *(_ctx.world as *mut _)}, 61 | node_id: _ctx.node_id.clone(), 62 | })) 63 | )* 64 | } 65 | } 66 | }; 67 | } 68 | all_tuples!(impl_style_sheet_items_for_tuple, 0, 4, T); 69 | -------------------------------------------------------------------------------- /crates/rxy_core/src/style/view_member.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::blocks_in_conditions)] 2 | 3 | use crate::style::{ApplyStyleSheets, StyleSheets, StyledNodeTree}; 4 | use crate::{Renderer, ViewMember, ViewMemberCtx, ViewMemberIndex, ViewMemberOrigin}; 5 | 6 | pub type StyleItemIndex = u8; 7 | pub type StyleSheetIndex = u8; 8 | 9 | #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, PartialOrd, Ord)] 10 | pub enum StyleSheetLocation { 11 | Shared, 12 | Inline, 13 | } 14 | 15 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)] 16 | pub struct ApplyStyleSheetsMemberState { 17 | pub inline_sheet_index: StyleSheetIndex, 18 | pub inline_sheet_count: StyleSheetIndex, 19 | pub shared_sheet_index: StyleSheetIndex, 20 | pub shared_sheet_count: StyleSheetIndex, 21 | } 22 | 23 | impl ApplyStyleSheetsMemberState { 24 | pub fn get_and_increment_and_by_location( 25 | &mut self, 26 | location: StyleSheetLocation, 27 | ) -> StyleSheetIndex { 28 | match location { 29 | StyleSheetLocation::Inline => { 30 | let r = self.inline_sheet_index; 31 | self.inline_sheet_index += 1; 32 | r 33 | } 34 | StyleSheetLocation::Shared => { 35 | let r = self.shared_sheet_index; 36 | self.shared_sheet_index += 1; 37 | r 38 | } 39 | } 40 | } 41 | } 42 | 43 | impl ViewMemberOrigin for ApplyStyleSheets 44 | where 45 | R: Renderer, 46 | T: StyleSheets, 47 | { 48 | type Origin = Self; 49 | } 50 | 51 | impl ViewMember for ApplyStyleSheets 52 | where 53 | R: Renderer, 54 | R::NodeTree: StyledNodeTree, 55 | T: StyleSheets, 56 | { 57 | fn count() -> ViewMemberIndex { 58 | 1 59 | } 60 | 61 | fn unbuild(mut ctx: ViewMemberCtx, view_removed: bool) { 62 | if view_removed { 63 | return; 64 | } 65 | let Some(member_state) = ctx 66 | .indexed_view_member_state_mut::() 67 | .cloned() 68 | else { 69 | return; 70 | }; 71 | 72 | ctx.world 73 | .unbuild_style_sheet(ctx.node_id.clone(), member_state) 74 | .unwrap(); 75 | } 76 | 77 | fn build(self, mut ctx: ViewMemberCtx, _will_rebuild: bool) { 78 | let member_state = ctx 79 | .indexed_view_member_state_mut::() 80 | .cloned(); 81 | let is_first = member_state.is_none(); 82 | let new_member_state = ctx 83 | .world 84 | .build_style_sheets(ctx.node_id.clone(), self.0, member_state) 85 | .unwrap(); 86 | if is_first { 87 | ctx.set_indexed_view_member_state(new_member_state); 88 | } 89 | } 90 | 91 | fn rebuild(self, mut ctx: ViewMemberCtx) { 92 | let member_state = ctx 93 | .indexed_view_member_state_mut::() 94 | .cloned() 95 | .unwrap(); 96 | 97 | ctx.world 98 | .rebuild_style_sheet(ctx.node_id, self.0, member_state) 99 | .unwrap(); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /crates/rxy_core/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use core::any::TypeId; 2 | use core::hash::BuildHasherDefault; 3 | 4 | #[cfg(feature = "bevy")] 5 | pub use bevy_utils::all_tuples; 6 | #[cfg(feature = "bevy")] 7 | pub use bevy_utils::futures::now_or_never; 8 | #[cfg(feature = "bevy")] 9 | pub use bevy_utils::synccell::SyncCell; 10 | #[cfg(feature = "bevy")] 11 | pub use bevy_utils::OnDrop; 12 | 13 | #[cfg(not(feature = "bevy"))] 14 | pub use apis::now_or_never; 15 | #[cfg(not(feature = "bevy"))] 16 | pub use on_drop::OnDrop; 17 | #[cfg(not(feature = "bevy"))] 18 | pub use rxy_macro::{all_tuples, all_tuples_with_size}; 19 | #[cfg(not(feature = "bevy"))] 20 | pub use synccell::SyncCell; 21 | 22 | pub type HashMap = hashbrown::HashMap>; 23 | pub type AHasher = ahash::AHasher; 24 | 25 | mod on_drop; 26 | #[cfg(not(feature = "bevy"))] 27 | mod synccell; 28 | 29 | #[cfg(not(feature = "bevy"))] 30 | mod apis { 31 | use core::future::Future; 32 | use core::pin::Pin; 33 | use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; 34 | 35 | pub fn now_or_never(mut future: F) -> Option { 36 | let noop_waker = noop_waker(); 37 | let mut cx = Context::from_waker(&noop_waker); 38 | 39 | // SAFETY: `future` is not moved and the original value is shadowed 40 | let future = unsafe { Pin::new_unchecked(&mut future) }; 41 | 42 | match future.poll(&mut cx) { 43 | Poll::Ready(x) => Some(x), 44 | _ => None, 45 | } 46 | } 47 | 48 | unsafe fn noop_clone(_data: *const ()) -> RawWaker { 49 | noop_raw_waker() 50 | } 51 | 52 | unsafe fn noop(_data: *const ()) {} 53 | 54 | const NOOP_WAKER_VTABLE: RawWakerVTable = RawWakerVTable::new(noop_clone, noop, noop, noop); 55 | 56 | fn noop_raw_waker() -> RawWaker { 57 | RawWaker::new(core::ptr::null(), &NOOP_WAKER_VTABLE) 58 | } 59 | 60 | fn noop_waker() -> Waker { 61 | // SAFETY: the `RawWakerVTable` is just a big noop and doesn't violate any of the rules in `RawWakerVTable`s documentation 62 | // (which talks about retaining and releasing any "resources", of which there are none in this case) 63 | unsafe { Waker::from_raw(noop_raw_waker()) } 64 | } 65 | } 66 | 67 | pub type TypeIdMap = hashbrown::HashMap>; 68 | 69 | #[doc(hidden)] 70 | #[derive(Default)] 71 | pub struct NoOpTypeIdHasher(u64); 72 | 73 | // TypeId already contains a high-quality hash, so skip re-hashing that hash. 74 | impl core::hash::Hasher for NoOpTypeIdHasher { 75 | fn finish(&self) -> u64 { 76 | self.0 77 | } 78 | 79 | fn write(&mut self, bytes: &[u8]) { 80 | // This will never be called: TypeId always just calls write_u64 once! 81 | // This is a known trick and unlikely to change, but isn't officially guaranteed. 82 | // Don't break applications (slower fallback, just check in test): 83 | self.0 = bytes.iter().fold(self.0, |hash, b| { 84 | hash.rotate_left(8).wrapping_add(*b as u64) 85 | }); 86 | } 87 | 88 | fn write_u64(&mut self, i: u64) { 89 | self.0 = i; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /crates/rxy_core/src/utils/on_drop.rs: -------------------------------------------------------------------------------- 1 | use core::mem::ManuallyDrop; 2 | 3 | pub struct OnDrop { 4 | callback: ManuallyDrop, 5 | } 6 | 7 | impl OnDrop { 8 | /// Returns an object that will invoke the specified callback when dropped. 9 | #[allow(dead_code)] 10 | pub fn new(callback: F) -> Self { 11 | Self { 12 | callback: ManuallyDrop::new(callback), 13 | } 14 | } 15 | } 16 | 17 | impl Drop for OnDrop { 18 | fn drop(&mut self) { 19 | // SAFETY: We may move out of `self`, since this instance can never be observed after it's dropped. 20 | let callback = unsafe { ManuallyDrop::take(&mut self.callback) }; 21 | callback(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /crates/rxy_core/src/utils/synccell.rs: -------------------------------------------------------------------------------- 1 | //! A reimplementation of the currently unstable [`core::sync::Exclusive`] 2 | //! 3 | //! [`core::sync::Exclusive`]: https://doc.rust-lang.org/nightly/std/sync/struct.Exclusive.html 4 | 5 | /// See [`Exclusive`](https://github.com/rust-lang/rust/issues/98407) for stdlib's upcoming implementation, 6 | /// which should replace this one entirely. 7 | /// 8 | /// Provides a wrapper that allows making any type unconditionally [`Sync`] by only providing mutable access. 9 | #[repr(transparent)] 10 | pub struct SyncCell { 11 | inner: T, 12 | } 13 | 14 | impl SyncCell { 15 | /// Construct a new instance of a `SyncCell` from the given value. 16 | pub fn new(inner: T) -> Self { 17 | Self { inner } 18 | } 19 | 20 | /// Deconstruct this `SyncCell` into its inner value. 21 | pub fn to_inner(Self { inner }: Self) -> T { 22 | inner 23 | } 24 | } 25 | 26 | impl SyncCell { 27 | /// Get a reference to this `SyncCell`'s inner value. 28 | pub fn get(&mut self) -> &mut T { 29 | &mut self.inner 30 | } 31 | 32 | /// For types that implement [`Sync`], get shared access to this `SyncCell`'s inner value. 33 | pub fn read(&self) -> &T 34 | where 35 | T: Sync, 36 | { 37 | &self.inner 38 | } 39 | 40 | /// Build a mutable reference to a `SyncCell` from a mutable reference 41 | /// to its inner value, to skip constructing with [`new()`](SyncCell::new()). 42 | pub fn from_mut(r: &'_ mut T) -> &'_ mut SyncCell { 43 | // SAFETY: repr is transparent, so refs have the same layout; and `SyncCell` properties are `&mut`-agnostic 44 | unsafe { &mut *(r as *mut T as *mut SyncCell) } 45 | } 46 | } 47 | 48 | // SAFETY: `Sync` only allows multithreaded access via immutable reference. 49 | // As `SyncCell` requires an exclusive reference to access the wrapped value for `!Sync` types, 50 | // marking this type as `Sync` does not actually allow unsynchronized access to the inner value. 51 | unsafe impl Sync for SyncCell {} 52 | -------------------------------------------------------------------------------- /crates/rxy_macro/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "rxy_macro" 4 | version = "0.1.0" 5 | [lib] 6 | proc-macro = true 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | convert_case.workspace = true 12 | proc-macro2.workspace = true 13 | quote.workspace = true 14 | syn.workspace = true 15 | -------------------------------------------------------------------------------- /crates/rxy_macro/src/force_dynamic_view.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use quote::{quote, ToTokens}; 3 | use syn::{parse_macro_input, parse_quote, ItemFn, ReturnType}; 4 | 5 | pub fn common_impl_force_dynamic_view( 6 | is_current_crate: bool, 7 | is_into_view: bool, 8 | render: impl ToTokens, 9 | _input: TokenStream, 10 | item: TokenStream, 11 | ) -> TokenStream { 12 | let mut ast = parse_macro_input!(item as ItemFn); 13 | let path = if is_current_crate { 14 | quote!(crate) 15 | } else { 16 | quote!(rxy_core) 17 | }; 18 | let block = ast.block; 19 | 20 | let ReturnType::Type(_, ty) = &ast.sig.output else { 21 | panic!("require return type") 22 | }; 23 | ast.block = if is_into_view { 24 | parse_quote! { 25 | { 26 | use #path::IntoDynamicView; 27 | let r: #ty = #block; 28 | r.into_view().into_dynamic().into_view() 29 | } 30 | } 31 | } else { 32 | parse_quote! { 33 | { 34 | let r: #ty = #block; 35 | r.into_dynamic().into_view() 36 | } 37 | } 38 | }; 39 | 40 | ast.sig.output = parse_quote!(-> #path::BoxedDynamicViewView<#render>); 41 | 42 | ast.into_token_stream().into() 43 | } 44 | -------------------------------------------------------------------------------- /crates/rxy_macro/src/ident_count.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use quote::quote; 4 | use syn::{ 5 | parse::{Parse, ParseStream}, 6 | parse_macro_input, Ident, Result, Token, 7 | }; 8 | 9 | struct IdentCount(usize); 10 | 11 | impl Parse for IdentCount { 12 | fn parse(input: ParseStream) -> Result { 13 | Ok(IdentCount( 14 | input.parse_terminated(Ident::parse, Token![,])?.len(), 15 | )) 16 | } 17 | } 18 | 19 | pub fn ident_count(input: TokenStream) -> TokenStream { 20 | let ident_count = parse_macro_input!(input as IdentCount).0; 21 | 22 | TokenStream::from(quote! { 23 | #ident_count 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /crates/rxy_macro/src/into_view.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | 3 | use proc_macro2::Ident; 4 | use quote::quote; 5 | use syn::parse::Parse; 6 | use syn::{parse_macro_input, parse_quote, DeriveInput, GenericArgument, Generics, Type}; 7 | 8 | pub fn into_view(input: TokenStream) -> TokenStream { 9 | let ast = parse_macro_input!(input as DeriveInput); 10 | 11 | let struct_name = &ast.ident; 12 | let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); 13 | 14 | TokenStream::from(quote! { 15 | impl #impl_generics crate::IntoView for #struct_name #type_generics #where_clause { 16 | type View = #struct_name #type_generics; 17 | 18 | fn into_view(self) -> Self::View{ 19 | self 20 | } 21 | } 22 | }) 23 | } 24 | 25 | struct ImplIntoViewInput { 26 | ty: Type, 27 | where_predicate: Option, 28 | } 29 | 30 | impl Parse for ImplIntoViewInput { 31 | fn parse(input: syn::parse::ParseStream) -> syn::Result { 32 | Ok(Self { 33 | ty: input.parse()?, 34 | where_predicate: input.parse().ok(), 35 | }) 36 | } 37 | } 38 | 39 | pub fn impl_into_view(input: TokenStream) -> TokenStream { 40 | let ast = parse_macro_input!(input as ImplIntoViewInput); 41 | 42 | let type_path = if let Type::Path(type_path) = &ast.ty { 43 | Some(type_path) 44 | } else { 45 | None 46 | }; 47 | let generic_args = type_path.and_then(|n| { 48 | n.path.segments.iter().last().and_then(|n| { 49 | if let syn::PathArguments::AngleBracketed(n) = &n.arguments { 50 | Some(n.args.iter().map(|n| { 51 | if let GenericArgument::Type(n) = n { 52 | n 53 | } else { 54 | panic!("not type") 55 | } 56 | })) 57 | } else { 58 | None 59 | } 60 | }) 61 | }); 62 | 63 | let mut generics = Generics { 64 | where_clause: ast.where_predicate, 65 | ..Default::default() 66 | }; 67 | if let Some(generic_args) = generic_args { 68 | generic_args.for_each(|n| { 69 | generics.params.push(parse_quote! { 70 | #n 71 | }); 72 | }); 73 | } 74 | let r: Ident = parse_quote! {R}; 75 | generics.params.push(parse_quote! {#r}); 76 | generics.make_where_clause().predicates.push(parse_quote! { 77 | #r: Renderer 78 | }); 79 | let (impl_generics, _type_generics, where_clause) = &generics.split_for_impl(); 80 | 81 | let ty = &ast.ty; 82 | TokenStream::from(quote! { 83 | impl #impl_generics IntoView<#r> for #ty #where_clause { 84 | type View = #ty; 85 | 86 | #[inline] 87 | fn into_view(self) -> Self::View{ 88 | self 89 | } 90 | } 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /crates/rxy_macro/src/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::force_dynamic_view::common_impl_force_dynamic_view; 2 | use proc_macro::TokenStream; 3 | use quote::quote; 4 | use syn::{parse_macro_input, DeriveInput, ItemStruct}; 5 | 6 | mod into_view; 7 | 8 | mod all_tuples; 9 | mod force_dynamic_view; 10 | mod ident_count; 11 | 12 | #[proc_macro] 13 | pub fn all_tuples(input: TokenStream) -> TokenStream { 14 | all_tuples::all_tuples(input) 15 | } 16 | 17 | #[proc_macro] 18 | pub fn all_tuples_with_size(input: TokenStream) -> TokenStream { 19 | all_tuples::all_tuples_with_size(input) 20 | } 21 | 22 | fn impl_into_prop_value_wrapper(input: TokenStream) -> TokenStream { 23 | let ast = parse_macro_input!(input as DeriveInput); 24 | 25 | let struct_name = &ast.ident; 26 | let (impl_generics, type_generics, where_clause) = &ast.generics.split_for_impl(); 27 | 28 | TokenStream::from(quote! { 29 | impl #impl_generics rxy_ui::IntoSchemaPropValue> for #struct_name #type_generics #where_clause{ 30 | fn into(self) -> rxy_ui::IntoSchemaPropValueWrapper { 31 | rxy_ui::IntoSchemaPropValueWrapper(self) 32 | } 33 | } 34 | }) 35 | } 36 | 37 | #[proc_macro_derive(PropValueWrapper)] 38 | pub fn prop_value_wrapper(input: TokenStream) -> TokenStream { 39 | impl_into_prop_value_wrapper(input) 40 | } 41 | 42 | #[proc_macro_attribute] 43 | pub fn force_dynamic_view(input: TokenStream, item: TokenStream) -> TokenStream { 44 | common_impl_force_dynamic_view(true, false, quote!(R), input, item) 45 | } 46 | 47 | #[proc_macro_attribute] 48 | pub fn force_into_dynamic_view(input: TokenStream, item: TokenStream) -> TokenStream { 49 | common_impl_force_dynamic_view(true, true, quote!(R), input, item) 50 | } 51 | 52 | #[proc_macro] 53 | pub fn ident_count(input: TokenStream) -> TokenStream { 54 | ident_count::ident_count(input) 55 | } 56 | 57 | #[proc_macro] 58 | pub fn impl_into_view(input: TokenStream) -> TokenStream { 59 | into_view::impl_into_view(input) 60 | } 61 | 62 | #[proc_macro_derive(IntoView)] 63 | pub fn into_view(input: TokenStream) -> TokenStream { 64 | into_view::into_view(input) 65 | } 66 | 67 | #[proc_macro_derive(TypedStyle)] 68 | pub fn typed_style(input: TokenStream) -> TokenStream { 69 | let item_struct = parse_macro_input!(input as ItemStruct); 70 | 71 | let ident = item_struct.ident; 72 | let render = quote!(rxy_ui::prelude::BevyRenderer); 73 | TokenStream::from(quote! { 74 | impl Copy for #ident {} 75 | impl Clone for #ident { 76 | fn clone(&self) -> Self { 77 | *self 78 | } 79 | } 80 | 81 | impl rxy_ui::style::TypedStyleLabel for #ident {} 82 | 83 | impl Into> for #ident { 84 | fn into(self) -> rxy_ui::XValueWrapper { 85 | rxy_ui::XValueWrapper(self) 86 | } 87 | } 88 | 89 | impl rxy_ui::style::StyleSheets<#render> for #ident { 90 | fn style_sheets( 91 | self, 92 | ctx: rxy_ui::style::StyleSheetCtx<#render>, 93 | ) -> ( 94 | impl Iterator> + Send + 'static, 95 | rxy_ui::style::StyleSheetsInfo, 96 | ) { 97 | rxy_ui::style::typed_shared_style_sheets(core::any::TypeId::of::(), ctx) 98 | } 99 | } 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /crates/rxy_native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | edition = "2021" 3 | name = "rxy_native" 4 | version = "0.1.0" 5 | 6 | [dependencies] 7 | raw-window-handle = "0.6" 8 | rxy_core = { workspace = true, features = ["common_renderer", "native", "async-channel", "xy_reactive"] } 9 | vello = { version = "0.1.0" } 10 | xy_reactive = { workspace = true, optional = true, default-features = false } 11 | glam = { workspace = true } 12 | bevy_reflect = { workspace=true, optional = true, default-features = false, features = ["glam"] } 13 | bevy_ecs = { workspace=true, default-features = false } 14 | bevy_hierarchy = { workspace=true, default-features = false } 15 | rxy_bevy_ecs = { path = "../rxy_bevy_ecs" } 16 | 17 | mint = "0.5" 18 | smallvec.workspace = true 19 | #bevy_ecs = { workspace = true, default-features = false } 20 | bitflags = "2" 21 | kurbo = { version = "0.11", features = ["mint"] } 22 | taffy = { version = "0.4", default-features = false, features = ["taffy_tree", "std"] } 23 | tracing = "0.1" 24 | wgpu = "0.19" 25 | winit = "0.29" 26 | thiserror = "1.0" 27 | 28 | paste.workspace = true 29 | count-macro.workspace = true 30 | 31 | serde = { version = "1.0", features = ["derive"], optional = true } 32 | image = "0.25" 33 | cfg-if = "1.0" 34 | # compio = { version = "0.9", optional = true } 35 | tokio = { version = "1", default-features = false, optional = true, features = ["rt", "rt-multi-thread"] } 36 | 37 | [features] 38 | default = ["tokio", "flexbox"] 39 | #default = ["tokio", "flexbox", "dynamic_element", "reflect"] 40 | serialize = ["dep:serde", "taffy/serde", "kurbo/serde", "glam/serde"] 41 | reflect = ["bevy_reflect", "rxy_core/bevy_reflect", "bevy_ecs/bevy_reflect"] 42 | grid = ["taffy/grid"] 43 | flexbox = ["taffy/flexbox"] 44 | block_layout = ["taffy/block_layout"] 45 | dynamic_element = ["rxy_core/dynamic_element"] 46 | -------------------------------------------------------------------------------- /crates/rxy_native/src/draw_text.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "reflect")] 2 | use bevy_reflect::prelude::*; 3 | #[cfg(feature = "reflect")] 4 | use bevy_ecs::prelude::ReflectComponent; 5 | use kurbo::Affine; 6 | use vello::glyph::Glyph; 7 | use vello::peniko::{Brush, Color, Fill, Font}; 8 | use vello::skrifa::FontRef; 9 | use vello::Scene; 10 | 11 | pub(crate) fn to_font_ref(font: &Font) -> Option> { 12 | use vello::skrifa::raw::FileRef; 13 | let file_ref = FileRef::new(font.data.as_ref()).ok()?; 14 | match file_ref { 15 | FileRef::Font(font) => Some(font), 16 | FileRef::Collection(collection) => collection.get(font.index).ok(), 17 | } 18 | } 19 | 20 | #[derive(Clone, PartialEq, Debug)] 21 | #[cfg_attr(feature = "reflect", derive(Reflect), reflect(Default, PartialEq))] 22 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 23 | #[cfg_attr( 24 | all(feature = "reflect", feature = "serialize"), 25 | reflect(Serialize, Deserialize) 26 | )] 27 | pub struct TextStyle { 28 | pub font_size: f32, 29 | #[cfg_attr(feature = "reflect", reflect(ignore))] 30 | pub color: Brush, 31 | pub brush_alpha: f32, 32 | pub hint: bool, 33 | pub line_height: f32, 34 | #[cfg_attr(feature = "reflect", reflect(ignore))] 35 | pub font: Option, 36 | } 37 | 38 | impl Default for TextStyle { 39 | fn default() -> Self { 40 | Self { 41 | font_size: 18., 42 | color: Brush::Solid(Color::BLACK), 43 | brush_alpha: 1., 44 | hint: false, 45 | line_height: 1., 46 | font: None, 47 | } 48 | } 49 | } 50 | 51 | pub trait SceneExt { 52 | fn draw_text( 53 | &mut self, 54 | glyphs: impl Iterator, 55 | style: &TextStyle, 56 | transform: Affine, 57 | ); 58 | } 59 | 60 | impl SceneExt for Scene { 61 | fn draw_text( 62 | &mut self, 63 | glyphs: impl Iterator, 64 | style: &TextStyle, 65 | transform: Affine, 66 | ) { 67 | let font = style.font.as_ref().unwrap(); 68 | // { 69 | // let font_ref = to_font_ref(&font).unwrap(); 70 | // let axes = font_ref.axes(); 71 | // let variations: &[(&str, f32)] = &[]; 72 | // let var_loc = axes.location(variations.iter().copied()); 73 | // } 74 | self 75 | .draw_glyphs(&font) 76 | .font_size(style.font_size) 77 | .transform(transform) 78 | // .glyph_transform(glyph_transform) 79 | // .normalized_coords(var_loc.coords()) 80 | .brush(&style.color) 81 | .brush_alpha(style.brush_alpha) 82 | .hint(style.hint) 83 | .draw(Fill::NonZero, glyphs); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /crates/rxy_native/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(unused_imports)] 2 | #![allow(unused_variables)] 3 | pub use renderer::*; 4 | use rxy_core::{Element, ElementAttr, ElementViewChildren}; 5 | 6 | mod app; 7 | mod renderer; 8 | mod running_app; 9 | mod user_event; 10 | mod window; 11 | mod layout; 12 | mod draw; 13 | mod draw_text; 14 | pub mod world_ext; 15 | 16 | pub mod all_attrs { 17 | pub use crate::attrs::*; 18 | // pub use crate::elements::input_attrs::*; 19 | pub use crate::elements::element_span_attrs::*; 20 | } 21 | pub mod prelude { 22 | pub use crate::app::XyApp; 23 | pub use crate::renderer::common_renderer::*; 24 | pub use crate::renderer::*; 25 | 26 | pub use crate::Val; 27 | pub use vello::peniko::Color; 28 | 29 | pub use crate::elements::prelude::*; 30 | pub use crate::renderer::NativeElement; 31 | 32 | pub use super::all_attrs::{CommonAttrsElementViewBuilder, CommonAttrsViewBuilder}; 33 | // pub use super::renderer::event::*; 34 | // pub use super::renderer::view_builder_ext::*; 35 | } 36 | 37 | 38 | 39 | #[cfg(feature = "dynamic_element")] 40 | pub type DynamicNativeElement = rxy_core::DynamicElement; 41 | 42 | pub type NativeElementViewChildren = 43 | ElementViewChildren, CV>; 44 | 45 | pub type NativeElementAttrMember = ElementAttr; 46 | 47 | pub type TaskState = rxy_core::TaskState; -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/attr_values.rs: -------------------------------------------------------------------------------- 1 | use rxy_core::impl_attr_value_and_wrapper; 2 | use vello::kurbo::{Point, Rect, Shape}; 3 | use rxy_core::{ 4 | impl_attr_value, impl_x_value_wrappers, smallbox, AttrValue, 5 | SmallBox, XValueWrapper, S1, 6 | }; 7 | 8 | impl_attr_value_and_wrapper! { 9 | crate::Val, 10 | crate::Display, 11 | crate::PositionType, 12 | crate::Direction, 13 | crate::AlignItems, 14 | crate::JustifyItems, 15 | crate::AlignSelf, 16 | crate::JustifySelf, 17 | crate::AlignContent, 18 | crate::JustifyContent, 19 | crate::FlexDirection, 20 | crate::FlexWrap, 21 | // crate::GridAutoFlow, 22 | // crate::RepeatedGridTrack => crate::RepeatedGridTrack::auto(1), 23 | // crate::GridTrack, 24 | // crate::GridPlacement, 25 | crate::Visibility, 26 | crate::OverflowAxis 27 | } 28 | 29 | 30 | // impl Into> for i32 { 31 | // fn into(self) -> XValueWrapper { 32 | // XValueWrapper(crate::Val::Px(self as _)) 33 | // } 34 | // } 35 | // 36 | // impl Into> for f32 { 37 | // fn into(self) -> XValueWrapper { 38 | // XValueWrapper(crate::Val::Px(self)) 39 | // } 40 | // } 41 | // 42 | // impl Into> for bool { 43 | // fn into(self) -> XValueWrapper { 44 | // XValueWrapper( 45 | // self 46 | // .then(|| crate::Visibility::Visible) 47 | // .unwrap_or(crate::Visibility::Hidden), 48 | // ) 49 | // } 50 | // } 51 | 52 | 53 | // impl Into> for i32 { 54 | // fn into(self) -> XValueWrapper { 55 | // XValueWrapper(bevy_ui::ZIndex::Global(self)) 56 | // } 57 | // } 58 | // impl AttrValue for bevy_ui::ZIndex { 59 | // fn clone_att_value(&self) -> SmallBox { 60 | // smallbox!(*self) 61 | // } 62 | // 63 | // fn default_value() -> Self { 64 | // ::default() 65 | // } 66 | // 67 | // fn eq(&self, other: &Self) -> bool { 68 | // match self { 69 | // bevy_ui::ZIndex::Local(i) => match other { 70 | // bevy_ui::ZIndex::Local(o_i) => i == o_i, 71 | // bevy_ui::ZIndex::Global(_) => false, 72 | // }, 73 | // bevy_ui::ZIndex::Global(i) => match other { 74 | // bevy_ui::ZIndex::Local(_) => false, 75 | // bevy_ui::ZIndex::Global(o_i) => i == o_i, 76 | // }, 77 | // } 78 | // } 79 | // } -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/common_renderer.rs: -------------------------------------------------------------------------------- 1 | use crate::{all_attrs}; 2 | use crate::renderer::elements::{element_div, element_span}; 3 | use crate::renderer::{NativeElement, NativeRenderer}; 4 | use rxy_core::common_renderer::CommonRenderer; 5 | use rxy_core::{ 6 | define_common_view_fns, ElementAttrMember, ElementView, MapToAttrMarker, MemberOwner, XNest, 7 | }; 8 | use crate::elements::element_img; 9 | 10 | define_common_view_fns!(NativeRenderer); 11 | 12 | #[cfg(not(feature = "dynamic_element"))] 13 | impl CommonRenderer for NativeRenderer { 14 | type DivView = NativeElement; 15 | type TextView> = 16 | NativeElement; 17 | type ButtonView = NativeElement; 18 | type ImgView = NativeElement; 19 | type TextContentEA = all_attrs::content; 20 | 21 | fn crate_text( 22 | str: impl XNest> = T>, 23 | ) -> Self::TextView 24 | where 25 | T: ElementAttrMember, 26 | { 27 | NativeElement::default().members(str.map_inner::>()) 28 | } 29 | 30 | fn crate_div() -> Self::DivView { 31 | NativeElement::default() 32 | } 33 | 34 | fn crate_button() -> Self::ButtonView { 35 | NativeElement::default() 36 | } 37 | 38 | fn crate_img() -> Self::ImgView { 39 | NativeElement::default() 40 | } 41 | } 42 | 43 | #[cfg(feature = "dynamic_element")] 44 | use crate::DynamicNativeElement; 45 | #[cfg(feature = "dynamic_element")] 46 | impl CommonRenderer for NativeRenderer { 47 | type DivView = DynamicNativeElement; 48 | type TextView> = 49 | DynamicNativeElement; 50 | type ButtonView = DynamicNativeElement; 51 | type ImgView = DynamicNativeElement; 52 | type TextContentEA = all_attrs::content; 53 | 54 | fn crate_text( 55 | str: impl XNest> = T>, 56 | ) -> Self::TextView 57 | where 58 | T: ElementAttrMember, 59 | { 60 | DynamicNativeElement::default().members(str.map_inner::>()) 61 | } 62 | 63 | fn crate_div() -> Self::DivView { 64 | DynamicNativeElement::default() 65 | } 66 | 67 | fn crate_button() -> Self::ButtonView { 68 | DynamicNativeElement::default()/*.members(x_bundle(( 69 | FocusPolicy::default(), 70 | Interaction::default(), 71 | Button, 72 | Focusable::default(), 73 | )))*/ 74 | } 75 | 76 | fn crate_img() -> Self::ImgView { 77 | DynamicNativeElement::default() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/composite_attrs.rs: -------------------------------------------------------------------------------- 1 | use rxy_core::{impl_composite_attrs, impl_composite_attrs_use}; 2 | use crate::NativeRenderer; 3 | 4 | impl_composite_attrs_use!(); 5 | impl_composite_attrs!(NativeRenderer;MemberOwnerCompositeAttrs;MemberOwner); 6 | impl_composite_attrs!(NativeRenderer;ElementViewCompositeAttrs;ElementView); 7 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/elements/div.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_upper_case_globals)] 2 | #![allow(non_camel_case_types)] 3 | 4 | use kurbo::Vec2; 5 | use vello::peniko::Color; 6 | 7 | use rxy_core::{ElementType, ElementTypeUnTyped, RendererNodeId, RendererWorld}; 8 | use crate::{Display, Style, UiRect, Val}; 9 | use crate::NodeBundle; 10 | use crate::renderer::NativeRenderer; 11 | use crate::ui_node::{BackgroundColor, BorderColor, BorderRadius, Node, Outline}; 12 | use crate::world_ext::BevyWorldExt; 13 | 14 | #[derive(Default, Debug, Clone, Copy)] 15 | #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] 16 | pub struct element_div; 17 | 18 | impl ElementType for element_div { 19 | const TAG_NAME: &'static str = "div"; 20 | 21 | fn get() -> &'static dyn ElementTypeUnTyped { 22 | &element_div 23 | } 24 | 25 | fn spawn( 26 | world: &mut RendererWorld, 27 | parent: Option<&RendererNodeId>, 28 | reserve_node_id: Option>, 29 | ) -> RendererNodeId { 30 | let mut entity_world_mut = world.get_or_spawn_empty(parent, reserve_node_id); 31 | entity_world_mut.insert(NodeBundle{ 32 | ..Default::default() 33 | }); 34 | entity_world_mut.id() 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/elements/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | pub use div::*; 3 | pub use img::*; 4 | pub use span::*; 5 | use crate::NativeRenderer; 6 | 7 | mod div; 8 | mod img; 9 | mod span; 10 | 11 | pub mod prelude { 12 | use rxy_core::AttrIndex; 13 | use rxy_core::{attrs_fn_define, impl_attrs_for_element_type, impl_index_for_tys}; 14 | 15 | 16 | use crate::all_attrs::COMMON_ATTRS; 17 | use crate::element_attrs_fn_define; 18 | use crate::NativeRenderer; 19 | 20 | use super::*; 21 | 22 | element_attrs_fn_define! { 23 | [element_div] 24 | attrs = [] 25 | 26 | [element_span] 27 | attrs = [ 28 | content 29 | ] 30 | 31 | [element_img] 32 | attrs = [ 33 | // src 34 | // flip_x 35 | // flip_y 36 | ] 37 | } 38 | } 39 | 40 | #[cfg(feature = "reflect")] 41 | pub trait ElementTypeRegisterExt { 42 | fn register_element_types(&mut self) -> &mut Self; 43 | } 44 | 45 | #[cfg(feature = "reflect")] 46 | impl ElementTypeRegisterExt for bevy_reflect::TypeRegistry { 47 | fn register_element_types(&mut self) -> &mut Self { 48 | self.register::(); 49 | self.register::(); 50 | self.register::(); 51 | 52 | #[cfg(feature = "dynamic_element")] 53 | use rxy_core::ElementTypeTypeInfo; 54 | #[cfg(feature = "dynamic_element")] 55 | self 56 | .register_type_data::>() 57 | .register_type_data::>() 58 | .register_type_data::>(); 59 | self 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/layout/text.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "reflect")] 2 | use bevy_reflect::prelude::*; 3 | 4 | /// Defines the text direction. 5 | /// 6 | /// For example, English is written LTR (left-to-right) while Arabic is written RTL (right-to-left). 7 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] 8 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 9 | #[cfg_attr(feature = "reflect", derive(Reflect), reflect(Default, PartialEq))] 10 | #[cfg_attr( 11 | all(feature = "reflect", feature = "serialize"), 12 | reflect(Serialize, Deserialize) 13 | )] 14 | pub enum Direction { 15 | /// Inherit from parent node. 16 | Inherit, 17 | /// Text is written left to right. 18 | LeftToRight, 19 | /// Text is written right to left. 20 | RightToLeft, 21 | } 22 | 23 | impl Direction { 24 | pub const DEFAULT: Self = Self::Inherit; 25 | } 26 | 27 | impl Default for Direction { 28 | fn default() -> Self { 29 | Self::DEFAULT 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | use std::any::TypeId; 2 | use std::future::Future; 3 | 4 | use bevy_ecs::prelude::Entity; 5 | use bevy_ecs::world::World; 6 | 7 | pub use layout::*; 8 | pub use node_bundles::*; 9 | use rxy_core::{Element, NodeTree, Renderer, RendererNodeId}; 10 | pub use transform::*; 11 | pub use visibility::*; 12 | 13 | pub mod common_renderer; 14 | pub mod elements; 15 | 16 | mod attr_values; 17 | pub mod attrs; 18 | mod composite_attrs; 19 | mod layout; 20 | mod node_bundles; 21 | pub mod node_tree; 22 | mod taffy; 23 | mod tailwind_attrs; 24 | mod transform; 25 | pub mod ui_node; 26 | mod view_key; 27 | mod visibility; 28 | 29 | use crate::user_event::UserEventSender; 30 | pub use composite_attrs::*; 31 | pub use tailwind_attrs::*; 32 | 33 | rxy_bevy_ecs::define_bevy_ces_renderer! { 34 | #[cfg_attr(feature = "reflect", derive(bevy_reflect::Reflect))] 35 | pub struct NativeRenderer; 36 | } 37 | 38 | pub type NativeElement = Element; 39 | 40 | impl EcsRendererAssociated for NativeRenderer { 41 | type AssociatedNodeTreeScoped = UserEventSender; 42 | 43 | cfg_if::cfg_if! { 44 | if #[cfg(feature = "tokio")] { 45 | type AssociatedTask = tokio::task::JoinHandle; 46 | } /*else if #[cfg(feature = "compio")] { 47 | type AssociatedTask = compio::runtime::Task; 48 | } */else { 49 | compile_error!("No runtime feature enabled"); 50 | } 51 | } 52 | } 53 | 54 | impl EcsRenderer for NativeRenderer { 55 | fn spawn_task( 56 | future: impl Future + Send + 'static, 57 | ) -> ::Task { 58 | cfg_if::cfg_if! { 59 | if #[cfg(feature = "tokio")] { 60 | tokio::task::spawn(future) 61 | } /*else if #[cfg(feature = "compio")] { 62 | compio::runtime::spawn(future) 63 | } */else { 64 | panic!("No runtime feature enabled") 65 | } 66 | } 67 | } 68 | 69 | fn deferred_world_scoped(world: &World) -> Self::AssociatedNodeTreeScoped { 70 | world.non_send_resource::().clone() 71 | } 72 | 73 | fn set_visibility(world: &mut World, hidden: bool, node_id: &RendererNodeId) { 74 | if let Some(mut visibility) = world.get_mut::(*node_id) { 75 | *visibility = if hidden { 76 | Visibility::Hidden 77 | } else { 78 | Visibility::Inherited 79 | }; 80 | } 81 | } 82 | 83 | fn get_visibility(world: &World, node_id: &RendererNodeId) -> bool { 84 | world 85 | .get::(*node_id) 86 | .is_some_and(|n| *n == Visibility::Hidden) 87 | } 88 | 89 | fn scoped_type_state( 90 | world: &World, 91 | type_id: TypeId, 92 | f: impl FnOnce(Option<&S>) -> U, 93 | ) -> U { 94 | { 95 | #[cfg(feature = "reflect")] 96 | f(world 97 | .resource::() 98 | .read() 99 | .get_type_data::(type_id)) 100 | } 101 | { 102 | #[cfg(not(feature = "reflect"))] 103 | unimplemented!() 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/taffy.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ycysdf/rxy_ui/3566982bea631ebb1180b8b086fe6749da660d9d/crates/rxy_native/src/renderer/taffy.rs -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/tailwind_attrs.rs: -------------------------------------------------------------------------------- 1 | use crate::Visibility; 2 | use crate::{AlignItems, Display, FlexDirection, FlexWrap, JustifyContent, PositionType, Val}; 3 | use rxy_core::{impl_tailwind_attrs, impl_tailwind_attrs_use}; 4 | 5 | use crate::NativeRenderer; 6 | 7 | impl_tailwind_attrs_use!(); 8 | impl_tailwind_attrs!(NativeRenderer;MemberOwnerTailwindAttrs;MemberOwner); 9 | impl_tailwind_attrs!(NativeRenderer;ElementViewTailwindAttrs;ElementView); 10 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/transform/mod.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::bundle::Bundle; 2 | use bevy_ecs::component::Component; 3 | #[cfg(feature = "reflect")] 4 | use bevy_reflect::prelude::*; 5 | use glam::{Affine2, Affine3A, DAffine2, DVec2, Quat, Vec2, Vec3}; 6 | use kurbo::Affine; 7 | 8 | #[derive(Component, Debug, PartialEq, Clone, Copy)] 9 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 10 | #[cfg_attr(feature = "reflect", derive(Reflect), reflect(Default, PartialEq))] 11 | #[cfg_attr( 12 | all(feature = "reflect", feature = "serialize"), 13 | reflect(Serialize, Deserialize) 14 | )] 15 | pub struct GlobalTransform(pub Affine2); 16 | 17 | impl GlobalTransform { 18 | pub const IDENTITY: Self = Self(Affine2::IDENTITY); 19 | } 20 | 21 | impl Default for GlobalTransform { 22 | fn default() -> Self { 23 | Self::IDENTITY 24 | } 25 | } 26 | 27 | impl From for GlobalTransform { 28 | fn from(transform: Transform) -> Self { 29 | Self(transform.0) 30 | } 31 | } 32 | 33 | impl Into for GlobalTransform { 34 | fn into(self) -> Affine { 35 | (&self).into() 36 | } 37 | } 38 | 39 | impl Into for &GlobalTransform { 40 | fn into(self) -> Affine { 41 | let x = self.0.to_cols_array(); 42 | Affine::new([x[0] as _, x[1] as _, x[2] as _, x[3] as _, x[4] as _, x[5] as _]) 43 | } 44 | } 45 | 46 | #[derive(Component, Debug, PartialEq, Clone, Copy)] 47 | #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] 48 | #[cfg_attr(feature = "reflect", derive(Reflect), reflect(Default, PartialEq))] 49 | #[cfg_attr( 50 | all(feature = "reflect", feature = "serialize"), 51 | reflect(Serialize, Deserialize) 52 | )] 53 | pub struct Transform(pub Affine2); 54 | 55 | impl Transform { 56 | pub const IDENTITY: Self = Transform(Affine2::IDENTITY); 57 | } 58 | 59 | impl Default for Transform { 60 | fn default() -> Self { 61 | Self::IDENTITY 62 | } 63 | } 64 | 65 | #[derive(Bundle, Clone, Copy, Debug, Default)] 66 | pub struct TransformBundle { 67 | pub local: Transform, 68 | pub global: GlobalTransform, 69 | } 70 | 71 | impl TransformBundle { 72 | /// An identity [`TransformBundle`] with no translation, rotation, and a scale of 1 on all axes. 73 | pub const IDENTITY: Self = TransformBundle { 74 | local: Transform::IDENTITY, 75 | global: GlobalTransform::IDENTITY, 76 | }; 77 | 78 | /// Creates a new [`TransformBundle`] from a [`Transform`]. 79 | /// 80 | /// This initializes [`GlobalTransform`] as identity, to be updated later by the 81 | /// [`PostUpdate`] schedule. 82 | #[inline] 83 | pub const fn from_transform(transform: Transform) -> Self { 84 | TransformBundle { 85 | local: transform, 86 | ..Self::IDENTITY 87 | } 88 | } 89 | } 90 | 91 | impl From for TransformBundle { 92 | #[inline] 93 | fn from(transform: Transform) -> Self { 94 | Self::from_transform(transform) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /crates/rxy_native/src/renderer/view_key.rs: -------------------------------------------------------------------------------- 1 | // use crate::renderer::NativeRenderer; 2 | // use bevy_ecs::prelude::Entity; 3 | // use bevy_hierarchy::DespawnRecursiveExt; 4 | // use rxy_core::{NodeTree, RendererNodeId, RendererWorld, ViewKey}; 5 | // 6 | // impl ViewKey for Entity { 7 | // fn remove(self, world: &mut RendererWorld) { 8 | // world.entity_mut(self).despawn_recursive(); 9 | // } 10 | // 11 | // #[inline] 12 | // fn insert_before( 13 | // &self, 14 | // world: &mut RendererWorld, 15 | // parent: Option<&RendererNodeId>, 16 | // before_node_id: Option<&RendererNodeId>, 17 | // ) { 18 | // world.insert_before(parent, before_node_id, std::slice::from_ref(self)); 19 | // } 20 | // #[inline] 21 | // fn set_visibility(&self, world: &mut RendererWorld, hidden: bool) { 22 | // world.set_visibility(hidden, self) 23 | // } 24 | // 25 | // #[inline] 26 | // fn state_node_id(&self) -> Option> { 27 | // Some(*self) 28 | // } 29 | // 30 | // fn reserve_key( 31 | // world: &mut RendererWorld, 32 | // _will_rebuild: bool, 33 | // _parent: RendererNodeId, 34 | // spawn: bool, 35 | // ) -> Self { 36 | // world.reserve_node_id() 37 | // } 38 | // 39 | // fn first_node_id( 40 | // &self, 41 | // _world: &RendererWorld, 42 | // ) -> Option> { 43 | // Some(*self) 44 | // } 45 | // } 46 | -------------------------------------------------------------------------------- /crates/rxy_native/src/user_event.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::prelude::World; 2 | use bevy_ecs::world::CommandQueue; 3 | use winit::event_loop::EventLoopProxy; 4 | 5 | use rxy_core::{DeferredNodeTreeScoped, RendererWorld}; 6 | 7 | use crate::window::{XyWindow, XyWindowSurfaceRenderer}; 8 | use crate::NativeRenderer; 9 | 10 | pub enum EventLoopUserEvent { 11 | CommandQueue(CommandQueue), 12 | WindowSurfaceReady { 13 | xy_window: XyWindow, 14 | surface_renderer: XyWindowSurfaceRenderer, 15 | }, 16 | } 17 | 18 | #[derive(Clone)] 19 | pub struct UserEventSender { 20 | event_proxy: EventLoopProxy, 21 | } 22 | 23 | impl UserEventSender { 24 | #[inline] 25 | pub fn send(&self, f: impl FnOnce(&mut World) + Send + 'static) { 26 | let mut command_queue = CommandQueue::default(); 27 | command_queue.push(f); 28 | self.send_queue(command_queue) 29 | } 30 | 31 | #[inline] 32 | pub fn send_queue(&self, command_queue: CommandQueue) { 33 | let _ = self 34 | .event_proxy 35 | .send_event(EventLoopUserEvent::CommandQueue(command_queue)); 36 | } 37 | } 38 | 39 | impl UserEventSender { 40 | pub fn new(event_proxy: EventLoopProxy) -> Self { 41 | UserEventSender { event_proxy } 42 | } 43 | } 44 | 45 | impl DeferredNodeTreeScoped for UserEventSender { 46 | fn scoped(&self, f: impl FnOnce(&mut RendererWorld) + Send + 'static) { 47 | self.send(f) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /crates/rxy_web_dom/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rxy_web_dom" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | rxy_core = { workspace = true, default-features = false, features = ["web_dom", "common_renderer", "attr_index_u16"] } 10 | hashbrown = "0.14" 11 | #count-macro.workspace = true 12 | #paste.workspace = true 13 | # 14 | #async-channel.workspace = true 15 | #smallvec.workspace = true 16 | #futures-lite.workspace = true 17 | # 18 | #oneshot.workspace = true 19 | 20 | #xy_reactive = { workspace = true, optional = true, features = ["web"] } 21 | wasm-bindgen = "0.2" 22 | wasm-bindgen-futures.workspace = true 23 | slotmap.workspace = true 24 | web-sys.workspace = true 25 | paste.workspace = true -------------------------------------------------------------------------------- /crates/rxy_web_dom/README.md: -------------------------------------------------------------------------------- 1 | # This renderer is just an experiment! -------------------------------------------------------------------------------- /crates/rxy_web_dom/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod renderer; 2 | 3 | pub use renderer::*; 4 | 5 | pub mod prelude { 6 | pub use super::attrs::CommonAttrsViewBuilder; 7 | pub use crate::build_on_body; 8 | pub use crate::elements::*; 9 | pub use crate::renderer::common_renderer::*; 10 | pub use crate::renderer::event::HtmlElementEvents; 11 | pub use crate::renderer::WebElement; 12 | pub use crate::WebRenderer; 13 | } 14 | -------------------------------------------------------------------------------- /crates/rxy_web_dom/src/renderer/attr_values.rs: -------------------------------------------------------------------------------- 1 | use rxy_core::XValueWrapper; 2 | use std::borrow::Cow; 3 | -------------------------------------------------------------------------------- /crates/rxy_web_dom/src/renderer/common_renderer.rs: -------------------------------------------------------------------------------- 1 | use crate::attrs::node_value; 2 | use crate::elements::{element_button, element_div, element_img, element_text}; 3 | use crate::renderer::WebRenderer; 4 | use crate::WebElement; 5 | use rxy_core::common_renderer::CommonRenderer; 6 | use rxy_core::{define_common_view_fns, ElementAttrMember, MapToAttrMarker, MemberOwner, XNest}; 7 | 8 | define_common_view_fns!(WebRenderer); 9 | 10 | impl CommonRenderer for WebRenderer { 11 | type DivView = WebElement; 12 | type TextView> = WebElement; 13 | type ButtonView = WebElement; 14 | type ImgView = WebElement; 15 | type TextContentEA = node_value; 16 | 17 | fn crate_text( 18 | str: impl XNest> = T>, 19 | ) -> Self::TextView 20 | where 21 | T: ElementAttrMember, 22 | { 23 | WebElement::default().members(str.map_inner::>()) 24 | } 25 | 26 | fn crate_div() -> Self::DivView { 27 | WebElement::default() 28 | } 29 | 30 | fn crate_button() -> Self::ButtonView { 31 | WebElement::default() 32 | } 33 | 34 | fn crate_img() -> Self::ImgView { 35 | WebElement::default() 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/xy_reactive/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /crates/xy_reactive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "xy_reactive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | cfg-if = "1" 8 | futures = "0.3" 9 | lazy_static = "1" 10 | parking_lot = "0.12" 11 | pin-project-lite = "0.2" 12 | rustc-hash = "1" 13 | serde_json = "1" 14 | slotmap = "1" 15 | smallvec = "1" 16 | thiserror = "1" 17 | bevy_tasks = { workspace = true, features = ["multi_threaded"], optional = true } 18 | serde = { version = "1", features = ["derive"], optional = false } 19 | glib = { version = "0.18", optional = true } 20 | wasm-bindgen-futures = { version = "0.4", optional = true } 21 | wasm-bindgen = { version = "0.2", optional = true } 22 | web-sys = { version = "0.3", features = ["console"], optional = true } 23 | serde-wasm-bindgen = { version = "0.6", optional = true } 24 | js-sys = { version = "0.3", optional = true } 25 | tokio = { version = "1", features = ["rt", "macros"], optional = true } 26 | tracing = { version = "0.1.40", optional = true } 27 | 28 | # serialization formats 29 | serde-lite = { version = "0.5", optional = true } 30 | miniserde = { version = "0.1", optional = true } 31 | rkyv = { version = "0.7.39", features = [ 32 | "validation", 33 | "uuid", 34 | "strict", 35 | ], optional = true } 36 | base64 = { version = "0.21", optional = true } 37 | 38 | [dev-dependencies] 39 | tokio-test = "0.4" 40 | tokio = { version = "1", features = ["rt", "macros", "rt-multi-thread"] } 41 | 42 | [features] 43 | default = [] 44 | bevy = ["dep:bevy_tasks"] 45 | glib = ["dep:glib"] 46 | hydration = [] 47 | serde = [ 48 | # "dep:serde" 49 | ] 50 | tracing = ["dep:tracing"] 51 | tokio = ["dep:tokio"] 52 | web = [ 53 | "dep:js-sys", 54 | "dep:serde-wasm-bindgen", 55 | "dep:wasm-bindgen", 56 | "dep:wasm-bindgen-futures", 57 | "dep:web-sys", 58 | ] 59 | miniserde = ["dep:miniserde"] 60 | rkyv = ["dep:rkyv", "dep:base64"] 61 | serde-lite = ["dep:serde-lite"] 62 | 63 | 64 | [[example]] 65 | name = "playground" 66 | path = "examples/playground.rs" -------------------------------------------------------------------------------- /crates/xy_reactive/README.md: -------------------------------------------------------------------------------- 1 | fork from https://github.com/gbj/tachys/tree/main/tachy_reaccy -------------------------------------------------------------------------------- /crates/xy_reactive/examples/playground.rs: -------------------------------------------------------------------------------- 1 | use parking_lot::RwLock; 2 | use std::mem; 3 | use std::sync::Arc; 4 | use xy_reactive::prelude::*; 5 | 6 | pub async fn tick() { 7 | tokio::time::sleep(std::time::Duration::from_micros(1)).await; 8 | } 9 | 10 | #[tokio::main] 11 | async fn main() { 12 | let first = RwSignal::new("Greg"); 13 | let last = RwSignal::new("Johnston"); 14 | let use_last = RwSignal::new(true); 15 | 16 | let combined_count = Arc::new(RwLock::new(0)); 17 | 18 | mem::forget(Effect::new_sync({ 19 | let combined_count = Arc::clone(&combined_count); 20 | move |_| { 21 | *combined_count.write() += 1; 22 | if use_last.get() { 23 | println!("{} {}", first.get(), last.get()); 24 | } else { 25 | println!("{}", first.get()); 26 | } 27 | } 28 | })); 29 | 30 | tick().await; 31 | assert_eq!(*combined_count.read(), 1); 32 | 33 | println!("\nsetting `first` to Bob"); 34 | first.set("Bob"); 35 | tick().await; 36 | assert_eq!(*combined_count.read(), 2); 37 | 38 | println!("\nsetting `last` to Bob"); 39 | last.set("Thompson"); 40 | tick().await; 41 | assert_eq!(*combined_count.read(), 3); 42 | 43 | println!("\nsetting `use_last` to false"); 44 | use_last.set(false); 45 | tick().await; 46 | assert_eq!(*combined_count.read(), 4); 47 | 48 | println!("\nsetting `last` to Jones"); 49 | last.set("Jones"); 50 | tick().await; 51 | assert_eq!(*combined_count.read(), 4); 52 | 53 | println!("\nsetting `last` to Jones"); 54 | last.set("Smith"); 55 | tick().await; 56 | assert_eq!(*combined_count.read(), 4); 57 | 58 | println!("\nsetting `last` to Stevens"); 59 | last.set("Stevens"); 60 | tick().await; 61 | assert_eq!(*combined_count.read(), 4); 62 | 63 | println!("\nsetting `use_last` to true"); 64 | use_last.set(true); 65 | tick().await; 66 | assert_eq!(*combined_count.read(), 5); 67 | } 68 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/async_signal/mod.rs: -------------------------------------------------------------------------------- 1 | mod derived; 2 | mod resource; 3 | 4 | use crate::{arena::Owner, source::AnySubscriber, Observer}; 5 | pub use derived::*; 6 | use futures::Future; 7 | use pin_project_lite::pin_project; 8 | pub use resource::*; 9 | use std::{ 10 | pin::Pin, 11 | task::{Context, Poll}, 12 | }; 13 | 14 | #[derive(Debug, Default, Clone, PartialEq, Eq)] 15 | pub enum AsyncState { 16 | #[default] 17 | Loading, 18 | Complete(T), 19 | Reloading(T), 20 | } 21 | 22 | impl AsyncState { 23 | pub fn current_value(&self) -> Option<&T> { 24 | match &self { 25 | AsyncState::Loading => None, 26 | AsyncState::Complete(val) | AsyncState::Reloading(val) => Some(val), 27 | } 28 | } 29 | 30 | pub fn loading(&self) -> bool { 31 | matches!(&self, AsyncState::Loading | AsyncState::Reloading(_)) 32 | } 33 | } 34 | 35 | pin_project! { 36 | pub struct ScopedFuture { 37 | owner: Option, 38 | observer: Option, 39 | #[pin] 40 | fut: Fut, 41 | } 42 | } 43 | 44 | impl ScopedFuture { 45 | pub fn new(fut: Fut) -> Self { 46 | let owner = Owner::current(); 47 | let observer = Observer::get(); 48 | Self { 49 | owner, 50 | observer, 51 | fut, 52 | } 53 | } 54 | } 55 | 56 | impl Future for ScopedFuture { 57 | type Output = Fut::Output; 58 | 59 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 60 | let this = self.project(); 61 | match (this.owner, this.observer) { 62 | (None, None) => this.fut.poll(cx), 63 | (None, Some(obs)) => obs.with_observer(|| this.fut.poll(cx)), 64 | (Some(owner), None) => owner.with_cleanup(|| this.fut.poll(cx)), 65 | (Some(owner), Some(observer)) => owner 66 | .with_cleanup(|| observer.with_observer(|| this.fut.poll(cx))), 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/context.rs: -------------------------------------------------------------------------------- 1 | use crate::arena::Owner; 2 | use std::any::{Any, TypeId}; 3 | 4 | impl Owner { 5 | fn provide_context(&self, value: T) { 6 | self.inner 7 | .write() 8 | .contexts 9 | .insert(value.type_id(), Box::new(value)); 10 | } 11 | 12 | fn use_context(&self) -> Option { 13 | let ty = TypeId::of::(); 14 | let inner = self.inner.read(); 15 | let mut parent = inner.parent.as_ref().and_then(|p| p.upgrade()); 16 | let contexts = &self.inner.read().contexts; 17 | if let Some(context) = contexts.get(&ty) { 18 | context.downcast_ref::().cloned() 19 | } else { 20 | while let Some(ref this_parent) = parent.clone() { 21 | let this_parent = this_parent.read(); 22 | let contexts = &this_parent.contexts; 23 | let value = contexts.get(&ty); 24 | let downcast = value 25 | .and_then(|context| context.downcast_ref::().cloned()); 26 | if let Some(value) = downcast { 27 | return Some(value); 28 | } else { 29 | parent = 30 | this_parent.parent.as_ref().and_then(|p| p.upgrade()); 31 | } 32 | } 33 | None 34 | } 35 | } 36 | } 37 | 38 | pub fn provide_context(value: T) { 39 | if let Some(owner) = Owner::current() { 40 | owner.provide_context(value); 41 | } 42 | } 43 | 44 | pub fn use_context() -> Option { 45 | Owner::current().and_then(|owner| owner.use_context()) 46 | } 47 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/lib.rs: -------------------------------------------------------------------------------- 1 | // TODO: 2 | #![allow(warnings)] 3 | 4 | mod arena; 5 | // pub mod async_signal; 6 | // pub mod context; 7 | pub mod effect; 8 | pub mod memo; 9 | mod notify; 10 | pub mod render_effect; 11 | pub mod selector; 12 | #[cfg(feature = "serde")] 13 | mod serde; 14 | pub mod serialization; 15 | // pub mod shared_context; 16 | pub mod signal; 17 | mod signal_get_ext; 18 | pub mod signal_traits; 19 | mod source; 20 | pub mod spawn; 21 | pub mod store; 22 | 23 | pub use signal_get_ext::*; 24 | 25 | use crate::source::AnySubscriber; 26 | pub use arena::{Owner, Root}; 27 | use futures::{Future, Stream}; 28 | use std::{cell::RefCell, pin::Pin}; 29 | 30 | pub mod prelude { 31 | pub use crate::{ 32 | // async_signal::{AsyncDerived, Resource}, 33 | // context::{provide_context, use_context}, 34 | effect::{create_effect, Effect}, 35 | memo::{use_memo, ArcMemo, Memo}, 36 | render_effect::create_render_effect, 37 | signal::{ 38 | use_rw_signal, use_signal, ArcRwSignal, ArcWriteSignal, ReadSignal, RwSignal, WriteSignal, 39 | }, 40 | signal_traits::*, 41 | store::{StoreField, StoreFieldIndex, StoreFieldIterator}, 42 | BoolSignalExt, 43 | Root, 44 | }; 45 | } 46 | 47 | thread_local! { 48 | static OBSERVER: RefCell> = RefCell::new(None); 49 | } 50 | 51 | pub type PinnedFuture = Pin + Send + Sync>>; 52 | pub type PinnedStream = Pin + Send + Sync>>; 53 | 54 | pub(crate) struct Observer {} 55 | 56 | impl Observer { 57 | fn get() -> Option { 58 | OBSERVER.with(|o| o.borrow().clone()) 59 | } 60 | 61 | fn is(observer: &AnySubscriber) -> bool { 62 | OBSERVER.with(|o| o.borrow().as_ref() == Some(observer)) 63 | } 64 | 65 | fn take() -> Option { 66 | OBSERVER.with(|o| o.borrow_mut().take()) 67 | } 68 | 69 | fn set(observer: Option) { 70 | OBSERVER.with(|o| *o.borrow_mut() = observer); 71 | } 72 | } 73 | 74 | pub fn untrack(fun: impl FnOnce() -> T) -> T { 75 | let prev = Observer::take(); 76 | let value = fun(); 77 | Observer::set(prev); 78 | value 79 | } 80 | 81 | #[cfg(feature = "web")] 82 | pub fn log(s: &str) { 83 | web_sys::console::log_1(&wasm_bindgen::JsValue::from_str(s)); 84 | } 85 | 86 | #[cfg(not(feature = "web"))] 87 | pub fn log(s: &str) { 88 | println!("{s}"); 89 | } 90 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/notify.rs: -------------------------------------------------------------------------------- 1 | //use browser_only_send::BrowserOnly; 2 | use core::sync::atomic::Ordering::Relaxed; 3 | use futures::{task::AtomicWaker, Stream}; 4 | use std::{ 5 | fmt::Debug, 6 | hash::Hash, 7 | pin::Pin, 8 | sync::{atomic::AtomicBool, Arc}, 9 | task::{Context, Poll}, 10 | }; 11 | 12 | #[derive(Debug)] 13 | pub(crate) struct Sender(Arc); 14 | 15 | #[derive(Debug)] 16 | pub(crate) struct Receiver(Arc); 17 | 18 | #[derive(Debug, Default)] 19 | struct Inner { 20 | waker: AtomicWaker, 21 | set: AtomicBool, 22 | } 23 | 24 | pub fn channel() -> (Sender, Receiver) { 25 | let inner = Arc::new(Inner { 26 | waker: AtomicWaker::new(), 27 | set: AtomicBool::new(false), 28 | }); 29 | (Sender(Arc::clone(&inner)), Receiver(inner)) 30 | } 31 | 32 | impl Sender { 33 | pub fn notify(&mut self) { 34 | self.0.set.store(true, Relaxed); 35 | self.0.waker.wake(); 36 | } 37 | } 38 | 39 | impl Stream for Receiver { 40 | type Item = (); 41 | 42 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 43 | self.0.waker.register(cx.waker()); 44 | 45 | if self.0.set.swap(false, Relaxed) { 46 | Poll::Ready(Some(())) 47 | } else { 48 | Poll::Pending 49 | } 50 | } 51 | } 52 | 53 | impl Hash for Sender { 54 | fn hash(&self, state: &mut H) { 55 | Arc::as_ptr(&self.0).hash(state) 56 | } 57 | } 58 | 59 | impl PartialEq for Sender { 60 | fn eq(&self, other: &Self) -> bool { 61 | Arc::ptr_eq(&self.0, &other.0) 62 | } 63 | } 64 | 65 | impl Eq for Sender {} 66 | 67 | impl Hash for Receiver { 68 | fn hash(&self, state: &mut H) { 69 | Arc::as_ptr(&self.0).hash(state) 70 | } 71 | } 72 | 73 | impl PartialEq for Receiver { 74 | fn eq(&self, other: &Self) -> bool { 75 | Arc::ptr_eq(&self.0, &other.0) 76 | } 77 | } 78 | 79 | impl Eq for Receiver {} 80 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/render_effect.rs: -------------------------------------------------------------------------------- 1 | use crate::effect::ErasureEffect; 2 | use crate::{ 3 | arena::Owner, 4 | effect::EffectInner, 5 | notify::channel, 6 | source::{AnySubscriber, SourceSet, Subscriber, ToAnySubscriber}, 7 | spawn::spawn_local, 8 | }; 9 | use futures::StreamExt; 10 | use parking_lot::RwLock; 11 | use std::{ 12 | fmt::Debug, 13 | mem, 14 | sync::{Arc, Weak}, 15 | }; 16 | 17 | pub fn create_render_effect(fun: impl FnMut(Option) -> T + 'static) -> RenderEffect { 18 | RenderEffect::new(fun) 19 | } 20 | 21 | pub struct RenderEffect 22 | where 23 | T: 'static, 24 | { 25 | value: Arc>>, 26 | inner: Arc>, 27 | } 28 | 29 | impl Debug for RenderEffect { 30 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 31 | f.debug_struct("RenderEffect") 32 | .field("inner", &Arc::as_ptr(&self.inner)) 33 | .finish() 34 | } 35 | } 36 | 37 | impl RenderEffect 38 | where 39 | T: 'static, 40 | { 41 | pub fn new(fun: impl FnMut(Option) -> T + 'static) -> Self { 42 | Self::new_with_value(fun, None) 43 | } 44 | 45 | pub fn new_with_value( 46 | mut fun: impl FnMut(Option) -> T + 'static, 47 | initial_value: Option, 48 | ) -> Self { 49 | let (observer, mut rx) = channel(); 50 | let value = Arc::new(RwLock::new(None)); 51 | let owner = Owner::new(); 52 | let inner = Arc::new(RwLock::new(EffectInner { 53 | owner: owner.clone(), 54 | observer, 55 | sources: SourceSet::new(), 56 | })); 57 | 58 | let initial_value = Some(owner.with(|| { 59 | inner 60 | .to_any_subscriber() 61 | .with_observer(|| fun(initial_value)) 62 | })); 63 | *value.write() = initial_value; 64 | 65 | spawn_local({ 66 | let value = Arc::clone(&value); 67 | let subscriber = inner.to_any_subscriber(); 68 | 69 | async move { 70 | while rx.next().await.is_some() { 71 | subscriber.clear_sources(&subscriber); 72 | 73 | let old_value = mem::take(&mut *value.write()); 74 | let new_value = owner.with_cleanup(|| subscriber.with_observer(|| fun(old_value))); 75 | *value.write() = Some(new_value); 76 | } 77 | } 78 | }); 79 | RenderEffect { value, inner } 80 | } 81 | 82 | pub fn with_value_mut(&self, fun: impl FnOnce(&mut T) -> U) -> Option { 83 | self.value.write().as_mut().map(fun) 84 | } 85 | 86 | pub fn take_value(&self) -> Option { 87 | self.value.write().take() 88 | } 89 | 90 | pub fn erase(self) -> ErasureEffect { 91 | ErasureEffect { 92 | value: self.value.data_ptr() as usize, 93 | inner: self.inner, 94 | } 95 | } 96 | } 97 | 98 | impl ToAnySubscriber for RenderEffect { 99 | fn to_any_subscriber(&self) -> AnySubscriber { 100 | AnySubscriber( 101 | self.inner.data_ptr() as usize, 102 | Arc::downgrade(&self.inner) as Weak, 103 | ) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/selector.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | prelude::SignalWith, render_effect::RenderEffect, signal::ArcRwSignal, 3 | signal_traits::SignalUpdate, 4 | }; 5 | use parking_lot::RwLock; 6 | use rustc_hash::FxHashMap; 7 | use std::{hash::Hash, sync::Arc}; 8 | 9 | /// A conditional signal that only notifies subscribers when a change 10 | /// in the source signal’s value changes whether the given function is true. 11 | #[derive(Clone)] 12 | pub struct Selector 13 | where 14 | T: PartialEq + Eq + Clone + Hash + 'static, 15 | { 16 | subs: Arc>>>, 17 | v: Arc>>, 18 | #[allow(clippy::type_complexity)] 19 | f: Arc bool>, 20 | } 21 | 22 | impl Selector 23 | where 24 | T: PartialEq + Eq + Clone + Hash + 'static, 25 | { 26 | pub fn new(source: impl Fn() -> T + Clone + 'static) -> Self { 27 | Self::new_with_fn(source, PartialEq::eq) 28 | } 29 | 30 | pub fn new_with_fn( 31 | source: impl Fn() -> T + Clone + 'static, 32 | f: impl Fn(&T, &T) -> bool + Clone + 'static, 33 | ) -> Self { 34 | let subs: Arc>>> = Default::default(); 35 | let v: Arc>> = Default::default(); 36 | let f = Arc::new(f) as Arc bool>; 37 | 38 | RenderEffect::new({ 39 | let subs = Arc::clone(&subs); 40 | let f = Arc::clone(&f); 41 | let v = Arc::clone(&v); 42 | move |prev: Option| { 43 | let next_value = source(); 44 | *v.write() = Some(next_value.clone()); 45 | if prev.as_ref() != Some(&next_value) { 46 | for (key, signal) in &*subs.read() { 47 | if f(key, &next_value) || (prev.is_some() && f(key, prev.as_ref().unwrap())) { 48 | signal.update(|n| *n = true); 49 | } 50 | } 51 | } 52 | next_value 53 | } 54 | }); 55 | 56 | Selector { subs, v, f } 57 | } 58 | 59 | /// Reactively checks whether the given key is selected. 60 | pub fn selected(&self, key: T) -> bool { 61 | let read = { 62 | let mut subs = self.subs.write(); 63 | subs 64 | .entry(key.clone()) 65 | .or_insert_with(|| ArcRwSignal::new(false)) 66 | .clone() 67 | }; 68 | _ = read.try_with(|n| *n); 69 | (self.f)(&key, self.v.read().as_ref().unwrap()) 70 | } 71 | 72 | /// Removes the listener for the given key. 73 | pub fn remove(&self, key: &T) { 74 | let mut subs = self.subs.write(); 75 | subs.remove(key); 76 | } 77 | 78 | /// Clears the listeners for all keys. 79 | pub fn clear(&self) { 80 | let mut subs = self.subs.write(); 81 | subs.clear(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/serde.rs: -------------------------------------------------------------------------------- 1 | use crate::{signal::RwSignal, signal_traits::SignalWith}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | impl Serialize for RwSignal { 5 | fn serialize(&self, serializer: S) -> Result 6 | where 7 | S: serde::Serializer, 8 | { 9 | self.with(|value| value.serialize(serializer)) 10 | } 11 | } 12 | 13 | impl<'de, T: Send + Sync + Deserialize<'de>> Deserialize<'de> for RwSignal { 14 | fn deserialize(deserializer: D) -> Result 15 | where 16 | D: serde::Deserializer<'de>, 17 | { 18 | T::deserialize(deserializer).map(RwSignal::new) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/shared_context/hydrate.rs: -------------------------------------------------------------------------------- 1 | use super::{SerializedDataId, SharedContext}; 2 | use crate::{PinnedFuture, PinnedStream}; 3 | use core::fmt::Debug; 4 | use js_sys::Array; 5 | use std::sync::atomic::{AtomicUsize, Ordering}; 6 | use wasm_bindgen::prelude::wasm_bindgen; 7 | 8 | #[wasm_bindgen] 9 | extern "C" { 10 | static __RESOLVED_RESOURCES: Array; 11 | } 12 | 13 | #[derive(Default)] 14 | pub struct HydrateSharedContext { 15 | id: AtomicUsize, 16 | } 17 | 18 | impl HydrateSharedContext { 19 | pub fn new() -> Self { 20 | Self { 21 | id: AtomicUsize::new(0), 22 | } 23 | } 24 | } 25 | 26 | impl Debug for HydrateSharedContext { 27 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 28 | f.debug_struct("HydrateSharedContext").finish() 29 | } 30 | } 31 | 32 | impl SharedContext for HydrateSharedContext { 33 | fn next_id(&self) -> SerializedDataId { 34 | let id = self.id.fetch_add(1, Ordering::Relaxed); 35 | SerializedDataId(id) 36 | } 37 | 38 | fn write_async(&self, _id: SerializedDataId, _fut: PinnedFuture) {} 39 | 40 | fn read_data(&self, id: &SerializedDataId) -> Option { 41 | __RESOLVED_RESOURCES.get(id.0 as u32).as_string() 42 | } 43 | 44 | fn await_data(&self, _id: &SerializedDataId) -> Option { 45 | todo!() 46 | } 47 | 48 | fn pending_data(&self) -> Option> { 49 | None 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/shared_context/islands.rs: -------------------------------------------------------------------------------- 1 | // TODO 2 | -------------------------------------------------------------------------------- /crates/xy_reactive/src/shared_context/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "web")] 2 | mod hydrate; 3 | mod islands; 4 | mod ssr; 5 | 6 | use crate::{PinnedFuture, PinnedStream}; 7 | #[cfg(feature = "web")] 8 | pub use hydrate::*; 9 | pub use islands::*; 10 | use serde::{Deserialize, Serialize}; 11 | pub use ssr::*; 12 | use std::fmt::Debug; 13 | 14 | pub trait SharedContext: Debug { 15 | /// Returns the next in a series of IDs that is unique to a particular request and response. 16 | /// 17 | /// This should not be used as a global unique ID mechanism. It is specific to the process 18 | /// of serializing and deserializing data from the server to the browser as part of an HTTP 19 | /// response. 20 | fn next_id(&self) -> SerializedDataId; 21 | 22 | /// The given [`Future`] should resolve with some data that can be serialized 23 | /// from the server to the client. This will be polled as part of the process of 24 | /// building the HTTP response, *not* when it is first created. 25 | /// 26 | /// In browser implementations, this should be a no-op. 27 | fn write_async(&self, id: SerializedDataId, fut: PinnedFuture); 28 | 29 | /// Reads the current value of some data from the shared context, if it has been 30 | /// sent from the server. This returns the serialized data as a `String` that should 31 | /// be deserialized using [`Serializable::de`]. 32 | /// 33 | /// On the server and in client-side rendered implementations, this should 34 | /// always return [`None`]. 35 | fn read_data(&self, id: &SerializedDataId) -> Option; 36 | 37 | /// Returns a [`Future`] that resolves with a `String` that should 38 | /// be deserialized using [`Serializable::de`] once the given piece of server 39 | /// data has resolved. 40 | /// 41 | /// On the server and in client-side rendered implementations, this should 42 | /// return a [`Future`] that is immediately ready with [`None`]. 43 | fn await_data(&self, id: &SerializedDataId) -> Option; 44 | 45 | /// Returns some [`Stream`] of HTML that contains JavaScript `