use bevy::{prelude::*, window::PrimaryWindow}; use bevy_egui_next::{egui, EguiContexts, EguiPlugin, EguiSettings}; struct Images { bevy_icon: Handle, bevy_icon_inverted: Handle, } impl FromWorld for Images { fn from_world(world: &mut World) -> Self { let asset_server = world.get_resource_mut::().unwrap(); Self { bevy_icon: asset_server.load("icon.png"), bevy_icon_inverted: asset_server.load("icon_inverted.png"), } } } /// This example demonstrates the following functionality and use-cases of bevy_egui_next: /// - rendering loaded assets; /// - toggling hidpi scaling (by pressing '/' button); /// - configuring egui contexts during the startup. fn main() { App::new() .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0))) .insert_resource(Msaa::Sample4) .init_resource::() .add_plugins(DefaultPlugins) .add_plugins(EguiPlugin) .add_systems(Startup, configure_visuals_system) .add_systems(Startup, configure_ui_state_system) .add_systems(Update, update_ui_scale_factor_system) .add_systems(Update, ui_example_system) .run(); } #[derive(Default, Resource)] struct UiState { label: String, value: f32, painting: Painting, inverted: bool, egui_texture_handle: Option, is_window_open: bool, } fn configure_visuals_system(mut contexts: EguiContexts) { contexts.ctx_mut().set_visuals(egui::Visuals { window_rounding: 0.0.into(), ..Default::default() }); } fn configure_ui_state_system(mut ui_state: ResMut) { ui_state.is_window_open = true; } fn update_ui_scale_factor_system( keyboard_input: Res>, mut toggle_scale_factor: Local>, mut egui_settings: ResMut, windows: Query<&Window, With>, ) { if keyboard_input.just_pressed(KeyCode::Slash) || toggle_scale_factor.is_none() { *toggle_scale_factor = Some(!toggle_scale_factor.unwrap_or(true)); if let Ok(window) = windows.get_single() { let scale_factor = if toggle_scale_factor.unwrap() { 1.0 } else { 1.0 / window.scale_factor() }; egui_settings.scale_factor = scale_factor; } } } fn ui_example_system( mut ui_state: ResMut, // You are not required to store Egui texture ids in systems. We store this one here just to // demonstrate that rendering by using a texture id of a removed image is handled without // making bevy_egui_next panic. mut rendered_texture_id: Local, mut is_initialized: Local, // If you need to access the ids from multiple systems, you can also initialize the `Images` // resource while building the app and use `Res` instead. images: Local, mut contexts: EguiContexts, ) { let egui_texture_handle = ui_state .egui_texture_handle .get_or_insert_with(|| { contexts.ctx_mut().load_texture( "example-image", egui::ColorImage::example(), Default::default(), ) }) .clone(); let mut load = false; let mut remove = false; let mut invert = false; if !*is_initialized { *is_initialized = true; *rendered_texture_id = contexts.add_image(images.bevy_icon.clone_weak()); } let ctx = contexts.ctx_mut(); egui::SidePanel::left("side_panel") .default_width(200.0) .show(ctx, |ui| { ui.heading("Side Panel"); ui.horizontal(|ui| { ui.label("Write something: "); ui.text_edit_singleline(&mut ui_state.label); }); ui.add(egui::widgets::Image::new(egui::load::SizedTexture::new( egui_texture_handle.id(), egui_texture_handle.size_vec2(), ))); ui.add(egui::Slider::new(&mut ui_state.value, 0.0..=10.0).text("value")); if ui.button("Increment").clicked() { ui_state.value += 1.0; } ui.allocate_space(egui::Vec2::new(1.0, 100.0)); ui.horizontal(|ui| { load = ui.button("Load").clicked(); invert = ui.button("Invert").clicked(); remove = ui.button("Remove").clicked(); }); ui.add(egui::widgets::Image::new(egui::load::SizedTexture::new( *rendered_texture_id, [256.0, 256.0], ))); ui.allocate_space(egui::Vec2::new(1.0, 10.0)); ui.checkbox(&mut ui_state.is_window_open, "Window Is Open"); ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| { ui.add(egui::Hyperlink::from_label_and_url( "powered by egui", "https://github.com/emilk/egui/", )); }); }); egui::TopBottomPanel::top("top_panel").show(ctx, |ui| { // The top panel is often a good place for a menu bar: egui::menu::bar(ui, |ui| { egui::menu::menu_button(ui, "File", |ui| { if ui.button("Quit").clicked() { std::process::exit(0); } }); }); }); egui::CentralPanel::default().show(ctx, |ui| { ui.heading("Egui Template"); ui.hyperlink("https://github.com/emilk/egui_template"); ui.add(egui::github_link_file_line!( "https://github.com/mvlabat/bevy_egui_next/blob/main/", "Direct link to source code." )); egui::warn_if_debug_build(ui); ui.separator(); ui.heading("Central Panel"); ui.label("The central panel is the region left after adding TopPanels and SidePanels."); ui.label("It is often a great place for big things, like drawings:"); ui.heading("Draw with your mouse to paint:"); ui_state.painting.ui_control(ui); egui::Frame::dark_canvas(ui.style()).show(ui, |ui| { ui_state.painting.ui_content(ui); }); }); egui::Window::new("Window") .vscroll(true) .open(&mut ui_state.is_window_open) .show(ctx, |ui| { ui.label("Windows can be moved by dragging them."); ui.label("They are automatically sized based on contents."); ui.label("You can turn on resizing and scrolling if you like."); ui.label("You would normally chose either panels OR windows."); }); if invert { ui_state.inverted = !ui_state.inverted; } if load || invert { // If an image is already added to the context, it'll return an existing texture id. if ui_state.inverted { *rendered_texture_id = contexts.add_image(images.bevy_icon_inverted.clone_weak()); } else { *rendered_texture_id = contexts.add_image(images.bevy_icon.clone_weak()); }; } if remove { contexts.remove_image(&images.bevy_icon); contexts.remove_image(&images.bevy_icon_inverted); } } struct Painting { lines: Vec>, stroke: egui::Stroke, } impl Default for Painting { fn default() -> Self { Self { lines: Default::default(), stroke: egui::Stroke::new(1.0, egui::Color32::LIGHT_BLUE), } } } impl Painting { pub fn ui_control(&mut self, ui: &mut egui::Ui) -> egui::Response { ui.horizontal(|ui| { egui::stroke_ui(ui, &mut self.stroke, "Stroke"); ui.separator(); if ui.button("Clear Painting").clicked() { self.lines.clear(); } }) .response } pub fn ui_content(&mut self, ui: &mut egui::Ui) { let (response, painter) = ui.allocate_painter(ui.available_size_before_wrap(), egui::Sense::drag()); let rect = response.rect; if self.lines.is_empty() { self.lines.push(vec![]); } let current_line = self.lines.last_mut().unwrap(); if let Some(pointer_pos) = response.interact_pointer_pos() { let canvas_pos = pointer_pos - rect.min; if current_line.last() != Some(&canvas_pos) { current_line.push(canvas_pos); } } else if !current_line.is_empty() { self.lines.push(vec![]); } for line in &self.lines { if line.len() >= 2 { let points: Vec = line.iter().map(|p| rect.min + *p).collect(); painter.add(egui::Shape::line(points, self.stroke)); } } } }