├── .gitignore ├── Cargo.toml ├── README.md ├── TODO.md ├── crates ├── bevy_mod_stylebuilder │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── atlas_loader.rs │ │ ├── builder_background.rs │ │ ├── builder_border_color.rs │ │ ├── builder_border_radius.rs │ │ ├── builder_font.rs │ │ ├── builder_layout.rs │ │ ├── builder_outline.rs │ │ ├── builder_pointer_events.rs │ │ ├── builder_texture_atlas.rs │ │ ├── builder_visibility.rs │ │ ├── builder_z_index.rs │ │ ├── lib.rs │ │ ├── style_builder.rs │ │ ├── style_commands.rs │ │ ├── style_params.rs │ │ └── text_styles.rs ├── bevy_picking_backdrop │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── bevy_reactor_builder │ ├── Cargo.toml │ └── src │ │ ├── cond.rs │ │ ├── effect.rs │ │ ├── for_each.rs │ │ ├── for_index.rs │ │ ├── insert.rs │ │ ├── lcs.rs │ │ ├── lib.rs │ │ ├── style.rs │ │ ├── switch.rs │ │ ├── test_condition.rs │ │ ├── text.rs │ │ ├── ui_builder.rs │ │ └── ui_template.rs ├── bevy_reactor_inspect │ ├── Cargo.toml │ └── src │ │ ├── inspector_panel.rs │ │ └── lib.rs ├── bevy_reactor_obsidian │ ├── Cargo.toml │ └── src │ │ ├── animation │ │ ├── bistable_transition.rs │ │ └── mod.rs │ │ ├── assets │ │ ├── fonts │ │ │ ├── Fira_Sans │ │ │ │ ├── FiraSans-Black.ttf │ │ │ │ ├── FiraSans-BlackItalic.ttf │ │ │ │ ├── FiraSans-Bold.ttf │ │ │ │ ├── FiraSans-BoldItalic.ttf │ │ │ │ ├── FiraSans-ExtraBold.ttf │ │ │ │ ├── FiraSans-ExtraBoldItalic.ttf │ │ │ │ ├── FiraSans-ExtraLight.ttf │ │ │ │ ├── FiraSans-ExtraLightItalic.ttf │ │ │ │ ├── FiraSans-Italic.ttf │ │ │ │ ├── FiraSans-Light.ttf │ │ │ │ ├── FiraSans-LightItalic.ttf │ │ │ │ ├── FiraSans-Medium.ttf │ │ │ │ ├── FiraSans-MediumItalic.ttf │ │ │ │ ├── FiraSans-Regular.ttf │ │ │ │ ├── FiraSans-SemiBold.ttf │ │ │ │ ├── FiraSans-SemiBoldItalic.ttf │ │ │ │ ├── FiraSans-Thin.ttf │ │ │ │ ├── FiraSans-ThinItalic.ttf │ │ │ │ └── OFL.txt │ │ │ ├── Inter │ │ │ │ ├── Inter-Italic-VariableFont_opsz,wght.ttf │ │ │ │ ├── Inter-VariableFont_opsz,wght.ttf │ │ │ │ ├── OFL.txt │ │ │ │ ├── README.txt │ │ │ │ └── static │ │ │ │ │ ├── Inter_18pt-Black.ttf │ │ │ │ │ ├── Inter_18pt-BlackItalic.ttf │ │ │ │ │ ├── Inter_18pt-Bold.ttf │ │ │ │ │ ├── Inter_18pt-BoldItalic.ttf │ │ │ │ │ ├── Inter_18pt-ExtraBold.ttf │ │ │ │ │ ├── Inter_18pt-ExtraBoldItalic.ttf │ │ │ │ │ ├── Inter_18pt-ExtraLight.ttf │ │ │ │ │ ├── Inter_18pt-ExtraLightItalic.ttf │ │ │ │ │ ├── Inter_18pt-Italic.ttf │ │ │ │ │ ├── Inter_18pt-Light.ttf │ │ │ │ │ ├── Inter_18pt-LightItalic.ttf │ │ │ │ │ ├── Inter_18pt-Medium.ttf │ │ │ │ │ ├── Inter_18pt-MediumItalic.ttf │ │ │ │ │ ├── Inter_18pt-Regular.ttf │ │ │ │ │ ├── Inter_18pt-SemiBold.ttf │ │ │ │ │ ├── Inter_18pt-SemiBoldItalic.ttf │ │ │ │ │ ├── Inter_18pt-Thin.ttf │ │ │ │ │ ├── Inter_18pt-ThinItalic.ttf │ │ │ │ │ ├── Inter_24pt-Black.ttf │ │ │ │ │ ├── Inter_24pt-BlackItalic.ttf │ │ │ │ │ ├── Inter_24pt-Bold.ttf │ │ │ │ │ ├── Inter_24pt-BoldItalic.ttf │ │ │ │ │ ├── Inter_24pt-ExtraBold.ttf │ │ │ │ │ ├── Inter_24pt-ExtraBoldItalic.ttf │ │ │ │ │ ├── Inter_24pt-ExtraLight.ttf │ │ │ │ │ ├── Inter_24pt-ExtraLightItalic.ttf │ │ │ │ │ ├── Inter_24pt-Italic.ttf │ │ │ │ │ ├── Inter_24pt-Light.ttf │ │ │ │ │ ├── Inter_24pt-LightItalic.ttf │ │ │ │ │ ├── Inter_24pt-Medium.ttf │ │ │ │ │ ├── Inter_24pt-MediumItalic.ttf │ │ │ │ │ ├── Inter_24pt-Regular.ttf │ │ │ │ │ ├── Inter_24pt-SemiBold.ttf │ │ │ │ │ ├── Inter_24pt-SemiBoldItalic.ttf │ │ │ │ │ ├── Inter_24pt-Thin.ttf │ │ │ │ │ ├── Inter_24pt-ThinItalic.ttf │ │ │ │ │ ├── Inter_28pt-Black.ttf │ │ │ │ │ ├── Inter_28pt-BlackItalic.ttf │ │ │ │ │ ├── Inter_28pt-Bold.ttf │ │ │ │ │ ├── Inter_28pt-BoldItalic.ttf │ │ │ │ │ ├── Inter_28pt-ExtraBold.ttf │ │ │ │ │ ├── Inter_28pt-ExtraBoldItalic.ttf │ │ │ │ │ ├── Inter_28pt-ExtraLight.ttf │ │ │ │ │ ├── Inter_28pt-ExtraLightItalic.ttf │ │ │ │ │ ├── Inter_28pt-Italic.ttf │ │ │ │ │ ├── Inter_28pt-Light.ttf │ │ │ │ │ ├── Inter_28pt-LightItalic.ttf │ │ │ │ │ ├── Inter_28pt-Medium.ttf │ │ │ │ │ ├── Inter_28pt-MediumItalic.ttf │ │ │ │ │ ├── Inter_28pt-Regular.ttf │ │ │ │ │ ├── Inter_28pt-SemiBold.ttf │ │ │ │ │ ├── Inter_28pt-SemiBoldItalic.ttf │ │ │ │ │ ├── Inter_28pt-Thin.ttf │ │ │ │ │ └── Inter_28pt-ThinItalic.ttf │ │ │ └── Open_Sans │ │ │ │ ├── OFL.txt │ │ │ │ ├── OpenSans-Italic-VariableFont_wdth,wght.ttf │ │ │ │ ├── OpenSans-VariableFont_wdth,wght.ttf │ │ │ │ ├── README.txt │ │ │ │ └── static │ │ │ │ ├── OpenSans-Bold.ttf │ │ │ │ ├── OpenSans-BoldItalic.ttf │ │ │ │ ├── OpenSans-ExtraBold.ttf │ │ │ │ ├── OpenSans-ExtraBoldItalic.ttf │ │ │ │ ├── OpenSans-Italic.ttf │ │ │ │ ├── OpenSans-Light.ttf │ │ │ │ ├── OpenSans-LightItalic.ttf │ │ │ │ ├── OpenSans-Medium.ttf │ │ │ │ ├── OpenSans-MediumItalic.ttf │ │ │ │ ├── OpenSans-Regular.ttf │ │ │ │ ├── OpenSans-SemiBold.ttf │ │ │ │ ├── OpenSans-SemiBoldItalic.ttf │ │ │ │ ├── OpenSans_Condensed-Bold.ttf │ │ │ │ ├── OpenSans_Condensed-BoldItalic.ttf │ │ │ │ ├── OpenSans_Condensed-ExtraBold.ttf │ │ │ │ ├── OpenSans_Condensed-ExtraBoldItalic.ttf │ │ │ │ ├── OpenSans_Condensed-Italic.ttf │ │ │ │ ├── OpenSans_Condensed-Light.ttf │ │ │ │ ├── OpenSans_Condensed-LightItalic.ttf │ │ │ │ ├── OpenSans_Condensed-Medium.ttf │ │ │ │ ├── OpenSans_Condensed-MediumItalic.ttf │ │ │ │ ├── OpenSans_Condensed-Regular.ttf │ │ │ │ ├── OpenSans_Condensed-SemiBold.ttf │ │ │ │ ├── OpenSans_Condensed-SemiBoldItalic.ttf │ │ │ │ ├── OpenSans_SemiCondensed-Bold.ttf │ │ │ │ ├── OpenSans_SemiCondensed-BoldItalic.ttf │ │ │ │ ├── OpenSans_SemiCondensed-ExtraBold.ttf │ │ │ │ ├── OpenSans_SemiCondensed-ExtraBoldItalic.ttf │ │ │ │ ├── OpenSans_SemiCondensed-Italic.ttf │ │ │ │ ├── OpenSans_SemiCondensed-Light.ttf │ │ │ │ ├── OpenSans_SemiCondensed-LightItalic.ttf │ │ │ │ ├── OpenSans_SemiCondensed-Medium.ttf │ │ │ │ ├── OpenSans_SemiCondensed-MediumItalic.ttf │ │ │ │ ├── OpenSans_SemiCondensed-Regular.ttf │ │ │ │ ├── OpenSans_SemiCondensed-SemiBold.ttf │ │ │ │ └── OpenSans_SemiCondensed-SemiBoldItalic.ttf │ │ ├── icons │ │ │ ├── add.png │ │ │ ├── add_box.png │ │ │ ├── checkmark.png │ │ │ ├── chevron_down.png │ │ │ ├── chevron_left.png │ │ │ ├── chevron_right.png │ │ │ ├── chevron_up.png │ │ │ ├── close.png │ │ │ ├── disc.png │ │ │ ├── gradient_thumb.png │ │ │ ├── lock.png │ │ │ ├── redo.png │ │ │ ├── remove.png │ │ │ ├── tune.png │ │ │ ├── undo.png │ │ │ ├── zoom_in.png │ │ │ └── zoom_out.png │ │ └── shaders │ │ │ ├── gradient_rect.wgsl │ │ │ ├── rounded_box.wgsl │ │ │ ├── rounded_rect.wgsl │ │ │ ├── slider_rect.wgsl │ │ │ └── swatch_rect.wgsl │ │ ├── colors.rs │ │ ├── controls │ │ ├── barrier.rs │ │ ├── button.rs │ │ ├── checkbox.rs │ │ ├── core_slider.rs │ │ ├── dialog.rs │ │ ├── disabled.rs │ │ ├── disclosure_toggle.rs │ │ ├── gradient_slider.rs │ │ ├── icon.rs │ │ ├── icon_button.rs │ │ ├── mod.rs │ │ ├── scrollview.rs │ │ ├── slider.rs │ │ ├── spacer.rs │ │ ├── spinbox.rs │ │ ├── splitter.rs │ │ ├── swatch.rs │ │ ├── swatch_grid.rs │ │ ├── toggle_state.rs │ │ └── tool_palette.rs │ │ ├── cursor.rs │ │ ├── focus_signal.rs │ │ ├── hover_signal.rs │ │ ├── input_dispatch.rs │ │ ├── lib.rs │ │ ├── materials │ │ ├── dot_grid.rs │ │ ├── draw_path.rs │ │ ├── gradient_rect.rs │ │ ├── mod.rs │ │ ├── slider_rect.rs │ │ └── swatch_rect.rs │ │ ├── rounded_corners.rs │ │ ├── scrolling.rs │ │ ├── size.rs │ │ ├── tab_navigation.rs │ │ └── typography.rs ├── bevy_reactor_overlays │ ├── Cargo.toml │ └── src │ │ ├── lib.rs │ │ ├── mesh_builder.rs │ │ ├── overlay.rs │ │ ├── overlay.wgsl │ │ ├── overlay_material.rs │ │ └── shape_builder.rs ├── bevy_reactor_signals │ ├── Cargo.toml │ └── src │ │ ├── callback.rs │ │ ├── derived.rs │ │ ├── ecx.rs │ │ ├── lib.rs │ │ ├── mutable.rs │ │ ├── rcx.rs │ │ ├── reaction.rs │ │ ├── signal.rs │ │ └── tracking_scope.rs ├── obsidian_ui │ ├── .DS_Store │ ├── Cargo.toml │ ├── assets │ │ ├── shaders │ │ │ ├── dot_grid.wgsl │ │ │ └── draw_path.wgsl │ │ ├── svg │ │ │ ├── button.svg │ │ │ ├── checkmark.svg │ │ │ └── gradient_thumb.svg │ │ └── textures │ │ │ ├── button.atlas.grid.ron │ │ │ └── button.png │ └── src │ │ ├── controls │ │ ├── listview.rs │ │ ├── menu.rs │ │ ├── mod.rs │ │ ├── node_graph.rs │ │ └── text_input.rs │ │ ├── floating.rs │ │ ├── lib.rs │ │ └── viewport.rs └── obsidian_ui_inspect │ ├── Cargo.toml │ └── src │ ├── attributes.rs │ ├── default_factory.rs │ ├── inspectable.rs │ ├── inspector.rs │ ├── inspector_factory.rs │ ├── inspectors │ ├── bool.rs │ ├── color.rs │ ├── enum.rs │ ├── f32.rs │ ├── fallback.rs │ ├── list.rs │ ├── mod.rs │ ├── struct.rs │ ├── tuple_struct.rs │ └── vec3.rs │ ├── lib.rs │ └── templates │ ├── color_edit.rs │ ├── field_label.rs │ ├── field_readonly_value.rs │ ├── inspector_panel.rs │ └── mod.rs ├── examples ├── animate.rs ├── builder.rs ├── buttons.rs ├── complex │ ├── assets │ │ └── unlock.png │ ├── main.rs │ ├── node_graph_demo.rs │ ├── reflect_demo.rs │ └── transform_overlay.rs ├── controls.rs ├── inspector.rs ├── iter.rs ├── markdown.rs ├── scroll.rs └── stateful.rs └── src ├── compositor.rs └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /Cargo.lock 3 | *~ 4 | /.vscode 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_reactor" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [workspace] 8 | members = ["crates/*"] 9 | exclude = [ 10 | "crates/bevy_reactor_overlays", 11 | "crates/bevy_picking_backdrop", 12 | "crates/obsidian_ui", 13 | "crates/obsidian_ui_inspect", 14 | ] 15 | 16 | [workspace.dependencies] 17 | bevy = { version = "0.15.0", features = ["ghost_nodes"] } 18 | bevy_reactor_builder = { path = "crates/bevy_reactor_builder" } 19 | bevy_reactor_obsidian = { path = "crates/bevy_reactor_obsidian" } 20 | bevy_reactor_signals = { path = "crates/bevy_reactor_signals" } 21 | bevy_reactor_inspect = { path = "crates/bevy_reactor_inspect" } 22 | bevy_mod_picking = "0.20.1" 23 | bevy_mod_stylebuilder = { path = "crates/bevy_mod_stylebuilder" } 24 | 25 | [dependencies] 26 | bevy = { workspace = true } 27 | bevy_reactor_signals = { workspace = true } 28 | bevy_reactor_builder = { workspace = true } 29 | bevy_mod_stylebuilder = { workspace = true } 30 | bevy_mod_picking = { workspace = true } 31 | impl-trait-for-tuples = "0.2.2" 32 | smallvec = "1.13.2" 33 | 34 | # Enable max optimizations for dependencies, but not for our code: 35 | [profile.dev.package."*"] 36 | opt-level = 3 37 | 38 | [dev-dependencies] 39 | bevy_reactor_obsidian = { workspace = true } 40 | bevy_reactor_inspect = { workspace = true } 41 | # bevy_reactor_overlays = { path = "crates/bevy_reactor_overlays" } 42 | # bevy_picking_backdrop = { path = "crates/bevy_picking_backdrop" } 43 | bevy-inspector-egui = "0.26.0" 44 | pulldown-cmark = "0.12.2" 45 | 46 | # [patch.crates-io] 47 | # bevy = { git = "https://github.com/bevyengine/bevy.git", version = "0.15.0-dev" } 48 | # bevy_ecs = { git = "https://github.com/bevyengine/bevy.git", version = "0.15.0-dev" } 49 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | - builder: 4 | - dialog 5 | - menu 6 | - menu bar 7 | - floating 8 | - viewport 9 | - effect 10 | - insert_dyn 11 | - border radius for swatch. 12 | - more efficient for_each? 13 | - rewrite README 14 | - Run to convergence. 15 | - Checkbox bug in inspector 16 | - caused by run reactions? 17 | - Need "run to convergence". 18 | - Menus: 19 | - Restore Focus 20 | - Shortcuts 21 | - Checkmarks and checkmark spacing 22 | - Property Editor. 23 | - Tuples (Nested) 24 | - Map 25 | - Array 26 | - Top level tuples 27 | - Top level newtype structs 28 | - Top level lists 29 | - Spinbox click to edit text 30 | - Slider click to edit text 31 | - Too many public members in TrackingScope. This happened because of the need to 32 | allow external crates to define their own reaction types. 33 | - `run_reactions` does a lot of excessive entity lookups, needed to satisfy the borrow checker. 34 | - Components and Resources as signals? 35 | - This is problematic because it would require Signal to impl Component/Resource. 36 | - Change tab key handling to use bubbled events. 37 | - Restore focus, focus-visible when dialog closes. 38 | - Clear focus when clicking on empty space. 39 | - Verify Razing / Despawning doesn't leak 40 | - Composite buffers. 41 | - Don't execute dialog content if dialog not open. (Maybe this already happens? Not sure.) 42 | - Can we make ForEach not require cloning the iterator? 43 | - Persist recent colors 44 | - Idea: dynamic style effects. 45 | - Nice, but borrowing... 46 | - Text Input 47 | - drag to select (requires text measurement) 48 | - correct rendering of selection rects and cursor 49 | - correct rendering of focus rect (just uses outline for now) 50 | - correct rendering of rounded corners 51 | 52 | # Node Graph 53 | 54 | - split into its own crate 55 | - color editor (embedded graph node version) 56 | - bug in shader when quadratics are straight 57 | - input should be a polyline 58 | - line colors should match terminal colors 59 | - better shadows 60 | - connecting and disconnecting 61 | - line should appear above when dragging, and have rounded ends. 62 | - gesture 63 | 64 | # Obsidian 65 | 66 | - Focus Outlines (improve appearance) 67 | - Graph editor. 68 | 69 | # Possible crate structure 70 | 71 | - bevy_reactor_signals 72 | - bevy_reactor_styles 73 | 74 | - obsidian_ui_core 75 | - obsidian_ui_controls 76 | - obsidian_ui_reflect 77 | - obsidian_ui_graph 78 | 79 | # Sticking Points 80 | 81 | - Currently For::each() requires cloning the iterator, but it shouldn't need to since only the 82 | closure is long-lived, not the iterator itself. Is there some way I can use Rust lifetimes to 83 | reduce the amount of cloning? 84 | - A long-standing request is the ability to use Bevy queries, but I have never figured out how 85 | to do change detection on a query (that is, you can write a query that detects changes to 86 | components, but there's no way to detect a change to query results). 87 | 88 | # Markdown 89 | 90 | https://crates.io/crates/pulldown-cmark 91 | 92 | struct MarkdownStyles { 93 | 94 | } 95 | 96 | ```rust 97 | trait MarkdownStyles { 98 | fn block(self, tag, entity: &EntityWorldMut); 99 | fn span(self, tag, entity: &EntityWorldMut); 100 | } 101 | ``` 102 | 103 | Markdown::new(text, styles) 104 | 105 | ```rust 106 | struct MarkdownTextStyle { 107 | font: Handle, 108 | size: f32, 109 | color: Color, 110 | } 111 | 112 | struct MarkdownBlockStyle { 113 | padding: Vec4, 114 | } 115 | 116 | struct MarkdownStyles { 117 | paragraph: MarkdownTextStyle, 118 | header_font: Handle, 119 | code: MarkdownTextStyle, 120 | 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_mod_stylebuilder" 3 | version = "0.1.3" 4 | edition = "2021" 5 | description = "A set of fluent builder utilities for Bevy UI styles." 6 | license = "MIT OR Apache-2.0" 7 | repository = "https://github.com/viridia/quill" 8 | keywords = ["bevy", "ui", "reactive"] 9 | 10 | [features] 11 | 12 | [dependencies] 13 | bevy = { workspace = true } 14 | bevy_mod_picking = { workspace = true } 15 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/README.md: -------------------------------------------------------------------------------- 1 | # bevy_mod_stylebuilder 2 | 3 | This crate provides a set of low-level utilities for configuring `bevy_ui` styles using a fluent 4 | API. A `StyleBuilder` is an object that understands how to insert, remove, and modify Bevy style 5 | components such as `Node`, `BackgroundColor` and so on, as well as the `Pickable` component used 6 | by `bevy_mod_picking`. 7 | 8 | `StyleBuilder` is extensible by implementing additional traits. In fact, all of the fluent methods 9 | are trait methods. 10 | 11 | ```rust 12 | use bevy_mod_stylebuilder::prelude::*; 13 | 14 | fn style_button(ss: &mut StyleBuilder) { 15 | ss.border(1) 16 | .display(ui::Display::Flex) 17 | .flex_direction(ui::FlexDirection::Row) 18 | .justify_content(ui::JustifyContent::Center) 19 | .align_items(ui::AlignItems::Center) 20 | .align_content(ui::AlignContent::Center) 21 | .padding((12, 0)) 22 | .border(0) 23 | .color(colors::FOREGROUND) 24 | .cursor(CursorIcon::Pointer); 25 | } 26 | ``` 27 | 28 | In most cases, you won't need to instantiate a `StyleBuilder` object yourself, the UI framework 29 | will pass one to you as a callback parameter. For framework authors, however, here are the steps 30 | needed to create a new `StyleBuilder`: 31 | 32 | ```rust 33 | /// Construct a new StyleBuilder instance with the entity and `Styles` component. 34 | let mut sb = StyleBuilder::new(&mut target, style); 35 | /// Apply one or more style functions. 36 | self.styles.apply(&mut sb); 37 | /// Call `.finish()` to write out the changes. 38 | sb.finish(); 39 | ``` 40 | 41 | Most style components such as `BackgroundColor` are modified immediately, however `Style` is 42 | treated as a special case because it has so many properties: it's cached in the `StyleBuilder` 43 | instance and then flushed out at the end via `finish()`. 44 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/atlas_loader.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | asset::{AssetLoader, AsyncReadExt}, 3 | math::{Rect, Vec2}, 4 | sprite::{TextureAtlas, TextureAtlasLayout}, 5 | }; 6 | use serde::{Deserialize, Serialize}; 7 | 8 | pub struct TextureAtlasLoader; 9 | 10 | #[derive(Debug, Deserialize, Serialize)] 11 | struct TextureAtlasSer { 12 | texture: String, 13 | size: Vec2, 14 | textures: Vec, 15 | } 16 | 17 | #[derive(Debug, Deserialize, Serialize)] 18 | struct TextureAtlasGridSer { 19 | texture: String, 20 | tile_size: Vec2, 21 | columns: usize, 22 | rows: usize, 23 | padding: Option, 24 | offset: Option, 25 | } 26 | 27 | impl AssetLoader for TextureAtlasLoader { 28 | type Asset = TextureAtlasLayout; 29 | type Settings = (); 30 | type Error = anyhow::Error; 31 | 32 | fn load<'a>( 33 | &'a self, 34 | reader: &'a mut bevy::asset::io::Reader, 35 | _settings: &'a Self::Settings, 36 | load_context: &'a mut bevy::asset::LoadContext, 37 | ) -> bevy::utils::BoxedFuture<'a, Result> { 38 | if let Some(ext) = load_context.asset_path().get_full_extension() { 39 | if ext == "atlas.grid.ron" { 40 | return Box::pin(async move { 41 | let mut bytes = Vec::new(); 42 | reader.read_to_end(&mut bytes).await?; 43 | let atlas_ser: TextureAtlasGridSer = 44 | ron::de::from_str(&String::from_utf8(bytes)?)?; 45 | let texture_path = load_context 46 | .asset_path() 47 | .resolve_embed(&atlas_ser.texture)?; 48 | let texture = load_context.load(&texture_path); 49 | let result = TextureAtlas::from_grid( 50 | texture, 51 | atlas_ser.tile_size, 52 | atlas_ser.columns, 53 | atlas_ser.rows, 54 | atlas_ser.padding, 55 | atlas_ser.offset, 56 | ); 57 | Ok(result) 58 | }); 59 | } 60 | } 61 | 62 | Box::pin(async move { 63 | let mut bytes = Vec::new(); 64 | reader.read_to_end(&mut bytes).await?; 65 | let atlas_ser: TextureAtlasSer = ron::de::from_str(&String::from_utf8(bytes)?)?; 66 | let texture_path = load_context 67 | .asset_path() 68 | .resolve_embed(&atlas_ser.texture)?; 69 | let texture = load_context.load(&texture_path); 70 | let mut result = TextureAtlas::new_empty(texture, atlas_ser.size); 71 | for texture in atlas_ser.textures.iter() { 72 | result.add_texture(*texture); 73 | } 74 | Ok(result) 75 | }) 76 | } 77 | 78 | fn extensions(&self) -> &[&str] { 79 | &["atlas.ron", "atlas.grid.ron"] 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_background.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | prelude::*, 3 | image::Image, 4 | ui::{self}, 5 | ui::widget::ImageNode, 6 | }; 7 | 8 | use super::style_builder::StyleBuilder; 9 | use super::style_params::{ColorParam, MaybeHandleOrPath}; 10 | 11 | #[allow(missing_docs)] 12 | pub trait StyleBuilderBackground { 13 | /// Set the background image of the target entity. 14 | fn background_image<'p>(&mut self, path: impl Into>) -> &mut Self; 15 | 16 | /// Set the background image of the target entity, and also explicitly configure the 17 | /// horizontal and vertical flip. 18 | fn background_image_flipped<'p>( 19 | &mut self, 20 | path: impl Into>, 21 | flip_x: bool, 22 | flip_y: bool, 23 | ) -> &mut Self; 24 | 25 | /// Set the background color, or `None` for transparent. 26 | fn background_color(&mut self, color: impl ColorParam) -> &mut Self; 27 | 28 | /// Set the background color, or `None` for transparent. 29 | fn background_image_color(&mut self, color: impl ColorParam) -> &mut Self; 30 | } 31 | 32 | impl<'a, 'w> StyleBuilderBackground for StyleBuilder<'a, 'w> { 33 | fn background_image<'p>(&mut self, path: impl Into>) -> &mut Self { 34 | self.background_image_flipped(path, false, false) 35 | } 36 | 37 | fn background_image_flipped<'p>( 38 | &mut self, 39 | path: impl Into>, 40 | flip_x: bool, 41 | flip_y: bool, 42 | ) -> &mut Self { 43 | let texture = match path.into() { 44 | MaybeHandleOrPath::Handle(h) => Some(h), 45 | MaybeHandleOrPath::Path(p) => Some(self.load_asset::(p)), 46 | MaybeHandleOrPath::None => None, 47 | }; 48 | match (texture, self.target.get_mut::()) { 49 | (Some(texture), Some(mut uii)) => { 50 | uii.image = texture; 51 | uii.flip_x = flip_x; 52 | uii.flip_y = flip_y; 53 | } 54 | (Some(texture), None) => { 55 | self.target.insert(ImageNode { 56 | image: texture, 57 | flip_x, 58 | flip_y, 59 | ..default() 60 | }); 61 | } 62 | (None, Some(_)) => { 63 | self.target.remove::(); 64 | } 65 | _ => (), 66 | }; 67 | self 68 | } 69 | 70 | fn background_color(&mut self, color: impl ColorParam) -> &mut Self { 71 | if let Some(color) = color.to_val() { 72 | self.target.insert(ui::BackgroundColor(color)); 73 | } else { 74 | self.target.remove::(); 75 | } 76 | self 77 | } 78 | 79 | fn background_image_color(&mut self, color: impl ColorParam) -> &mut Self { 80 | match (color.to_val(), self.target.get_mut::()) { 81 | (Some(color), Some(mut uii)) => { 82 | uii.color = color; 83 | } 84 | (Some(color), None) => { 85 | self.target.insert(ImageNode { color, ..default() }); 86 | } 87 | (None, Some(_)) => { 88 | self.target.remove::(); 89 | } 90 | _ => (), 91 | }; 92 | self 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_border_color.rs: -------------------------------------------------------------------------------- 1 | use bevy::ui; 2 | 3 | use super::style_builder::StyleBuilder; 4 | use crate::{style_commands::StyleCommands, ColorParam}; 5 | 6 | #[allow(missing_docs)] 7 | pub trait StyleBuilderBorderColor { 8 | fn border_color(&mut self, color: impl ColorParam) -> &mut Self; 9 | } 10 | 11 | impl<'a, 'w> StyleBuilderBorderColor for StyleBuilder<'a, 'w> { 12 | fn border_color(&mut self, color: impl ColorParam) -> &mut Self { 13 | if let Some(color) = color.to_val() { 14 | self.target.insert(ui::BorderColor(color)); 15 | } else { 16 | self.target.remove::(); 17 | } 18 | self 19 | } 20 | } 21 | 22 | impl<'a, 'w> StyleBuilderBorderColor for StyleCommands<'a, 'w> { 23 | fn border_color(&mut self, color: impl ColorParam) -> &mut Self { 24 | if let Some(color) = color.to_val() { 25 | self.target.insert(ui::BorderColor(color)); 26 | } else { 27 | self.target.remove::(); 28 | } 29 | self 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_border_radius.rs: -------------------------------------------------------------------------------- 1 | use super::style_builder::StyleBuilder; 2 | use crate::{style_commands::StyleCommands, BorderRadiusParam}; 3 | 4 | #[allow(missing_docs)] 5 | pub trait StyleBuilderBorderRadius { 6 | fn border_radius(&mut self, radius: impl BorderRadiusParam) -> &mut Self; 7 | } 8 | 9 | impl<'a, 'w> StyleBuilderBorderRadius for StyleBuilder<'a, 'w> { 10 | fn border_radius(&mut self, radius: impl BorderRadiusParam) -> &mut Self { 11 | self.target.insert(radius.to_border_radius()); 12 | self 13 | } 14 | } 15 | 16 | impl<'a, 'w> StyleBuilderBorderRadius for StyleCommands<'a, 'w> { 17 | fn border_radius(&mut self, radius: impl BorderRadiusParam) -> &mut Self { 18 | self.target.insert(radius.to_border_radius()); 19 | self 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_font.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use crate::text_styles::InheritableFontColor; 4 | use crate::{InheritableFont, InheritableFontSize, MaybeHandleOrPath}; 5 | 6 | use crate::{ColorParam, OptFloatParam, StyleBuilder, StyleCommands}; 7 | use bevy::prelude::*; 8 | 9 | pub trait StyleBuilderFont { 10 | fn color(&mut self, color: impl ColorParam) -> &mut Self; 11 | fn font<'p>(&mut self, path: impl Into>) -> &mut Self; 12 | fn font_size(&mut self, val: impl OptFloatParam) -> &mut Self; 13 | } 14 | 15 | impl<'a, 'w> StyleBuilderFont for StyleBuilder<'a, 'w> { 16 | fn color(&mut self, color: impl ColorParam) -> &mut Self { 17 | match color.to_val() { 18 | Some(color) => self.target.insert(InheritableFontColor(color)), 19 | None => self.target.remove::(), 20 | }; 21 | self 22 | } 23 | 24 | fn font<'p>(&mut self, path: impl Into>) -> &mut Self { 25 | let font = match path.into() { 26 | MaybeHandleOrPath::Handle(h) => Some(h), 27 | MaybeHandleOrPath::Path(p) => Some(self.load_asset::(p)), 28 | MaybeHandleOrPath::None => None, 29 | }; 30 | match font { 31 | Some(font) => self.target.insert(InheritableFont(font)), 32 | None => self.target.remove::(), 33 | }; 34 | self 35 | } 36 | 37 | fn font_size(&mut self, val: impl OptFloatParam) -> &mut Self { 38 | match val.to_val() { 39 | Some(size) => self.target.insert(InheritableFontSize(size)), 40 | None => self.target.remove::(), 41 | }; 42 | self 43 | } 44 | } 45 | 46 | impl<'a, 'w> StyleBuilderFont for StyleCommands<'a, 'w> { 47 | fn color(&mut self, color: impl ColorParam) -> &mut Self { 48 | match color.to_val() { 49 | Some(color) => self.target.insert(InheritableFontColor(color)), 50 | None => self.target.remove::(), 51 | }; 52 | self 53 | } 54 | 55 | fn font<'p>(&mut self, path: impl Into>) -> &mut Self { 56 | let font = match path.into() { 57 | MaybeHandleOrPath::Handle(h) => Some(h), 58 | MaybeHandleOrPath::Path(p) => Some(self.load_asset::(p)), 59 | MaybeHandleOrPath::None => None, 60 | }; 61 | match font { 62 | Some(font) => self.target.insert(InheritableFont(font)), 63 | None => self.target.remove::(), 64 | }; 65 | self 66 | } 67 | 68 | fn font_size(&mut self, val: impl OptFloatParam) -> &mut Self { 69 | match val.to_val() { 70 | Some(size) => self.target.insert(InheritableFontSize(size)), 71 | None => self.target.remove::(), 72 | }; 73 | self 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_outline.rs: -------------------------------------------------------------------------------- 1 | use crate::{ColorParam, LengthParam, StyleBuilder, StyleCommands}; 2 | use bevy::ui; 3 | 4 | #[allow(missing_docs)] 5 | pub trait StyleBuilderOutline { 6 | fn outline_color(&mut self, color: impl ColorParam) -> &mut Self; 7 | fn outline_width(&mut self, length: impl LengthParam) -> &mut Self; 8 | fn outline_offset(&mut self, length: impl LengthParam) -> &mut Self; 9 | } 10 | 11 | impl<'a, 'w> StyleBuilderOutline for StyleBuilder<'a, 'w> { 12 | fn outline_color(&mut self, color: impl ColorParam) -> &mut Self { 13 | match (color.to_val(), self.target.get_mut::()) { 14 | (Some(color), Some(mut outline)) => { 15 | outline.color = color; 16 | } 17 | (None, Some(_)) => { 18 | self.target.remove::(); 19 | } 20 | (Some(color), None) => { 21 | self.target.insert(ui::Outline { 22 | color, 23 | ..Default::default() 24 | }); 25 | } 26 | (None, None) => (), 27 | }; 28 | self 29 | } 30 | 31 | fn outline_width(&mut self, length: impl LengthParam) -> &mut Self { 32 | match self.target.get_mut::() { 33 | Some(mut outline) => { 34 | outline.width = length.to_val(); 35 | } 36 | None => { 37 | self.target.insert(ui::Outline { 38 | width: length.to_val(), 39 | ..Default::default() 40 | }); 41 | } 42 | } 43 | self 44 | } 45 | 46 | fn outline_offset(&mut self, length: impl LengthParam) -> &mut Self { 47 | match self.target.get_mut::() { 48 | Some(mut outline) => { 49 | outline.offset = length.to_val(); 50 | } 51 | None => { 52 | self.target.insert(ui::Outline { 53 | offset: length.to_val(), 54 | ..Default::default() 55 | }); 56 | } 57 | } 58 | self 59 | } 60 | } 61 | 62 | impl<'a, 'w> StyleBuilderOutline for StyleCommands<'a, 'w> { 63 | fn outline_color(&mut self, color: impl ColorParam) -> &mut Self { 64 | match color.to_val() { 65 | None => { 66 | self.target.remove::(); 67 | } 68 | Some(color) => { 69 | self.target.insert(ui::Outline { 70 | color, 71 | ..Default::default() 72 | }); 73 | } 74 | }; 75 | self 76 | } 77 | 78 | fn outline_width(&mut self, length: impl LengthParam) -> &mut Self { 79 | self.target.insert(ui::Outline { 80 | width: length.to_val(), 81 | ..Default::default() 82 | }); 83 | self 84 | } 85 | 86 | fn outline_offset(&mut self, length: impl LengthParam) -> &mut Self { 87 | self.target.insert(ui::Outline { 88 | offset: length.to_val(), 89 | ..Default::default() 90 | }); 91 | self 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_pointer_events.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::PickingBehavior; 2 | 3 | use crate::style_commands::StyleCommands; 4 | 5 | use super::style_builder::StyleBuilder; 6 | 7 | #[allow(missing_docs)] 8 | pub trait StyleBuilderPointerEvents { 9 | fn pointer_events(&mut self, enabled: bool) -> &mut Self; 10 | } 11 | 12 | impl<'a, 'w> StyleBuilderPointerEvents for StyleBuilder<'a, 'w> { 13 | fn pointer_events(&mut self, enabled: bool) -> &mut Self { 14 | match enabled { 15 | true => self.target.remove::(), 16 | false => self.target.insert(PickingBehavior { 17 | should_block_lower: false, 18 | is_hoverable: false, 19 | }), 20 | }; 21 | self 22 | } 23 | } 24 | 25 | impl<'a, 'w> StyleBuilderPointerEvents for StyleCommands<'a, 'w> { 26 | fn pointer_events(&mut self, enabled: bool) -> &mut Self { 27 | match enabled { 28 | true => self.target.remove::(), 29 | false => self.target.insert(PickingBehavior { 30 | should_block_lower: false, 31 | is_hoverable: false, 32 | }), 33 | }; 34 | self 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_texture_atlas.rs: -------------------------------------------------------------------------------- 1 | use bevy::{sprite::TextureAtlas, ui::UiTextureAtlasImage}; 2 | 3 | use super::builder::{MaybeHandleOrPath, StyleBuilder}; 4 | 5 | #[allow(missing_docs)] 6 | pub trait StyleBuilderTextureAtlas { 7 | /// Set the background image of the target entity. 8 | fn texture_atlas<'p>(&mut self, path: impl Into>) -> &mut Self; 9 | 10 | /// Set the index of which tile is being used in the texture atlas 11 | fn texture_atlas_tile(&mut self, index: usize) -> &mut Self; 12 | 13 | /// Set the index of which tile is being used in the texture atlas, and also explicitly 14 | /// configure the horizontal and vertical flip. 15 | fn texture_atlas_tile_flipped( 16 | &mut self, 17 | tile_index: usize, 18 | flip_x: bool, 19 | flip_y: bool, 20 | ) -> &mut Self; 21 | } 22 | 23 | impl<'a, 'w> StyleBuilderTextureAtlas for StyleBuilder<'a, 'w> { 24 | fn texture_atlas<'p>(&mut self, path: impl Into>) -> &mut Self { 25 | todo!("Implement texture atlas loading"); 26 | // let texture = path 27 | // .to_path() 28 | // .map(|p| self.load_asset::(p)) 29 | // .unwrap(); 30 | // self.target.insert(texture); 31 | // self 32 | } 33 | 34 | fn texture_atlas_tile(&mut self, index: usize) -> &mut Self { 35 | match self.target.get_mut::() { 36 | Some(mut uii) => { 37 | uii.index = index; 38 | } 39 | None => { 40 | self.target.insert(UiTextureAtlasImage { 41 | index, 42 | flip_x: false, 43 | flip_y: false, 44 | }); 45 | } 46 | }; 47 | self 48 | } 49 | 50 | fn texture_atlas_tile_flipped( 51 | &mut self, 52 | index: usize, 53 | flip_x: bool, 54 | flip_y: bool, 55 | ) -> &mut Self { 56 | match self.target.get_mut::() { 57 | Some(mut uii) => { 58 | uii.index = index; 59 | uii.flip_x = flip_x; 60 | uii.flip_y = flip_y; 61 | } 62 | None => { 63 | self.target.insert(UiTextureAtlasImage { 64 | index, 65 | flip_x, 66 | flip_y, 67 | }); 68 | } 69 | }; 70 | self 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_visibility.rs: -------------------------------------------------------------------------------- 1 | use crate::style_commands::StyleCommands; 2 | 3 | use super::style_builder::StyleBuilder; 4 | use bevy::render::view::Visibility; 5 | 6 | #[allow(missing_docs)] 7 | pub trait StyleBuilderVisibility { 8 | fn visible(&mut self, visible: bool) -> &mut Self; 9 | } 10 | 11 | impl<'a, 'w> StyleBuilderVisibility for StyleBuilder<'a, 'w> { 12 | fn visible(&mut self, visible: bool) -> &mut Self { 13 | let visibility = if visible { 14 | Visibility::Visible 15 | } else { 16 | Visibility::Hidden 17 | }; 18 | match self.target.get_mut::() { 19 | Some(mut vis) => { 20 | if *vis != visibility { 21 | *vis = visibility 22 | } 23 | } 24 | None => { 25 | self.target.insert(visibility); 26 | } 27 | }; 28 | self 29 | } 30 | } 31 | 32 | impl<'a, 'w> StyleBuilderVisibility for StyleCommands<'a, 'w> { 33 | fn visible(&mut self, visible: bool) -> &mut Self { 34 | let visibility = if visible { 35 | Visibility::Visible 36 | } else { 37 | Visibility::Hidden 38 | }; 39 | self.target.insert(visibility); 40 | self 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/builder_z_index.rs: -------------------------------------------------------------------------------- 1 | use bevy::ui::ZIndex; 2 | 3 | use crate::{style_builder::StyleBuilder, style_commands::StyleCommands, ZIndexParam}; 4 | 5 | #[allow(missing_docs)] 6 | pub trait StyleBuilderZIndex { 7 | fn z_index(&mut self, index: impl ZIndexParam) -> &mut Self; 8 | } 9 | 10 | impl<'a, 'w> StyleBuilderZIndex for StyleBuilder<'a, 'w> { 11 | fn z_index(&mut self, index: impl ZIndexParam) -> &mut Self { 12 | match index.to_val() { 13 | ZIndex(0) => self.target.remove::(), 14 | val => self.target.insert(val), 15 | }; 16 | self 17 | } 18 | } 19 | 20 | impl<'a, 'w> StyleBuilderZIndex for StyleCommands<'a, 'w> { 21 | fn z_index(&mut self, index: impl ZIndexParam) -> &mut Self { 22 | match index.to_val() { 23 | ZIndex(0) => self.target.remove::(), 24 | val => self.target.insert(val), 25 | }; 26 | self 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/style_builder.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | //! Defines fluent builder for styles. 3 | 4 | use bevy::{asset::AssetPath, prelude::*, ui}; 5 | 6 | /// An object that provides a fluent interface for defining styles for bevy_ui nodes. 7 | /// Most components such as `BackgroundColor` are mutated immediately, however some component types 8 | /// such as `Node` are cached in the builder and not applied until `finish` is called. 9 | pub struct StyleBuilder<'a, 'w> { 10 | pub target: &'a mut EntityWorldMut<'w>, 11 | pub(crate) style: ui::Node, 12 | pub(crate) style_changed: bool, 13 | } 14 | 15 | impl<'a, 'w> StyleBuilder<'a, 'w> { 16 | /// Construct a new StyleBuilder instance. 17 | pub fn new(target: &'a mut EntityWorldMut<'w>, style: ui::Node) -> Self { 18 | Self { 19 | target, 20 | style, 21 | style_changed: false, 22 | } 23 | } 24 | 25 | /// Helper method for loading assets. 26 | pub fn load_asset(&mut self, path: AssetPath<'_>) -> Handle { 27 | self.target.world_scope(|world| { 28 | let server = world.get_resource::().unwrap(); 29 | server.load(path) 30 | }) 31 | } 32 | 33 | /// Consumes the [`StyleBuilder`] and applies the style to the target entity. 34 | pub fn finish(self) { 35 | if self.style_changed { 36 | self.target.insert(self.style); 37 | } 38 | } 39 | } 40 | 41 | // LineBreak(BreakLineOn), 42 | -------------------------------------------------------------------------------- /crates/bevy_mod_stylebuilder/src/style_commands.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | //! Defines fluent builder for styles. 3 | 4 | use bevy::{asset::AssetPath, prelude::*, ui}; 5 | 6 | /// A builder object that provides a fluent interface for defining styles for bevy_ui nodes. 7 | /// Most components such as `BackgroundColor` are mutated directly, however some component types 8 | /// such as `Node` are cached in the builder and not applied until `finish` is called. 9 | /// This is similar to the `StyleBuilder` but provides a commands-based interface. 10 | pub struct StyleCommands<'a, 'w> { 11 | pub target: &'a mut EntityCommands<'w>, 12 | pub(crate) asset_server: &'a AssetServer, 13 | pub(crate) style: ui::Node, 14 | pub(crate) style_changed: bool, 15 | } 16 | 17 | impl<'a, 'w> StyleCommands<'a, 'w> { 18 | /// Construct a new StyleBuilder instance. 19 | pub fn new( 20 | target: &'a mut EntityCommands<'w>, 21 | asset_server: &'a AssetServer, 22 | style: ui::Node, 23 | ) -> Self { 24 | Self { 25 | target, 26 | asset_server, 27 | style, 28 | style_changed: false, 29 | } 30 | } 31 | 32 | /// Helper method for loading assets. 33 | pub fn load_asset(&mut self, path: AssetPath<'_>) -> Handle { 34 | self.asset_server.load(path) 35 | } 36 | 37 | /// Consumes the [`StyleBuilder`] and applies the style to the target entity. 38 | pub fn finish(self) { 39 | if self.style_changed { 40 | self.target.insert(self.style); 41 | } 42 | } 43 | } 44 | 45 | // LineBreak(BreakLineOn), 46 | -------------------------------------------------------------------------------- /crates/bevy_picking_backdrop/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_picking_backdrop" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bevy = { workspace = true } 9 | bevy_mod_picking = { workspace = true } 10 | # bevy_app = { git = "https://github.com/bevyengine/bevy.git" } 11 | # bevy_ecs = { git = "https://github.com/bevyengine/bevy.git" } 12 | # bevy_reflect = { git = "https://github.com/bevyengine/bevy.git" } 13 | # bevy_render = { git = "https://github.com/bevyengine/bevy.git" } 14 | # bevy_picking_core = "0.18.0" 15 | -------------------------------------------------------------------------------- /crates/bevy_picking_backdrop/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A backend for `bevy_mod_picking` that always returns a hit. 2 | //! 3 | //! # Usage 4 | //! 5 | //! This backend always registers a hit on the designated backdrop entity, but it's order 6 | //! is set to be lower than the camera's order, so it will not interfere with other hits. 7 | 8 | #![allow(clippy::too_many_arguments, clippy::type_complexity)] 9 | #![deny(missing_docs)] 10 | 11 | use bevy::prelude::*; 12 | use bevy_mod_picking::{ 13 | backend::{ray::RayMap, HitData, PointerHits}, 14 | picking_core::PickSet, 15 | }; 16 | // use bevy_app::prelude::*; 17 | // use bevy_ecs::prelude::*; 18 | // use bevy_reflect::prelude::*; 19 | // use bevy_render::prelude::*; 20 | 21 | // use bevy_picking_core::backend::prelude::*; 22 | 23 | /// Marks a camera that should be used in the backdrop picking backend. 24 | /// Also marks the entity which is used as the backdrop. 25 | #[derive(Debug, Clone, Default, Component, Reflect)] 26 | #[reflect(Component, Default)] 27 | pub struct BackdropPickable; 28 | 29 | /// Adds the raycasting picking backend to your app. 30 | #[derive(Clone)] 31 | pub struct BackdropBackend; 32 | impl Plugin for BackdropBackend { 33 | fn build(&self, app: &mut App) { 34 | // app.add_systems(PreUpdate, update_hits.in_set(PickSet::Backend)) 35 | // .register_type::(); 36 | } 37 | } 38 | 39 | // / Returns a hit on the camera backdrop. 40 | // pub fn update_hits( 41 | // ray_map: Res, 42 | // picking_cameras: Query<&Camera, With>, 43 | // picking_backdrop: Query<(Entity, &BackdropPickable), Without>, 44 | // mut output_events: EventWriter, 45 | // ) { 46 | // let backdrop = picking_backdrop.get_single().unwrap(); 47 | 48 | // for (&ray_id, &_ray) in ray_map.map().iter() { 49 | // let Ok(camera) = picking_cameras.get(ray_id.camera) else { 50 | // continue; 51 | // }; 52 | 53 | // let hit_data = HitData::new(ray_id.camera, f32::MAX, None, None); 54 | // let picks = Vec::from([(backdrop.0, hit_data)]); 55 | // let order = camera.order as f32 - 1.0; 56 | // output_events.send(PointerHits::new(ray_id.pointer, picks, order)); 57 | // } 58 | // } 59 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_reactor_builder" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bevy = { workspace = true } 8 | bevy_mod_stylebuilder = { workspace = true } 9 | bevy_reactor_signals = { workspace = true } 10 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/effect.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | core::Name, 3 | prelude::{BuildChildren, Entity, EntityWorldMut, World}, 4 | ui::experimental::GhostNode, 5 | }; 6 | use bevy_reactor_signals::{Rcx, Reaction, ReactionCell, TrackingScope}; 7 | 8 | pub trait EntityEffectBuilder { 9 | fn effect< 10 | D: 'static, 11 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 12 | SF: Fn(D, &mut EntityWorldMut) + Send + Sync + 'static, 13 | >( 14 | &mut self, 15 | deps_fn: VF, 16 | style: SF, 17 | ) -> &mut Self; 18 | } 19 | 20 | impl<'w> EntityEffectBuilder for EntityWorldMut<'w> { 21 | fn effect< 22 | D: 'static, 23 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 24 | SF: Fn(D, &mut EntityWorldMut) + Send + Sync + 'static, 25 | >( 26 | &mut self, 27 | deps_fn: VF, 28 | effect_fn: SF, 29 | ) -> &mut Self { 30 | let mut scope = TrackingScope::new(self.world().last_change_tick()); 31 | let reaction = TargetedEffectReaction { 32 | target: self.id(), 33 | deps_fn, 34 | effect_fn, 35 | }; 36 | let owner = self.id(); 37 | self.world_scope(|world| { 38 | // Spawn a new reaction entity to contain the effect. 39 | let effect_owner = world.spawn(Name::new("Effect")).set_parent(owner).id(); 40 | reaction.apply(effect_owner, world, &mut scope); 41 | world.entity_mut(effect_owner).insert(( 42 | scope, 43 | ReactionCell::new(reaction), 44 | GhostNode::default(), 45 | )); 46 | }); 47 | self 48 | } 49 | } 50 | 51 | struct TargetedEffectReaction D, SF: Fn(D, &mut EntityWorldMut)> { 52 | target: Entity, 53 | deps_fn: VF, 54 | effect_fn: SF, 55 | } 56 | 57 | impl< 58 | D, 59 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 60 | SF: Fn(D, &mut EntityWorldMut) + Send + Sync + 'static, 61 | > TargetedEffectReaction 62 | { 63 | fn apply(&self, _owner: Entity, world: &mut World, tracking: &mut TrackingScope) { 64 | let rcx = Rcx::new(world, self.target, tracking); 65 | let val = (self.deps_fn)(&rcx); 66 | 67 | let mut target = world.entity_mut(self.target); 68 | (self.effect_fn)(val, &mut target); 69 | } 70 | } 71 | 72 | impl< 73 | D, 74 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 75 | SF: Fn(D, &mut EntityWorldMut) + Send + Sync + 'static, 76 | > Reaction for TargetedEffectReaction 77 | { 78 | fn react(&mut self, owner: Entity, world: &mut World, tracking: &mut TrackingScope) { 79 | self.apply(owner, world, tracking); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/insert.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, ui::experimental::GhostNode}; 2 | use bevy_reactor_signals::{Rcx, Reaction, ReactionCell, TrackingScope}; 3 | 4 | use crate::test_condition::TestCondition; 5 | 6 | pub trait InsertComponentBuilder { 7 | /// Add a static bundle to the element, when the condition is true. 8 | /// Removes the component when the condition is false. 9 | fn insert_if C + Send + Sync + 'static>( 10 | &mut self, 11 | condition: T, 12 | factory: F, 13 | ) -> &mut Self; 14 | } 15 | 16 | impl<'w> InsertComponentBuilder for EntityWorldMut<'w> { 17 | fn insert_if C + Send + Sync + 'static>( 18 | &mut self, 19 | condition: T, 20 | factory: F, 21 | ) -> &mut Self { 22 | let mut scope = TrackingScope::new(self.world().last_change_tick()); 23 | let mut reaction = ConditionalInsertComponentReaction { 24 | target: self.id(), 25 | condition, 26 | factory, 27 | prev_state: false, 28 | }; 29 | let owner = self.id(); 30 | self.world_scope(|world| { 31 | // Spawn a new reaction entity to contain the effect. 32 | let effect_owner = world.spawn_empty().set_parent(owner).id(); 33 | reaction.react(effect_owner, world, &mut scope); 34 | world.entity_mut(effect_owner).insert(( 35 | scope, 36 | ReactionCell::new(reaction), 37 | GhostNode::default(), 38 | )); 39 | }); 40 | self 41 | } 42 | } 43 | 44 | pub struct ConditionalInsertComponentReaction< 45 | C: Component, 46 | T: TestCondition, 47 | F: Fn() -> C + Send + Sync, 48 | > { 49 | target: Entity, 50 | factory: F, 51 | condition: T, 52 | prev_state: bool, 53 | } 54 | 55 | impl C + Send + Sync> Reaction 56 | for ConditionalInsertComponentReaction 57 | { 58 | fn react(&mut self, _owner: Entity, world: &mut World, tracking: &mut TrackingScope) { 59 | let rcx = Rcx::new(world, self.target, tracking); 60 | let condition = self.condition.test(&rcx); 61 | if condition != self.prev_state { 62 | self.prev_state = condition; 63 | let mut target = world.entity_mut(self.target); 64 | if condition { 65 | target.insert((self.factory)()); 66 | } else { 67 | target.remove::(); 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/lcs.rs: -------------------------------------------------------------------------------- 1 | /// Longest common substring 2 | pub fn lcs(arr1: &[T1], arr2: &[T2], comparator: F) -> (usize, usize, usize) 3 | where 4 | F: Fn(&T1, &T2) -> bool, 5 | { 6 | let mut longest = 0; 7 | let mut arr1_offset = 0; 8 | let mut arr2_offset = 0; 9 | let mut dp = vec![vec![0; arr2.len() + 1]; arr1.len() + 1]; 10 | 11 | for i in 1..=arr1.len() { 12 | for j in 1..=arr2.len() { 13 | if comparator(&arr1[i - 1], &arr2[j - 1]) { 14 | dp[i][j] = dp[i - 1][j - 1] + 1; 15 | if dp[i][j] > longest { 16 | longest = dp[i][j]; 17 | arr1_offset = i - longest; 18 | arr2_offset = j - longest; 19 | } 20 | } 21 | } 22 | } 23 | 24 | (arr1_offset, arr2_offset, longest) 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::lcs; 30 | 31 | #[test] 32 | fn test_empty() { 33 | let a: Vec = vec![]; 34 | let b: Vec = vec![]; 35 | 36 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 37 | assert_eq!(start_a, 0); 38 | assert_eq!(start_b, 0); 39 | assert_eq!(length, 0); 40 | } 41 | 42 | #[test] 43 | fn test_empty_left() { 44 | let a: Vec = vec![]; 45 | let b: Vec = vec![0, 1]; 46 | 47 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 48 | assert_eq!(start_a, 0); 49 | assert_eq!(start_b, 0); 50 | assert_eq!(length, 0); 51 | } 52 | 53 | #[test] 54 | fn test_empty_right() { 55 | let a: Vec = vec![]; 56 | let b: Vec = vec![0, 1]; 57 | 58 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 59 | assert_eq!(start_a, 0); 60 | assert_eq!(start_b, 0); 61 | assert_eq!(length, 0); 62 | } 63 | 64 | #[test] 65 | fn test_different() { 66 | let a: Vec = vec![1, 2]; 67 | let b: Vec = vec![3, 4]; 68 | 69 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 70 | assert_eq!(start_a, 0); 71 | assert_eq!(start_b, 0); 72 | assert_eq!(length, 0); 73 | } 74 | 75 | #[test] 76 | fn test_same() { 77 | let a: Vec = vec![1, 2, 3]; 78 | let b: Vec = vec![1, 2, 3]; 79 | 80 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 81 | assert_eq!(start_a, 0); 82 | assert_eq!(start_b, 0); 83 | assert_eq!(length, 3); 84 | } 85 | 86 | #[test] 87 | fn test_insert_start() { 88 | let a: Vec = vec![0, 1, 2, 3]; 89 | let b: Vec = vec![1, 2, 3]; 90 | 91 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 92 | assert_eq!(start_a, 1); 93 | assert_eq!(start_b, 0); 94 | assert_eq!(length, 3); 95 | } 96 | 97 | #[test] 98 | fn test_insert_end() { 99 | let a: Vec = vec![1, 2, 3, 4]; 100 | let b: Vec = vec![1, 2, 3]; 101 | 102 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 103 | assert_eq!(start_a, 0); 104 | assert_eq!(start_b, 0); 105 | assert_eq!(length, 3); 106 | } 107 | 108 | #[test] 109 | fn test_diff() { 110 | let a = vec![3, 1, 4, 1, 5, 9, 2, 6, 5]; 111 | let b = vec![2, 7, 1, 8, 2, 8, 1, 8, 2, 8, 4, 5, 9, 0]; 112 | 113 | let (start_a, start_b, length) = lcs(&a, &b, |x, y| x == y); 114 | assert_eq!(start_a, 4); 115 | assert_eq!(start_b, 11); 116 | assert_eq!(length, 2); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod cond; 2 | mod effect; 3 | mod for_each; 4 | mod for_index; 5 | mod insert; 6 | mod lcs; 7 | mod style; 8 | mod switch; 9 | mod test_condition; 10 | mod text; 11 | mod ui_builder; 12 | mod ui_template; 13 | 14 | pub use cond::CondBuilder; 15 | pub use effect::EntityEffectBuilder; 16 | pub use for_each::ForEachBuilder; 17 | pub use for_index::ForIndexBuilder; 18 | pub use insert::InsertComponentBuilder; 19 | pub use style::EntityStyleBuilder; 20 | pub use switch::SwitchBuilder; 21 | pub use text::TextBuilder; 22 | pub use ui_builder::{CreateChilden, UiBuilder}; 23 | pub use ui_template::{InvokeUiTemplate, UiTemplate}; 24 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/style.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | prelude::{BuildChildren, Entity, EntityWorldMut, World}, 3 | ui::{self, experimental::GhostNode}, 4 | }; 5 | use bevy_mod_stylebuilder::{StyleBuilder, StyleTuple}; 6 | use bevy_reactor_signals::{Rcx, Reaction, ReactionCell, TrackingScope}; 7 | 8 | pub trait EntityStyleBuilder { 9 | fn style(&mut self, style: S) -> &mut Self; 10 | fn styles(&mut self, styles: impl StyleTuple) -> &mut Self; 11 | fn style_dyn< 12 | D: 'static, 13 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 14 | SF: Fn(D, &mut StyleBuilder) + Send + Sync + 'static, 15 | >( 16 | &mut self, 17 | deps_fn: VF, 18 | style: SF, 19 | ) -> &mut Self; 20 | } 21 | 22 | impl<'w> EntityStyleBuilder for EntityWorldMut<'w> { 23 | fn style(&mut self, style_fn: S) -> &mut Self { 24 | let mut style = ui::Node::default(); 25 | if let Some(s) = self.get::() { 26 | style.clone_from(s); 27 | } 28 | let mut sb = StyleBuilder::new(self, style); 29 | style_fn(&mut sb); 30 | sb.finish(); 31 | self 32 | } 33 | 34 | fn styles(&mut self, styles: impl StyleTuple) -> &mut Self { 35 | let mut style = ui::Node::default(); 36 | if let Some(s) = self.get::() { 37 | style.clone_from(s); 38 | } 39 | let mut sb = StyleBuilder::new(self, style); 40 | styles.apply(&mut sb); 41 | sb.finish(); 42 | self 43 | } 44 | 45 | fn style_dyn< 46 | D: 'static, 47 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 48 | SF: Fn(D, &mut StyleBuilder) + Send + Sync + 'static, 49 | >( 50 | &mut self, 51 | deps_fn: VF, 52 | style_fn: SF, 53 | ) -> &mut Self { 54 | let mut scope = TrackingScope::new(self.world().last_change_tick()); 55 | let reaction = DynamicStyleReaction { 56 | target: self.id(), 57 | deps_fn, 58 | style_fn, 59 | }; 60 | let owner = self.id(); 61 | self.world_scope(|world| { 62 | // Spawn a new reaction entity to contain the effect. 63 | let effect_owner = world.spawn_empty().set_parent(owner).id(); 64 | reaction.apply(effect_owner, world, &mut scope); 65 | world.entity_mut(effect_owner).insert(( 66 | scope, 67 | ReactionCell::new(reaction), 68 | GhostNode::default(), 69 | )); 70 | }); 71 | self 72 | } 73 | } 74 | 75 | struct DynamicStyleReaction D, SF: Fn(D, &mut StyleBuilder)> { 76 | target: Entity, 77 | deps_fn: VF, 78 | style_fn: SF, 79 | } 80 | 81 | impl< 82 | D, 83 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 84 | SF: Fn(D, &mut StyleBuilder) + Send + Sync + 'static, 85 | > DynamicStyleReaction 86 | { 87 | fn apply(&self, _owner: Entity, world: &mut World, tracking: &mut TrackingScope) { 88 | let rcx = Rcx::new(world, self.target, tracking); 89 | let val = (self.deps_fn)(&rcx); 90 | 91 | let mut target = world.entity_mut(self.target); 92 | let mut style = ui::Node::default(); 93 | if let Some(s) = target.get::() { 94 | style.clone_from(s); 95 | } 96 | let mut sb = StyleBuilder::new(&mut target, style); 97 | (self.style_fn)(val, &mut sb); 98 | sb.finish(); 99 | } 100 | } 101 | 102 | impl< 103 | D, 104 | VF: Fn(&Rcx) -> D + Send + Sync + 'static, 105 | SF: Fn(D, &mut StyleBuilder) + Send + Sync + 'static, 106 | > Reaction for DynamicStyleReaction 107 | { 108 | fn react(&mut self, owner: Entity, world: &mut World, tracking: &mut TrackingScope) { 109 | self.apply(owner, world, tracking); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/test_condition.rs: -------------------------------------------------------------------------------- 1 | use bevy_reactor_signals::{Rcx, Signal}; 2 | 3 | /// Trait that abstracts over the boolean condition that controls the If. We use this trait 4 | /// to allow boolean signals to be passed directly as conditions. 5 | pub trait TestCondition: Send + Sync { 6 | fn test(&self, rcx: &Rcx) -> bool; 7 | } 8 | 9 | impl bool> TestCondition for F { 10 | fn test(&self, rcx: &Rcx) -> bool { 11 | self(rcx) 12 | } 13 | } 14 | 15 | impl TestCondition for bool { 16 | fn test(&self, _rcx: &Rcx) -> bool { 17 | *self 18 | } 19 | } 20 | 21 | impl TestCondition for Signal { 22 | fn test(&self, rcx: &Rcx) -> bool { 23 | self.get(rcx) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/bevy_reactor_builder/src/ui_template.rs: -------------------------------------------------------------------------------- 1 | use crate::UiBuilder; 2 | 3 | pub trait UiTemplate { 4 | fn build(&self, builder: &mut UiBuilder); 5 | } 6 | 7 | pub trait InvokeUiTemplate { 8 | fn invoke(&mut self, template: T) -> &mut Self; 9 | } 10 | 11 | impl<'w> InvokeUiTemplate for UiBuilder<'w> { 12 | fn invoke(&mut self, template: T) -> &mut Self { 13 | template.build(self); 14 | self 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /crates/bevy_reactor_inspect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_reactor_inspect" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bevy = { workspace = true } 9 | bevy_mod_stylebuilder = { workspace = true } 10 | bevy_reactor_signals = { workspace = true } 11 | bevy_reactor_builder = { workspace = true } 12 | bevy_reactor_obsidian = { workspace = true } 13 | -------------------------------------------------------------------------------- /crates/bevy_reactor_inspect/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bevy::app::{Plugin, Startup, Update}; 2 | use bevy_mod_stylebuilder::StyleBuilderPlugin; 3 | use bevy_reactor_obsidian::ObsidianUiPlugin; 4 | use bevy_reactor_signals::SignalsPlugin; 5 | use inspector_panel::{copy_top_level_entities, create_inspector_panel, TopLevelEntities}; 6 | 7 | mod inspector_panel; 8 | 9 | pub struct WorldInspector; 10 | 11 | impl Plugin for WorldInspector { 12 | fn build(&self, app: &mut bevy::prelude::App) { 13 | app.init_resource::() 14 | .add_plugins((SignalsPlugin, StyleBuilderPlugin, ObsidianUiPlugin)) 15 | .add_systems(Startup, create_inspector_panel) 16 | .add_systems(Update, copy_top_level_entities); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_reactor_obsidian" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bevy = { workspace = true } 9 | bevy_mod_stylebuilder = { workspace = true } 10 | bevy_reactor_signals = { workspace = true } 11 | bevy_reactor_builder = { workspace = true } 12 | accesskit = "0.17.1" 13 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Black.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-BlackItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraLight.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-Thin.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Fira_Sans/FiraSans-ThinItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/Inter-VariableFont_opsz,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/Inter-VariableFont_opsz,wght.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Black.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-BlackItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraLight.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-Thin.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_18pt-ThinItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Black.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-BlackItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraLight.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-Thin.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_24pt-ThinItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Black.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-BlackItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-BlackItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraLight.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraLight.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraLightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ExtraLightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-Thin.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ThinItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Inter/static/Inter_28pt-ThinItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/OpenSans-Italic-VariableFont_wdth,wght.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/OpenSans-VariableFont_wdth,wght.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/README.txt: -------------------------------------------------------------------------------- 1 | Open Sans Variable Font 2 | ======================= 3 | 4 | This download contains Open Sans as both variable fonts and static fonts. 5 | 6 | Open Sans is a variable font with these axes: 7 | wdth 8 | wght 9 | 10 | This means all the styles are contained in these files: 11 | OpenSans-VariableFont_wdth,wght.ttf 12 | OpenSans-Italic-VariableFont_wdth,wght.ttf 13 | 14 | If your app fully supports variable fonts, you can now pick intermediate styles 15 | that aren’t available as static fonts. Not all apps support variable fonts, and 16 | in those cases you can use the static font files for Open Sans: 17 | static/OpenSans_Condensed-Light.ttf 18 | static/OpenSans_Condensed-Regular.ttf 19 | static/OpenSans_Condensed-Medium.ttf 20 | static/OpenSans_Condensed-SemiBold.ttf 21 | static/OpenSans_Condensed-Bold.ttf 22 | static/OpenSans_Condensed-ExtraBold.ttf 23 | static/OpenSans_SemiCondensed-Light.ttf 24 | static/OpenSans_SemiCondensed-Regular.ttf 25 | static/OpenSans_SemiCondensed-Medium.ttf 26 | static/OpenSans_SemiCondensed-SemiBold.ttf 27 | static/OpenSans_SemiCondensed-Bold.ttf 28 | static/OpenSans_SemiCondensed-ExtraBold.ttf 29 | static/OpenSans-Light.ttf 30 | static/OpenSans-Regular.ttf 31 | static/OpenSans-Medium.ttf 32 | static/OpenSans-SemiBold.ttf 33 | static/OpenSans-Bold.ttf 34 | static/OpenSans-ExtraBold.ttf 35 | static/OpenSans_Condensed-LightItalic.ttf 36 | static/OpenSans_Condensed-Italic.ttf 37 | static/OpenSans_Condensed-MediumItalic.ttf 38 | static/OpenSans_Condensed-SemiBoldItalic.ttf 39 | static/OpenSans_Condensed-BoldItalic.ttf 40 | static/OpenSans_Condensed-ExtraBoldItalic.ttf 41 | static/OpenSans_SemiCondensed-LightItalic.ttf 42 | static/OpenSans_SemiCondensed-Italic.ttf 43 | static/OpenSans_SemiCondensed-MediumItalic.ttf 44 | static/OpenSans_SemiCondensed-SemiBoldItalic.ttf 45 | static/OpenSans_SemiCondensed-BoldItalic.ttf 46 | static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf 47 | static/OpenSans-LightItalic.ttf 48 | static/OpenSans-Italic.ttf 49 | static/OpenSans-MediumItalic.ttf 50 | static/OpenSans-SemiBoldItalic.ttf 51 | static/OpenSans-BoldItalic.ttf 52 | static/OpenSans-ExtraBoldItalic.ttf 53 | 54 | Get started 55 | ----------- 56 | 57 | 1. Install the font files you want to use 58 | 59 | 2. Use your app's font picker to view the font family and all the 60 | available styles 61 | 62 | Learn more about variable fonts 63 | ------------------------------- 64 | 65 | https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts 66 | https://variablefonts.typenetwork.com 67 | https://medium.com/variable-fonts 68 | 69 | In desktop apps 70 | 71 | https://theblog.adobe.com/can-variable-fonts-illustrator-cc 72 | https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts 73 | 74 | Online 75 | 76 | https://developers.google.com/fonts/docs/getting_started 77 | https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide 78 | https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts 79 | 80 | Installing fonts 81 | 82 | MacOS: https://support.apple.com/en-us/HT201749 83 | Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux 84 | Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows 85 | 86 | Android Apps 87 | 88 | https://developers.google.com/fonts/docs/android 89 | https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts 90 | 91 | License 92 | ------- 93 | Please read the full license text (OFL.txt) to understand the permissions, 94 | restrictions and requirements for usage, redistribution, and modification. 95 | 96 | You can use them in your products & projects – print or digital, 97 | commercial or otherwise. 98 | 99 | This isn't legal advice, please consider consulting a lawyer and see the full 100 | license for all details. 101 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_Condensed-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Bold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-BoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-ExtraBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Italic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Light.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-LightItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-LightItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Medium.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-MediumItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-MediumItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-Regular.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBold.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/fonts/Open_Sans/static/OpenSans_SemiCondensed-SemiBoldItalic.ttf -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/add.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/add_box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/add_box.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/checkmark.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/chevron_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/chevron_down.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/chevron_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/chevron_left.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/chevron_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/chevron_right.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/chevron_up.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/chevron_up.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/close.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/disc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/disc.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/gradient_thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/gradient_thumb.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/lock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/lock.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/redo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/redo.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/remove.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/tune.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/tune.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/undo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/undo.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/zoom_in.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/zoom_in.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/icons/zoom_out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/bevy_reactor_obsidian/src/assets/icons/zoom_out.png -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/shaders/gradient_rect.wgsl: -------------------------------------------------------------------------------- 1 | // This shader draws a rounded rect with a given input color 2 | #import bevy_ui::ui_vertex_output::UiVertexOutput 3 | 4 | @group(1) @binding(0) 5 | var num_color_stops: vec4; 6 | 7 | @group(1) @binding(1) 8 | var color_stops: array, 8>; 9 | 10 | @group(1) @binding(3) 11 | var cap_size: vec4; 12 | 13 | @fragment 14 | fn fragment(in: UiVertexOutput) -> @location(0) vec4 { 15 | let t = (in.uv.x - 0.1) * 1.0 / 0.8 * f32(num_color_stops.x - 1); 16 | let color_index_lo = clamp(i32(floor(t)), 0, num_color_stops.x - 1); 17 | let color_index_hi = clamp(i32(ceil(t)), 0, num_color_stops.x - 1); 18 | let color_lo = color_stops[color_index_lo]; 19 | let color_hi = color_stops[color_index_hi]; 20 | let color = mix(color_lo, color_hi, t - f32(color_index_lo)); 21 | 22 | let uv = (in.uv - vec2(0.5, 0.5)) * in.size / 8.; 23 | let check = select(0.0, 1.0, (fract(uv.x) < 0.5) != (fract(uv.y) < 0.5)); 24 | let bg = mix(vec3(0.4, 0.4, 0.4), vec3(0.6, 0.6, 0.6), check); 25 | let c = srgb_to_linear(mix(bg, color.rgb, color.w)); 26 | 27 | let size = vec2(in.size.x, in.size.y); 28 | let external_distance = sd_rounded_box((in.uv - 0.5) * size, size, vec4(size.y * 0.5)); 29 | let alpha = smoothstep(0.5, -0.5, external_distance); 30 | 31 | return vec4(c, alpha); 32 | } 33 | 34 | // Convert sRGB to linear color space because we interpolate in sRGB space. 35 | fn srgb_to_linear(srgb: vec3) -> vec3 { 36 | let a = 0.055; 37 | let srgbLow = srgb / 12.92; 38 | let srgbHigh = pow((srgb + a) / (1.0 + a), vec3(2.4, 2.4, 2.4)); 39 | let linear = mix(srgbLow, srgbHigh, step(vec3(0.04045, 0.04045, 0.04045), srgb)); 40 | return linear; 41 | } 42 | 43 | // From: https://github.com/bevyengine/bevy/pull/8973 44 | // The returned value is the shortest distance from the given point to the boundary of the rounded box. 45 | // Negative values indicate that the point is inside the rounded box, positive values that the point is outside, and zero is exactly on the boundary. 46 | // arguments 47 | // point -> The function will return the distance from this point to the closest point on the boundary. 48 | // size -> The maximum width and height of the box. 49 | // corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left: 50 | // x = top left, y = top right, z = bottom right, w = bottom left. 51 | fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> f32 { 52 | // if 0.0 < y then select bottom left (w) and bottom right corner radius (z) 53 | // else select top left (x) and top right corner radius (y) 54 | let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y); 55 | // w and z are swapped so that both pairs are in left to right order, otherwise this second select statement would return the incorrect value for the bottom pair. 56 | let radius = select(rs.x, rs.y, 0.0 < point.x); 57 | // Vector from the corner closest to the point, to the point 58 | let corner_to_point = abs(point) - 0.5 * size; 59 | // Vector from the center of the radius circle to the point 60 | let q = corner_to_point + radius; 61 | // length from center of the radius circle to the point, 0s a component if the point is not within the quadrant of the radius circle that is part of the curved corner. 62 | let l = length(max(q, vec2(0.0))); 63 | let m = min(max(q.x, q.y), 0.0); 64 | return l + m - radius; 65 | } 66 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/shaders/rounded_box.wgsl: -------------------------------------------------------------------------------- 1 | // From: https://github.com/bevyengine/bevy/pull/8973 2 | // The returned value is the shortest distance from the given point to the boundary of the rounded box. 3 | // Negative values indicate that the point is inside the rounded box, positive values that the point is outside, and zero is exactly on the boundary. 4 | // arguments 5 | // point -> The function will return the distance from this point to the closest point on the boundary. 6 | // size -> The maximum width and height of the box. 7 | // corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left: 8 | // x = top left, y = top right, z = bottom right, w = bottom left. 9 | fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> f32 { 10 | // if 0.0 < y then select bottom left (w) and bottom right corner radius (z) 11 | // else select top left (x) and top right corner radius (y) 12 | let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y); 13 | // w and z are swapped so that both pairs are in left to right order, otherwise this second select statement would return the incorrect value for the bottom pair. 14 | let radius = select(rs.x, rs.y, 0.0 < point.x); 15 | // Vector from the corner closest to the point, to the point 16 | let corner_to_point = abs(point) - 0.5 * size; 17 | // Vector from the center of the radius circle to the point 18 | let q = corner_to_point + radius; 19 | // length from center of the radius circle to the point, 0s a component if the point is not within the quadrant of the radius circle that is part of the curved corner. 20 | let l = length(max(q, vec2(0.0))); 21 | let m = min(max(q.x, q.y), 0.0); 22 | return l + m - radius; 23 | } 24 | 25 | fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, inset: vec4) -> f32 { 26 | let inner_size = size - inset.xy - inset.zw; 27 | let inner_center = inset.xy + 0.5 * inner_size - 0.5 *size; 28 | let inner_point = point - inner_center; 29 | 30 | var r = radius; 31 | 32 | // top left corner 33 | r.x = r.x - max(inset.x, inset.y); 34 | 35 | // top right corner 36 | r.y = r.y - max(inset.z, inset.y); 37 | 38 | // bottom right corner 39 | r.z = r.z - max(inset.z, inset.w); 40 | 41 | // bottom left corner 42 | r.w = r.w - max(inset.x, inset.w); 43 | 44 | let half_size = inner_size * 0.5; 45 | let min = min(half_size.x, half_size.y); 46 | 47 | r = min(max(r, vec4(0.0)), vec4(min)); 48 | 49 | return sd_rounded_box(inner_point, inner_size, r); 50 | } -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/shaders/rounded_rect.wgsl: -------------------------------------------------------------------------------- 1 | // This shader draws a rounded rect with a given input color 2 | #import bevy_ui::ui_vertex_output::UiVertexOutput 3 | // #import "embedded://bevy_quill_obsidian/assets/shaders/rounded_box.wgsl"::sd_rounded_box 4 | 5 | @group(1) @binding(0) 6 | var color: vec4; 7 | 8 | @group(1) @binding(1) 9 | var radius: vec4; 10 | 11 | @fragment 12 | fn fragment(in: UiVertexOutput) -> @location(0) vec4 { 13 | // the UVs are now adjusted around the middle of the rect. 14 | let uv = in.uv - 0.5; 15 | let size = vec2(in.size.x, in.size.y); 16 | 17 | let external_distance = sd_rounded_box(uv * size, size, radius); 18 | let alpha = smoothstep(0.01, 0.0, external_distance); 19 | 20 | return vec4(color.rgb, alpha); 21 | } 22 | 23 | // From: https://github.com/bevyengine/bevy/pull/8973 24 | // The returned value is the shortest distance from the given point to the boundary of the rounded box. 25 | // Negative values indicate that the point is inside the rounded box, positive values that the point is outside, and zero is exactly on the boundary. 26 | // arguments 27 | // point -> The function will return the distance from this point to the closest point on the boundary. 28 | // size -> The maximum width and height of the box. 29 | // corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left: 30 | // x = top left, y = top right, z = bottom right, w = bottom left. 31 | fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> f32 { 32 | // if 0.0 < y then select bottom left (w) and bottom right corner radius (z) 33 | // else select top left (x) and top right corner radius (y) 34 | let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y); 35 | // w and z are swapped so that both pairs are in left to right order, otherwise this second select statement would return the incorrect value for the bottom pair. 36 | let radius = select(rs.x, rs.y, 0.0 < point.x); 37 | // Vector from the corner closest to the point, to the point 38 | let corner_to_point = abs(point) - 0.5 * size; 39 | // Vector from the center of the radius circle to the point 40 | let q = corner_to_point + radius; 41 | // length from center of the radius circle to the point, 0s a component if the point is not within the quadrant of the radius circle that is part of the curved corner. 42 | let l = length(max(q, vec2(0.0))); 43 | let m = min(max(q.x, q.y), 0.0); 44 | return l + m - radius; 45 | } 46 | 47 | fn sd_inset_rounded_box(point: vec2, size: vec2, radius: vec4, inset: vec4) -> f32 { 48 | let inner_size = size - inset.xy - inset.zw; 49 | let inner_center = inset.xy + 0.5 * inner_size - 0.5 *size; 50 | let inner_point = point - inner_center; 51 | 52 | var r = radius; 53 | 54 | // top left corner 55 | r.x = r.x - max(inset.x, inset.y); 56 | 57 | // top right corner 58 | r.y = r.y - max(inset.z, inset.y); 59 | 60 | // bottom right corner 61 | r.z = r.z - max(inset.z, inset.w); 62 | 63 | // bottom left corner 64 | r.w = r.w - max(inset.x, inset.w); 65 | 66 | let half_size = inner_size * 0.5; 67 | let min = min(half_size.x, half_size.y); 68 | 69 | r = min(max(r, vec4(0.0)), vec4(min)); 70 | 71 | return sd_rounded_box(inner_point, inner_size, r); 72 | } -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/shaders/slider_rect.wgsl: -------------------------------------------------------------------------------- 1 | // This shader draws a two-toned rounded rect for a slider widget. 2 | #import bevy_ui::ui_vertex_output::UiVertexOutput 3 | 4 | @group(1) @binding(0) 5 | var color_lo: vec4; 6 | 7 | @group(1) @binding(1) 8 | var color_hi: vec4; 9 | 10 | @group(1) @binding(2) 11 | var value: vec4; 12 | 13 | @group(1) @binding(3) 14 | var radius: vec4; 15 | 16 | @fragment 17 | fn fragment(in: UiVertexOutput) -> @location(0) vec4 { 18 | let uv = in.uv - 0.5; 19 | let size = vec2(in.size.x, in.size.y); 20 | let color = select(color_lo, color_hi, in.uv.x <= value.x); 21 | let external_distance = sd_rounded_box((in.uv - 0.5) * size, size, vec4(radius)); 22 | let alpha = smoothstep(0.5, -0.5, external_distance); 23 | 24 | return vec4(color.rgb, alpha); 25 | } 26 | 27 | // From: https://github.com/bevyengine/bevy/pull/8973 28 | // The returned value is the shortest distance from the given point to the boundary of the rounded box. 29 | // Negative values indicate that the point is inside the rounded box, positive values that the point is outside, and zero is exactly on the boundary. 30 | // arguments 31 | // point -> The function will return the distance from this point to the closest point on the boundary. 32 | // size -> The maximum width and height of the box. 33 | // corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left: 34 | // x = top left, y = top right, z = bottom right, w = bottom left. 35 | fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> f32 { 36 | // if 0.0 < y then select bottom left (w) and bottom right corner radius (z) 37 | // else select top left (x) and top right corner radius (y) 38 | let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y); 39 | // w and z are swapped so that both pairs are in left to right order, otherwise this second select statement would return the incorrect value for the bottom pair. 40 | let radius = select(rs.x, rs.y, 0.0 < point.x); 41 | // Vector from the corner closest to the point, to the point 42 | let corner_to_point = abs(point) - 0.5 * size; 43 | // Vector from the center of the radius circle to the point 44 | let q = corner_to_point + radius; 45 | // length from center of the radius circle to the point, 0s a component if the point is not within the quadrant of the radius circle that is part of the curved corner. 46 | let l = length(max(q, vec2(0.0))); 47 | let m = min(max(q.x, q.y), 0.0); 48 | return l + m - radius; 49 | } 50 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/assets/shaders/swatch_rect.wgsl: -------------------------------------------------------------------------------- 1 | // This shader draws a rounded rect with a given input color 2 | #import bevy_ui::ui_vertex_output::UiVertexOutput 3 | 4 | @group(1) @binding(0) 5 | var color: vec4; 6 | 7 | @group(1) @binding(1) 8 | var radius: vec4; 9 | 10 | @fragment 11 | fn fragment(in: UiVertexOutput) -> @location(0) vec4 { 12 | let uv = (in.uv - vec2(0.5, 0.5)) * in.size / 8.; 13 | let check = select(0.0, 1.0, (fract(uv.x) < 0.5) != (fract(uv.y) < 0.5)); 14 | let bg = mix(vec3(0.4, 0.4, 0.4), vec3(0.6, 0.6, 0.6), check); 15 | let c = srgb_to_linear(mix(bg, color.rgb, color.w)); 16 | 17 | let size = vec2(in.size.x, in.size.y); 18 | let external_distance = sd_rounded_box((in.uv - 0.5) * size, size, radius); 19 | let alpha = smoothstep(0.5, -0.5, external_distance); 20 | 21 | return vec4(c, alpha); 22 | } 23 | 24 | // Convert sRGB to linear color space because we interpolate in sRGB space. 25 | fn srgb_to_linear(srgb: vec3) -> vec3 { 26 | let a = 0.055; 27 | let srgbLow = srgb / 12.92; 28 | let srgbHigh = pow((srgb + a) / (1.0 + a), vec3(2.4, 2.4, 2.4)); 29 | let linear = mix(srgbLow, srgbHigh, step(vec3(0.04045, 0.04045, 0.04045), srgb)); 30 | return linear; 31 | } 32 | 33 | // From: https://github.com/bevyengine/bevy/pull/8973 34 | // The returned value is the shortest distance from the given point to the boundary of the rounded box. 35 | // Negative values indicate that the point is inside the rounded box, positive values that the point is outside, and zero is exactly on the boundary. 36 | // arguments 37 | // point -> The function will return the distance from this point to the closest point on the boundary. 38 | // size -> The maximum width and height of the box. 39 | // corner_radii -> The radius of each rounded corner. Ordered counter clockwise starting top left: 40 | // x = top left, y = top right, z = bottom right, w = bottom left. 41 | fn sd_rounded_box(point: vec2, size: vec2, corner_radii: vec4) -> f32 { 42 | // if 0.0 < y then select bottom left (w) and bottom right corner radius (z) 43 | // else select top left (x) and top right corner radius (y) 44 | let rs = select(corner_radii.xy, corner_radii.wz, 0.0 < point.y); 45 | // w and z are swapped so that both pairs are in left to right order, otherwise this second select statement would return the incorrect value for the bottom pair. 46 | let radius = select(rs.x, rs.y, 0.0 < point.x); 47 | // Vector from the corner closest to the point, to the point 48 | let corner_to_point = abs(point) - 0.5 * size; 49 | // Vector from the center of the radius circle to the point 50 | let q = corner_to_point + radius; 51 | // length from center of the radius circle to the point, 0s a component if the point is not within the quadrant of the radius circle that is part of the curved corner. 52 | let l = length(max(q, vec2(0.0))); 53 | let m = min(max(q.x, q.y), 0.0); 54 | return l + m - radius; 55 | } 56 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/colors.rs: -------------------------------------------------------------------------------- 1 | use bevy::color::Srgba; 2 | 3 | /// Standard colors for the Obsidian UI. 4 | 5 | // From https://github.com/coreh/bevy-rfcs/blob/editor-design-system/rfcs/69-editor-design-system.md 6 | pub const U1: Srgba = Srgba::new(0.094, 0.094, 0.102, 1.0); 7 | pub const U2: Srgba = Srgba::new(0.137, 0.137, 0.149, 1.0); 8 | pub const U3: Srgba = Srgba::new(0.224, 0.224, 0.243, 1.0); 9 | pub const U4: Srgba = Srgba::new(0.486, 0.486, 0.529, 1.0); 10 | pub const U5: Srgba = Srgba::new(1.0, 1.0, 1.0, 1.0); 11 | pub const BACKGROUND: Srgba = Srgba::new(0.118, 0.118, 0.133, 1.0); 12 | pub const FOREGROUND: Srgba = Srgba::new(0.925, 0.925, 0.925, 1.0); 13 | pub const DIM: Srgba = Srgba::new(0.7, 0.7, 0.7, 1.0); 14 | pub const ACCENT: Srgba = Srgba::new(0.055, 0.647, 0.914, 1.0); 15 | pub const ANIMATION: Srgba = Srgba::new(0.514, 0.094, 0.263, 1.0); 16 | pub const ASSET: Srgba = Srgba::new(0.576, 0.200, 0.918, 1.0); 17 | pub const CODE: Srgba = Srgba::new(0.969, 0.298, 0.000, 1.0); 18 | pub const LIGHT: Srgba = Srgba::new(0.988, 0.827, 0.302, 1.0); 19 | pub const RESOURCE: Srgba = Srgba::new(0.063, 0.725, 0.506, 1.0); 20 | pub const X_RED: Srgba = Srgba::new(0.600, 0.000, 0.000, 1.0); 21 | pub const Y_GREEN: Srgba = Srgba::new(0.000, 0.467, 0.000, 1.0); 22 | pub const Z_BLUE: Srgba = Srgba::new(0.000, 0.000, 0.800, 1.0); 23 | pub const PRIMARY: Srgba = Srgba::new(0.341, 0.435, 0.525, 1.0); 24 | pub const PRIMARY_ACC: Srgba = Srgba::new(0.475, 0.604, 0.733, 1.0); 25 | pub const DESTRUCTIVE: Srgba = Srgba::new(0.525, 0.341, 0.404, 1.0); 26 | pub const DESTRUCTIVE_ACC: Srgba = Srgba::new(0.733, 0.475, 0.612, 1.0); 27 | pub const TRANSPARENT: Srgba = Srgba::new(0.0, 0.0, 0.0, 0.0); 28 | pub const FOCUS: Srgba = Srgba::new(0.055, 0.647, 0.914, 0.15); 29 | pub const TEXT_SELECT: Srgba = Srgba::new(0.055, 0.647, 0.914, 0.5); 30 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/barrier.rs: -------------------------------------------------------------------------------- 1 | use crate::input_dispatch::{FocusKeyboardInput, SetKeyboardFocus}; 2 | use bevy::{ecs::world::DeferredWorld, input::ButtonState, prelude::*}; 3 | use bevy_reactor_signals::{Callback, RunCallback}; 4 | 5 | /// Component for a backdrop element, one that covers the entire screen, blocks click events 6 | /// from reaching elements behind it, and can be used to close a dialog or menu. 7 | #[derive(Component)] 8 | pub struct Barrier { 9 | pub(crate) on_close: Option, 10 | } 11 | 12 | pub(crate) fn barrier_on_key_input( 13 | mut trigger: Trigger, 14 | q_state: Query<&Barrier>, 15 | mut world: DeferredWorld, 16 | ) { 17 | if let Ok(bstate) = q_state.get(trigger.entity()) { 18 | let event = &trigger.event().0; 19 | if event.state == ButtonState::Pressed 20 | && !event.repeat 21 | && (event.key_code == KeyCode::Escape) 22 | { 23 | if let Some(on_close) = bstate.on_close { 24 | trigger.propagate(false); 25 | world.run_callback(on_close, ()); 26 | } 27 | } 28 | } 29 | } 30 | 31 | pub(crate) fn barrier_on_pointer_down( 32 | mut trigger: Trigger>, 33 | q_state: Query<&Barrier>, 34 | mut world: DeferredWorld, 35 | ) { 36 | if let Ok(bstate) = q_state.get(trigger.entity()) { 37 | let checkbox_id = trigger.entity(); 38 | world.set_keyboard_focus(checkbox_id); 39 | trigger.propagate(false); 40 | if let Some(on_close) = bstate.on_close { 41 | world.run_callback(on_close, ()); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/core_slider.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | 3 | use super::Disabled; 4 | 5 | #[derive(Clone, Debug, Component)] 6 | pub struct ValueChange(pub T); 7 | 8 | impl Event for ValueChange { 9 | type Traversal = &'static Parent; 10 | 11 | const AUTO_PROPAGATE: bool = true; 12 | } 13 | 14 | /// A headless slider widget, which can be used to build custom sliders. This component emits 15 | /// [`ValueChange`] events when the slider value changes. Note that the value in the event is 16 | /// unclamped - the reason is that the receiver may want to quantize or otherwise modify the value 17 | /// before clamping. It is the receiver's responsibility to update the slider's value when 18 | /// the value change event is received. 19 | #[derive(Component)] 20 | #[require(DragState)] 21 | pub struct CoreSlider { 22 | pub value: f32, 23 | pub min: f32, 24 | pub max: f32, 25 | } 26 | 27 | impl CoreSlider { 28 | /// Constructg a new [`CoreSlider`]. 29 | pub fn new(value: f32, min: f32, max: f32) -> Self { 30 | Self { value, min, max } 31 | } 32 | 33 | /// Get the current value of the slider. 34 | pub fn value(&self) -> f32 { 35 | self.value 36 | } 37 | 38 | /// Set the value of the slider, clamping it to the min and max values. 39 | pub fn set_value(&mut self, value: f32) { 40 | self.value = value.clamp(self.min, self.max); 41 | } 42 | } 43 | 44 | /// Component used to manage the state of a slider during dragging. 45 | #[derive(Component, Default)] 46 | pub struct DragState { 47 | /// Whether the slider is currently being dragged. 48 | dragging: bool, 49 | /// The value of the slider when dragging started. 50 | offset: f32, 51 | } 52 | 53 | pub(crate) fn slider_on_drag_start( 54 | mut trigger: Trigger>, 55 | mut q_state: Query<(&CoreSlider, &mut DragState, Has)>, 56 | ) { 57 | if let Ok((slider, mut drag, disabled)) = q_state.get_mut(trigger.entity()) { 58 | trigger.propagate(false); 59 | if !disabled { 60 | drag.dragging = true; 61 | drag.offset = slider.value; 62 | } 63 | } 64 | } 65 | 66 | pub(crate) fn slider_on_drag( 67 | mut trigger: Trigger>, 68 | mut q_state: Query<(&ComputedNode, &CoreSlider, &mut DragState)>, 69 | mut commands: Commands, 70 | ) { 71 | if let Ok((node, slider, drag)) = q_state.get_mut(trigger.entity()) { 72 | trigger.propagate(false); 73 | if drag.dragging { 74 | let distance = trigger.event().distance; 75 | // Measure node width and slider value. 76 | let slider_width = node.size().x; 77 | let range = slider.max - slider.min; 78 | let new_value = if range > 0. { 79 | drag.offset + (distance.x * range) / slider_width 80 | } else { 81 | slider.min + range * 0.5 82 | }; 83 | commands.trigger_targets(ValueChange(new_value), trigger.entity()); 84 | } 85 | } 86 | } 87 | 88 | pub(crate) fn slider_on_drag_end( 89 | mut trigger: Trigger>, 90 | mut q_state: Query<(&CoreSlider, &mut DragState)>, 91 | ) { 92 | if let Ok((_slider, mut drag)) = q_state.get_mut(trigger.entity()) { 93 | trigger.propagate(false); 94 | if drag.dragging { 95 | drag.dragging = false; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/disabled.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::{Component, Entity, World}; 2 | use bevy_reactor_signals::Rcx; 3 | 4 | /// A marker component to indicate that a widget is disabled. 5 | #[derive(Component, Debug, Clone, Copy)] 6 | pub struct Disabled; 7 | 8 | /// Trait which defines a method to check if an entity is disabled. 9 | pub trait IsDisabled { 10 | /// Returns true if the given entity is disabled. 11 | fn is_disabled(&self, entity: Entity) -> bool; 12 | } 13 | 14 | impl<'p, 'w> IsDisabled for Rcx<'p, 'w> { 15 | fn is_disabled(&self, entity: Entity) -> bool { 16 | self.world().get::(entity).is_some() 17 | } 18 | } 19 | 20 | impl IsDisabled for World { 21 | fn is_disabled(&self, entity: Entity) -> bool { 22 | self.get::(entity).is_some() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/icon.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_mod_stylebuilder::*; 3 | use bevy_reactor_builder::{EntityStyleBuilder, UiBuilder, UiTemplate}; 4 | use bevy_reactor_signals::{IntoSignal, Signal}; 5 | 6 | use crate::colors; 7 | 8 | /// Control that displays an icon. 9 | #[derive(Clone)] 10 | pub struct Icon { 11 | /// Asset path for the icon 12 | pub icon: HandleOrOwnedPath, 13 | 14 | /// Size of the icon in pixels. 15 | pub size: Vec2, 16 | 17 | /// Color of the icon. 18 | pub color: Signal, 19 | 20 | /// Additional styles to apply to the icon 21 | pub style: StyleHandle, 22 | } 23 | 24 | impl Icon { 25 | /// Create a new `Icon` from a `&str` or `Handle`. 26 | pub fn new(icon: impl Into>) -> Self { 27 | Self { 28 | icon: icon.into(), 29 | ..default() 30 | } 31 | } 32 | 33 | /// Set the size of the icon. 34 | pub fn size(mut self, size: Vec2) -> Self { 35 | self.size = size; 36 | self 37 | } 38 | 39 | /// Set the color of the icon. 40 | pub fn color(mut self, color: impl IntoSignal) -> Self { 41 | self.color = color.into_signal(); 42 | self 43 | } 44 | 45 | /// Set the style of the icon. 46 | pub fn style(mut self, style: S) -> Self { 47 | self.style = style.into_handle(); 48 | self 49 | } 50 | } 51 | 52 | impl Default for Icon { 53 | fn default() -> Self { 54 | Self { 55 | icon: HandleOrOwnedPath::default(), 56 | size: Vec2::splat(12.0), 57 | color: Signal::Constant(colors::FOREGROUND.into()), 58 | style: StyleHandle::default(), 59 | } 60 | } 61 | } 62 | 63 | impl UiTemplate for Icon { 64 | fn build(&self, builder: &mut UiBuilder) { 65 | let icon = self.icon.clone(); 66 | let size = self.size; 67 | let color = self.color; 68 | 69 | builder 70 | .spawn(Node { ..default() }) 71 | .styles(( 72 | move |sb: &mut StyleBuilder| { 73 | sb.width(size.x).height(size.y).background_image(&icon); 74 | }, 75 | self.style.clone(), 76 | )) 77 | .style_dyn( 78 | move |rcx| color.get(rcx), 79 | |color, sb| { 80 | sb.background_image_color(color); 81 | }, 82 | ); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/mod.rs: -------------------------------------------------------------------------------- 1 | mod barrier; 2 | mod button; 3 | mod checkbox; 4 | mod core_slider; 5 | mod dialog; 6 | mod disabled; 7 | mod disclosure_toggle; 8 | mod gradient_slider; 9 | mod icon; 10 | mod icon_button; 11 | mod scrollview; 12 | mod slider; 13 | mod spacer; 14 | mod spinbox; 15 | mod splitter; 16 | mod swatch; 17 | mod swatch_grid; 18 | mod toggle_state; 19 | mod tool_palette; 20 | 21 | use bevy::app::Plugin; 22 | pub use button::{Button, ButtonVariant}; 23 | pub use checkbox::Checkbox; 24 | pub use core_slider::CoreSlider; 25 | pub use dialog::{Dialog, DialogBody, DialogFooter, DialogHeader}; 26 | pub use disabled::{Disabled, IsDisabled}; 27 | pub use disclosure_toggle::DisclosureToggle; 28 | pub use gradient_slider::{ColorGradient, GradientSlider}; 29 | pub use icon::Icon; 30 | pub use icon_button::IconButton; 31 | pub use scrollview::ScrollView; 32 | pub use slider::Slider; 33 | pub use spacer::Spacer; 34 | pub use spinbox::SpinBox; 35 | pub use splitter::{Splitter, SplitterDirection}; 36 | pub use swatch::Swatch; 37 | pub use swatch_grid::SwatchGrid; 38 | pub use tool_palette::{ToolButton, ToolPalette}; 39 | 40 | pub(crate) struct ControlEventsPlugin; 41 | 42 | impl Plugin for ControlEventsPlugin { 43 | fn build(&self, app: &mut bevy::app::App) { 44 | app.add_observer(toggle_state::toggle_on_key_input) 45 | .add_observer(toggle_state::toggle_on_pointer_click) 46 | .add_observer(button::button_on_key_event) 47 | .add_observer(button::button_on_pointer_down) 48 | .add_observer(button::button_on_pointer_up) 49 | .add_observer(button::button_on_pointer_click) 50 | .add_observer(button::button_on_pointer_drag_end) 51 | .add_observer(button::button_on_pointer_cancel) 52 | .add_observer(barrier::barrier_on_key_input) 53 | .add_observer(barrier::barrier_on_pointer_down) 54 | .add_observer(core_slider::slider_on_drag_start) 55 | .add_observer(core_slider::slider_on_drag_end) 56 | .add_observer(core_slider::slider_on_drag); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/spacer.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_mod_stylebuilder::*; 3 | use bevy_reactor_builder::{EntityStyleBuilder, UiBuilder, UiTemplate}; 4 | 5 | fn style_spacer(ss: &mut StyleBuilder) { 6 | ss.flex_grow(1.); 7 | } 8 | 9 | /// A spacer widget that fills the available space. 10 | #[derive(Clone, Default)] 11 | pub struct Spacer; 12 | 13 | impl UiTemplate for Spacer { 14 | fn build(&self, builder: &mut UiBuilder) { 15 | builder.spawn(Node::default()).style(style_spacer); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/controls/toggle_state.rs: -------------------------------------------------------------------------------- 1 | use crate::input_dispatch::{FocusKeyboardInput, SetKeyboardFocus}; 2 | use bevy::{ecs::world::DeferredWorld, input::ButtonState, prelude::*}; 3 | use bevy_reactor_signals::{Callback, RunCallback, Signal}; 4 | 5 | use super::Disabled; 6 | 7 | #[derive(Component)] 8 | pub struct ToggleState { 9 | pub(crate) checked: Signal, 10 | pub(crate) on_change: Option>, 11 | } 12 | 13 | pub(crate) fn toggle_on_key_input( 14 | mut trigger: Trigger, 15 | q_state: Query<(&ToggleState, Has)>, 16 | mut world: DeferredWorld, 17 | ) { 18 | if let Ok((tstate, disabled)) = q_state.get(trigger.entity()) { 19 | let event = &trigger.event().0; 20 | if !disabled 21 | && event.state == ButtonState::Pressed 22 | && !event.repeat 23 | && (event.key_code == KeyCode::Enter || event.key_code == KeyCode::Space) 24 | { 25 | let is_checked = tstate.checked.get(&world); 26 | if let Some(on_change) = tstate.on_change { 27 | trigger.propagate(false); 28 | world.run_callback(on_change, !is_checked); 29 | } 30 | } 31 | } 32 | } 33 | 34 | pub(crate) fn toggle_on_pointer_click( 35 | mut trigger: Trigger>, 36 | q_state: Query<(&ToggleState, Has)>, 37 | mut world: DeferredWorld, 38 | ) { 39 | if let Ok((tstate, disabled)) = q_state.get(trigger.entity()) { 40 | let checkbox_id = trigger.entity(); 41 | world.set_keyboard_focus(checkbox_id); 42 | trigger.propagate(false); 43 | if let Some(on_change) = tstate.on_change { 44 | if !disabled { 45 | let is_checked = tstate.checked.get(&world); 46 | world.run_callback(on_change, !is_checked); 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/focus_signal.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | a11y::Focus, 3 | ecs::{entity::Entity, world::World}, 4 | hierarchy::Parent, 5 | }; 6 | use bevy_reactor_builder::UiBuilder; 7 | use bevy_reactor_signals::Signal; 8 | 9 | use crate::input_dispatch::KeyboardFocusVisible; 10 | 11 | /// True if the given entity is a descendant of the given ancestor. 12 | fn is_descendant(world: &World, e: &Entity, ancestor: &Entity) -> bool { 13 | let mut ha = e; 14 | loop { 15 | if ha == ancestor { 16 | return true; 17 | } 18 | match world.get_entity(*ha).map(|e| e.get::()) { 19 | Ok(Some(parent)) => ha = parent, 20 | _ => return false, 21 | } 22 | } 23 | } 24 | 25 | /// Method to create a signal that tracks whether a target entity has focus. 26 | pub trait CreateFocusSignal { 27 | /// Signal that returns true when the the target has focus. 28 | fn create_focus_signal(&mut self, target: Entity) -> Signal; 29 | 30 | /// Signal that returns true when the the target, or a descendant, has focus. 31 | fn create_focus_within_signal(&mut self, target: Entity) -> Signal; 32 | 33 | /// Signal that returns true when the the target has focus and the focus ring is visible. 34 | fn create_focus_visible_signal(&mut self, target: Entity) -> Signal; 35 | 36 | /// Signal that returns true when the the target, or a descendant, has focus, and the 37 | /// focus ring is visible. 38 | fn create_focus_within_visible_signal(&mut self, target: Entity) -> Signal; 39 | } 40 | 41 | impl<'w> CreateFocusSignal for UiBuilder<'w> { 42 | fn create_focus_signal(&mut self, target: Entity) -> Signal { 43 | self.create_derived(move |rcx| { 44 | let focus = rcx.read_resource::(); 45 | focus.0 == Some(target) 46 | }) 47 | } 48 | 49 | fn create_focus_within_signal(&mut self, target: Entity) -> Signal { 50 | self.create_derived(move |rcx| { 51 | let focus = rcx.read_resource::(); 52 | match focus.0 { 53 | Some(focus) => is_descendant(rcx.world(), &focus, &target), 54 | None => false, 55 | } 56 | }) 57 | } 58 | 59 | fn create_focus_visible_signal(&mut self, target: Entity) -> Signal { 60 | self.create_derived(move |rcx| { 61 | let visible = rcx.read_resource::(); 62 | let focus = rcx.read_resource::(); 63 | visible.0 && focus.0 == Some(target) 64 | }) 65 | } 66 | 67 | fn create_focus_within_visible_signal(&mut self, target: Entity) -> Signal { 68 | self.create_derived(move |rcx| { 69 | let visible = rcx.read_resource::(); 70 | if !visible.0 { 71 | return false; 72 | } 73 | let focus = rcx.read_resource::(); 74 | match focus.0 { 75 | Some(focus) => is_descendant(rcx.world(), &focus, &target), 76 | None => false, 77 | } 78 | }) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/hover_signal.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | hierarchy::Parent, 3 | picking::{focus::HoverMap, pointer::PointerId}, 4 | prelude::*, 5 | }; 6 | use bevy_reactor_builder::UiBuilder; 7 | use bevy_reactor_signals::Signal; 8 | 9 | /// Component which tracks whether the pointer is hovering over an entity. 10 | #[derive(Default, Component)] 11 | pub(crate) struct Hovering(pub bool); 12 | 13 | // Note: previously this was implemented as a Reaction, however it was reacting every frame 14 | // because HoverMap is mutated every frame regardless of whether or not it changed. 15 | pub(crate) fn update_hover_states( 16 | hover_map: Option>, 17 | mut hovers: Query<(Entity, &mut Hovering)>, 18 | parent_query: Query<&Parent>, 19 | ) { 20 | let Some(hover_map) = hover_map else { return }; 21 | let hover_set = hover_map.get(&PointerId::Mouse); 22 | for (entity, mut hoverable) in hovers.iter_mut() { 23 | let is_hovering = match hover_set { 24 | Some(map) => map.iter().any(|(ha, _)| { 25 | *ha == entity || parent_query.iter_ancestors(*ha).any(|e| e == entity) 26 | }), 27 | None => false, 28 | }; 29 | if hoverable.0 != is_hovering { 30 | hoverable.0 = is_hovering; 31 | } 32 | } 33 | } 34 | 35 | /// Method to create a signal that tracks whether the mouse is hovering over the given entity. 36 | pub trait CreateHoverSignal { 37 | /// Signal that returns true when the mouse is hovering over the given entity or a descendant. 38 | fn create_hover_signal(&mut self, target: Entity) -> Signal; 39 | } 40 | 41 | impl<'w> CreateHoverSignal for UiBuilder<'w> { 42 | fn create_hover_signal(&mut self, target: Entity) -> Signal { 43 | self.world_mut().entity_mut(target).insert(Hovering(false)); 44 | let hovering = self.create_derived(move |rcx| { 45 | rcx.read_component::(target) 46 | .map(|h| h.0) 47 | .unwrap_or(false) 48 | }); 49 | hovering 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/input_dispatch.rs: -------------------------------------------------------------------------------- 1 | use bevy::{a11y::Focus, ecs::world::DeferredWorld, input::keyboard::KeyboardInput, prelude::*}; 2 | 3 | #[derive(Clone, Debug, Component)] 4 | pub struct FocusKeyboardInput(pub KeyboardInput); 5 | 6 | impl Event for FocusKeyboardInput { 7 | type Traversal = &'static Parent; 8 | 9 | const AUTO_PROPAGATE: bool = true; 10 | } 11 | 12 | #[derive(Clone, Debug, Resource)] 13 | pub struct KeyboardFocus(pub Option); 14 | 15 | #[derive(Clone, Debug, Resource)] 16 | pub struct KeyboardFocusVisible(pub bool); 17 | 18 | /// Marker component for which entity should receive keyboard input if nothing is focused. 19 | #[derive(Clone, Debug, Component)] 20 | pub struct DefaultKeyHandler; 21 | 22 | pub trait SetKeyboardFocus { 23 | fn set_keyboard_focus(&mut self, entity: Entity); 24 | fn clear_keyboard_focus(&mut self); 25 | } 26 | 27 | impl SetKeyboardFocus for World { 28 | fn set_keyboard_focus(&mut self, entity: Entity) { 29 | if let Some(mut focus) = self.get_resource_mut::() { 30 | focus.0 = Some(entity); 31 | } 32 | if let Some(mut focus) = self.get_resource_mut::() { 33 | focus.0 = Some(entity); 34 | } 35 | } 36 | 37 | fn clear_keyboard_focus(&mut self) { 38 | if let Some(mut focus) = self.get_resource_mut::() { 39 | focus.0 = None; 40 | } 41 | if let Some(mut focus) = self.get_resource_mut::() { 42 | focus.0 = None; 43 | } 44 | } 45 | } 46 | 47 | impl<'w> SetKeyboardFocus for DeferredWorld<'w> { 48 | fn set_keyboard_focus(&mut self, entity: Entity) { 49 | if let Some(mut focus) = self.get_resource_mut::() { 50 | focus.0 = Some(entity); 51 | } 52 | if let Some(mut focus) = self.get_resource_mut::() { 53 | focus.0 = Some(entity); 54 | } 55 | } 56 | 57 | fn clear_keyboard_focus(&mut self) { 58 | if let Some(mut focus) = self.get_resource_mut::() { 59 | focus.0 = None; 60 | } 61 | if let Some(mut focus) = self.get_resource_mut::() { 62 | focus.0 = None; 63 | } 64 | } 65 | } 66 | 67 | /// Plugin which registers the system for dispatching keyboard events based on focus and 68 | /// hover state. 69 | pub struct InputDispatchPlugin; 70 | 71 | impl Plugin for InputDispatchPlugin { 72 | fn build(&self, app: &mut App) { 73 | app.insert_resource(KeyboardFocus(None)) 74 | .insert_resource(KeyboardFocusVisible(false)) 75 | .add_systems(Update, (dispatch_keyboard_input, sync_a11y_focus)); 76 | } 77 | } 78 | 79 | // Q: Is there a way to do this as an observer or hook? 80 | fn sync_a11y_focus(focus: Res, mut a11y_focus: ResMut) { 81 | if a11y_focus.0 != focus.0 { 82 | a11y_focus.0 = focus.0 83 | } 84 | } 85 | 86 | fn dispatch_keyboard_input( 87 | mut key_events: EventReader, 88 | focus: Res, 89 | q_default_handler: Query>, 90 | mut commands: Commands, 91 | ) { 92 | // If an element has keyboard focus, then dispatch the key event to that element. 93 | if let Some(focus_elt) = focus.0 { 94 | for ev in key_events.read() { 95 | commands.trigger_targets(FocusKeyboardInput(ev.clone()), focus_elt); 96 | } 97 | } else if let Some(ent) = q_default_handler.iter().next() { 98 | for ev in key_events.read() { 99 | commands.trigger_targets(FocusKeyboardInput(ev.clone()), ent); 100 | } 101 | } else if !key_events.is_empty() { 102 | // warn!("No focus entity and no default keyboard handler: try inserting DefaultKeyHandler on your top-level entity"); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/materials/dot_grid.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy::reflect::TypePath; 3 | use bevy::render::render_resource::*; 4 | 5 | #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)] 6 | pub struct DotGridMaterial { 7 | #[uniform(0)] 8 | pub(crate) color_bg: Vec4, 9 | #[uniform(1)] 10 | pub(crate) color_fg: Vec4, 11 | } 12 | 13 | impl UiMaterial for DotGridMaterial { 14 | fn fragment_shader() -> ShaderRef { 15 | "obsidian_ui://shaders/dot_grid.wgsl".into() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/materials/gradient_rect.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy::reflect::TypePath; 3 | use bevy::render::render_resource::*; 4 | 5 | #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)] 6 | pub(crate) struct GradientRectMaterial { 7 | #[uniform(0)] 8 | pub(crate) num_color_stops: IVec4, 9 | #[uniform(1)] 10 | pub(crate) color_stops: [Vec4; 8], 11 | #[uniform(3)] 12 | pub(crate) cap_size: f32, 13 | } 14 | 15 | impl UiMaterial for GradientRectMaterial { 16 | fn fragment_shader() -> ShaderRef { 17 | "embedded://bevy_reactor_obsidian/assets/shaders/gradient_rect.wgsl".into() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/materials/mod.rs: -------------------------------------------------------------------------------- 1 | // mod dot_grid; 2 | // mod draw_path; 3 | mod gradient_rect; 4 | mod slider_rect; 5 | mod swatch_rect; 6 | 7 | // pub(crate) use dot_grid::DotGridMaterial; 8 | // pub(crate) use draw_path::*; 9 | pub(crate) use gradient_rect::GradientRectMaterial; 10 | pub(crate) use slider_rect::SliderRectMaterial; 11 | pub(crate) use swatch_rect::SwatchRectMaterial; 12 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/materials/slider_rect.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy::reflect::TypePath; 3 | use bevy::render::render_resource::*; 4 | 5 | #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)] 6 | pub struct SliderRectMaterial { 7 | #[uniform(0)] 8 | pub(crate) color_lo: Vec4, 9 | #[uniform(1)] 10 | pub(crate) color_hi: Vec4, 11 | #[uniform(2)] 12 | pub(crate) value: Vec4, 13 | #[uniform(3)] 14 | pub(crate) radius: Vec4, // TopLeft, TopRight, BottomRight, BottomLeft 15 | } 16 | 17 | impl UiMaterial for SliderRectMaterial { 18 | fn fragment_shader() -> ShaderRef { 19 | "embedded://bevy_reactor_obsidian/assets/shaders/slider_rect.wgsl".into() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/materials/swatch_rect.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy::reflect::TypePath; 3 | use bevy::render::render_resource::*; 4 | 5 | #[derive(AsBindGroup, Asset, TypePath, Debug, Clone)] 6 | pub(crate) struct SwatchRectMaterial { 7 | #[uniform(0)] 8 | pub(crate) color: Vec4, 9 | #[uniform(1)] 10 | pub(crate) border_radius: Vec4, 11 | } 12 | 13 | impl UiMaterial for SwatchRectMaterial { 14 | fn fragment_shader() -> ShaderRef { 15 | "embedded://bevy_reactor_obsidian/assets/shaders/swatch_rect.wgsl".into() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/rounded_corners.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_mod_stylebuilder::{StyleBuilder, StyleBuilderBorderRadius}; 3 | 4 | /// Options for rendering rounded corners. 5 | #[allow(missing_docs)] 6 | #[derive(Debug, Clone, Copy, Default, PartialEq)] 7 | pub enum RoundedCorners { 8 | None, 9 | #[default] 10 | All, 11 | TopLeft, 12 | TopRight, 13 | BottomRight, 14 | BottomLeft, 15 | Top, 16 | Right, 17 | Bottom, 18 | Left, 19 | } 20 | 21 | impl RoundedCorners { 22 | /// Convert the `RoundedCorners` to a `Vec4` for use in a shader. 23 | pub fn to_vec(&self, radius: f32) -> Vec4 { 24 | match self { 25 | RoundedCorners::None => Vec4::new(0.0, 0.0, 0.0, 0.0), 26 | RoundedCorners::All => Vec4::new(radius, radius, radius, radius), 27 | RoundedCorners::TopLeft => Vec4::new(radius, 0.0, 0.0, 0.0), 28 | RoundedCorners::TopRight => Vec4::new(0.0, radius, 0.0, 0.0), 29 | RoundedCorners::BottomRight => Vec4::new(0.0, 0.0, radius, 0.0), 30 | RoundedCorners::BottomLeft => Vec4::new(0.0, 0.0, 0.0, radius), 31 | RoundedCorners::Top => Vec4::new(radius, radius, 0.0, 0.0), 32 | RoundedCorners::Right => Vec4::new(0.0, radius, radius, 0.0), 33 | RoundedCorners::Bottom => Vec4::new(0.0, 0.0, radius, radius), 34 | RoundedCorners::Left => Vec4::new(radius, 0.0, 0.0, radius), 35 | } 36 | } 37 | 38 | /// Convert the `RoundedCorners` to a `BorderRadius` for use in a `Node`. 39 | pub fn to_border_radius(&self, radius: f32) -> BorderRadius { 40 | let radius = bevy::ui::Val::Px(radius); 41 | let zero = bevy::ui::Val::Px(0.0); 42 | match self { 43 | RoundedCorners::None => BorderRadius::all(zero), 44 | RoundedCorners::All => BorderRadius::all(radius), 45 | RoundedCorners::TopLeft => BorderRadius { 46 | top_left: radius, 47 | top_right: zero, 48 | bottom_right: zero, 49 | bottom_left: zero, 50 | }, 51 | RoundedCorners::TopRight => BorderRadius { 52 | top_left: zero, 53 | top_right: radius, 54 | bottom_right: zero, 55 | bottom_left: zero, 56 | }, 57 | RoundedCorners::BottomRight => BorderRadius { 58 | top_left: zero, 59 | top_right: zero, 60 | bottom_right: radius, 61 | bottom_left: zero, 62 | }, 63 | RoundedCorners::BottomLeft => BorderRadius { 64 | top_left: zero, 65 | top_right: zero, 66 | bottom_right: zero, 67 | bottom_left: radius, 68 | }, 69 | RoundedCorners::Top => BorderRadius { 70 | top_left: radius, 71 | top_right: radius, 72 | bottom_right: zero, 73 | bottom_left: zero, 74 | }, 75 | RoundedCorners::Right => BorderRadius { 76 | top_left: zero, 77 | top_right: radius, 78 | bottom_right: radius, 79 | bottom_left: zero, 80 | }, 81 | RoundedCorners::Bottom => BorderRadius { 82 | top_left: zero, 83 | top_right: zero, 84 | bottom_right: radius, 85 | bottom_left: radius, 86 | }, 87 | RoundedCorners::Left => BorderRadius { 88 | top_left: radius, 89 | top_right: zero, 90 | bottom_right: zero, 91 | bottom_left: radius, 92 | }, 93 | } 94 | } 95 | 96 | pub fn to_border_style(&self, radius: f32) -> impl Fn(&mut StyleBuilder) { 97 | let radius = self.to_border_radius(radius); 98 | move |sb: &mut StyleBuilder| { 99 | sb.border_radius(radius); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/size.rs: -------------------------------------------------------------------------------- 1 | /// Standard sizes for buttons and other widgets that have size variants. 2 | #[derive(Clone, Copy, PartialEq, Debug, Default)] 3 | #[allow(missing_docs)] 4 | pub enum Size { 5 | Xl, 6 | Lg, 7 | #[default] 8 | Md, 9 | Sm, 10 | Xs, 11 | Xxs, 12 | Xxxs, 13 | } 14 | 15 | impl Size { 16 | /// Returns the height of the widget in pixels. 17 | pub fn height(&self) -> f32 { 18 | match self { 19 | Size::Xl => 3.0 * 12.0, 20 | Size::Lg => 2.5 * 12.0, 21 | Size::Md => 2.0 * 12.0, 22 | Size::Sm => 1.85 * 12.0, 23 | Size::Xs => 1.65 * 12.0, 24 | Size::Xxs => 1.45 * 12.0, 25 | Size::Xxxs => 1.3 * 12.0, 26 | } 27 | } 28 | 29 | /// Returns the height of the widget in pixels. 30 | pub fn border_radius(&self) -> f32 { 31 | match self { 32 | Size::Xl => 4.25, 33 | Size::Lg => 4.0, 34 | Size::Md => 3.5, 35 | Size::Sm => 3.0, 36 | Size::Xs => 3.0, 37 | Size::Xxs => 3.0, 38 | Size::Xxxs => 3.0, 39 | } 40 | } 41 | 42 | /// Returns the desired font size for the widget. 43 | pub fn font_size(&self) -> f32 { 44 | match self { 45 | Size::Xl => 18.0, 46 | Size::Lg => 16.0, 47 | Size::Md => 14.0, 48 | Size::Sm => 13.0, 49 | Size::Xs => 12.0, 50 | Size::Xxs => 10.0, 51 | Size::Xxxs => 9.0, 52 | } 53 | } 54 | 55 | /// Returns the dialog width for this size. 56 | pub fn dialog_width(&self) -> f32 { 57 | match self { 58 | Size::Xl => 800.0, 59 | Size::Lg => 600.0, 60 | Size::Md => 400.0, 61 | Size::Sm => 300.0, 62 | Size::Xs => 200.0, 63 | Size::Xxs => 150.0, 64 | Size::Xxxs => 100.0, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /crates/bevy_reactor_obsidian/src/typography.rs: -------------------------------------------------------------------------------- 1 | use bevy_mod_stylebuilder::{StyleBuilder, StyleBuilderFont}; 2 | 3 | /// Default text style for UI. 4 | pub fn text_default(ss: &mut StyleBuilder) { 5 | ss.font("embedded://bevy_reactor_obsidian/assets/fonts/Fira_Sans/FiraSans-Medium.ttf") 6 | .font_size(14); 7 | } 8 | 9 | /// When we need to emphasize a label 10 | pub fn text_strong(ss: &mut StyleBuilder) { 11 | ss.font("embedded://bevy_reactor_obsidian/assets/fonts/Fira_Sans/FiraSans-Bold.ttf") 12 | .font_size(14); 13 | } 14 | -------------------------------------------------------------------------------- /crates/bevy_reactor_overlays/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_reactor_overlays" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bevy = { workspace = true } 9 | bevy_reactor_signals = { workspace = true } 10 | # bevy = "0.13.1" 11 | bevy_mod_picking = { workspace = true } 12 | bevy_reactor = { path = "../.." } 13 | -------------------------------------------------------------------------------- /crates/bevy_reactor_overlays/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod mesh_builder; 2 | mod overlay; 3 | mod overlay_material; 4 | mod shape_builder; 5 | 6 | use bevy::{app::Plugin, asset::embedded_asset, pbr::MaterialPlugin}; 7 | pub use overlay::Overlay; 8 | pub use shape_builder::{PolygonOptions, ShapeBuilder, StrokeMarker}; 9 | 10 | use crate::overlay_material::OverlayMaterial; 11 | 12 | use self::overlay_material::UnderlayMaterial; 13 | 14 | /// Plugin for the overlays module. 15 | pub struct OverlaysPlugin; 16 | 17 | impl Plugin for OverlaysPlugin { 18 | fn build(&self, app: &mut bevy::app::App) { 19 | embedded_asset!(app, "overlay.wgsl"); 20 | app.add_plugins(( 21 | MaterialPlugin::::default(), 22 | MaterialPlugin::::default(), 23 | )); 24 | } 25 | } 26 | 27 | /// Overlay that builds flat shapes. 28 | pub type OverlayShape = Overlay; 29 | -------------------------------------------------------------------------------- /crates/bevy_reactor_overlays/src/mesh_builder.rs: -------------------------------------------------------------------------------- 1 | use bevy::render::mesh::{Mesh, PrimitiveTopology}; 2 | 3 | /// Trait that abstracts the construction of a mesh. 4 | pub trait MeshBuilder { 5 | fn topology() -> PrimitiveTopology; 6 | 7 | /// Build the mesh, consuming the builder. 8 | fn build(self, mesh: &mut Mesh); 9 | } 10 | -------------------------------------------------------------------------------- /crates/bevy_reactor_overlays/src/overlay.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_core_pipeline::tonemapping::tone_mapping 2 | #import bevy_pbr::{ 3 | mesh_view_bindings::view, 4 | mesh_functions as mfns, 5 | mesh_bindings::mesh, 6 | } 7 | 8 | @group(2) @binding(1) 9 | var color: vec4; 10 | 11 | struct Vertex { 12 | @location(0) position: vec3, 13 | }; 14 | 15 | struct VertexOutput { 16 | @builtin(position) position: vec4, 17 | }; 18 | 19 | @vertex 20 | fn vertex(vertex: Vertex, @builtin(instance_index) instance_index: u32) -> VertexOutput { 21 | var out: VertexOutput; 22 | out.position = mfns::mesh_position_local_to_clip( 23 | mfns::get_model_matrix(instance_index), 24 | vec4(vertex.position, 1.0) 25 | ); 26 | 27 | return out; 28 | } 29 | 30 | @fragment 31 | fn fragment( 32 | @builtin(front_facing) is_front: bool, 33 | mesh: VertexOutput, 34 | ) -> @location(0) vec4 { 35 | return tone_mapping(color, view.color_grading); 36 | } 37 | -------------------------------------------------------------------------------- /crates/bevy_reactor_overlays/src/overlay_material.rs: -------------------------------------------------------------------------------- 1 | use bevy::{ 2 | asset::Asset, 3 | color::LinearRgba, 4 | pbr::{Material, MaterialPipeline, MaterialPipelineKey}, 5 | reflect::TypePath, 6 | render::{ 7 | alpha::AlphaMode, 8 | mesh::MeshVertexBufferLayoutRef, 9 | render_resource::{ 10 | AsBindGroup, CompareFunction, RenderPipelineDescriptor, ShaderRef, 11 | SpecializedMeshPipelineError, 12 | }, 13 | }, 14 | }; 15 | 16 | /// Material for overlays 17 | #[derive(Debug, Clone, AsBindGroup, Asset, TypePath, Default)] 18 | pub struct OverlayMaterial { 19 | #[uniform(1)] 20 | pub(crate) color: LinearRgba, 21 | } 22 | 23 | #[allow(unused_variables)] 24 | impl Material for OverlayMaterial { 25 | fn vertex_shader() -> ShaderRef { 26 | "embedded://bevy_reactor_overlays/overlay.wgsl".into() 27 | } 28 | 29 | fn fragment_shader() -> ShaderRef { 30 | "embedded://bevy_reactor_overlays/overlay.wgsl".into() 31 | } 32 | 33 | fn alpha_mode(&self) -> AlphaMode { 34 | AlphaMode::Blend 35 | } 36 | 37 | fn specialize( 38 | pipeline: &MaterialPipeline, 39 | descriptor: &mut RenderPipelineDescriptor, 40 | layout: &MeshVertexBufferLayoutRef, 41 | key: MaterialPipelineKey, 42 | ) -> Result<(), SpecializedMeshPipelineError> { 43 | if let Some(ref mut depth_stencil) = descriptor.depth_stencil { 44 | depth_stencil.depth_write_enabled = true; 45 | depth_stencil.depth_compare = CompareFunction::GreaterEqual; 46 | } 47 | Ok(()) 48 | } 49 | } 50 | 51 | /// Material for occluded overlays 52 | #[derive(Debug, Clone, AsBindGroup, Asset, TypePath, Default)] 53 | pub struct UnderlayMaterial { 54 | #[uniform(1)] 55 | pub(crate) color: LinearRgba, 56 | } 57 | 58 | #[allow(unused_variables)] 59 | impl Material for UnderlayMaterial { 60 | fn vertex_shader() -> ShaderRef { 61 | "embedded://bevy_reactor_overlays/overlay.wgsl".into() 62 | } 63 | 64 | fn fragment_shader() -> ShaderRef { 65 | "embedded://bevy_reactor_overlays/overlay.wgsl".into() 66 | } 67 | 68 | fn alpha_mode(&self) -> AlphaMode { 69 | AlphaMode::Blend 70 | } 71 | 72 | fn specialize( 73 | pipeline: &MaterialPipeline, 74 | descriptor: &mut RenderPipelineDescriptor, 75 | layout: &MeshVertexBufferLayoutRef, 76 | key: MaterialPipelineKey, 77 | ) -> Result<(), SpecializedMeshPipelineError> { 78 | if let Some(ref mut depth_stencil) = descriptor.depth_stencil { 79 | depth_stencil.depth_write_enabled = true; 80 | depth_stencil.depth_compare = CompareFunction::Less; 81 | } 82 | Ok(()) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/bevy_reactor_signals/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_reactor_signals" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | bevy = { workspace = true } 11 | -------------------------------------------------------------------------------- /crates/bevy_reactor_signals/src/callback.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy::{ 4 | ecs::{ 5 | system::SystemId, 6 | world::{Command, DeferredWorld}, 7 | }, 8 | prelude::*, 9 | }; 10 | 11 | use crate::Ecx; 12 | 13 | /// Contains a reference to a callback. `P` is the type of the props. 14 | #[derive(PartialEq, Debug)] 15 | pub struct Callback { 16 | pub(crate) id: SystemId, ()>, 17 | } 18 | 19 | impl

Callback

{ 20 | /// Construct a new callback 21 | pub fn new(id: SystemId, ()>) -> Self { 22 | Self { id } 23 | } 24 | } 25 | 26 | impl

Copy for Callback

{} 27 | impl

Clone for Callback

{ 28 | fn clone(&self) -> Self { 29 | *self 30 | } 31 | } 32 | 33 | pub trait AnyCallback: 'static { 34 | fn remove(&self, world: &mut World); 35 | } 36 | 37 | impl AnyCallback for Callback

{ 38 | fn remove(&self, world: &mut World) { 39 | // println!("Removing callback"); 40 | world.unregister_system(self.id).unwrap(); 41 | } 42 | } 43 | 44 | /// Component which tracks ownership of callbacks. 45 | #[derive(Component, Default)] 46 | pub struct CallbackOwner(Vec>); 47 | 48 | impl CallbackOwner { 49 | /// Construct a new `CallbackOwner` component. 50 | pub fn new() -> Self { 51 | Self::default() 52 | } 53 | 54 | /// Add an entry to the list of owned callbacks. 55 | pub fn add(&mut self, callback: Callback

) { 56 | self.0.push(Arc::new(callback)); 57 | } 58 | } 59 | 60 | pub(crate) fn cleanup_callbacks(world: &mut World) { 61 | world 62 | .register_component_hooks::() 63 | .on_remove(|mut world, entity, _component| { 64 | let mut callbacks = world.get_mut::(entity).unwrap(); 65 | let mut callbacks = std::mem::take(&mut callbacks.0); 66 | for callback in callbacks.drain(..) { 67 | world.commands().queue(UnregisterCallbackCmd(callback)); 68 | } 69 | }); 70 | } 71 | 72 | /// A trait for invoking callbacks. 73 | pub trait RunCallback { 74 | /// Invoke a callback with the given props. 75 | fn run_callback(&mut self, callback: Callback

, props: P); 76 | } 77 | 78 | /// A mutable reactive context. This allows write access to reactive data sources. 79 | impl RunCallback for World { 80 | /// Invoke a callback with the given props. 81 | /// 82 | /// Arguments: 83 | /// * `callback` - The callback to invoke. 84 | /// * `props` - The props to pass to the callback. 85 | fn run_callback

(&mut self, callback: Callback

, props: P) { 86 | self.run_system_with_input(callback.id, props).unwrap(); 87 | } 88 | } 89 | 90 | /// A mutable reactive context. This allows write access to reactive data sources. 91 | impl<'w> RunCallback for DeferredWorld<'w> { 92 | /// Invoke a callback with the given props. 93 | /// 94 | /// Arguments: 95 | /// * `callback` - The callback to invoke. 96 | /// * `props` - The props to pass to the callback. 97 | fn run_callback(&mut self, callback: Callback

, props: P) { 98 | self.commands().run_system_with_input(callback.id, props); 99 | } 100 | } 101 | 102 | impl<'p, 'w> RunCallback for Ecx<'p, 'w> { 103 | fn run_callback(&mut self, callback: Callback

, props: P) { 104 | self.world_mut().run_callback(callback, props); 105 | } 106 | } 107 | 108 | impl<'w, 's> RunCallback for Commands<'w, 's> { 109 | fn run_callback(&mut self, callback: Callback

, props: P) { 110 | self.run_system_with_input(callback.id, props) 111 | } 112 | } 113 | 114 | pub(crate) struct UnregisterCallbackCmd(pub(crate) Arc); 115 | 116 | impl Command for UnregisterCallbackCmd { 117 | fn apply(self, world: &mut World) { 118 | self.0.remove(world) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /crates/bevy_reactor_signals/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Implementation of the reactive signals pattern for Bevy. 2 | #![warn(missing_docs)] 3 | 4 | use bevy::app::{App, Plugin, Update}; 5 | 6 | mod callback; 7 | mod derived; 8 | mod ecx; 9 | mod mutable; 10 | mod rcx; 11 | mod reaction; 12 | mod signal; 13 | mod tracking_scope; 14 | 15 | use callback::cleanup_callbacks; 16 | pub use callback::{Callback, CallbackOwner, RunCallback}; 17 | pub use derived::{create_derived, Derived, ReadDerived}; 18 | pub use ecx::Ecx; 19 | pub use mutable::{create_mutable, CreateMutable, Mutable, ReadMutable, WriteMutable}; 20 | pub use rcx::Rcx; 21 | pub use reaction::*; 22 | pub use signal::IntoSignal; 23 | pub use signal::Signal; 24 | pub use tracking_scope::TrackingScope; 25 | pub use tracking_scope::TrackingScopeTracing; 26 | use tracking_scope::{cleanup_tracking_scopes, run_reactions}; 27 | 28 | /// Plugin that adds the reactive UI system to the app. 29 | pub struct SignalsPlugin; 30 | 31 | impl Plugin for SignalsPlugin { 32 | fn build(&self, app: &mut App) { 33 | cleanup_tracking_scopes(app.world_mut()); 34 | cleanup_callbacks(app.world_mut()); 35 | app.add_systems(Update, run_reactions); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /crates/bevy_reactor_signals/src/reaction.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use bevy::ecs::{component::Component, entity::Entity, world::World}; 4 | 5 | use crate::tracking_scope::TrackingScope; 6 | 7 | /// Trait representing a reaction to changes in dependencies. The trait's [`react`] method 8 | /// is called when the dependencies change (dependencies are tracked in a separate 9 | /// [`TrackingScope`] component). 10 | /// 11 | /// Note that the reaction is not automatically run when it is first created - it's the 12 | /// responsibility of the caller to call [`react`] at least once. The reason for this is 13 | /// that under normal circumstances, we want [`react`] to be run synchronously. 14 | pub trait Reaction { 15 | /// Update the reaction code in response to changes in dependencies. 16 | /// 17 | /// Arguments: 18 | /// - `owner`: The entity that owns this reaction and tracking scope. 19 | /// - `world`: The Bevy world. 20 | /// - `tracking`: The tracking scope for the reaction. 21 | fn react(&mut self, owner: Entity, world: &mut World, tracking: &mut TrackingScope); 22 | } 23 | 24 | /// Component which contains a reference to a reaction. Generally the entity will also 25 | /// have a [`TrackingScope`] component. 26 | #[derive(Component)] 27 | pub struct ReactionCell(pub Arc>); 28 | 29 | impl ReactionCell { 30 | /// Construct a new [`ReactionCell`]. 31 | pub fn new(reaction: R) -> Self { 32 | Self(Arc::new(Mutex::new(reaction))) 33 | } 34 | } 35 | 36 | // /// Command which performs the initial (startup) reaction. 37 | // pub struct InitialReactionCommand(Entity); 38 | 39 | // impl Command for InitialReactionCommand { 40 | // fn apply(self, world: &mut World) { 41 | // let entity = self.0; 42 | // let Some(reaction) = world.get::(entity).map(|r| r.0.clone()) else { 43 | // return; 44 | // }; 45 | // let Some(mut tracking) = world.get_mut::(entity) else { 46 | // return; 47 | // }; 48 | // reaction 49 | // .lock() 50 | // .unwrap() 51 | // .react(entity, world, &mut *tracking); 52 | // } 53 | // } 54 | -------------------------------------------------------------------------------- /crates/bevy_reactor_signals/src/signal.rs: -------------------------------------------------------------------------------- 1 | use crate::{derived::ReadDerived, mutable::ReadMutable, Derived, Mutable}; 2 | 3 | /// What type of reactive node underlies this signal. "Signals" in this framework represent 4 | /// any kind of reactive data source, including mutable variables, derived signals, and memoized 5 | /// computations. 6 | #[derive(Copy)] 7 | pub enum Signal { 8 | /// A mutable variable that can be read and written to. 9 | Mutable(Mutable), 10 | 11 | /// A readonly value that is computed from other signals. 12 | Derived(Derived), 13 | 14 | /// A constant value, mainly useful for establishing defaults. 15 | Constant(T), 16 | } 17 | 18 | impl Clone for Signal 19 | where 20 | T: Clone, 21 | { 22 | fn clone(&self) -> Self { 23 | match self { 24 | Signal::Mutable(mutable) => Signal::Mutable(*mutable), 25 | Signal::Derived(derived) => Signal::Derived(*derived), 26 | Signal::Constant(value) => Signal::Constant(value.clone()), 27 | } 28 | } 29 | } 30 | 31 | impl Signal 32 | where 33 | T: Copy + Send + Sync + 'static, 34 | { 35 | /// Read the value of the signal using Copy semantics. 36 | pub fn get(&self, rc: &R) -> T { 37 | match self { 38 | Signal::Mutable(mutable) => rc.read_mutable(mutable), 39 | Signal::Derived(derived) => rc.read_derived(derived), 40 | Signal::Constant(value) => *value, 41 | } 42 | } 43 | } 44 | 45 | impl Signal 46 | where 47 | T: Clone + Send + Sync + 'static, 48 | { 49 | /// Read the value of the signal using Copy semantics. 50 | pub fn get_clone(&self, rc: &R) -> T { 51 | match self { 52 | Signal::Mutable(mutable) => rc.read_mutable_clone(mutable), 53 | Signal::Derived(derived) => rc.read_derived_clone(derived), 54 | Signal::Constant(value) => value.clone(), 55 | } 56 | } 57 | } 58 | 59 | impl Signal 60 | where 61 | T: Send + Sync + 'static, 62 | { 63 | /// Read the value of the signal using a mapping function. 64 | pub fn map U>(&self, rc: &R, f: F) -> U { 65 | match self { 66 | Signal::Mutable(mutable) => rc.read_mutable_map(mutable, f), 67 | Signal::Derived(derived) => rc.read_derived_map(derived, f), 68 | Signal::Constant(value) => f(value), 69 | } 70 | } 71 | } 72 | 73 | /// Implement default if T has a default. 74 | impl Default for Signal 75 | where 76 | T: Default + Send + Sync + 'static, 77 | { 78 | fn default() -> Self { 79 | Self::Constant(Default::default()) 80 | } 81 | } 82 | 83 | /// Trait that defines values that can be converted into a `Signal`. 84 | pub trait IntoSignal { 85 | /// Convert the value into a `Signal`. For most types, this will be a `Signal::Constant`. 86 | /// For `Mutable` and `Derived` signals, this will be a `Signal::Mutable` or `Signal::Derived` 87 | fn into_signal(self) -> Signal; 88 | } 89 | 90 | impl IntoSignal for T { 91 | fn into_signal(self) -> Signal { 92 | Signal::Constant(self) 93 | } 94 | } 95 | 96 | impl IntoSignal for Mutable { 97 | fn into_signal(self) -> Signal { 98 | Signal::Mutable(self) 99 | } 100 | } 101 | 102 | impl IntoSignal for Derived { 103 | fn into_signal(self) -> Signal { 104 | Signal::Derived(self) 105 | } 106 | } 107 | 108 | impl IntoSignal for Signal { 109 | fn into_signal(self) -> Signal { 110 | self 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /crates/obsidian_ui/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/obsidian_ui/.DS_Store -------------------------------------------------------------------------------- /crates/obsidian_ui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obsidian_ui" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bevy = { workspace = true } 9 | bevy_mod_picking = { workspace = true } 10 | bevy_mod_stylebuilder = { workspace = true } 11 | bevy_reactor_signals = { workspace = true } 12 | bevy_reactor = { path = "../.." } 13 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/shaders/dot_grid.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_ui::ui_vertex_output::UiVertexOutput 2 | 3 | @group(1) @binding(0) 4 | var color_bg: vec4; 5 | 6 | @group(1) @binding(1) 7 | var color_fg: vec4; 8 | 9 | @fragment 10 | fn fragment(in: UiVertexOutput) -> @location(0) vec4 { 11 | let size = vec2(in.size.x, in.size.y); 12 | let cell = fract(in.uv * size / 16.) * 16.; 13 | return select(color_bg, color_fg, cell.x <= 1.5 && cell.y <= 1.5); 14 | } 15 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/shaders/draw_path.wgsl: -------------------------------------------------------------------------------- 1 | #import bevy_ui::ui_vertex_output::UiVertexOutput 2 | 3 | const OP_MOVE_TO: u32 = 0u; 4 | const OP_LINE_TO: u32 = 1u; 5 | const OP_QUAD1: u32 = 2u; 6 | const OP_QUAD2: u32 = 3u; 7 | 8 | struct PathCommand { 9 | op: u32, 10 | pos: vec2, 11 | } 12 | 13 | @group(1) @binding(0) 14 | var color: vec4; 15 | 16 | @group(1) @binding(1) 17 | var width: f32; 18 | 19 | @group(1) @binding(2) 20 | var commands: array; 21 | 22 | @fragment 23 | fn fragment(in: UiVertexOutput) -> @location(0) vec4 { 24 | let pt = vec2(in.size.x, in.size.y) * in.uv; 25 | let d = distance_to_path(pt); 26 | let a = 1.0 - smoothstep(width * 0.5 - 0.3, width * 0.5 + 0.3, d); 27 | return vec4(color.rgb, color.a * a); 28 | } 29 | 30 | fn distance_to_path(pt: vec2) -> f32 { 31 | var prev = vec2(0., 0.); 32 | var dist: f32 = 10000000.0; 33 | let n = arrayLength(&commands); 34 | for (var i = 0u; i < n; i = i + 1u) { 35 | let cmd = commands[i]; 36 | if (cmd.op == OP_MOVE_TO) { 37 | prev = cmd.pos; 38 | } else if (cmd.op == OP_LINE_TO) { 39 | let next = cmd.pos; 40 | dist = min(dist, distance_sq_to_line(pt, prev, next)); 41 | prev = next; 42 | } else if (cmd.op == OP_QUAD1) { 43 | let ctrl = cmd.pos; 44 | let next = commands[i + 1].pos; 45 | dist = min(dist, distance_sq_to_quadratic(pt, prev, ctrl, next)); 46 | i = i + 1u; 47 | prev = next; 48 | } else if (cmd.op == OP_QUAD2) { 49 | prev = cmd.pos; 50 | } 51 | } 52 | return sqrt(dist); 53 | } 54 | 55 | fn distance_sq_to_line(pt: vec2, a: vec2, b: vec2) -> f32 { 56 | let pa = pt - a; 57 | let ba = b - a; 58 | let h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0); 59 | return dot2(pa - ba * h); 60 | } 61 | 62 | // From https://iquilezles.org/articles/distfunctions2d/ 63 | fn distance_sq_to_quadratic(pos: vec2, A: vec2, B: vec2, C: vec2) -> f32 { 64 | let a = B - A; 65 | let b = A - 2.0 * B + C; 66 | let c = a * 2.0; 67 | let d = A - pos; 68 | let kk = 1.0 / dot(b, b); 69 | let kx = kk * dot(a, b); 70 | let ky = kk * (2.0 * dot(a, a)+dot(d, b)) / 3.0; 71 | let kz = kk * dot(d, a); 72 | var res = 0.0; 73 | let p = ky - kx * kx; 74 | let p3 = p * p * p; 75 | let q = kx * (2.0 * kx * kx - 3.0 * ky) + kz; 76 | var h = q * q + 4.0 * p3; 77 | if (h >= 0.0) { 78 | h = sqrt(h); 79 | let x = (vec2(h, -h) - q) / 2.0; 80 | let uv = sign(x) * pow(abs(x), vec2(1.0 / 3.0)); 81 | let t = clamp(uv.x + uv.y - kx, 0.0, 1.0); 82 | res = dot2(d + (c + b * t) * t); 83 | } else { 84 | let z = sqrt(-p); 85 | let v = acos( q/(p * z * 2.0) ) / 3.0; 86 | let m = cos(v); 87 | let n = sin(v) * 1.732050808; 88 | let t = clamp(vec3(m + m,-n - m,n - m) * z - kx, vec3(0.0), vec3(1.0)); 89 | res = min(dot2(d + (c + b * t.x) * t.x), 90 | dot2(d + (c + b * t.y) * t.y)); 91 | // the third root cannot be the closest 92 | // res = min(res,dot2(d+(c+b*t.z)*t.z)); 93 | } 94 | return res; 95 | } 96 | 97 | fn dot2(v: vec2) -> f32 { 98 | return dot(v, v); 99 | } 100 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/svg/button.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16 | 36 | 42 | 43 | 45 | 49 | 60 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/svg/checkmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 44 | 45 | 47 | 51 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/svg/gradient_thumb.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 39 | 48 | 49 | 51 | 55 | 60 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/textures/button.atlas.grid.ron: -------------------------------------------------------------------------------- 1 | ( 2 | texture: "./button.png", 3 | tile_size: (16, 16), 4 | rows: 3, 5 | columns: 3, 6 | ) 7 | -------------------------------------------------------------------------------- /crates/obsidian_ui/assets/textures/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/crates/obsidian_ui/assets/textures/button.png -------------------------------------------------------------------------------- /crates/obsidian_ui/src/controls/listview.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, ui}; 2 | use bevy_mod_stylebuilder::*; 3 | use bevy_reactor::*; 4 | use bevy_reactor_signals::Cx; 5 | 6 | use crate::colors; 7 | 8 | use super::ScrollView; 9 | 10 | fn style_listview(ss: &mut StyleBuilder) { 11 | ss.background_color(colors::U1) 12 | .border_radius(5.0) 13 | .padding(3); 14 | } 15 | 16 | fn style_listview_inner(ss: &mut StyleBuilder) { 17 | ss.display(ui::Display::Flex) 18 | .flex_direction(ui::FlexDirection::Column) 19 | .align_items(ui::AlignItems::Stretch); 20 | } 21 | 22 | /// A scrollable list of items. 23 | #[derive(Clone, Default)] 24 | pub struct ListView { 25 | /// Additional styles to be applied to the list view. 26 | pub style: StyleHandle, 27 | 28 | /// The content of the dialog header. 29 | pub children: ChildArray, 30 | } 31 | 32 | impl ListView { 33 | /// Create a new list view. 34 | pub fn new() -> Self { 35 | Self::default() 36 | } 37 | 38 | /// Set additional styles to be applied to the list view. 39 | pub fn style(mut self, style: S) -> Self { 40 | self.style = style.into_handle(); 41 | self 42 | } 43 | 44 | /// Set the child views for this element. 45 | pub fn children(mut self, children: V) -> Self { 46 | self.children = children.to_child_array(); 47 | self 48 | } 49 | } 50 | 51 | impl ViewTemplate for ListView { 52 | fn create(&self, _cx: &mut Cx) -> impl IntoView { 53 | ScrollView::new() 54 | .children( 55 | Element::::new() 56 | .named("ListView") 57 | .style(style_listview_inner) 58 | .children(self.children.clone()), 59 | ) 60 | .style((style_listview, self.style.clone())) 61 | .scroll_enable_y(true) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /crates/obsidian_ui/src/controls/mod.rs: -------------------------------------------------------------------------------- 1 | mod button; 2 | mod checkbox; 3 | mod dialog; 4 | mod disclosure_toggle; 5 | mod gradient_slider; 6 | mod icon; 7 | mod icon_button; 8 | mod listview; 9 | mod menu; 10 | mod node_graph; 11 | mod scrollview; 12 | mod slider; 13 | mod spacer; 14 | mod spinbox; 15 | mod splitter; 16 | mod swatch; 17 | mod swatch_grid; 18 | mod text_input; 19 | mod tool_palette; 20 | 21 | pub use button::*; 22 | pub use checkbox::*; 23 | pub use dialog::*; 24 | pub use disclosure_toggle::*; 25 | pub use gradient_slider::*; 26 | pub use icon::*; 27 | pub use icon_button::*; 28 | pub use listview::*; 29 | pub use menu::*; 30 | pub use node_graph::*; 31 | pub use scrollview::{ScrollView, Scrollbar, ScrollbarProps}; 32 | pub use slider::*; 33 | pub use spacer::*; 34 | pub use spinbox::*; 35 | pub use splitter::*; 36 | pub use swatch::*; 37 | pub use swatch_grid::*; 38 | pub use text_input::*; 39 | pub use tool_palette::*; 40 | -------------------------------------------------------------------------------- /crates/obsidian_ui/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! An opinionated library of ui editor controls for Bevy. 2 | 3 | #![warn(missing_docs)] 4 | 5 | use bevy::{app::*, ui::UiMaterialPlugin}; 6 | use bevy_mod_picking::prelude::EventListenerPlugin; 7 | use controls::MenuCloseEvent; 8 | use materials::{ 9 | DotGridMaterial, DrawPathMaterial, GradientRectMaterial, SliderRectMaterial, SwatchRectMaterial, 10 | }; 11 | 12 | /// Utilities for animation. 13 | pub mod animation; 14 | 15 | /// Module containing standard color definitions. 16 | #[allow(missing_docs)] 17 | pub mod colors; 18 | 19 | /// Module containing interactive and layout control widgets. 20 | pub mod controls; 21 | 22 | /// Module containing utilities for creating custom window cursors. 23 | pub mod cursor; 24 | 25 | /// Utilities for tabbing between widgets. 26 | pub mod focus; 27 | 28 | /// Utilities for floating popups. 29 | pub mod floating; 30 | 31 | /// Module containing extensions to `Cx`. 32 | pub mod hooks; 33 | 34 | /// Module containing custom materials. 35 | pub mod materials; 36 | 37 | /// Utilities for managing scrolling views. 38 | pub mod scrolling; 39 | 40 | /// Module containing standard sizes. 41 | pub mod size; 42 | 43 | /// Module of utilities for embedding a 3D viewport in the 2D UI. 44 | pub mod viewport; 45 | 46 | /// Standard styles for fonts. 47 | pub mod typography; 48 | 49 | /// Plugin for the Obsidian UI library. 50 | pub struct ObsidianUiPlugin; 51 | 52 | use scrolling::ScrollWheel; 53 | 54 | mod rounded_corners; 55 | pub use rounded_corners::RoundedCorners; 56 | 57 | impl Plugin for ObsidianUiPlugin { 58 | fn build(&self, app: &mut App) { 59 | app.add_plugins(( 60 | UiMaterialPlugin::::default(), 61 | UiMaterialPlugin::::default(), 62 | UiMaterialPlugin::::default(), 63 | UiMaterialPlugin::::default(), 64 | UiMaterialPlugin::::default(), 65 | hooks::BistableTransitionPlugin, 66 | animation::AnimatedTransitionPlugin, 67 | focus::KeyboardInputPlugin, 68 | )) 69 | .add_plugins(( 70 | EventListenerPlugin::::default(), 71 | EventListenerPlugin::::default(), 72 | )) 73 | .add_event::() 74 | .add_systems( 75 | Update, 76 | ( 77 | scrolling::handle_scroll_events, 78 | scrolling::update_scroll_positions, 79 | cursor::update_cursor, 80 | ), 81 | ) 82 | .add_systems(PostUpdate, floating::position_floating); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/obsidian_ui/src/viewport.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, render::camera::Viewport}; 2 | 3 | /// Used to create margins around the viewport so that side panels don't overwrite the 3d scene. 4 | #[derive(Default, Resource, PartialEq, Debug)] 5 | #[allow(missing_docs)] 6 | pub struct ViewportInset { 7 | pub left: f32, 8 | pub right: f32, 9 | pub top: f32, 10 | pub bottom: f32, 11 | } 12 | 13 | /// Marker which identifies which camera is displayed in the viewport. 14 | #[derive(Component)] 15 | pub struct ViewportCamera; 16 | 17 | /// A marker component for that identifies which element contains the 3d view. The 18 | /// `update_viewport_inset` system measures the on-screen position of the UiNode that this 19 | /// component is attached to, and updates the screen position of the 3D view to match it. 20 | #[derive(Component, Clone)] 21 | pub struct ViewportInsetElement; 22 | 23 | /// Update the viewport inset based on the global position of the ui element representing the 24 | /// viewport. 25 | pub fn update_viewport_inset( 26 | windows: Query<&Window>, 27 | query: Query<(&Node, &GlobalTransform), With>, 28 | mut viewport_inset: ResMut, 29 | ) { 30 | // `physical_pixels = logical_pixels * scale_factor` 31 | let mut inset = ViewportInset::default(); 32 | match query.get_single() { 33 | Ok((node, transform)) => { 34 | let rect = node.logical_rect(transform); 35 | let window = windows.single(); 36 | let ww = window.resolution.physical_width() as f32; 37 | let wh = window.resolution.physical_height() as f32; 38 | let sf = window.resolution.scale_factor(); 39 | 40 | inset.left = rect.min.x; 41 | inset.top = rect.min.y; 42 | inset.right = ww / sf - rect.max.x; 43 | inset.bottom = wh / sf - rect.max.y; 44 | } 45 | Err(_) => { 46 | if query.iter().count() > 1 { 47 | error!("Multiple ViewportInset elements!"); 48 | } 49 | } 50 | } 51 | 52 | if inset != *viewport_inset { 53 | *viewport_inset.as_mut() = inset; 54 | } 55 | } 56 | 57 | /// Update the camera viewport and fov properties based on the window size and the viewport 58 | /// margins. 59 | pub fn update_camera_viewport( 60 | viewport_inset: Res, 61 | windows: Query<&Window>, 62 | mut camera_query: Query<(&mut Camera, &mut Projection), With>, 63 | ) { 64 | let window = windows.single(); 65 | let ww = window.resolution.physical_width() as f32; 66 | let wh = window.resolution.physical_height() as f32; 67 | let sf = window.resolution.scale_factor(); 68 | let left = (viewport_inset.left * sf).clamp(0., ww); 69 | let right = (viewport_inset.right * sf).clamp(0., ww); 70 | let top = (viewport_inset.top * sf).clamp(0., wh); 71 | let bottom = (viewport_inset.bottom * sf).clamp(0., wh); 72 | let vw = (ww - left - right).max(1.); 73 | let vh = (wh - top - bottom).max(1.); 74 | 75 | let (mut camera, _) = camera_query.single_mut(); 76 | camera.viewport = Some(Viewport { 77 | physical_position: UVec2::new(left as u32, top as u32), 78 | physical_size: UVec2::new(vw as u32, vh as u32), 79 | ..default() 80 | }); 81 | } 82 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "obsidian_ui_inspect" 3 | version = "0.1.0" 4 | edition = "2021" 5 | publish = false 6 | 7 | [dependencies] 8 | bevy = { workspace = true } 9 | bevy_reactor_signals = { workspace = true } 10 | bevy_mod_stylebuilder = { workspace = true } 11 | obsidian_ui = { path = "../obsidian_ui" } 12 | bevy_reactor = { path = "../.." } 13 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/attributes.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use bevy::reflect::Reflect; 4 | 5 | /// An attribute that specifies the minimum and maximum allowed values for a field. 6 | /// This range is inclusive. 7 | /// 8 | /// This attribute can be applied to numeric fields. It can also be applied to aggregate types 9 | /// that have a numeric type parameter, such as an `Option` or `Vec`. 10 | #[derive(Debug, Clone, Reflect)] 11 | pub struct ValueRange(pub Range); 12 | 13 | /// An attribute that specifies how many decimal digits of precision should be allowed. 14 | /// If the field is an integer, this will be ignored. If present, field values will be 15 | /// rounded to the nearest value with the specified number of decimal digits. 16 | /// 17 | /// This attribute can be applied to numeric fields. It can also be applied to aggregate types 18 | /// that have a numeric type parameter, such as an `Option` or `Vec`. 19 | #[derive(Debug, Clone, Reflect)] 20 | pub struct Precision(pub usize); 21 | 22 | /// An attribute that specifies the increment and decrement step size for a numeric field. 23 | /// If not present, the step size will be determined from the precision. If the precision is 24 | /// not present, a heuristic will be used based on the range. 25 | /// 26 | /// This attribute can be applied to numeric fields. It can also be applied to aggregate types 27 | /// that have a numeric type parameter, such as an `Option` or `Vec`. 28 | #[derive(Debug, Clone, Reflect)] 29 | pub struct Step(pub T); 30 | 31 | /// An attribute that specifies that a text field should be displayed as a multiline text field. 32 | /// This also means that newlines can be inserted into the text. 33 | #[derive(Debug, Clone, Reflect)] 34 | pub struct Multiline; 35 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/default_factory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use crate::{ 4 | inspectors::{ 5 | bool::BooleanFieldInspector, color::SrgbaInspector, f32::F32FieldInspector, 6 | fallback::FallbackInspector, list::ListInspector, r#enum::EnumInspector, 7 | r#struct::NestedStruct, tuple_struct::NestedTupleStruct, vec3::Vec3FieldInspector, 8 | }, 9 | templates::{field_label::FieldLabel, field_readonly_value::FieldReadonlyValue}, 10 | Inspectable, InspectorFactory, 11 | }; 12 | use bevy::reflect::ReflectRef; 13 | use bevy_reactor::*; 14 | use bevy_reactor_signals::Cx; 15 | 16 | #[derive(Default)] 17 | pub struct DefaultInspectorFactory; 18 | 19 | impl InspectorFactory for DefaultInspectorFactory { 20 | fn create_inspector(&self, cx: &Cx, field: Arc) -> Option { 21 | let reflect = field.reflect(cx)?; 22 | match reflect.reflect_ref() { 23 | ReflectRef::Struct(s) => match s.reflect_type_path() { 24 | "bevy_color::srgba::Srgba" => Some(SrgbaInspector(field.clone()).into_view()), 25 | "glam::Vec3" => Some(Vec3FieldInspector(field.clone()).into_view()), 26 | _ => Some(NestedStruct(field.clone()).into_view()), 27 | }, 28 | ReflectRef::TupleStruct(_) => Some(NestedTupleStruct(field.clone()).into_view()), 29 | ReflectRef::Tuple(_) => Some( 30 | Fragment::new(( 31 | FieldLabel { 32 | field: field.clone(), 33 | }, 34 | FieldReadonlyValue::new().children("Tuple:TODO"), 35 | )) 36 | .into_view(), 37 | ), 38 | ReflectRef::List(_) => Some(ListInspector(field.clone()).into_view()), 39 | ReflectRef::Array(_) => Some( 40 | Fragment::new(( 41 | FieldLabel { 42 | field: field.clone(), 43 | }, 44 | FieldReadonlyValue::new().children("Array:TODO"), 45 | )) 46 | .into_view(), 47 | ), 48 | ReflectRef::Map(_) => Some( 49 | Fragment::new(( 50 | FieldLabel { 51 | field: field.clone(), 52 | }, 53 | FieldReadonlyValue::new().children("Map:TODO"), 54 | )) 55 | .into_view(), 56 | ), 57 | ReflectRef::Enum(_) => Some(EnumInspector(field.clone()).into_view()), 58 | ReflectRef::Value(v) => match v.reflect_type_path() { 59 | "bool" => Some(BooleanFieldInspector(field.clone()).into_view()), 60 | "f32" => Some(F32FieldInspector(field.clone()).into_view()), 61 | _ => Some(FallbackInspector(field.clone()).into_view()), 62 | }, 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspector.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy::reflect::{ParsedPath, ReflectKind}; 4 | use bevy_reactor::*; 5 | use bevy_reactor_signals::{Cx, RunContextSetup}; 6 | use obsidian_ui::controls::Spacer; 7 | 8 | use crate::{ 9 | inspectors::{ 10 | r#struct::{StructFieldList, StructInspectorHeaderControls}, 11 | tuple_struct::TupleStructElements, 12 | }, 13 | templates::inspector_panel::InspectorPanel, 14 | Inspectable, InspectableRoot, 15 | }; 16 | 17 | pub struct Inspector { 18 | // Reference to the entity being inspected 19 | target: Arc, 20 | } 21 | 22 | impl Inspector { 23 | pub fn new(target: Arc) -> Self { 24 | Self { target } 25 | } 26 | 27 | fn create_fields(&self, cx: &mut Cx, inspectable: Arc) -> ViewRef { 28 | let access = inspectable.clone(); 29 | let field_type = 30 | cx.create_memo(move |cx| access.reflect(cx).unwrap().reflect_kind().to_owned()); 31 | DynamicKeyed::new( 32 | move |cx| field_type.get(cx), 33 | move |ftype| match ftype { 34 | ReflectKind::Struct => StructFieldList(inspectable.clone()).into_view(), 35 | ReflectKind::TupleStruct => TupleStructElements(inspectable.clone()).into_view(), 36 | ReflectKind::Tuple => todo!(), 37 | ReflectKind::List => todo!(), 38 | ReflectKind::Array => todo!(), 39 | ReflectKind::Map => todo!(), 40 | ReflectKind::Enum => todo!(), 41 | ReflectKind::Value => todo!(), 42 | }, 43 | ) 44 | .into_view() 45 | } 46 | } 47 | 48 | impl ViewTemplate for Inspector { 49 | fn create(&self, cx: &mut Cx) -> impl IntoView { 50 | let path = ParsedPath(vec![]); 51 | let inspectable = Arc::new(Inspectable { 52 | root: self.target.clone(), 53 | name: self.target.name(cx).clone(), 54 | value_path: path.clone(), 55 | field_path: path, 56 | can_remove: true, 57 | attributes: None, 58 | }); 59 | InspectorPanel::new() 60 | .title(( 61 | self.target.name(cx), 62 | Spacer, 63 | StructInspectorHeaderControls { 64 | target: inspectable.clone(), 65 | }, 66 | )) 67 | .body(self.create_fields(cx, inspectable)) 68 | .expanded(true) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspector_factory.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy::prelude::*; 4 | use bevy_reactor::*; 5 | use bevy_reactor_signals::Cx; 6 | 7 | use crate::Inspectable; 8 | 9 | /// Trait that defines a factory for creating inspectors. Multiple factories can be registered, 10 | /// and the first one that returns true will be used to create the inspector. 11 | pub trait InspectorFactory: Sync + Send { 12 | /// Examine the reflect data and decide what kind of widget to create to edit the 13 | /// data. Can return false if the data is not in a supported format. 14 | fn create_inspector(&self, reflect: &Cx, field: Arc) -> Option; 15 | } 16 | 17 | #[derive(Resource, Default)] 18 | pub struct InspectorFactoryRegistry(pub Vec>); 19 | 20 | impl InspectorFactoryRegistry { 21 | pub fn create_inspector(&self, cx: &Cx, inspectable: Arc) -> Option { 22 | for factory in self.0.iter() { 23 | if let Some(view_ref) = factory.create_inspector(cx, inspectable.clone()) { 24 | return Some(view_ref); 25 | } 26 | } 27 | None 28 | } 29 | } 30 | 31 | pub trait RegisterInspectorFactory { 32 | fn register_inspector(&mut self) -> &mut Self; 33 | } 34 | 35 | impl RegisterInspectorFactory for App { 36 | fn register_inspector(&mut self) -> &mut Self { 37 | match self 38 | .world_mut() 39 | .get_resource_mut::() 40 | { 41 | Some(mut registry) => { 42 | registry.0.push(Box::::default()); 43 | } 44 | None => { 45 | self.world_mut() 46 | .insert_resource(InspectorFactoryRegistry(vec![Box::::default()])); 47 | } 48 | } 49 | self 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspectors/bool.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy::prelude::*; 4 | use bevy_mod_stylebuilder::*; 5 | use bevy_reactor::*; 6 | use bevy_reactor_signals::{Cx, RunContextSetup}; 7 | use obsidian_ui::controls::Checkbox; 8 | 9 | use crate::{templates::field_label::FieldLabel, Inspectable}; 10 | 11 | pub struct BooleanFieldInspector(pub(crate) Arc); 12 | 13 | impl ViewTemplate for BooleanFieldInspector { 14 | fn create(&self, cx: &mut Cx) -> impl IntoView { 15 | let field = self.0.clone(); 16 | let is_checked = cx.create_memo(move |cx| { 17 | if let Some(value) = field.reflect(cx) { 18 | if value.is::() { 19 | return *value.downcast_ref::().unwrap(); 20 | } 21 | } 22 | false 23 | }); 24 | 25 | let field = self.0.clone(); 26 | Fragment::new(( 27 | FieldLabel { 28 | field: field.clone(), 29 | }, 30 | Checkbox { 31 | checked: is_checked, 32 | on_change: Some(cx.create_callback(move |cx: &mut Cx, value: bool| { 33 | field.set_value(cx, value.as_reflect()); 34 | })), 35 | style: StyleHandle::new(|ss: &mut StyleBuilder| { 36 | ss.justify_self(JustifySelf::Start); 37 | }), 38 | ..default() 39 | }, 40 | )) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspectors/color.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy::{prelude::*, ui}; 4 | use bevy_mod_stylebuilder::*; 5 | use bevy_reactor::*; 6 | use bevy_reactor_signals::{Cx, RunContextSetup}; 7 | use obsidian_ui::{ 8 | colors, 9 | controls::{Icon, MenuButton, MenuPopup, Spacer, Swatch}, 10 | floating::{FloatAlign, FloatSide}, 11 | size::Size, 12 | }; 13 | 14 | fn style_field(ss: &mut StyleBuilder) { 15 | ss.flex_direction(ui::FlexDirection::Row) 16 | .align_items(ui::AlignItems::Center) 17 | .justify_content(ui::JustifyContent::FlexStart) 18 | .color(colors::FOREGROUND); 19 | } 20 | 21 | fn style_swatch(ss: &mut StyleBuilder) { 22 | ss.width(16).height(16).margin_right(4); 23 | } 24 | 25 | fn style_menu_icon(ss: &mut StyleBuilder) { 26 | ss.margin((2, 0)); 27 | } 28 | 29 | use crate::{ 30 | templates::{ 31 | color_edit::{ColorEdit, ColorEditState, ColorMode}, 32 | field_label::FieldLabel, 33 | }, 34 | Inspectable, 35 | }; 36 | 37 | pub struct SrgbaInspector(pub(crate) Arc); 38 | 39 | impl ViewTemplate for SrgbaInspector { 40 | fn create(&self, cx: &mut Cx) -> impl IntoView { 41 | let field = self.0.clone(); 42 | let value = cx.create_memo(move |cx| { 43 | if let Some(value) = field.reflect(cx) { 44 | if value.is::() { 45 | return *value.downcast_ref::().unwrap(); 46 | } 47 | } 48 | Srgba::NONE 49 | }); 50 | 51 | let state = cx.create_mutable(ColorEditState { 52 | mode: ColorMode::Rgb, 53 | rgb: Srgba::default(), 54 | hsl: Hsla::default(), 55 | }); 56 | 57 | let field = self.0.clone(); 58 | cx.create_effect(move |cx| { 59 | let next_state = state.get(cx); 60 | if let Some(reflect) = field.reflect(cx) { 61 | let value = *reflect.downcast_ref::().unwrap(); 62 | if value != next_state.rgb { 63 | field.set_value(cx, &next_state.rgb); 64 | } 65 | } 66 | }); 67 | 68 | Fragment::new(( 69 | FieldLabel { 70 | field: self.0.clone(), 71 | }, 72 | Element::::new().style(style_field).children(( 73 | Swatch::new(value).style(style_swatch), 74 | text_computed(move |cx| { 75 | let value = value.get(cx); 76 | value.to_hex() 77 | }), 78 | Spacer, 79 | MenuButton::new() 80 | .children( 81 | Icon::new("obsidian_ui://icons/tune.png") 82 | .size(Vec2::splat(16.0)) 83 | .style(style_menu_icon) 84 | .color(Color::from(colors::DIM)), 85 | ) 86 | .popup( 87 | MenuPopup::new() 88 | .side(FloatSide::Right) 89 | .align(FloatAlign::Start) 90 | .children(ColorEdit::new( 91 | state, 92 | cx.create_callback(move |cx, st: ColorEditState| { 93 | state.set(cx, st); 94 | }), 95 | )), 96 | ) 97 | .size(Size::Xxs) 98 | .minimal(true) 99 | .no_caret(true), 100 | )), 101 | )) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspectors/f32.rs: -------------------------------------------------------------------------------- 1 | use std::{ops::Range, sync::Arc}; 2 | 3 | use bevy::reflect::Reflect; 4 | use bevy_reactor::*; 5 | use bevy_reactor_signals::{Cx, RunContextSetup}; 6 | use obsidian_ui::controls::{Slider, SpinBox}; 7 | 8 | use crate::{templates::field_label::FieldLabel, Inspectable, Precision, Step, ValueRange}; 9 | 10 | #[derive(Clone, Debug)] 11 | struct F32Attrs { 12 | range: Option>, 13 | precision: usize, 14 | step: f32, 15 | } 16 | 17 | pub struct F32FieldInspector(pub(crate) Arc); 18 | 19 | impl ViewTemplate for F32FieldInspector { 20 | fn create(&self, cx: &mut Cx) -> impl IntoView { 21 | let field = self.0.clone(); 22 | let value = cx.create_memo(move |cx| match field.reflect(cx) { 23 | Some(value) if value.is::() => *value.downcast_ref::().unwrap(), 24 | _ => 0.0, 25 | }); 26 | 27 | let field = self.0.clone(); 28 | let mut slider_params = F32Attrs { 29 | range: None, 30 | precision: 0, 31 | step: 1.0, 32 | }; 33 | 34 | if let Some(attrs) = field.attributes { 35 | if let Some(range) = attrs.get::>() { 36 | slider_params.range = Some(range.0.clone()); 37 | slider_params.precision = 38 | (2. - (range.0.end - range.0.start).log10().ceil()).max(0.) as usize; 39 | } 40 | if let Some(precision) = attrs.get::() { 41 | slider_params.precision = precision.0; 42 | } 43 | if let Some(step) = attrs.get::>() { 44 | slider_params.step = step.0; 45 | } else { 46 | slider_params.step = 10.0f32.powi(-(slider_params.precision as i32)); 47 | } 48 | } 49 | 50 | // let field = self.field.clone(); 51 | Fragment::new(( 52 | FieldLabel { 53 | field: field.clone(), 54 | }, 55 | // Don't need `Cond` here because condition is not reactive; reflection data 56 | // is constant. 57 | match slider_params.range { 58 | Some(range) => Slider::new() 59 | .min(range.start) 60 | .max(range.end) 61 | .precision(slider_params.precision) 62 | .step(slider_params.step) 63 | .value(value) 64 | .on_change(cx.create_callback(move |cx, value: f32| { 65 | field.update(cx, &|reflect| { 66 | reflect.apply(value.as_reflect()); 67 | }); 68 | })) 69 | .into_view(), 70 | None => SpinBox::new() 71 | .precision(slider_params.precision) 72 | .step(slider_params.step) 73 | .value(value) 74 | .on_change(cx.create_callback(move |cx, value: f32| { 75 | field.update(cx, &|reflect| { 76 | reflect.apply(value.as_reflect()); 77 | }); 78 | })) 79 | .into_view(), 80 | }, 81 | )) 82 | .into_view() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspectors/fallback.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy_reactor::*; 4 | use bevy_reactor_signals::Cx; 5 | 6 | use crate::{ 7 | templates::{field_label::FieldLabel, field_readonly_value::FieldReadonlyValue}, 8 | Inspectable, 9 | }; 10 | 11 | /// Field editor for when no specific editor is available. 12 | pub struct FallbackInspector(pub(crate) Arc); 13 | 14 | impl ViewTemplate for FallbackInspector { 15 | fn create(&self, cx: &mut Cx) -> impl IntoView { 16 | let field = self.0.clone(); 17 | let Some(reflect) = field.reflect(cx) else { 18 | return ().into_view(); 19 | }; 20 | Fragment::new(( 21 | FieldLabel { 22 | field: self.0.clone(), 23 | }, 24 | FieldReadonlyValue::new() 25 | .children(format!("Fallback: {}", reflect.reflect_type_path())), 26 | )) 27 | .into_view() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/inspectors/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bool; 2 | pub mod color; 3 | pub mod r#enum; 4 | pub mod r#f32; 5 | pub mod fallback; 6 | pub mod list; 7 | pub mod r#struct; 8 | pub mod tuple_struct; 9 | pub mod vec3; 10 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod attributes; 2 | mod default_factory; 3 | mod inspectable; 4 | mod inspector; 5 | mod inspector_factory; 6 | mod inspectors; 7 | mod templates; 8 | 9 | use bevy::app::{App, Plugin}; 10 | use default_factory::DefaultInspectorFactory; 11 | 12 | pub use attributes::*; 13 | pub use inspectable::*; 14 | pub use inspector::*; 15 | pub use inspector_factory::*; 16 | use templates::color_edit::RecentColors; 17 | 18 | pub struct InspectorPlugin; 19 | 20 | impl Plugin for InspectorPlugin { 21 | fn build(&self, app: &mut App) { 22 | app.register_inspector::() 23 | .init_resource::(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/templates/field_readonly_value.rs: -------------------------------------------------------------------------------- 1 | use bevy::{prelude::*, ui}; 2 | use bevy_mod_stylebuilder::*; 3 | use bevy_reactor::*; 4 | use bevy_reactor_signals::Cx; 5 | use obsidian_ui::{colors, typography}; 6 | 7 | fn style_field_readonly_value(ss: &mut StyleBuilder) { 8 | ss.display(ui::Display::Flex) 9 | .flex_direction(ui::FlexDirection::Row) 10 | .align_items(ui::AlignItems::Center) 11 | .justify_content(ui::JustifyContent::FlexStart) 12 | .border(1) 13 | .border_color(colors::U3) 14 | .font_size(16) 15 | .color(colors::DIM) 16 | .padding((4, 1)); 17 | } 18 | 19 | /// Readonly value displayed as text in the inspector. 20 | #[derive(Clone, Default)] 21 | pub struct FieldReadonlyValue { 22 | /// The text representation of the value. 23 | pub children: ChildArray, 24 | /// Additional styles for the label. 25 | pub style: StyleHandle, 26 | } 27 | 28 | impl FieldReadonlyValue { 29 | /// Create a new readonly value with the given text. 30 | pub fn new() -> Self { 31 | Self::default() 32 | } 33 | 34 | /// Set the child views for this element. 35 | pub fn children(mut self, children: V) -> Self { 36 | self.children = children.to_child_array(); 37 | self 38 | } 39 | 40 | /// Set the additional styles for the button. 41 | #[allow(dead_code)] 42 | pub fn style(mut self, style: S) -> Self { 43 | self.style = style.into_handle(); 44 | self 45 | } 46 | } 47 | 48 | impl ViewTemplate for FieldReadonlyValue { 49 | fn create(&self, _cx: &mut Cx) -> impl IntoView { 50 | Element::::new() 51 | .style(( 52 | typography::text_default, 53 | style_field_readonly_value, 54 | self.style.clone(), 55 | )) 56 | .children(self.children.clone()) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /crates/obsidian_ui_inspect/src/templates/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod color_edit; 2 | pub mod field_label; 3 | pub mod field_readonly_value; 4 | pub mod inspector_panel; 5 | -------------------------------------------------------------------------------- /examples/complex/assets/unlock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/viridia/bevy_reactor_2/8d8967ba734705695c8728ddcb19fdd3d7a88cff/examples/complex/assets/unlock.png -------------------------------------------------------------------------------- /examples/complex/reflect_demo.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use bevy::prelude::*; 4 | use bevy_reactor::*; 5 | use bevy_reactor_signals::Cx; 6 | 7 | #[derive(Debug, Reflect, Clone, Default)] 8 | pub enum TestEnum { 9 | #[default] 10 | Unit, 11 | Float(f32), 12 | Color(Srgba), 13 | Struct { 14 | position: Vec3, 15 | color: Srgba, 16 | }, 17 | } 18 | 19 | #[derive(Resource, Debug, Reflect, Clone, Default)] 20 | pub struct TestStruct { 21 | pub selected: bool, 22 | 23 | #[reflect(@ValueRange::(0.0..1.0))] 24 | pub scale: f32, 25 | 26 | pub color: Srgba, 27 | pub position: Vec3, 28 | pub unlit: Option, 29 | 30 | #[reflect(@ValueRange::(0.0..10.0))] 31 | pub roughness: Option, 32 | 33 | #[reflect(@Precision(2))] 34 | pub metalness: Option, 35 | 36 | #[reflect(@ValueRange::(0.0..1000.0))] 37 | pub factors: Vec, 38 | } 39 | 40 | #[derive(Resource, Debug, Reflect, Clone, Default)] 41 | pub struct TestStruct2 { 42 | pub nested: TestStruct, 43 | pub choice: TestEnum, 44 | } 45 | 46 | #[derive(Resource, Debug, Reflect, Clone, Default)] 47 | pub struct TestStruct3(pub bool); 48 | 49 | pub struct ResourcePropertyInspector { 50 | marker: std::marker::PhantomData, 51 | } 52 | 53 | impl ResourcePropertyInspector { 54 | pub fn new() -> Self { 55 | Self { 56 | marker: std::marker::PhantomData, 57 | } 58 | } 59 | } 60 | 61 | impl ViewTemplate for ResourcePropertyInspector { 62 | fn create(&self, _cx: &mut Cx) -> impl IntoView { 63 | Inspector::new(Arc::>::default()) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /examples/stateful.rs: -------------------------------------------------------------------------------- 1 | //! Example which uses states and a switch view. 2 | 3 | use bevy::{color::palettes, prelude::*}; 4 | use bevy_mod_stylebuilder::*; 5 | use bevy_reactor_builder::{ 6 | CreateChilden, EntityStyleBuilder, InvokeUiTemplate, SwitchBuilder, TextBuilder, UiBuilder, 7 | UiTemplate, 8 | }; 9 | use bevy_reactor_signals::{Rcx, SignalsPlugin}; 10 | 11 | fn style_test(ss: &mut StyleBuilder) { 12 | ss.display(Display::Flex) 13 | .flex_direction(FlexDirection::Row) 14 | .border(3) 15 | .padding(3); 16 | } 17 | 18 | #[derive(States, Debug, Clone, PartialEq, Eq, Hash, Default)] 19 | pub enum GameState { 20 | #[default] 21 | Play, 22 | Pause, 23 | Intro, 24 | } 25 | 26 | fn main() { 27 | App::new() 28 | .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) 29 | .insert_state(GameState::Intro) 30 | .add_plugins((SignalsPlugin, StyleBuilderPlugin)) 31 | .add_systems(Startup, setup_view_root) 32 | .add_systems(Update, (close_on_esc, handle_key_input)) 33 | .run(); 34 | } 35 | 36 | fn setup_view_root(world: &mut World) { 37 | let camera = world.spawn((Camera::default(), Camera2d)).id(); 38 | 39 | world 40 | .spawn(Node::default()) 41 | .style(style_test) 42 | .insert(TargetCamera(camera)) 43 | .insert(BorderColor(palettes::css::LIME.into())) 44 | .create_children(|builder| { 45 | builder.text("State: "); 46 | builder.invoke(StateName); 47 | }); 48 | } 49 | 50 | struct StateName; 51 | 52 | impl UiTemplate for StateName { 53 | fn build(&self, builder: &mut UiBuilder) { 54 | builder.switch( 55 | |rcx: &Rcx| rcx.read_resource::>().get().clone(), 56 | |builder| { 57 | builder 58 | .case(GameState::Intro, |builder| { 59 | builder.text("Intro"); 60 | }) 61 | .case(GameState::Pause, |builder| { 62 | builder.text("Pause"); 63 | }) 64 | .fallback(|builder| { 65 | builder.text("Play"); 66 | }); 67 | }, 68 | ); 69 | } 70 | } 71 | 72 | fn handle_key_input( 73 | state: Res>, 74 | mut next_state: ResMut>, 75 | key: Res>, 76 | ) { 77 | if key.just_pressed(KeyCode::Space) { 78 | match state.get() { 79 | GameState::Intro => next_state.set(GameState::Play), 80 | GameState::Play => next_state.set(GameState::Pause), 81 | GameState::Pause => next_state.set(GameState::Play), 82 | } 83 | } 84 | } 85 | 86 | pub fn close_on_esc(input: Res>, mut exit: EventWriter) { 87 | if input.just_pressed(KeyCode::Escape) { 88 | exit.send(AppExit::Success); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A fine-grained reactive framework for Bevy. 2 | 3 | #![warn(missing_docs)] 4 | --------------------------------------------------------------------------------