diff --git a/README.md b/README.md index 954e1ffd..08fbb93c 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,10 @@ cargo mobile init After some straightforward prompts, you'll be asked to select a template pack. Template packs are used to generate project boilerplate, i.e. using the `wry` template pack gives you a [wry](https://github.com/tauri-apps/wry) project that runs out-of-the-box on desktop and mobile. -| name | info | -| --------- | ------------------------------------- | -| wry | Minimal wry project | +| name | info | +| --------- | ----------------------------------------------------------------------------------------------------------------------| +| wry | Minimal wry project | +| egui | Full egui + winit + wgpu example based on [agdk-egui example](https://github.com/rust-mobile/rust-android-examples) | **Template pack contribution is welcomed** diff --git a/templates/apps/egui/.gitignore.hbs b/templates/apps/egui/.gitignore.hbs new file mode 100644 index 00000000..c3cb0ce4 --- /dev/null +++ b/templates/apps/egui/.gitignore.hbs @@ -0,0 +1,10 @@ +# Rust +target/ +**/*.rs.bk + +# cargo-mobile +.cargo/ +/gen + +# macOS +.DS_Store diff --git a/templates/apps/egui/Cargo.toml.hbs b/templates/apps/egui/Cargo.toml.hbs new file mode 100644 index 00000000..9c49ff9e --- /dev/null +++ b/templates/apps/egui/Cargo.toml.hbs @@ -0,0 +1,38 @@ +[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" + +[package.metadata.cargo-android] +app-dependencies = [ + "com.google.android.material:material:1.8.0", +] +project-dependencies = [ "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.21" ] +app-plugins = [ "org.jetbrains.kotlin.android" ] +app-theme-parent = "Theme.MaterialComponents.DayNight.DarkActionBar" + +[dependencies] +log = "0.4.14" +chrono = "0.4" +pollster = "0.2" +egui = "0.22" +wgpu = "0.16" +winit = { version = "0.28", features = ["android-native-activity"] } +egui_demo_lib = "0.22" +egui-winit = { version = "0.22", default-features = false } +egui-wgpu = { version = "0.22", features = [ "winit" ] } + +[target.'cfg(target_os = "android")'.dependencies] +android_logger = "0.13.1" + +[target.'cfg(not(target_os = "android"))'.dependencies] +env_logger = "0.9" diff --git a/templates/apps/egui/README.md b/templates/apps/egui/README.md new file mode 100644 index 00000000..055b886d --- /dev/null +++ b/templates/apps/egui/README.md @@ -0,0 +1,5 @@ +# egui + +This is an example based on [agdk-egui example](https://github.com/rust-mobile/rust-android-examples), 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). diff --git a/templates/apps/egui/gen/bin/desktop.rs.hbs b/templates/apps/egui/gen/bin/desktop.rs.hbs new file mode 100644 index 00000000..a054a346 --- /dev/null +++ b/templates/apps/egui/gen/bin/desktop.rs.hbs @@ -0,0 +1,4 @@ +fn main() { + #[cfg(not(any(target_os = "android", target_os = "ios")))] + {{snake-case app.name}}::main(); +} diff --git a/templates/apps/egui/src/lib.rs b/templates/apps/egui/src/lib.rs new file mode 100644 index 00000000..9c0e1c9d --- /dev/null +++ b/templates/apps/egui/src/lib.rs @@ -0,0 +1,168 @@ +#[cfg(target_os = "android")] +use winit::platform::android::activity::AndroidApp; + +use winit::event::Event::*; +use winit::event_loop::{ControlFlow, EventLoop, EventLoopBuilder, EventLoopWindowTarget}; + +use egui_wgpu::winit::Painter; +use egui_winit::State; + +const INITIAL_WIDTH: u32 = 1920; +const INITIAL_HEIGHT: u32 = 1080; + +/// A custom event type for the winit app. +enum Event { + RequestRedraw, +} + +/// Enable egui to request redraws via a custom Winit event... +#[derive(Clone)] +struct RepaintSignal(std::sync::Arc>>); + +fn create_window( + event_loop: &EventLoopWindowTarget, + state: &mut State, + painter: &mut Painter, +) -> winit::window::Window { + let window = winit::window::WindowBuilder::new() + .with_decorations(true) + .with_resizable(true) + .with_transparent(false) + .with_title("egui winit + wgpu example") + .with_inner_size(winit::dpi::PhysicalSize { + width: INITIAL_WIDTH, + height: INITIAL_HEIGHT, + }) + .build(event_loop) + .unwrap(); + + pollster::block_on(painter.set_window(Some(&window))).unwrap(); + + // NB: calling set_window will lazily initialize render state which + // means we will be able to query the maximum supported texture + // dimensions + if let Some(max_size) = painter.max_texture_side() { + state.set_max_texture_side(max_size); + } + + let pixels_per_point = window.scale_factor() as f32; + state.set_pixels_per_point(pixels_per_point); + + window.request_redraw(); + + window +} + +fn _main(event_loop: EventLoop) { + let ctx = egui::Context::default(); + let repaint_signal = RepaintSignal(std::sync::Arc::new(std::sync::Mutex::new( + event_loop.create_proxy(), + ))); + ctx.set_request_repaint_callback(move |_| { + repaint_signal + .0 + .lock() + .unwrap() + .send_event(Event::RequestRedraw) + .ok(); + }); + + let mut state = State::new(&event_loop); + let mut painter = Painter::new( + egui_wgpu::WgpuConfiguration::default(), + 1, // msaa samples + None, + false, + ); + let mut window: Option = None; + let mut egui_demo_windows = egui_demo_lib::DemoWindows::default(); + + event_loop.run(move |event, event_loop, control_flow| match event { + Resumed => match window { + None => { + window = Some(create_window(event_loop, &mut state, &mut painter)); + } + Some(ref window) => { + pollster::block_on(painter.set_window(Some(window))).unwrap(); + window.request_redraw(); + } + }, + Suspended => { + window = None; + } + RedrawRequested(..) => { + if let Some(window) = window.as_ref() { + let raw_input = state.take_egui_input(window); + + let full_output = ctx.run(raw_input, |ctx| { + egui_demo_windows.ui(ctx); + }); + state.handle_platform_output(window, &ctx, full_output.platform_output); + + painter.paint_and_update_textures( + state.pixels_per_point(), + egui::Rgba::default().to_array(), + &ctx.tessellate(full_output.shapes), + &full_output.textures_delta, + false, + ); + + if full_output.repaint_after.is_zero() { + window.request_redraw(); + } + } + } + MainEventsCleared | UserEvent(Event::RequestRedraw) => { + if let Some(window) = window.as_ref() { + window.request_redraw(); + } + } + WindowEvent { event, .. } => { + match event { + winit::event::WindowEvent::Resized(size) => { + painter.on_window_resized(size.width, size.height); + } + winit::event::WindowEvent::CloseRequested => { + *control_flow = ControlFlow::Exit; + } + _ => {} + } + + let response = state.on_event(&ctx, &event); + if response.repaint { + if let Some(window) = window.as_ref() { + window.request_redraw(); + } + } + } + _ => (), + }); +} + +#[allow(dead_code)] +#[cfg(target_os = "android")] +#[no_mangle] +fn android_main(app: AndroidApp) { + use winit::platform::android::EventLoopBuilderExtAndroid; + + android_logger::init_once( + android_logger::Config::default().with_max_level(log::LevelFilter::Warn), + ); + + let event_loop = EventLoopBuilder::with_user_event() + .with_android_app(app) + .build(); + _main(event_loop); +} + +#[allow(dead_code)] +#[cfg(not(target_os = "android"))] +fn main() { + env_logger::builder() + .filter_level(log::LevelFilter::Warn) + .parse_default_env() + .init(); + + let event_loop = EventLoopBuilder::with_user_event().build(); + _main(event_loop); +} diff --git a/templates/platforms/android-studio/app/src/main/res/values/styles.xml.hbs b/templates/platforms/android-studio/app/src/main/res/values/styles.xml.hbs index 5af3348a..a7d298ef 100644 --- a/templates/platforms/android-studio/app/src/main/res/values/styles.xml.hbs +++ b/templates/platforms/android-studio/app/src/main/res/values/styles.xml.hbs @@ -2,6 +2,7 @@ \ No newline at end of file