├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── assets └── food │ ├── 13_bacon.png │ ├── 15_burger.png │ ├── 18_burrito.png │ ├── 20_bagel.png │ ├── 22_cheesecake.png │ ├── 24_cheesepuff.png │ ├── 26_chocolate.png │ └── 28_cookies.png └── src ├── catppuccin ├── catppuccin_egui.rs └── mod.rs ├── expand_list_example ├── expand_list.rs └── mod.rs ├── interactive_text ├── mod.rs └── text_layout.rs ├── main.rs └── notification_example ├── mod.rs ├── notification.rs └── notification_producer.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bevy_egui_examples" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | bevy = "0.14" 8 | bevy_egui = "0.28" 9 | 10 | # Using copy of catppuccin until it is updated to egui 0.23 11 | # catppuccin-egui = "3.1.0" 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 goto64 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bevy_egui_examples 2 | Some examples on how egui can be used with Bevy engine 3 | 4 | # Credits 5 | Food images by ghostpixxells. 6 | https://ghostpixxells.itch.io/pixelfood 7 | -------------------------------------------------------------------------------- /assets/food/13_bacon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/13_bacon.png -------------------------------------------------------------------------------- /assets/food/15_burger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/15_burger.png -------------------------------------------------------------------------------- /assets/food/18_burrito.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/18_burrito.png -------------------------------------------------------------------------------- /assets/food/20_bagel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/20_bagel.png -------------------------------------------------------------------------------- /assets/food/22_cheesecake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/22_cheesecake.png -------------------------------------------------------------------------------- /assets/food/24_cheesepuff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/24_cheesepuff.png -------------------------------------------------------------------------------- /assets/food/26_chocolate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/26_chocolate.png -------------------------------------------------------------------------------- /assets/food/28_cookies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/goto64/bevy_egui_examples/34a7139b856fa919f136aa132f75b42accb980a4/assets/food/28_cookies.png -------------------------------------------------------------------------------- /src/catppuccin/catppuccin_egui.rs: -------------------------------------------------------------------------------- 1 | //! Soothing pastel theme for [egui](egui). 2 | //! 3 | //! To use, call [`set_theme`](crate::set_theme) with the egui context 4 | //! and a [`Theme`](crate::Theme). 5 | //! 6 | //! # Example 7 | //! 8 | //! ```rust 9 | //! struct App; 10 | //! impl eframe::App for App { 11 | //! fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { 12 | //! catppuccin_egui::set_theme(ctx, catppuccin_egui::MACCHIATO); 13 | //! egui::CentralPanel::default().show(&ctx, |ui| { 14 | //! ui.label("Hello, world!"); 15 | //! }); 16 | //! } 17 | //! } 18 | //! ``` 19 | //! 20 | //! You can also customize your own theme: 21 | //! 22 | //! ```rust 23 | //! use catppuccin_egui::{Theme, MOCHA}; 24 | //! const MY_MOCHA: Theme = Theme { 25 | //! red: egui::Color32::from_rgb(255, 0, 0), 26 | //! ..MOCHA 27 | //! }; 28 | //! ``` 29 | //! 30 | 31 | #![allow(dead_code)] 32 | 33 | use bevy_egui::egui; 34 | use crate::egui::{epaint, style, Color32}; 35 | 36 | /// Apply the given theme to a [`Context`](egui::Context). 37 | pub fn set_theme(ctx: &egui::Context, theme: Theme) { 38 | let old = ctx.style().visuals.clone(); 39 | ctx.set_visuals(theme.visuals(old)); 40 | } 41 | 42 | /// Apply the given theme to a [`Style`](egui::Style). 43 | /// 44 | /// # Example 45 | /// 46 | /// ```rust 47 | /// # use egui::__run_test_ctx; 48 | /// # __run_test_ctx(|ctx| { 49 | /// let mut style = (*ctx.style()).clone(); 50 | /// catppuccin_egui::set_style_theme(&mut style, catppuccin_egui::MACCHIATO); 51 | /// ctx.set_style(style); 52 | /// # }); 53 | /// ``` 54 | pub fn set_style_theme(style: &mut egui::Style, theme: Theme) { 55 | let old = style.visuals.clone(); 56 | style.visuals = theme.visuals(old); 57 | } 58 | 59 | fn make_widget_visual( 60 | old: style::WidgetVisuals, 61 | theme: &Theme, 62 | bg_fill: egui::Color32, 63 | ) -> style::WidgetVisuals { 64 | style::WidgetVisuals { 65 | bg_fill, 66 | weak_bg_fill: bg_fill, 67 | bg_stroke: egui::Stroke { 68 | color: theme.overlay1, 69 | ..old.bg_stroke 70 | }, 71 | fg_stroke: egui::Stroke { 72 | color: theme.text, 73 | ..old.fg_stroke 74 | }, 75 | ..old 76 | } 77 | } 78 | 79 | /// The colors for a theme variant. 80 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 81 | pub struct Theme { 82 | pub rosewater: Color32, 83 | pub flamingo: Color32, 84 | pub pink: Color32, 85 | pub mauve: Color32, 86 | pub red: Color32, 87 | pub maroon: Color32, 88 | pub peach: Color32, 89 | pub yellow: Color32, 90 | pub green: Color32, 91 | pub teal: Color32, 92 | pub sky: Color32, 93 | pub sapphire: Color32, 94 | pub blue: Color32, 95 | pub lavender: Color32, 96 | pub text: Color32, 97 | pub subtext1: Color32, 98 | pub subtext0: Color32, 99 | pub overlay2: Color32, 100 | pub overlay1: Color32, 101 | pub overlay0: Color32, 102 | pub surface2: Color32, 103 | pub surface1: Color32, 104 | pub surface0: Color32, 105 | pub base: Color32, 106 | pub mantle: Color32, 107 | pub crust: Color32, 108 | } 109 | 110 | impl Theme { 111 | fn visuals(&self, old: egui::Visuals) -> egui::Visuals { 112 | let is_latte = *self == LATTE; 113 | egui::Visuals { 114 | override_text_color: Some(self.text), 115 | hyperlink_color: self.rosewater, 116 | faint_bg_color: self.surface0, 117 | extreme_bg_color: self.crust, 118 | code_bg_color: self.mantle, 119 | warn_fg_color: self.peach, 120 | error_fg_color: self.maroon, 121 | window_fill: self.base, 122 | panel_fill: self.base, 123 | window_stroke: egui::Stroke { 124 | color: self.overlay1, 125 | ..old.window_stroke 126 | }, 127 | widgets: style::Widgets { 128 | noninteractive: make_widget_visual(old.widgets.noninteractive, self, self.base), 129 | inactive: make_widget_visual(old.widgets.inactive, self, self.surface0), 130 | hovered: make_widget_visual(old.widgets.hovered, self, self.surface2), 131 | active: make_widget_visual(old.widgets.active, self, self.surface1), 132 | open: make_widget_visual(old.widgets.open, self, self.surface0), 133 | }, 134 | selection: style::Selection { 135 | bg_fill: self.blue.linear_multiply(if is_latte { 0.4 } else { 0.2 }), 136 | stroke: egui::Stroke { 137 | color: self.overlay1, 138 | ..old.selection.stroke 139 | }, 140 | }, 141 | window_shadow: epaint::Shadow { 142 | color: self.base, 143 | ..old.window_shadow 144 | }, 145 | popup_shadow: epaint::Shadow { 146 | color: self.base, 147 | ..old.popup_shadow 148 | }, 149 | dark_mode: !is_latte, 150 | ..old 151 | } 152 | } 153 | } 154 | 155 | pub const LATTE: Theme = Theme { 156 | rosewater: Color32::from_rgb(220, 138, 120), 157 | flamingo: Color32::from_rgb(221, 120, 120), 158 | pink: Color32::from_rgb(234, 118, 203), 159 | mauve: Color32::from_rgb(136, 57, 239), 160 | red: Color32::from_rgb(210, 15, 57), 161 | maroon: Color32::from_rgb(230, 69, 83), 162 | peach: Color32::from_rgb(254, 100, 11), 163 | yellow: Color32::from_rgb(223, 142, 29), 164 | green: Color32::from_rgb(64, 160, 43), 165 | teal: Color32::from_rgb(23, 146, 153), 166 | sky: Color32::from_rgb(4, 165, 229), 167 | sapphire: Color32::from_rgb(32, 159, 181), 168 | blue: Color32::from_rgb(30, 102, 245), 169 | lavender: Color32::from_rgb(114, 135, 253), 170 | text: Color32::from_rgb(76, 79, 105), 171 | subtext1: Color32::from_rgb(92, 95, 119), 172 | subtext0: Color32::from_rgb(108, 111, 133), 173 | overlay2: Color32::from_rgb(124, 127, 147), 174 | overlay1: Color32::from_rgb(140, 143, 161), 175 | overlay0: Color32::from_rgb(156, 160, 176), 176 | surface2: Color32::from_rgb(172, 176, 190), 177 | surface1: Color32::from_rgb(188, 192, 204), 178 | surface0: Color32::from_rgb(204, 208, 218), 179 | base: Color32::from_rgb(239, 241, 245), 180 | mantle: Color32::from_rgb(230, 233, 239), 181 | crust: Color32::from_rgb(220, 224, 232), 182 | }; 183 | 184 | pub const FRAPPE: Theme = Theme { 185 | rosewater: Color32::from_rgb(242, 213, 207), 186 | flamingo: Color32::from_rgb(238, 190, 190), 187 | pink: Color32::from_rgb(244, 184, 228), 188 | mauve: Color32::from_rgb(202, 158, 230), 189 | red: Color32::from_rgb(231, 130, 132), 190 | maroon: Color32::from_rgb(234, 153, 156), 191 | peach: Color32::from_rgb(239, 159, 118), 192 | yellow: Color32::from_rgb(229, 200, 144), 193 | green: Color32::from_rgb(166, 209, 137), 194 | teal: Color32::from_rgb(129, 200, 190), 195 | sky: Color32::from_rgb(153, 209, 219), 196 | sapphire: Color32::from_rgb(133, 193, 220), 197 | blue: Color32::from_rgb(140, 170, 238), 198 | lavender: Color32::from_rgb(186, 187, 241), 199 | text: Color32::from_rgb(198, 208, 245), 200 | subtext1: Color32::from_rgb(181, 191, 226), 201 | subtext0: Color32::from_rgb(165, 173, 206), 202 | overlay2: Color32::from_rgb(148, 156, 187), 203 | overlay1: Color32::from_rgb(131, 139, 167), 204 | overlay0: Color32::from_rgb(115, 121, 148), 205 | surface2: Color32::from_rgb(98, 104, 128), 206 | surface1: Color32::from_rgb(81, 87, 109), 207 | surface0: Color32::from_rgb(65, 69, 89), 208 | base: Color32::from_rgb(48, 52, 70), 209 | mantle: Color32::from_rgb(41, 44, 60), 210 | crust: Color32::from_rgb(35, 38, 52), 211 | }; 212 | 213 | pub const MACCHIATO: Theme = Theme { 214 | rosewater: Color32::from_rgb(244, 219, 214), 215 | flamingo: Color32::from_rgb(240, 198, 198), 216 | pink: Color32::from_rgb(245, 189, 230), 217 | mauve: Color32::from_rgb(198, 160, 246), 218 | red: Color32::from_rgb(237, 135, 150), 219 | maroon: Color32::from_rgb(238, 153, 160), 220 | peach: Color32::from_rgb(245, 169, 127), 221 | yellow: Color32::from_rgb(238, 212, 159), 222 | green: Color32::from_rgb(166, 218, 149), 223 | teal: Color32::from_rgb(139, 213, 202), 224 | sky: Color32::from_rgb(145, 215, 227), 225 | sapphire: Color32::from_rgb(125, 196, 228), 226 | blue: Color32::from_rgb(138, 173, 244), 227 | lavender: Color32::from_rgb(183, 189, 248), 228 | text: Color32::from_rgb(202, 211, 245), 229 | subtext1: Color32::from_rgb(184, 192, 224), 230 | subtext0: Color32::from_rgb(165, 173, 203), 231 | overlay2: Color32::from_rgb(147, 154, 183), 232 | overlay1: Color32::from_rgb(128, 135, 162), 233 | overlay0: Color32::from_rgb(110, 115, 141), 234 | surface2: Color32::from_rgb(91, 96, 120), 235 | surface1: Color32::from_rgb(73, 77, 100), 236 | surface0: Color32::from_rgb(54, 58, 79), 237 | base: Color32::from_rgb(36, 39, 58), 238 | mantle: Color32::from_rgb(30, 32, 48), 239 | crust: Color32::from_rgb(24, 25, 38), 240 | }; 241 | 242 | pub const MOCHA: Theme = Theme { 243 | rosewater: Color32::from_rgb(245, 224, 220), 244 | flamingo: Color32::from_rgb(242, 205, 205), 245 | pink: Color32::from_rgb(245, 194, 231), 246 | mauve: Color32::from_rgb(203, 166, 247), 247 | red: Color32::from_rgb(243, 139, 168), 248 | maroon: Color32::from_rgb(235, 160, 172), 249 | peach: Color32::from_rgb(250, 179, 135), 250 | yellow: Color32::from_rgb(249, 226, 175), 251 | green: Color32::from_rgb(166, 227, 161), 252 | teal: Color32::from_rgb(148, 226, 213), 253 | sky: Color32::from_rgb(137, 220, 235), 254 | sapphire: Color32::from_rgb(116, 199, 236), 255 | blue: Color32::from_rgb(137, 180, 250), 256 | lavender: Color32::from_rgb(180, 190, 254), 257 | text: Color32::from_rgb(205, 214, 244), 258 | subtext1: Color32::from_rgb(186, 194, 222), 259 | subtext0: Color32::from_rgb(166, 173, 200), 260 | overlay2: Color32::from_rgb(147, 153, 178), 261 | overlay1: Color32::from_rgb(127, 132, 156), 262 | overlay0: Color32::from_rgb(108, 112, 134), 263 | surface2: Color32::from_rgb(88, 91, 112), 264 | surface1: Color32::from_rgb(69, 71, 90), 265 | surface0: Color32::from_rgb(49, 50, 68), 266 | base: Color32::from_rgb(30, 30, 46), 267 | mantle: Color32::from_rgb(24, 24, 37), 268 | crust: Color32::from_rgb(17, 17, 27), 269 | }; 270 | -------------------------------------------------------------------------------- /src/catppuccin/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod catppuccin_egui; -------------------------------------------------------------------------------- /src/expand_list_example/expand_list.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{egui, EguiContexts}; 3 | use bevy_egui::egui::{emath, Frame, Pos2, TextureId, Ui}; 4 | use bevy_egui::egui::load::SizedTexture; 5 | use crate::notification_example::notification::Notifications; 6 | 7 | /// Demonstration of how to create an expandable selection list with icons and text 8 | pub struct ExpansionListPlugin; 9 | 10 | impl Plugin for ExpansionListPlugin { 11 | fn build(&self, app: &mut App) { 12 | app 13 | .init_resource::() 14 | .add_systems(Startup, load_food_images) 15 | .add_systems(Update, expand_list); 16 | } 17 | } 18 | 19 | #[derive(Resource)] 20 | pub struct ExpansionList { 21 | expanded: bool, 22 | selected: usize, 23 | } 24 | 25 | impl Default for ExpansionList { 26 | fn default() -> Self { 27 | Self { 28 | expanded: true, 29 | selected: 0, 30 | } 31 | } 32 | } 33 | 34 | fn expand_list( 35 | mut contexts: EguiContexts, 36 | mut expansion_list: ResMut, 37 | food_images: Res, 38 | mut notifications: ResMut, 39 | ) { 40 | egui::Window::new("Expansion list") 41 | .resizable(false) 42 | .collapsible(false) 43 | .default_pos(Pos2{x: 50.0, y: 50.0}) 44 | .show(contexts.ctx_mut(), |ui| { 45 | ui.set_width(225.0); 46 | 47 | egui::TopBottomPanel::show_animated_between_inside( 48 | ui, expansion_list.expanded, 49 | egui::TopBottomPanel::top("ListCollapsed").show_separator_line(false).frame(Frame::none()), 50 | egui::TopBottomPanel::top("ListExpanded").show_separator_line(false).frame(Frame::none()), 51 | |ui, _| 52 | { 53 | if !expansion_list.expanded { // Collapsed 54 | ui.label(bigger_text(String::from("Click to change food:"))); 55 | if food_images.clickable_food_button( 56 | ui, food_images.images[expansion_list.selected], food_images.names[expansion_list.selected].clone(), None) { 57 | expansion_list.expanded = true; 58 | } 59 | } else { // Expanded 60 | ui.label(bigger_text(String::from("Select food:"))); 61 | let selected_food = expanded_selection_ui(ui, &food_images, expansion_list.selected); 62 | if selected_food.is_some() { 63 | expansion_list.selected = selected_food.unwrap(); 64 | expansion_list.expanded = false; 65 | } 66 | } 67 | 68 | ui.add_space(10.0); 69 | if ui.button(bigger_text(format!("Eat {}", food_images.names[expansion_list.selected]))).clicked() { 70 | notifications.add_notification(format!("Eating {}", food_images.names[expansion_list.selected])); 71 | } 72 | }); 73 | }); 74 | } 75 | 76 | fn bigger_text(text: String) -> egui::RichText { 77 | return egui::RichText::new(text).size(14.0); 78 | } 79 | 80 | const LIST_HIGHLIGHT_COLOR: egui::Color32 = egui::Color32::from_rgb(52, 66, 73); 81 | 82 | fn expanded_selection_ui( 83 | ui: &mut Ui, 84 | food_images: &FoodImages, 85 | prev_food: usize 86 | ) -> Option { 87 | let mut selected_food = None; 88 | 89 | for i in 0..food_images.images.len() { 90 | if food_images.clickable_food_button( 91 | ui, food_images.images[i], food_images.names[i].clone(), 92 | if prev_food == i { Some(LIST_HIGHLIGHT_COLOR) } else { None }) { 93 | selected_food = Some(i); 94 | } 95 | } 96 | 97 | return selected_food; 98 | } 99 | 100 | #[derive(Resource)] 101 | struct FoodImages { 102 | images: Vec, 103 | names: Vec, 104 | } 105 | 106 | impl FoodImages { 107 | fn clickable_food_button(&self, ui: &mut Ui, image: TextureId, text: impl Into, col: Option) -> bool { 108 | let image = egui::Image::new(egui::widgets::ImageSource::Texture( 109 | SizedTexture { id: image, size: emath::Vec2 { x: 32.0, y: 32.0 } })); 110 | let mut btn = egui::Button::image_and_text( 111 | image, egui::RichText::new(text).size(16.0).monospace()) 112 | .min_size(emath::Vec2 { x: 220.0, y: 32.0 }); 113 | if col.is_some() { 114 | btn = btn.fill(col.unwrap()); 115 | } 116 | let clicked = ui.add(btn).clicked(); 117 | 118 | return clicked; 119 | } 120 | } 121 | 122 | fn load_food_images( 123 | asset_server: Res, 124 | mut contexts: EguiContexts, 125 | mut commands: Commands, 126 | ) { 127 | let mut images: Vec = Vec::new(); 128 | images.push(ui_add_image(&mut contexts, &asset_server, "food/13_bacon.png")); 129 | images.push(ui_add_image(&mut contexts, &asset_server, "food/15_burger.png")); 130 | images.push(ui_add_image(&mut contexts, &asset_server, "food/18_burrito.png")); 131 | images.push(ui_add_image(&mut contexts, &asset_server, "food/20_bagel.png")); 132 | images.push(ui_add_image(&mut contexts, &asset_server, "food/22_cheesecake.png")); 133 | images.push(ui_add_image(&mut contexts, &asset_server, "food/24_cheesepuff.png")); 134 | images.push(ui_add_image(&mut contexts, &asset_server, "food/26_chocolate.png")); 135 | images.push(ui_add_image(&mut contexts, &asset_server, "food/28_cookies.png")); 136 | 137 | let names: Vec = vec![ 138 | String::from("Bacon"), 139 | String::from("Burger"), 140 | String::from("Burrito"), 141 | String::from("Bagel"), 142 | String::from("Cheesecake"), 143 | String::from("Cheese puff"), 144 | String::from("Chocolate"), 145 | String::from("Cookie"), 146 | ]; 147 | 148 | assert_eq!(images.len(), names.len()); 149 | 150 | commands.insert_resource(FoodImages { 151 | images, 152 | names, 153 | }); 154 | } 155 | 156 | fn ui_add_image(contexts: &mut EguiContexts, asset_server: &AssetServer, path: &'static str) -> TextureId { 157 | let img: Handle = asset_server.load(path); 158 | return contexts.add_image(img); 159 | } -------------------------------------------------------------------------------- /src/expand_list_example/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod expand_list; -------------------------------------------------------------------------------- /src/interactive_text/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod text_layout; -------------------------------------------------------------------------------- /src/interactive_text/text_layout.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{egui, EguiContexts}; 3 | use bevy_egui::egui::{Color32, Pos2, Sense}; 4 | use crate::notification_example::notification::Notifications; 5 | 6 | pub struct TextLayoutPlugin; 7 | 8 | impl Plugin for TextLayoutPlugin { 9 | fn build(&self, app: &mut App) { 10 | app 11 | .init_resource::() 12 | .add_systems(Update, text_layout_window); 13 | } 14 | } 15 | 16 | #[derive(Resource)] 17 | pub struct InteractiveTextState { 18 | hovered: bool, 19 | } 20 | 21 | impl Default for InteractiveTextState { 22 | fn default() -> Self { 23 | Self { 24 | hovered: false, 25 | } 26 | } 27 | } 28 | 29 | pub fn text_layout_window( 30 | mut contexts: EguiContexts, 31 | mut notifications: ResMut, 32 | mut text_state: ResMut, 33 | ) { 34 | egui::Window::new("Interactive Text") 35 | .resizable(true) 36 | .collapsible(false) 37 | .default_pos(Pos2{x: 400.0, y: 250.0}) 38 | .default_width(200.0) 39 | .show(contexts.ctx_mut(), |ui| { 40 | let txt1 = egui::RichText::new("This is an example of a paragraph of text. You can").size(14.0); 41 | let mut txt2 = egui::RichText::new("click this link").size(14.0).underline(); 42 | if text_state.hovered { 43 | txt2 = txt2.color(Color32::LIGHT_YELLOW); 44 | } else { 45 | txt2 = txt2.color(Color32::LIGHT_BLUE); 46 | } 47 | let txt3 = egui::RichText::new("to display a notification").size(14.0); 48 | 49 | ui.horizontal_wrapped(|ui| { 50 | ui.label(txt1); 51 | let interactive_txt = ui.add(egui::Label::new(txt2).sense(Sense::click())); 52 | text_state.hovered = interactive_txt.hovered(); 53 | if interactive_txt.clicked() { 54 | notifications.add_notification(String::from("This is a notification from the interactive text")); 55 | } 56 | ui.label(txt3); 57 | }); 58 | }); 59 | } 60 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use bevy::prelude::*; 2 | use bevy_egui::{egui, EguiContexts, EguiPlugin}; 3 | use bevy_egui::egui::{epaint}; 4 | use crate::catppuccin::catppuccin_egui; 5 | use crate::expand_list_example::expand_list::ExpansionListPlugin; 6 | use crate::interactive_text::text_layout::TextLayoutPlugin; 7 | use crate::notification_example::notification::NotificationsPlugin; 8 | use crate::notification_example::notification_producer::{NotificationProducer, ui_notification_producer}; 9 | 10 | mod notification_example; 11 | mod expand_list_example; 12 | mod catppuccin; 13 | mod interactive_text; 14 | 15 | fn main() { 16 | App::new() 17 | .add_plugins(DefaultPlugins.set(WindowPlugin { 18 | primary_window: Some(Window { ..Default::default() }), 19 | ..default() 20 | }).set(ImagePlugin::default_nearest())) 21 | .add_plugins(EguiPlugin) 22 | .add_plugins((NotificationsPlugin, ExpansionListPlugin, TextLayoutPlugin)) 23 | .init_resource::() 24 | .add_systems(Startup, ui_theme_selection) 25 | .add_systems(Update, ui_notification_producer) 26 | .run(); 27 | } 28 | 29 | fn ui_theme_selection(mut contexts: EguiContexts) { 30 | catppuccin_egui::set_theme(contexts.ctx_mut(), catppuccin_egui::MOCHA); 31 | 32 | let old = contexts.ctx_mut().style().visuals.clone(); 33 | contexts.ctx_mut().set_visuals(egui::Visuals { 34 | window_shadow: epaint::Shadow { 35 | offset: [1.0, 2.0].into(), 36 | blur: 9.0, 37 | spread: 8.0, 38 | color: catppuccin_egui::MOCHA.base, 39 | }, 40 | ..old 41 | }); 42 | } -------------------------------------------------------------------------------- /src/notification_example/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod notification; 2 | pub mod notification_producer; -------------------------------------------------------------------------------- /src/notification_example/notification.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | use bevy::prelude::*; 3 | use bevy_egui::{egui, EguiContexts}; 4 | use bevy_egui::egui::{Pos2, Ui}; 5 | 6 | /// Plugin to display text notifications that slide in from the right 7 | pub struct NotificationsPlugin; 8 | 9 | impl Plugin for NotificationsPlugin { 10 | fn build(&self, app: &mut App) { 11 | app 12 | .insert_resource(Notifications::new()) 13 | .add_systems(Update, display_notification); 14 | } 15 | } 16 | 17 | #[derive(Resource)] 18 | pub struct Notifications { 19 | queued: VecDeque, 20 | displaying: Option, 21 | timeout: f32, 22 | offset_x: f32, 23 | } 24 | 25 | impl Notifications { 26 | pub fn new() -> Self { 27 | Self { 28 | queued: VecDeque::new(), 29 | displaying: None, 30 | timeout: 0.0, 31 | offset_x: 0.0, 32 | } 33 | } 34 | 35 | pub fn add_notification(&mut self, notif_text: String) { 36 | self.queued.push_back(notif_text); 37 | } 38 | } 39 | 40 | const NOTIFICATION_TIMER_SECS: f32 = 4.0; 41 | const NOTIFICATION_PAUSE_SECS: f32 = 0.5; 42 | 43 | fn display_notification( 44 | mut contexts: EguiContexts, 45 | mut notifications: ResMut, 46 | time: Res