Skip to content

Commit

Permalink
Merge pull request #14 from mvlabat/multiple-windows-support
Browse files Browse the repository at this point in the history
Multiple windows support
  • Loading branch information
vladbat00 authored Apr 10, 2021
2 parents 58e7c17 + bf7bd00 commit 99bf2be
Show file tree
Hide file tree
Showing 9 changed files with 713 additions and 224 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 10-Apr-2021

Huge thanks to @jakobhellermann and @Weasy666 for contributing to this release!

### Added

- Implement Egui 0.11.0 support ([#12](https://github.com/mvlabat/bevy_egui/pull/12) by @Weasy666 and @jakobhellermann).
- Implement multiple windows support ([#14](https://github.com/mvlabat/bevy_egui/pull/14) by @jakobhellermann).

## [0.3.0] - 02-Mar-2021

### Added
Expand Down
14 changes: 6 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,21 @@

This crate provides a [Egui](https://github.com/emilk/egui) integration for the [Bevy](https://github.com/bevyengine/bevy) game engine.

**Trying out:**

An example WASM project is live at [mvlabat.github.io/bevy_egui_web_showcase](https://mvlabat.github.io/bevy_egui_web_showcase/index.html) [[source](https://github.com/mvlabat/bevy_egui_web_showcase)].

**Features:**
- Desktop and web ([bevy_webgl2](https://github.com/mrk-its/bevy_webgl2)) platforms support
- Clipboard (web support is limited to the same window, see [rust-windowing/winit#1829](https://github.com/rust-windowing/winit/issues/1829))
- Opening URLs
- Multiple windows support (see [./examples/two_windows.rs](./examples/two_windows.rs))

`bevy_egui` can be compiled with using only `bevy` and `egui` as dependencies: `manage_clipboard` and `open_url` features,
that require additional crates, can be disabled.

![bevy_egui](bevy_egui.png)

## Trying out

An example WASM project is live at [mvlabat.github.io/bevy_egui_web_showcase](https://mvlabat.github.io/bevy_egui_web_showcase/index.html) [[source](https://github.com/mvlabat/bevy_egui_web_showcase)].

**Note** that in order to use `bevy_egui`in WASM you need [bevy_webgl2](https://github.com/mrk-its/bevy_webgl2) of at least `0.5.0` version.

## Usage

Here's a minimal usage example:
Expand All @@ -47,8 +46,7 @@ fn main() {
}

fn ui_example(mut egui_context: ResMut<EguiContext>) {
let ctx = &mut egui_context.ctx;
egui::Window::new("Hello").show(ctx, |ui| {
egui::Window::new("Hello").show(egui_context.ctx(), |ui| {
ui.label("world");
});
}
Expand Down
5 changes: 2 additions & 3 deletions examples/simple.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,8 @@ fn main() {
.run();
}

fn ui_example(mut egui_context: ResMut<EguiContext>) {
let ctx = &mut egui_context.ctx;
egui::Window::new("Hello").show(ctx, |ui| {
fn ui_example(egui_context: Res<EguiContext>) {
egui::Window::new("Hello").show(egui_context.ctx(), |ui| {
ui.label("world");
});
}
308 changes: 308 additions & 0 deletions examples/two_windows.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,308 @@
use bevy::{
prelude::*,
render::{
camera::{ActiveCameras, Camera},
pass::*,
render_graph::{
base::MainPass, CameraNode, PassNode, RenderGraph, WindowSwapChainNode,
WindowTextureNode,
},
texture::{Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsage},
},
window::{CreateWindow, WindowDescriptor, WindowId},
};
use bevy_egui::{egui, EguiContext, EguiPlugin};

const BEVY_TEXTURE_ID: u64 = 0;

/// This example creates a second window and draws a mesh from two different cameras.
fn main() {
App::build()
.insert_resource(Msaa { samples: 4 })
.add_state(AppState::CreateWindow)
.add_plugins(DefaultPlugins)
.add_plugin(EguiPlugin)
.add_startup_system(load_assets.system())
.add_system_set(
SystemSet::on_update(AppState::CreateWindow).with_system(setup_window.system()),
)
.add_system_set(SystemSet::on_update(AppState::Setup).with_system(setup.system()))
.add_system_set(SystemSet::on_update(AppState::Done).with_system(ui_second_window.system()))
.add_system(ui_first_window.system())
.run();
}

struct SecondWindow {
id: WindowId,
}

// NOTE: this "state based" approach to multiple windows is a short term workaround.
// Future Bevy releases shouldn't require such a strict order of operations.
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
enum AppState {
CreateWindow,
Setup,
Done,
}

fn setup_window(
mut app_state: ResMut<State<AppState>>,
mut create_window_events: EventWriter<CreateWindow>,
) {
let window_id = WindowId::new();

// sends out a "CreateWindow" event, which will be received by the windowing backend
create_window_events.send(CreateWindow {
id: window_id,
descriptor: WindowDescriptor {
width: 800.,
height: 600.,
vsync: false,
title: "second window".to_string(),
..Default::default()
},
});

app_state.set(AppState::Setup).unwrap();
}

mod second_window {
pub const SWAP_CHAIN: &str = "second_window_swap_chain";
pub const DEPTH_TEXTURE: &str = "second_window_depth_texture";
pub const CAMERA_NODE: &str = "secondary_camera";
pub const CAMERA_NAME: &str = "Secondary";
pub const SAMPLED_COLOR_ATTACHMENT: &str = "second_multi_sampled_color_attachment";
pub const PASS: &str = "second_window_pass";
}

fn load_assets(mut egui_context: ResMut<EguiContext>, assets: Res<AssetServer>) {
let texture_handle = assets.load("icon.png");
egui_context.set_egui_texture(BEVY_TEXTURE_ID, texture_handle);
}

fn setup_pipeline(
render_graph: &mut RenderGraph,
active_cameras: &mut ActiveCameras,
msaa: &Msaa,
window_id: WindowId,
) {
// here we setup our render graph to draw our second camera to the new window's swap chain

// add a swapchain node for our new window
render_graph.add_node(
second_window::SWAP_CHAIN,
WindowSwapChainNode::new(window_id),
);

// add a new depth texture node for our new window
render_graph.add_node(
second_window::DEPTH_TEXTURE,
WindowTextureNode::new(
window_id,
TextureDescriptor {
format: TextureFormat::Depth32Float,
usage: TextureUsage::OUTPUT_ATTACHMENT,
sample_count: msaa.samples,
..Default::default()
},
),
);

// add a new camera node for our new window
render_graph.add_system_node(
second_window::CAMERA_NODE,
CameraNode::new(second_window::CAMERA_NAME),
);

// add a new render pass for our new window / camera
let mut second_window_pass = PassNode::<&MainPass>::new(PassDescriptor {
color_attachments: vec![msaa.color_attachment_descriptor(
TextureAttachment::Input("color_attachment".to_string()),
TextureAttachment::Input("color_resolve_target".to_string()),
Operations {
load: LoadOp::Clear(Color::rgb(0.5, 0.5, 0.8)),
store: true,
},
)],
depth_stencil_attachment: Some(RenderPassDepthStencilAttachmentDescriptor {
attachment: TextureAttachment::Input("depth".to_string()),
depth_ops: Some(Operations {
load: LoadOp::Clear(1.0),
store: true,
}),
stencil_ops: None,
}),
sample_count: msaa.samples,
});

second_window_pass.add_camera(second_window::CAMERA_NAME);
active_cameras.add(second_window::CAMERA_NAME);

render_graph.add_node(second_window::PASS, second_window_pass);

render_graph
.add_slot_edge(
second_window::SWAP_CHAIN,
WindowSwapChainNode::OUT_TEXTURE,
second_window::PASS,
if msaa.samples > 1 {
"color_resolve_target"
} else {
"color_attachment"
},
)
.unwrap();

render_graph
.add_slot_edge(
second_window::DEPTH_TEXTURE,
WindowTextureNode::OUT_TEXTURE,
second_window::PASS,
"depth",
)
.unwrap();

render_graph
.add_node_edge(second_window::CAMERA_NODE, second_window::PASS)
.unwrap();

if msaa.samples > 1 {
render_graph.add_node(
second_window::SAMPLED_COLOR_ATTACHMENT,
WindowTextureNode::new(
window_id,
TextureDescriptor {
size: Extent3d {
depth: 1,
width: 1,
height: 1,
},
mip_level_count: 1,
sample_count: msaa.samples,
dimension: TextureDimension::D2,
format: TextureFormat::default(),
usage: TextureUsage::OUTPUT_ATTACHMENT,
},
),
);

render_graph
.add_slot_edge(
second_window::SAMPLED_COLOR_ATTACHMENT,
WindowSwapChainNode::OUT_TEXTURE,
second_window::PASS,
"color_attachment",
)
.unwrap();
}

bevy_egui::setup_pipeline(
render_graph,
msaa,
bevy_egui::RenderGraphConfig {
window_id,
egui_pass: "egui_pass2",
main_pass: second_window::PASS,
swap_chain_node: second_window::SWAP_CHAIN,
depth_texture: second_window::DEPTH_TEXTURE,
sampled_color_attachment: second_window::SAMPLED_COLOR_ATTACHMENT,
transform_node: "egui_transform2",
},
);
}

fn setup(
mut commands: Commands,
mut app_state: ResMut<State<AppState>>,
windows: Res<Windows>,
mut active_cameras: ResMut<ActiveCameras>,
mut render_graph: ResMut<RenderGraph>,
mut meshes: ResMut<Assets<Mesh>>,
msaa: Res<Msaa>,
) {
// get the non-default window id
let window_id = match windows
.iter()
.find(|w| w.id() != WindowId::default())
.map(|w| w.id())
{
Some(x) => x,
None => return,
};

setup_pipeline(&mut render_graph, &mut active_cameras, &msaa, window_id);

// SETUP SCENE

// add entities to the world
commands.spawn_bundle(PbrBundle {
mesh: meshes.add(Mesh::from(shape::Cube { size: 1.0 })),
..Default::default()
});
// light
commands.spawn_bundle(LightBundle {
transform: Transform::from_xyz(4.0, 5.0, 4.0),
..Default::default()
});
// main camera
commands.spawn_bundle(PerspectiveCameraBundle {
transform: Transform::from_xyz(0.0, 0.0, 6.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});
// second window camera
commands.spawn_bundle(PerspectiveCameraBundle {
camera: Camera {
name: Some("Secondary".to_string()),
window: window_id,
..Default::default()
},
transform: Transform::from_xyz(6.0, 0.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y),
..Default::default()
});

commands.insert_resource(SecondWindow { id: window_id });

app_state.set(AppState::Done).unwrap();
}

#[derive(Default)]
struct UiState {
label: String,
}

fn ui_first_window(egui_context: Res<EguiContext>, mut ui_state: Local<UiState>) {
egui::Window::new("First Window")
.scroll(true)
.show(egui_context.ctx(), |ui| {
ui.horizontal(|ui| {
ui.label("Write something: ");
ui.text_edit_singleline(&mut ui_state.label);
});

ui.add(egui::widgets::Image::new(
egui::TextureId::User(BEVY_TEXTURE_ID),
[256.0, 256.0],
));
});
}

fn ui_second_window(
egui_context: Res<EguiContext>,
second_window: Res<SecondWindow>,
mut ui_state: Local<UiState>,
) {
egui::Window::new("Second Window").scroll(true).show(
egui_context.ctx_for_window(second_window.id),
|ui| {
ui.horizontal(|ui| {
ui.label("Write something else: ");
ui.text_edit_singleline(&mut ui_state.label);
});

ui.add(egui::widgets::Image::new(
egui::TextureId::User(BEVY_TEXTURE_ID),
[256.0, 256.0],
));
},
);
}
Loading

0 comments on commit 99bf2be

Please sign in to comment.