-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tracking issue: Misc improvements to multi-viewports #3556
Comments
emilk
added a commit
that referenced
this issue
Nov 16, 2023
* Closes #1044 --- (new PR description written by @emilk) ## Overview This PR introduces the concept of `Viewports`, which on the native eframe backend corresponds to native OS windows. You can spawn a new viewport using `Context::show_viewport` and `Cotext::show_viewport_immediate`. These needs to be called every frame the viewport should be visible. This is implemented by the native `eframe` backend, but not the web one. ## Viewport classes The viewports form a tree of parent-child relationships. There are different classes of viewports. ### Root vieport The root viewport is the original viewport, and cannot be closed without closing the application. ### Deferred viewports These are created with `Context::show_viewport`. Deferred viewports take a closure that is called by the integration at a later time, perhaps multiple times. Deferred viewports are repainted independenantly of the parent viewport. This means communication with them need to done via channels, or `Arc/Mutex`. This is the most performant type of child viewport, though a bit more cumbersome to work with compared to immediate viewports. ### Immediate viewports These are created with `Context::show_viewport_immediate`. Immediate viewports take a `FnOnce` closure, similar to other egui functions, and is called immediately. This makes communication with them much simpler than with deferred viewports, but this simplicity comes at a cost: whenever tha parent viewports needs to be repainted, so will the child viewport, and vice versa. This means that if you have `N` viewports you are poentially doing `N` times as much CPU work. However, if all your viewports are showing animations, and thus are repainting constantly anyway, this doesn't matter. In short: immediate viewports are simpler to use, but can waste a lot of CPU time. ### Embedded viewports These are not real, independenant viewports, but is a fallback mode for when the integration does not support real viewports. In your callback is called with `ViewportClass::Embedded` it means you need to create an `egui::Window` to wrap your ui in, which will then be embedded in the parent viewport, unable to escape it. ## Using the viewports Only one viewport is active at any one time, identified wth `Context::viewport_id`. You can send commands to other viewports using `Context::send_viewport_command_to`. There is an example in <https://github.com/emilk/egui/tree/master/examples/multiple_viewports/src/main.rs>. ## For integrations There are several changes relevant to integrations. * There is a [`crate::RawInput::viewport`] with information about the current viewport. * The repaint callback set by `Context::set_request_repaint_callback` now points to which viewport should be repainted. * `Context::run` now returns a list of viewports in `FullOutput` which should result in their own independant windows * There is a new `Context::set_immediate_viewport_renderer` for setting up the immediate viewport integration * If you support viewports, you need to call `Context::set_embed_viewports(false)`, or all new viewports will be embedded (the default behavior). ## Future work * Make it easy to wrap child viewports in the same chrome as `egui::Window` * Automatically show embedded viewports using `egui::Window` * Use the new `ViewportBuilder` in `eframe::NativeOptions` * Automatically position new viewport windows (they currently cover each other) * Add a `Context` method for listing all existing viewports Find more at #3556 --- <details> <summary> Outdated PR description by @konkitoman </summary> ## Inspiration - Godot because the app always work desktop or single_window because of embedding - Dear ImGui viewport system ## What is a Viewport A Viewport is a egui isolated component! Can be used by the egui integration to create native windows! When you create a Viewport is possible that the backend do not supports that! So you need to check if the Viewport was created or you are in the normal egui context! This is how you can do that: ```rust if ctx.viewport_id() != ctx.parent_viewport_id() { // In here you add the code for the viewport context, like egui::CentralPanel::default().show(ctx, |ui|{ ui.label("This is in a native window!"); }); }else{ // In here you add the code for when viewport cannot be created! // You cannot use CentralPanel in here because you will override the app CentralPanel egui::Window::new("Virtual Viewport").show(ctx, |ui|{ ui.label("This is without a native window!\nThis is in a embedded viewport"); }); } ``` This PR do not support for drag and drop between Viewports! After this PR is accepted i will begin work to intregrate the Viewport system in `egui::Window`! The `egui::Window` i want to behave the same on desktop and web The `egui::Window` will be like Godot Window ## Changes and new These are only public structs and functions! <details> <summary> ## New </summary> - `egui::ViewportId` - `egui::ViewportBuilder` This is like winit WindowBuilder - `egui::ViewportCommand` With this you can set any winit property on a viewport, when is a native window! - `egui::Context::new` - `egui::Context::create_viewport` - `egui::Context::create_viewport_sync` - `egui::Context::viewport_id` - `egui::Context::parent_viewport_id` - `egui::Context::viewport_id_pair` - `egui::Context::set_render_sync_callback` - `egui::Context::is_desktop` - `egui::Context::force_embedding` - `egui::Context::set_force_embedding` - `egui::Context::viewport_command` - `egui::Context::send_viewport_command_to` - `egui::Context::input_for` - `egui::Context::input_mut_for` - `egui::Context::frame_nr_for` - `egui::Context::request_repaint_for` - `egui::Context::request_repaint_after_for` - `egui::Context::requested_repaint_last_frame` - `egui::Context::requested_repaint_last_frame_for` - `egui::Context::requested_repaint` - `egui::Context::requested_repaint_for` - `egui::Context::inner_rect` - `egui::Context::outer_rect` - `egui::InputState::inner_rect` - `egui::InputState::outer_rect` - `egui::WindowEvent` </details> <details> <summary> ## Changes </summary> - `egui::Context::run` Now needs the viewport that we want to render! - `egui::Context::begin_frame` Now needs the viewport that we want to render! - `egui::Context::tessellate` Now needs the viewport that we want to render! - `egui::FullOutput` ```diff - repaint_after + viewports + viewport_commands ``` - `egui::RawInput` ```diff + inner_rect + outer_rect ``` - `egui::Event` ```diff + WindowEvent ``` </details> ### Async Viewport Async means that is independent from other viewports! Is created by `egui::Context::create_viewport` To be used you will need to wrap your state in `Arc<RwLock<T>>` Look at viewports example to understand how to use it! ### Sync Viewport Sync means that is dependent on his parent! Is created by `egui::Context::create_viewport_sync` This will pause the parent then render itself the resumes his parent! #### :warning: This currently will make the fps/2 for every sync viewport ### Common #### :warning: Attention You will need to do this when you render your content ```rust ctx.create_viewport(ViewportBuilder::new("Simple Viewport"), | ctx | { let content = |ui: &mut egui::Ui|{ ui.label("Content"); }; // This will make the content a popup if cannot create a native window if ctx.viewport_id() != ctx.parent_viewport_id() { egui::CentralPanel::default().show(ctx, content); } else { egui::Area::new("Simple Viewport").show(ctx, |ui| { egui::Frame::popup(ui.style()).show(ui, content); }); }; }); ```` ## What you need to know as egui user ### If you are using eframe You don't need to change anything! ### If you have a manual implementation Now `egui::run` or `egui::begin` and `egui::tessellate` will need the current viewport id! You cannot create a `ViewportId` only `ViewportId::MAIN` If you make a single window app you will set the viewport id to be `egui::ViewportId::MAIN` or see the `examples/pure_glow` If you want to have multiples window support look at `crates/eframe` glow or wgpu implementations! ## If you want to try this - cargo run -p viewports ## This before was wanted to change This will probably be in feature PR's ### egui::Window To create a native window when embedded was set to false You can try that in viewports example before: [78a0ae8](78a0ae8) ### egui popups, context_menu, tooltip To be a native window </details> --------- Co-authored-by: Konkitoman <[email protected]> Co-authored-by: Emil Ernerfeldt <[email protected]> Co-authored-by: Pablo Sichert <[email protected]>
Not sure if out of scope for this issue but one that I'm particularly interested in:
My use case for this, btw, is improving performance of the Membrane extension by splitting the app into canvases that can be re-rendered independently. |
emilk
changed the title
Improvement to multi-viewports
Tracking issue: Misc improvements to multi-viewports
Nov 16, 2023
That defiantly belongs here! We can break out smaller issues from this one as people start working on them |
emilk
added a commit
that referenced
this issue
Nov 18, 2023
* Part of #3556 ## In short You now almost never need to use `eframe::Frame` - instead use `ui.input(|i| i.viewport())` for information about the current viewport (native window), and use `ctx.send_viewport_cmd` to modify it. ## In detail This PR removes most commands from `eframe::Frame`, and replaces them with `ViewportCommand`. So `frame.close()` becomes `ctx.send_viewport_cmd(ViewportCommand::Close)`, etc. `frame.info().window_info` is now also gone, replaced with `ui.input(|i| i.viewport())`. `frame.info().native_pixels_per_point` is replaced with `ui.input(|i| i.raw.native_pixels_per_point)`. `RawInput` now contains one `ViewportInfo` for each viewport. Screenshots are taken with `ctx.send_viewport_cmd(ViewportCommand::Screenshots)` and are returned in `egui::Event` which you can check with: ``` ust ui.input(|i| { for event in &i.raw.events { if let egui::Event::Screenshot { viewport_id, image } = event { // handle it here } } }); ``` ### Motivation You no longer need to pass around the `&eframe::Frame` everywhere. This also opens the door for other integrations to use the same API of `ViewportCommand`s.
emilk
added a commit
that referenced
this issue
Nov 19, 2023
* Part of #3556 This PR replaces a bunch of options in `eframe::NativeOptions` with `egui::ViewportBuilder`. For instance: ``` diff let options = eframe::NativeOptions { - initial_window_size: Some(egui::vec2(320.0, 240.0)), - drag_and_drop_support: true, + viewport: egui::ViewportBuilder::default() + .with_inner_size([320.0, 240.0]) + .with_drag_and_drop(true), centered: true, ..Default::default() }; ```
Open
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Multi-viewports were implemented in #3172, but there are several features missing that would make it a lot nicer, including (in no particular order):
crate::Window
crate::Window
ViewportBuilder
ineframe::NativeOptions
Context
method for listing all existing viewportsMemory
ViewportInfo
ViewportInfo
for every viewport inRawInput
The text was updated successfully, but these errors were encountered: