mirror of
https://github.com/emilk/egui.git
synced 2026-06-27 07:03:14 -04:00
Merge branch 'master' into web_handle
This commit is contained in:
@@ -7,9 +7,16 @@ edition = "2021"
|
||||
rust-version = "1.61"
|
||||
publish = false
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
eframe = { path = "../../eframe", features = ["glow"] }
|
||||
egui_glow = { path = "../../egui_glow" }
|
||||
glow = "0.11"
|
||||
three-d = { version = "0.12", default-features = false } # 2022-05-22
|
||||
three-d = { version = "0.13", default-features = false }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies] # Web dependencies
|
||||
wasm-bindgen = "0.2" # Core bindings
|
||||
wasm-bindgen-futures = "0.4" # Core bindings
|
||||
console_error_panic_hook = "0.1" # For logging
|
||||
|
||||
@@ -14,3 +14,7 @@ If you are content of having egui sit on top of a 3D background, take a look at:
|
||||
```sh
|
||||
cargo run -p custom_3d_three-d
|
||||
```
|
||||
|
||||
```
|
||||
wasm-pack build examples/custom_3d_three-d --target web
|
||||
```
|
||||
|
||||
38
examples/custom_3d_three-d/index.html
Normal file
38
examples/custom_3d_three-d/index.html
Normal file
@@ -0,0 +1,38 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="my" style="position: absolute;top:0;bottom: 0;left: 0;right: 0;margin:auto;"></canvas>
|
||||
<!-- Note the usage of `type=module` here as this is an ES6 module -->
|
||||
<script type="module">
|
||||
// Use ES module import syntax to import functionality from the module
|
||||
// that we have compiled.
|
||||
//
|
||||
// Note that the `default` import is an initialization function which
|
||||
// will "boot" the module and make it ready to use. Currently browsers
|
||||
// don't support natively imported WebAssembly as an ES module, but
|
||||
// eventually the manual initialization won't be required!
|
||||
import init from './pkg/custom_3d_three_d.js';
|
||||
|
||||
async function run() {
|
||||
// First up we need to actually load the wasm file, so we use the
|
||||
// default export to inform it where the wasm file is located on the
|
||||
// server, and then we wait on the returned promise to wait for the
|
||||
// wasm to be loaded.
|
||||
// It may look like this: `await init('./pkg/without_a_bundler_bg.wasm');`,
|
||||
// but there is also a handy default inside `init` function, which uses
|
||||
// `import.meta` to locate the wasm file relatively to js file
|
||||
//
|
||||
// Note that instead of a string here you can also pass in an instance
|
||||
// of `WebAssembly.Module` which allows you to compile your own module.
|
||||
// Also note that the promise, when resolved, yields the wasm module's
|
||||
// exports which is the same as importing the `*_bg` module in other
|
||||
// modes
|
||||
await init('./pkg/custom_3d_three_d_bg.wasm');
|
||||
}
|
||||
|
||||
run();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
19
examples/custom_3d_three-d/src/lib.rs
Normal file
19
examples/custom_3d_three-d/src/lib.rs
Normal file
@@ -0,0 +1,19 @@
|
||||
mod main;
|
||||
|
||||
// Entry point for wasm
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen(start)]
|
||||
pub async fn start() -> Result<(), JsValue> {
|
||||
std::panic::set_hook(Box::new(console_error_panic_hook::hook));
|
||||
|
||||
let web_options = eframe::WebOptions::default();
|
||||
eframe::start_web(
|
||||
"my",
|
||||
web_options,
|
||||
Box::new(|cc| Box::new(main::MyApp::new(cc))),
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
#![allow(dead_code)]
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn main() {
|
||||
let options = eframe::NativeOptions {
|
||||
initial_window_size: Some(egui::vec2(550.0, 610.0)),
|
||||
@@ -17,12 +19,12 @@ fn main() {
|
||||
);
|
||||
}
|
||||
|
||||
struct MyApp {
|
||||
pub struct MyApp {
|
||||
angle: f32,
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
pub fn new(_cc: &eframe::CreationContext<'_>) -> Self {
|
||||
Self { angle: 0.2 }
|
||||
}
|
||||
}
|
||||
@@ -41,7 +43,28 @@ impl eframe::App for MyApp {
|
||||
|
||||
egui::ScrollArea::both().show(ui, |ui| {
|
||||
egui::Frame::canvas(ui.style()).show(ui, |ui| {
|
||||
self.custom_painting(ui);
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag());
|
||||
|
||||
self.angle += response.drag_delta().x * 0.01;
|
||||
|
||||
// Clone locals so we can move them into the paint callback:
|
||||
let angle = self.angle;
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(
|
||||
move |info, painter| {
|
||||
with_three_d(painter.gl(), |three_d| {
|
||||
three_d.frame(
|
||||
FrameInput::new(&three_d.context, &info, painter),
|
||||
angle,
|
||||
);
|
||||
});
|
||||
},
|
||||
)),
|
||||
};
|
||||
ui.painter().add(callback);
|
||||
});
|
||||
ui.label("Drag to rotate!");
|
||||
});
|
||||
@@ -54,120 +77,160 @@ impl eframe::App for MyApp {
|
||||
}
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
fn custom_painting(&mut self, ui: &mut egui::Ui) {
|
||||
let (rect, response) =
|
||||
ui.allocate_exact_size(egui::Vec2::splat(512.0), egui::Sense::drag());
|
||||
|
||||
self.angle += response.drag_delta().x * 0.01;
|
||||
|
||||
// Clone locals so we can move them into the paint callback:
|
||||
let angle = self.angle;
|
||||
|
||||
let callback = egui::PaintCallback {
|
||||
rect,
|
||||
callback: std::sync::Arc::new(egui_glow::CallbackFn::new(move |info, painter| {
|
||||
with_three_d_context(painter.gl(), |three_d| {
|
||||
paint_with_three_d(three_d, &info, angle);
|
||||
});
|
||||
})),
|
||||
};
|
||||
ui.painter().add(callback);
|
||||
}
|
||||
}
|
||||
|
||||
/// We get a [`glow::Context`] from `eframe`, but we want a [`three_d::Context`].
|
||||
/// We get a [`glow::Context`] from `eframe` and we want to construct a [`ThreeDApp`].
|
||||
///
|
||||
/// Sadly we can't just create a [`three_d::Context`] in [`MyApp::new`] and pass it
|
||||
/// to the [`egui::PaintCallback`] because [`three_d::Context`] isn't `Send+Sync`, which
|
||||
/// [`egui::PaintCallback`] is.
|
||||
fn with_three_d_context<R>(
|
||||
gl: &std::sync::Arc<glow::Context>,
|
||||
f: impl FnOnce(&three_d::Context) -> R,
|
||||
) -> R {
|
||||
/// Sadly we can't just create a [`ThreeDApp`] in [`MyApp::new`] and pass it
|
||||
/// to the [`egui::PaintCallback`] because [`glow::Context`] isn't `Send+Sync` on web, which
|
||||
/// [`egui::PaintCallback`] needs. If you do not target web, then you can construct the [`ThreeDApp`] in [`MyApp::new`].
|
||||
fn with_three_d<R>(gl: &std::sync::Arc<glow::Context>, f: impl FnOnce(&mut ThreeDApp) -> R) -> R {
|
||||
use std::cell::RefCell;
|
||||
thread_local! {
|
||||
pub static THREE_D: RefCell<Option<three_d::Context>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
// If you are using the depth buffer you need to do this:
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
gl.enable(glow::DEPTH_TEST);
|
||||
if !cfg!(target_arch = "wasm32") {
|
||||
gl.disable(glow::FRAMEBUFFER_SRGB);
|
||||
}
|
||||
gl.clear(glow::DEPTH_BUFFER_BIT);
|
||||
pub static THREE_D: RefCell<Option<ThreeDApp>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
THREE_D.with(|three_d| {
|
||||
let mut three_d = three_d.borrow_mut();
|
||||
let three_d =
|
||||
three_d.get_or_insert_with(|| three_d::Context::from_gl_context(gl.clone()).unwrap());
|
||||
let three_d = three_d.get_or_insert_with(|| ThreeDApp::new(gl.clone()));
|
||||
f(three_d)
|
||||
})
|
||||
}
|
||||
|
||||
fn paint_with_three_d(three_d: &three_d::Context, info: &egui::PaintCallbackInfo, angle: f32) {
|
||||
// Based on https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs
|
||||
use three_d::*;
|
||||
|
||||
// Set where to paint
|
||||
let viewport = info.viewport_in_pixels();
|
||||
let viewport = Viewport {
|
||||
x: viewport.left_px.round() as _,
|
||||
y: viewport.from_bottom_px.round() as _,
|
||||
width: viewport.width_px.round() as _,
|
||||
height: viewport.height_px.round() as _,
|
||||
};
|
||||
|
||||
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
|
||||
let clip_rect = info.clip_rect_in_pixels();
|
||||
three_d.set_scissor(ScissorBox {
|
||||
x: clip_rect.left_px.round() as _,
|
||||
y: clip_rect.from_bottom_px.round() as _,
|
||||
width: clip_rect.width_px.round() as _,
|
||||
height: clip_rect.height_px.round() as _,
|
||||
});
|
||||
|
||||
let camera = Camera::new_perspective(
|
||||
three_d,
|
||||
viewport,
|
||||
vec3(0.0, 0.0, 2.0),
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
degrees(45.0),
|
||||
0.1,
|
||||
10.0,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Create a CPU-side mesh consisting of a single colored triangle
|
||||
let positions = vec![
|
||||
vec3(0.5, -0.5, 0.0), // bottom right
|
||||
vec3(-0.5, -0.5, 0.0), // bottom left
|
||||
vec3(0.0, 0.5, 0.0), // top
|
||||
];
|
||||
let colors = vec![
|
||||
Color::new(255, 0, 0, 255), // bottom right
|
||||
Color::new(0, 255, 0, 255), // bottom left
|
||||
Color::new(0, 0, 255, 255), // top
|
||||
];
|
||||
let cpu_mesh = CpuMesh {
|
||||
positions: Positions::F32(positions),
|
||||
colors: Some(colors),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let mut model = Gm::new(
|
||||
Mesh::new(three_d, &cpu_mesh).unwrap(),
|
||||
ColorMaterial::default(),
|
||||
);
|
||||
|
||||
// Set the current transformation of the triangle
|
||||
model.set_transformation(Mat4::from_angle_y(radians(angle)));
|
||||
|
||||
// Render the triangle with the color material which uses the per vertex colors defined at construction
|
||||
model.render(&camera, &[]).unwrap();
|
||||
///
|
||||
/// Translates from egui input to three-d input
|
||||
///
|
||||
pub struct FrameInput<'a> {
|
||||
screen: three_d::RenderTarget<'a>,
|
||||
viewport: three_d::Viewport,
|
||||
scissor_box: three_d::ScissorBox,
|
||||
}
|
||||
|
||||
impl FrameInput<'_> {
|
||||
pub fn new(
|
||||
context: &three_d::Context,
|
||||
info: &egui::PaintCallbackInfo,
|
||||
painter: &egui_glow::Painter,
|
||||
) -> Self {
|
||||
use three_d::*;
|
||||
|
||||
// Disable sRGB textures for three-d
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
use glow::HasContext as _;
|
||||
context.disable(glow::FRAMEBUFFER_SRGB);
|
||||
}
|
||||
|
||||
// Constructs a screen render target to render the final image to
|
||||
let screen = painter.intermediate_fbo().map_or_else(
|
||||
|| {
|
||||
RenderTarget::screen(
|
||||
context,
|
||||
info.viewport.width() as u32,
|
||||
info.viewport.height() as u32,
|
||||
)
|
||||
},
|
||||
|fbo| {
|
||||
RenderTarget::from_framebuffer(
|
||||
context,
|
||||
info.viewport.width() as u32,
|
||||
info.viewport.height() as u32,
|
||||
fbo,
|
||||
)
|
||||
},
|
||||
);
|
||||
|
||||
// Set where to paint
|
||||
let viewport = info.viewport_in_pixels();
|
||||
let viewport = Viewport {
|
||||
x: viewport.left_px.round() as _,
|
||||
y: viewport.from_bottom_px.round() as _,
|
||||
width: viewport.width_px.round() as _,
|
||||
height: viewport.height_px.round() as _,
|
||||
};
|
||||
|
||||
// Respect the egui clip region (e.g. if we are inside an `egui::ScrollArea`).
|
||||
let clip_rect = info.clip_rect_in_pixels();
|
||||
let scissor_box = ScissorBox {
|
||||
x: clip_rect.left_px.round() as _,
|
||||
y: clip_rect.from_bottom_px.round() as _,
|
||||
width: clip_rect.width_px.round() as _,
|
||||
height: clip_rect.height_px.round() as _,
|
||||
};
|
||||
Self {
|
||||
screen,
|
||||
scissor_box,
|
||||
viewport,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Based on the `three-d` [Triangle example](https://github.com/asny/three-d/blob/master/examples/triangle/src/main.rs).
|
||||
/// This is where you'll need to customize
|
||||
///
|
||||
use three_d::*;
|
||||
pub struct ThreeDApp {
|
||||
context: Context,
|
||||
camera: Camera,
|
||||
model: Gm<Mesh, ColorMaterial>,
|
||||
}
|
||||
|
||||
impl ThreeDApp {
|
||||
pub fn new(gl: std::sync::Arc<glow::Context>) -> Self {
|
||||
let context = Context::from_gl_context(gl).unwrap();
|
||||
// Create a camera
|
||||
let camera = Camera::new_perspective(
|
||||
Viewport::new_at_origo(1, 1),
|
||||
vec3(0.0, 0.0, 2.0),
|
||||
vec3(0.0, 0.0, 0.0),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
degrees(45.0),
|
||||
0.1,
|
||||
10.0,
|
||||
);
|
||||
|
||||
// Create a CPU-side mesh consisting of a single colored triangle
|
||||
let positions = vec![
|
||||
vec3(0.5, -0.5, 0.0), // bottom right
|
||||
vec3(-0.5, -0.5, 0.0), // bottom left
|
||||
vec3(0.0, 0.5, 0.0), // top
|
||||
];
|
||||
let colors = vec![
|
||||
Color::new(255, 0, 0, 255), // bottom right
|
||||
Color::new(0, 255, 0, 255), // bottom left
|
||||
Color::new(0, 0, 255, 255), // top
|
||||
];
|
||||
let cpu_mesh = CpuMesh {
|
||||
positions: Positions::F32(positions),
|
||||
colors: Some(colors),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
// Construct a model, with a default color material, thereby transferring the mesh data to the GPU
|
||||
let model = Gm::new(Mesh::new(&context, &cpu_mesh), ColorMaterial::default());
|
||||
Self {
|
||||
context,
|
||||
camera,
|
||||
model,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn frame(&mut self, frame_input: FrameInput<'_>, angle: f32) -> Option<glow::Framebuffer> {
|
||||
// Ensure the viewport matches the current window viewport which changes if the window is resized
|
||||
self.camera.set_viewport(frame_input.viewport);
|
||||
|
||||
// Set the current transformation of the triangle
|
||||
self.model
|
||||
.set_transformation(Mat4::from_angle_y(radians(angle)));
|
||||
|
||||
// Get the screen render target to be able to render something on the screen
|
||||
frame_input
|
||||
.screen
|
||||
// Clear the color and depth of the screen render target
|
||||
.clear_partially(frame_input.scissor_box, ClearState::depth(1.0))
|
||||
// Render the triangle with the color material which uses the per vertex colors defined at construction
|
||||
.render_partially(frame_input.scissor_box, &self.camera, &[&self.model], &[]);
|
||||
|
||||
frame_input.screen.into_framebuffer() // Take back the screen fbo, we will continue to use it.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,8 @@ fn custon_window_frame(
|
||||
rect
|
||||
};
|
||||
let title_bar_response =
|
||||
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::drag());
|
||||
if title_bar_response.drag_started() {
|
||||
ui.interact(title_bar_rect, Id::new("title_bar"), Sense::click());
|
||||
if title_bar_response.is_pointer_button_down_on() {
|
||||
frame.drag_window();
|
||||
}
|
||||
|
||||
|
||||
12
examples/serial_windows/Cargo.toml
Normal file
12
examples/serial_windows/Cargo.toml
Normal file
@@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "serial_windows"
|
||||
version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.61"
|
||||
publish = false
|
||||
|
||||
|
||||
[dependencies]
|
||||
eframe = { path = "../../eframe" }
|
||||
8
examples/serial_windows/README.md
Normal file
8
examples/serial_windows/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
Demonstrates how to open several windows after each other.
|
||||
|
||||
NOTE: this doesn't work on Mac due to <https://github.com/rust-windowing/winit/issues/2431>.
|
||||
See also <https://github.com/emilk/egui/issues/1918>.
|
||||
|
||||
```sh
|
||||
cargo run -p serial_windows
|
||||
```
|
||||
53
examples/serial_windows/src/main.rs
Normal file
53
examples/serial_windows/src/main.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||
|
||||
use eframe::egui;
|
||||
|
||||
fn main() {
|
||||
if cfg!(target_os = "macos") {
|
||||
eprintln!("WARNING: this example does not work on Mac! See https://github.com/emilk/egui/issues/1918");
|
||||
}
|
||||
|
||||
let options = eframe::NativeOptions {
|
||||
run_and_return: true,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
eprintln!("Starting first window…");
|
||||
eframe::run_native(
|
||||
"First Window",
|
||||
options.clone(),
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
eprintln!("Starting second window…");
|
||||
eframe::run_native(
|
||||
"Second Window",
|
||||
options.clone(),
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
|
||||
std::thread::sleep(std::time::Duration::from_secs(2));
|
||||
|
||||
eprintln!("Starting third window…");
|
||||
eframe::run_native(
|
||||
"Third Window",
|
||||
options,
|
||||
Box::new(|_cc| Box::new(MyApp::default())),
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MyApp {}
|
||||
|
||||
impl eframe::App for MyApp {
|
||||
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
|
||||
egui::CentralPanel::default().show(ctx, |ui| {
|
||||
if ui.button("Close").clicked() {
|
||||
eprintln!("Pressed Close button");
|
||||
frame.quit();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user