Skip to content
Open
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ After some straightforward prompts, you'll be asked to select a template pack. T
| --------- | --------------------------------------------------------------------------------------------------------------------------------- |
| bevy | Minimal Bevy project derived from [sprite](https://github.com/bevyengine/bevy/blob/master/examples/2d/sprite.rs) example |
| bevy-demo | Bevy [breakout](https://github.com/bevyengine/bevy/blob/master/examples/game/breakout.rs) demo |
| egui | Full egui + winit + wgpu example inspired by [egui_example](https://github.com/hasenbanck/egui_example) |
| wgpu | Minimal wgpu project derived from [hello-triangle](https://github.com/gfx-rs/wgpu-rs/tree/master/examples/hello-triangle) example |
| winit | Minimal winit project derived from [window](https://github.com/rust-windowing/winit/tree/master/examples/window) example |

Expand Down
10 changes: 10 additions & 0 deletions templates/apps/egui/.gitignore.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Rust
target/
**/*.rs.bk

# cargo-mobile
.cargo/
/gen

# macOS
.DS_Store
33 changes: 33 additions & 0 deletions templates/apps/egui/Cargo.toml.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
name = "{{app.name}}"
version = "0.1.0"
authors = ["{{author}}"]
edition = "2018"
resolver = "2"

[lib]
crate-type = ["staticlib", "cdylib", "rlib"]

[[bin]]
name = "{{app.name}}-desktop"
path = "gen/bin/desktop.rs"

[dependencies]
egui_wgpu_backend = "0.16"
chrono = "0.4"
pollster = "0.2"
egui = "0.16.1"
epi = "0.16"
wgpu = "0.12"
winit = "0.26"
egui_demo_lib = "0.16"
mobile-entry-point = "0.1"
egui-winit = "0.16"

[target.'cfg(target_os = "android")'.dependencies]
android_logger = "0.10.1"
log = "0.4.14"
ndk-glue = "0.5.0"

[target.'cfg(not(target_os = "android"))'.dependencies]
simple_logger = "1.16.0"
5 changes: 5 additions & 0 deletions templates/apps/egui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# egui

This is an example inspired by [this repo](https://github.com/hasenbanck/egui_example), using `egui`, `winit` and `wgpu` to run [egui_demo_app](https://github.com/emilk/egui/tree/master/egui_demo_app).

To run this on desktop, just do `cargo run` like normal! For mobile, use `cargo android run` and `cargo apple run` respectively (or use `cargo android open` and `cargo apple open` to open in Android Studio and Xcode respectively).
4 changes: 4 additions & 0 deletions templates/apps/egui/gen/bin/desktop.rs.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
fn main() {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{{snake-case app.name}}::start_app();
}
232 changes: 232 additions & 0 deletions templates/apps/egui/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
use std::iter;
use std::time::Instant;

use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
use epi::*;
use winit::event::Event::*;
use winit::event_loop::ControlFlow;

use mobile_entry_point::mobile_entry_point;
#[cfg(target_os = "android")]
use ndk_glue;

/// A custom event type for the winit app.
#[derive(Debug)]
enum Event {
RequestRedraw,
}

static INITIAL_WIDTH: u32 = 1280;
static INITIAL_HEIGHT: u32 = 720;

/// This is the repaint signal type that egui needs for requesting a repaint from another thread.
/// It sends the custom RequestRedraw event to the winit event loop.
struct ExampleRepaintSignal(std::sync::Mutex<winit::event_loop::EventLoopProxy<Event>>);

impl epi::backend::RepaintSignal for ExampleRepaintSignal {
fn request_repaint(&self) {
self.0.lock().unwrap().send_event(Event::RequestRedraw).ok();
}
}

#[cfg(target_os = "android")]
fn init_logging() {
android_logger::init_once(
android_logger::Config::default()
.with_min_level(log::Level::Trace)
.with_tag("{{app.name}}"),
);
}

#[cfg(not(target_os = "android"))]
fn init_logging() {
simple_logger::SimpleLogger::new().with_utc_timestamps().init().unwrap();
}

/// A simple egui + wgpu + winit based example.
#[mobile_entry_point]
fn main() {
init_logging();
let event_loop = winit::event_loop::EventLoop::with_user_event();
let window = winit::window::WindowBuilder::new()
.with_title("A fantastic window!")
.with_inner_size(winit::dpi::LogicalSize::new(INITIAL_WIDTH, INITIAL_HEIGHT))
.build(&event_loop)
.unwrap();

let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY);

let mut surface = if cfg!(target_os = "android") {
None
} else {
Some(unsafe { instance.create_surface(&window) })
};

// WGPU 0.11+ support force fallback (if HW implementation not supported), set it to true or false (optional).
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
compatible_surface: surface.as_ref(),
force_fallback_adapter: false,
}))
.unwrap();

let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
features: wgpu::Features::default(),
limits: wgpu::Limits::default(),
label: None,
},
None,
))
.unwrap();

let surface_format = if let Some(surface) = &surface {
surface.get_preferred_format(&adapter).unwrap()
} else {
// if Surface is none, we're guaranteed to be on android
wgpu::TextureFormat::Rgba8UnormSrgb
};

let mut surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: surface_format,
width: INITIAL_WIDTH,
height: INITIAL_HEIGHT,
present_mode: wgpu::PresentMode::Mailbox,
};

if let Some(surface) = &mut surface {
surface.configure(&device, &surface_config);
}

let repaint_signal = std::sync::Arc::new(ExampleRepaintSignal(std::sync::Mutex::new(
event_loop.create_proxy(),
)));

// We use the egui_wgpu_backend crate as the render backend.
let mut egui_rpass = RenderPass::new(&device, surface_format, 1);

// Display the demo application that ships with egui.
let mut demo_app = egui_demo_lib::WrapApp::default();

let mut previous_frame_time = None;

let mut state = egui_winit::State::new(&window);
let mut ctx = egui::CtxRef::default();

event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Poll;

let mut redraw = || {
if let Some(surface) = &surface {
let output_frame = match surface.get_current_texture() {
Ok(frame) => frame,
Err(e) => {
eprintln!("Dropped frame with error: {}", e);
return;
}
};
let output_view = output_frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());

let egui_start = Instant::now();
let raw_input: egui::RawInput = state.take_egui_input(&window);
ctx.begin_frame(raw_input);

let app_output = epi::backend::AppOutput {
..Default::default()
};

let frame_data = epi::backend::FrameData {
info: epi::IntegrationInfo {
name: "egui_winit",
web_info: None,
cpu_usage: previous_frame_time,
native_pixels_per_point: Some(window.scale_factor() as _),
prefer_dark_mode: None,
},
output: app_output,
repaint_signal: repaint_signal.clone(),
};

let frame = Frame::new(frame_data);

// Draw the demo application.
demo_app.update(&ctx, &frame);

// End the UI frame. We could now handle the output and draw the UI with the backend.
let (_output, paint_commands) = ctx.end_frame();
let paint_jobs = ctx.tessellate(paint_commands);

let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32;
previous_frame_time = Some(frame_time);

let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("encoder"),
});

// Upload all resources for the GPU.
let screen_descriptor = ScreenDescriptor {
physical_width: surface_config.width,
physical_height: surface_config.height,
scale_factor: window.scale_factor() as f32,
};
egui_rpass.update_texture(&device, &queue, &ctx.font_image());
egui_rpass.update_user_textures(&device, &queue);
egui_rpass.update_buffers(&device, &queue, &paint_jobs, &screen_descriptor);

// Record all render passes.
egui_rpass
.execute(
&mut encoder,
&output_view,
&paint_jobs,
&screen_descriptor,
Some(wgpu::Color::BLACK),
)
.unwrap();
// Submit the commands.
queue.submit(iter::once(encoder.finish()));

// Redraw egui
output_frame.present();
};
};
match event {
RedrawRequested(..) | MainEventsCleared => {
redraw();
}
Resumed => {
let s = unsafe { instance.create_surface(&window) };
surface_config.format = s.get_preferred_format(&adapter).unwrap();
s.configure(&device, &surface_config);
surface = Some(s);
}
Suspended => {
surface = None;
}
WindowEvent { event, .. } => {
match event {
winit::event::WindowEvent::Resized(size) => {
if size.width != 0 && size.height != 0 {
// Recreate the swap chain with the new size
surface_config.width = size.width;
surface_config.height = size.height;
if let Some(surface) = &surface {
surface.configure(&device, &surface_config);
}
}
}
winit::event::WindowEvent::CloseRequested => {
*control_flow = ControlFlow::Exit;
}
_ => {
state.on_event(&ctx, &event);
}
};
}
_ => (),
}
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<!-- Base application theme. -->
<style name="AppTheme" parent="android:Theme.Material.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="android:windowFullscreen">true</item>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's so that apps are in fullscreen by default and the status bar doesn't overlay the app's content (which I've seen was also an issue in other examples, such as the wgpu one)

</style>

</resources>