Replace `eframe::Frame` commands and `WindowInfo` with egui (#3564)

* Part of https://github.com/emilk/egui/issues/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.
This commit is contained in:
Emil Ernerfeldt 2023-11-18 19:27:53 +01:00 committed by GitHub
parent 3e37e9dfc7
commit 1571027556
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 905 additions and 915 deletions

View File

@ -230,11 +230,6 @@ pub trait App {
fn warm_up_enabled(&self) -> bool {
false
}
/// Called each time after the rendering the UI.
///
/// Can be used to access pixel data with [`Frame::screenshot`]
fn post_rendering(&mut self, _window_size_px: [u32; 2], _frame: &Frame) {}
}
/// Selects the level of hardware graphics acceleration.
@ -732,9 +727,6 @@ pub struct Frame {
/// Information about the integration.
pub(crate) info: IntegrationInfo,
/// Where the app can issue commands back to the integration.
pub(crate) output: backend::AppOutput,
/// A place where you can store custom data in a way that persists when you restart the app.
pub(crate) storage: Option<Box<dyn Storage>>,
@ -746,11 +738,6 @@ pub struct Frame {
#[cfg(feature = "wgpu")]
pub(crate) wgpu_render_state: Option<egui_wgpu::RenderState>,
/// If [`Frame::request_screenshot`] was called during a frame, this field will store the screenshot
/// such that it can be retrieved during [`App::post_rendering`] with [`Frame::screenshot`]
#[cfg(not(target_arch = "wasm32"))]
pub(crate) screenshot: std::cell::Cell<Option<egui::ColorImage>>,
/// Raw platform window handle
#[cfg(not(target_arch = "wasm32"))]
pub(crate) raw_window_handle: RawWindowHandle,
@ -799,67 +786,6 @@ impl Frame {
self.storage.as_deref()
}
/// Request the current frame's pixel data. Needs to be retrieved by calling [`Frame::screenshot`]
/// during [`App::post_rendering`].
#[cfg(not(target_arch = "wasm32"))]
pub fn request_screenshot(&mut self) {
self.output.screenshot_requested = true;
}
/// Cancel a request made with [`Frame::request_screenshot`].
#[cfg(not(target_arch = "wasm32"))]
pub fn cancel_screenshot_request(&mut self) {
self.output.screenshot_requested = false;
}
/// During [`App::post_rendering`], use this to retrieve the pixel data that was requested during
/// [`App::update`] via [`Frame::request_screenshot`].
///
/// Returns None if:
/// * Called in [`App::update`]
/// * [`Frame::request_screenshot`] wasn't called on this frame during [`App::update`]
/// * The rendering backend doesn't support this feature (yet). Currently implemented for wgpu and glow, but not with wasm as target.
/// * Wgpu's GL target is active (not yet supported)
/// * Retrieving the data was unsuccessful in some way.
///
/// See also [`egui::ColorImage::region`]
///
/// ## Example generating a capture of everything within a square of 100 pixels located at the top left of the app and saving it with the [`image`](crates.io/crates/image) crate:
/// ```
/// struct MyApp;
///
/// impl eframe::App for MyApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
/// // In real code the app would render something here
/// frame.request_screenshot();
/// // Things that are added to the frame after the call to
/// // request_screenshot() will still be included.
/// }
///
/// fn post_rendering(&mut self, _window_size: [u32; 2], frame: &eframe::Frame) {
/// if let Some(screenshot) = frame.screenshot() {
/// let pixels_per_point = frame.info().native_pixels_per_point;
/// let region = egui::Rect::from_two_pos(
/// egui::Pos2::ZERO,
/// egui::Pos2{ x: 100., y: 100. },
/// );
/// let top_left_corner = screenshot.region(&region, pixels_per_point);
/// image::save_buffer(
/// "top_left.png",
/// top_left_corner.as_raw(),
/// top_left_corner.width() as u32,
/// top_left_corner.height() as u32,
/// image::ColorType::Rgba8,
/// ).unwrap();
/// }
/// }
/// }
/// ```
#[cfg(not(target_arch = "wasm32"))]
pub fn screenshot(&self) -> Option<egui::ColorImage> {
self.screenshot.take()
}
/// A place where you can store custom data in a way that persists when you restart the app.
pub fn storage_mut(&mut self) -> Option<&mut (dyn Storage + 'static)> {
self.storage.as_deref_mut()
@ -891,141 +817,6 @@ impl Frame {
pub fn wgpu_render_state(&self) -> Option<&egui_wgpu::RenderState> {
self.wgpu_render_state.as_ref()
}
/// Tell `eframe` to close the desktop window.
///
/// The window will not close immediately, but at the end of the this frame.
///
/// Calling this will likely result in the app quitting, unless
/// you have more code after the call to [`crate::run_native`].
#[cfg(not(target_arch = "wasm32"))]
#[doc(alias = "exit")]
#[doc(alias = "quit")]
pub fn close(&mut self) {
log::debug!("eframe::Frame::close called");
self.output.close = true;
}
/// Minimize or unminimize window. (native only)
#[cfg(not(target_arch = "wasm32"))]
pub fn set_minimized(&mut self, minimized: bool) {
self.output.minimized = Some(minimized);
}
/// Bring the window into focus (native only). Has no effect on Wayland, or if the window is minimized or invisible.
///
/// This method puts the window on top of other applications and takes input focus away from them,
/// which, if unexpected, will disturb the user.
#[cfg(not(target_arch = "wasm32"))]
pub fn focus(&mut self) {
self.output.focus = Some(true);
}
/// If the window is unfocused, attract the user's attention (native only).
///
/// Typically, this means that the window will flash on the taskbar, or bounce, until it is interacted with.
///
/// When the window comes into focus, or if `None` is passed, the attention request will be automatically reset.
///
/// See [winit's documentation][user_attention_details] for platform-specific effect details.
///
/// [user_attention_details]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html
#[cfg(not(target_arch = "wasm32"))]
pub fn request_user_attention(&mut self, kind: egui::UserAttentionType) {
self.output.attention = Some(kind);
}
/// Maximize or unmaximize window. (native only)
#[cfg(not(target_arch = "wasm32"))]
pub fn set_maximized(&mut self, maximized: bool) {
self.output.maximized = Some(maximized);
}
/// Tell `eframe` to close the desktop window.
#[cfg(not(target_arch = "wasm32"))]
#[deprecated = "Renamed `close`"]
pub fn quit(&mut self) {
self.close();
}
/// Set the desired inner size of the window (in egui points).
#[cfg(not(target_arch = "wasm32"))]
pub fn set_window_size(&mut self, size: egui::Vec2) {
self.output.window_size = Some(size);
self.info.window_info.size = size; // so that subsequent calls see the updated value
}
/// Set the desired title of the window.
#[cfg(not(target_arch = "wasm32"))]
pub fn set_window_title(&mut self, title: &str) {
self.output.window_title = Some(title.to_owned());
}
/// Set whether to show window decorations (i.e. a frame around you app).
///
/// If false it will be difficult to move and resize the app.
#[cfg(not(target_arch = "wasm32"))]
pub fn set_decorations(&mut self, decorated: bool) {
self.output.decorated = Some(decorated);
}
/// Turn borderless fullscreen on/off (native only).
#[cfg(not(target_arch = "wasm32"))]
pub fn set_fullscreen(&mut self, fullscreen: bool) {
self.output.fullscreen = Some(fullscreen);
self.info.window_info.fullscreen = fullscreen; // so that subsequent calls see the updated value
}
/// set the position of the outer window.
#[cfg(not(target_arch = "wasm32"))]
pub fn set_window_pos(&mut self, pos: egui::Pos2) {
self.output.window_pos = Some(pos);
self.info.window_info.position = Some(pos); // so that subsequent calls see the updated value
}
/// When called, the native window will follow the
/// movement of the cursor while the primary mouse button is down.
///
/// Does not work on the web.
#[cfg(not(target_arch = "wasm32"))]
pub fn drag_window(&mut self) {
self.output.drag_window = true;
}
/// Set the visibility of the window.
#[cfg(not(target_arch = "wasm32"))]
pub fn set_visible(&mut self, visible: bool) {
self.output.visible = Some(visible);
}
/// On desktop: Set the window always on top.
///
/// (Wayland desktop currently not supported)
#[cfg(not(target_arch = "wasm32"))]
pub fn set_always_on_top(&mut self, always_on_top: bool) {
self.output.always_on_top = Some(always_on_top);
}
/// On desktop: Set the window to be centered.
///
/// (Wayland desktop currently not supported)
#[cfg(not(target_arch = "wasm32"))]
pub fn set_centered(&mut self) {
if let Some(monitor_size) = self.info.window_info.monitor_size {
let inner_size = self.info.window_info.size;
if monitor_size.x > 1.0 && monitor_size.y > 1.0 {
let x = (monitor_size.x - inner_size.x) / 2.0;
let y = (monitor_size.y - inner_size.y) / 2.0;
self.set_window_pos(egui::Pos2 { x, y });
}
}
}
/// for integrations only: call once per frame
#[cfg(any(feature = "glow", feature = "wgpu"))]
pub(crate) fn take_app_output(&mut self) -> backend::AppOutput {
std::mem::take(&mut self.output)
}
}
/// Information about the web environment (if applicable).
@ -1039,38 +830,6 @@ pub struct WebInfo {
pub location: Location,
}
/// Information about the application's main window, if available.
#[cfg(not(target_arch = "wasm32"))]
#[derive(Clone, Debug)]
pub struct WindowInfo {
/// Coordinates of the window's outer top left corner, relative to the top left corner of the first display.
///
/// Unit: egui points (logical pixels).
///
/// `None` = unknown.
pub position: Option<egui::Pos2>,
/// Are we in fullscreen mode?
pub fullscreen: bool,
/// Are we minimized?
pub minimized: bool,
/// Are we maximized?
pub maximized: bool,
/// Is the window focused and able to receive input?
///
/// This should be the same as [`egui::InputState::focused`].
pub focused: bool,
/// Window inner size in egui points (logical pixels).
pub size: egui::Vec2,
/// Current monitor size in egui points (logical pixels)
pub monitor_size: Option<egui::Vec2>,
}
/// Information about the URL.
///
/// Everything has been percent decoded (`%20` -> ` ` etc).
@ -1141,13 +900,6 @@ pub struct IntegrationInfo {
/// Seconds of cpu usage (in seconds) of UI code on the previous frame.
/// `None` if this is the first frame.
pub cpu_usage: Option<f32>,
/// The OS native pixels-per-point
pub native_pixels_per_point: Option<f32>,
/// The position and size of the native window.
#[cfg(not(target_arch = "wasm32"))]
pub window_info: WindowInfo,
}
// ----------------------------------------------------------------------------
@ -1211,68 +963,3 @@ pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, valu
/// [`Storage`] key used for app
pub const APP_KEY: &str = "app";
// ----------------------------------------------------------------------------
/// You only need to look here if you are writing a backend for `epi`.
pub(crate) mod backend {
/// Action that can be taken by the user app.
#[derive(Clone, Debug, Default)]
#[must_use]
pub struct AppOutput {
/// Set to `true` to close the native window (which often quits the app).
#[cfg(not(target_arch = "wasm32"))]
pub close: bool,
/// Set to some size to resize the outer window (e.g. glium window) to this size.
#[cfg(not(target_arch = "wasm32"))]
pub window_size: Option<egui::Vec2>,
/// Set to some string to rename the outer window (e.g. glium window) to this title.
#[cfg(not(target_arch = "wasm32"))]
pub window_title: Option<String>,
/// Set to some bool to change window decorations.
#[cfg(not(target_arch = "wasm32"))]
pub decorated: Option<bool>,
/// Set to some bool to change window fullscreen.
#[cfg(not(target_arch = "wasm32"))] // TODO: implement fullscreen on web
pub fullscreen: Option<bool>,
/// Set to true to drag window while primary mouse button is down.
#[cfg(not(target_arch = "wasm32"))]
pub drag_window: bool,
/// Set to some position to move the outer window (e.g. glium window) to this position
#[cfg(not(target_arch = "wasm32"))]
pub window_pos: Option<egui::Pos2>,
/// Set to some bool to change window visibility.
#[cfg(not(target_arch = "wasm32"))]
pub visible: Option<bool>,
/// Set to some bool to tell the window always on top.
#[cfg(not(target_arch = "wasm32"))]
pub always_on_top: Option<bool>,
/// Set to some bool to minimize or unminimize window.
#[cfg(not(target_arch = "wasm32"))]
pub minimized: Option<bool>,
/// Set to some bool to maximize or unmaximize window.
#[cfg(not(target_arch = "wasm32"))]
pub maximized: Option<bool>,
/// Set to some bool to focus window.
#[cfg(not(target_arch = "wasm32"))]
pub focus: Option<bool>,
/// Set to request a user's attention to the native window.
#[cfg(not(target_arch = "wasm32"))]
pub attention: Option<egui::UserAttentionType>,
#[cfg(not(target_arch = "wasm32"))]
pub screenshot_requested: bool,
}
}

View File

@ -4,61 +4,13 @@ use winit::event_loop::EventLoopWindowTarget;
use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _};
use egui::{DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair};
use egui_winit::{native_pixels_per_point, EventResponse, WindowSettings};
use egui::{
DeferredViewportUiCallback, NumExt as _, ViewportBuilder, ViewportId, ViewportIdPair,
ViewportInfo,
};
use egui_winit::{EventResponse, WindowSettings};
use crate::{epi, Theme, WindowInfo};
#[derive(Default)]
pub struct WindowState {
// We cannot simply call `winit::Window::is_minimized/is_maximized`
// because that deadlocks on mac.
pub minimized: bool,
pub maximized: bool,
}
pub fn read_window_info(
window: &winit::window::Window,
pixels_per_point: f32,
window_state: &WindowState,
) -> WindowInfo {
let position = window
.outer_position()
.ok()
.map(|pos| pos.to_logical::<f32>(pixels_per_point.into()))
.map(|pos| egui::Pos2 { x: pos.x, y: pos.y });
let monitor = window.current_monitor().is_some();
let monitor_size = if monitor {
let size = window
.current_monitor()
.unwrap()
.size()
.to_logical::<f32>(pixels_per_point.into());
Some(egui::vec2(size.width, size.height))
} else {
None
};
let size = window
.inner_size()
.to_logical::<f32>(pixels_per_point.into());
// NOTE: calling window.is_minimized() or window.is_maximized() deadlocks on Mac.
WindowInfo {
position,
fullscreen: window.fullscreen().is_some(),
minimized: window_state.minimized,
maximized: window_state.maximized,
focused: window.has_focus(),
size: egui::Vec2 {
x: size.width,
y: size.height,
},
monitor_size,
}
}
use crate::{epi, Theme};
pub fn window_builder<E>(
event_loop: &EventLoopWindowTarget<E>,
@ -208,97 +160,6 @@ fn largest_monitor_point_size<E>(event_loop: &EventLoopWindowTarget<E>) -> egui:
}
}
pub fn handle_app_output(
window: &winit::window::Window,
current_pixels_per_point: f32,
app_output: epi::backend::AppOutput,
window_state: &mut WindowState,
) {
crate::profile_function!();
let epi::backend::AppOutput {
close: _,
window_size,
window_title,
decorated,
fullscreen,
drag_window,
window_pos,
visible: _, // handled in post_present
always_on_top,
screenshot_requested: _, // handled by the rendering backend,
minimized,
maximized,
focus,
attention,
} = app_output;
if let Some(decorated) = decorated {
window.set_decorations(decorated);
}
if let Some(window_size) = window_size {
window.set_inner_size(
winit::dpi::PhysicalSize {
width: (current_pixels_per_point * window_size.x).round(),
height: (current_pixels_per_point * window_size.y).round(),
}
.to_logical::<f32>(native_pixels_per_point(window) as f64),
);
}
if let Some(fullscreen) = fullscreen {
window.set_fullscreen(fullscreen.then_some(winit::window::Fullscreen::Borderless(None)));
}
if let Some(window_title) = window_title {
window.set_title(&window_title);
}
if let Some(window_pos) = window_pos {
window.set_outer_position(winit::dpi::LogicalPosition {
x: window_pos.x as f64,
y: window_pos.y as f64,
});
}
if drag_window {
window.drag_window().ok();
}
if let Some(always_on_top) = always_on_top {
use winit::window::WindowLevel;
window.set_window_level(if always_on_top {
WindowLevel::AlwaysOnTop
} else {
WindowLevel::Normal
});
}
if let Some(minimized) = minimized {
window.set_minimized(minimized);
window_state.minimized = minimized;
}
if let Some(maximized) = maximized {
window.set_maximized(maximized);
window_state.maximized = maximized;
}
if !window.has_focus() {
if focus == Some(true) {
window.focus_window();
} else if let Some(attention) = attention {
use winit::window::UserAttentionType;
window.request_user_attention(match attention {
egui::UserAttentionType::Reset => None,
egui::UserAttentionType::Critical => Some(UserAttentionType::Critical),
egui::UserAttentionType::Informational => Some(UserAttentionType::Informational),
});
}
}
}
// ----------------------------------------------------------------------------
/// For loading/saving app state and/or egui memory to disk.
@ -313,10 +174,13 @@ pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
// ----------------------------------------------------------------------------
/// Everything needed to make a winit-based integration for [`epi`].
///
/// Only one instance per app (not one per viewport).
pub struct EpiIntegration {
pub frame: epi::Frame,
last_auto_save: Instant,
pub beginning: Instant,
is_first_frame: bool,
pub frame_start: Instant,
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
@ -325,7 +189,6 @@ pub struct EpiIntegration {
close: bool,
can_drag_window: bool,
window_state: WindowState,
follow_system_theme: bool,
#[cfg(feature = "persistence")]
persist_window: bool,
@ -350,30 +213,16 @@ impl EpiIntegration {
let memory = load_egui_memory(storage.as_deref()).unwrap_or_default();
egui_ctx.memory_mut(|mem| *mem = memory);
let native_pixels_per_point = window.scale_factor() as f32;
let window_state = WindowState {
minimized: window.is_minimized().unwrap_or(false),
maximized: window.is_maximized(),
};
let frame = epi::Frame {
info: epi::IntegrationInfo {
system_theme,
cpu_usage: None,
native_pixels_per_point: Some(native_pixels_per_point),
window_info: read_window_info(window, egui_ctx.pixels_per_point(), &window_state),
},
output: epi::backend::AppOutput {
visible: Some(true),
..Default::default()
},
storage,
#[cfg(feature = "glow")]
gl,
#[cfg(feature = "wgpu")]
wgpu_render_state,
screenshot: std::cell::Cell::new(None),
raw_display_handle: window.raw_display_handle(),
raw_window_handle: window.raw_window_handle(),
};
@ -390,12 +239,12 @@ impl EpiIntegration {
pending_full_output: Default::default(),
close: false,
can_drag_window: false,
window_state,
follow_system_theme: native_options.follow_system_theme,
#[cfg(feature = "persistence")]
persist_window: native_options.persist_window,
app_icon_setter,
beginning: Instant::now(),
is_first_frame: true,
frame_start: Instant::now(),
}
}
@ -432,10 +281,12 @@ impl EpiIntegration {
self.egui_ctx
.memory_mut(|mem| mem.set_everything_is_visible(true));
let raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT);
self.pre_update(window);
let mut raw_input = egui_winit.take_egui_input(window, ViewportIdPair::ROOT);
raw_input.viewports =
std::iter::once((ViewportId::ROOT, ViewportInfo::default())).collect();
self.pre_update();
let full_output = self.update(app, None, raw_input);
self.post_update(app, window);
self.post_update();
self.pending_full_output.append(full_output); // Handle it next frame
self.egui_ctx.memory_mut(|mem| *mem = saved_memory); // We don't want to remember that windows were huge.
self.egui_ctx.clear_animations();
@ -473,7 +324,7 @@ impl EpiIntegration {
..
} => self.can_drag_window = true,
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
self.frame.info.native_pixels_per_point = Some(*scale_factor as _);
egui_winit.egui_input_mut().native_pixels_per_point = Some(*scale_factor as _);
}
WindowEvent::ThemeChanged(winit_theme) if self.follow_system_theme => {
let theme = theme_from_winit_theme(*winit_theme);
@ -483,16 +334,12 @@ impl EpiIntegration {
_ => {}
}
egui_winit.on_event(&self.egui_ctx, event)
egui_winit.on_event(&self.egui_ctx, event, viewport_id)
}
pub fn pre_update(&mut self, window: &winit::window::Window) {
pub fn pre_update(&mut self) {
self.frame_start = Instant::now();
self.app_icon_setter.update();
self.frame.info.window_info =
read_window_info(window, self.egui_ctx.pixels_per_point(), &self.window_state);
}
/// Run user code - this can create immediate viewports, so hold no locks over this!
@ -513,6 +360,11 @@ impl EpiIntegration {
viewport_ui_cb(egui_ctx);
} else {
// Root viewport
if egui_ctx.input(|i| i.viewport().close_requested) {
self.close = app.on_close_event();
log::debug!("App::on_close_event returned {}", self.close);
}
crate::profile_scope!("App::update");
app.update(egui_ctx, &mut self.frame);
}
@ -522,45 +374,16 @@ impl EpiIntegration {
std::mem::take(&mut self.pending_full_output)
}
pub fn post_update(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
let app_output = {
let mut app_output = self.frame.take_app_output();
app_output.drag_window &= self.can_drag_window; // Necessary on Windows; see https://github.com/emilk/egui/pull/1108
self.can_drag_window = false;
if app_output.close {
self.close = app.on_close_event();
log::debug!("App::on_close_event returned {}", self.close);
}
self.frame.output.visible = app_output.visible; // this is handled by post_present
self.frame.output.screenshot_requested = app_output.screenshot_requested;
if self.frame.output.attention.is_some() {
self.frame.output.attention = None;
}
app_output
};
handle_app_output(
window,
self.egui_ctx.pixels_per_point(),
app_output,
&mut self.window_state,
);
pub fn post_update(&mut self) {
let frame_time = self.frame_start.elapsed().as_secs_f64() as f32;
self.frame.info.cpu_usage = Some(frame_time);
}
pub fn post_rendering(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
pub fn post_rendering(&mut self, window: &winit::window::Window) {
crate::profile_function!();
let inner_size = window.inner_size();
let window_size_px = [inner_size.width, inner_size.height];
app.post_rendering(window_size_px, &self.frame);
}
pub fn post_present(&mut self, window: &winit::window::Window) {
if let Some(visible) = self.frame.output.visible.take() {
crate::profile_scope!("window.set_visible");
window.set_visible(visible);
if std::mem::take(&mut self.is_first_frame) {
// We keep hidden until we've painted something. See https://github.com/emilk/egui/pull/2279
window.set_visible(true);
}
}

View File

@ -458,10 +458,12 @@ mod glow_integration {
use egui::{
epaint::ahash::HashMap, DeferredViewportUiCallback, ImmediateViewport, NumExt as _,
ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportOutput,
ViewportClass, ViewportIdMap, ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput,
};
use egui_winit::{create_winit_window_builder, process_viewport_commands, EventResponse};
use crate::native::epi_integration::EpiIntegration;
use super::*;
// Note: that the current Glutin API design tightly couples the GL context with
@ -480,7 +482,7 @@ mod glow_integration {
/// a Resumed event. On Android this ensures that any graphics state is only
/// initialized once the application has an associated `SurfaceView`.
struct GlowWinitRunning {
integration: epi_integration::EpiIntegration,
integration: EpiIntegration,
app: Box<dyn epi::App>,
// These needs to be shared with the immediate viewport renderer, hence the Rc/Arc/RefCells:
@ -527,14 +529,23 @@ mod glow_integration {
let (raw_input, viewport_ui_cb) = {
let mut glutin = self.glutin.borrow_mut();
let viewport = glutin.viewports.get_mut(&viewport_id).unwrap();
viewport.update_viewport_info();
let window = viewport.window.as_ref().unwrap();
let egui_winit = viewport.egui_winit.as_mut().unwrap();
let raw_input = egui_winit.take_egui_input(window, viewport.ids);
let mut raw_input = egui_winit.take_egui_input(window, viewport.ids);
let viewport_ui_cb = viewport.viewport_ui_cb.clone();
self.integration.pre_update(window);
self.integration.pre_update();
(raw_input, viewport.viewport_ui_cb.clone())
raw_input.time = Some(self.integration.beginning.elapsed().as_secs_f64());
raw_input.viewports = glutin
.viewports
.iter()
.map(|(id, viewport)| (*id, viewport.info.clone()))
.collect();
(raw_input, viewport_ui_cb)
};
// ------------------------------------------------------------
@ -577,7 +588,7 @@ mod glow_integration {
let gl_surface = viewport.gl_surface.as_ref().unwrap();
let egui_winit = viewport.egui_winit.as_mut().unwrap();
integration.post_update(app.as_mut(), window);
integration.post_update();
integration.handle_platform_output(window, viewport_id, platform_output, egui_winit);
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
@ -607,13 +618,18 @@ mod glow_integration {
);
{
let screenshot_requested = &mut integration.frame.output.screenshot_requested;
if *screenshot_requested {
*screenshot_requested = false;
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
if screenshot_requested {
let screenshot = painter.read_screen_rgba(screen_size_in_pixels);
integration.frame.screenshot.set(Some(screenshot));
egui_winit
.egui_input_mut()
.events
.push(egui::Event::Screenshot {
viewport_id,
image: screenshot.into(),
});
}
integration.post_rendering(app.as_mut(), window);
integration.post_rendering(window);
}
{
@ -627,8 +643,6 @@ mod glow_integration {
}
}
integration.post_present(window);
// give it time to settle:
#[cfg(feature = "__screenshot")]
if integration.egui_ctx.frame_nr() == 2 {
@ -716,6 +730,7 @@ mod glow_integration {
return EventResult::Exit;
}
}
_ => {}
}
@ -783,6 +798,8 @@ mod glow_integration {
ids: ViewportIdPair,
class: ViewportClass,
builder: ViewportBuilder,
info: ViewportInfo,
screenshot_requested: bool,
/// The user-callback that shows the ui.
/// None for immediate viewports.
@ -793,6 +810,19 @@ mod glow_integration {
egui_winit: Option<egui_winit::State>,
}
impl Viewport {
/// Update the stored `ViewportInfo`.
pub fn update_viewport_info(&mut self) {
let Some(window) = &self.window else {
return;
};
let Some(egui_winit) = &self.egui_winit else {
return;
};
egui_winit.update_viewport_info(&mut self.info, window);
}
}
/// This struct will contain both persistent and temporary glutin state.
///
/// Platform Quirks:
@ -935,9 +965,12 @@ mod glow_integration {
let mut viewport_from_window = HashMap::default();
let mut window_from_viewport = ViewportIdMap::default();
let mut info = ViewportInfo::default();
if let Some(window) = &window {
viewport_from_window.insert(window.id(), ViewportId::ROOT);
window_from_viewport.insert(ViewportId::ROOT, window.id());
info.minimized = window.is_minimized();
info.maximized = Some(window.is_maximized());
}
let mut viewports = ViewportIdMap::default();
@ -947,6 +980,8 @@ mod glow_integration {
ids: ViewportIdPair::ROOT,
class: ViewportClass::Root,
builder: viewport_builder,
info,
screenshot_requested: false,
viewport_ui_cb: None,
gl_surface: None,
window: window.map(Rc::new),
@ -1016,13 +1051,14 @@ mod glow_integration {
window
} else {
log::trace!("Window doesn't exist yet. Creating one now with finalize_window");
viewport
.window
.insert(Rc::new(glutin_winit::finalize_window(
event_loop,
create_winit_window_builder(&viewport.builder),
&self.gl_config,
)?))
let window = glutin_winit::finalize_window(
event_loop,
create_winit_window_builder(&viewport.builder),
&self.gl_config,
)?;
viewport.info.minimized = window.is_minimized();
viewport.info.maximized = Some(window.is_maximized());
viewport.window.insert(Rc::new(window))
};
{
@ -1178,7 +1214,7 @@ mod glow_integration {
{
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
initialize_or_update_viewport(
let viewport = initialize_or_update_viewport(
&mut self.viewports,
ids,
class,
@ -1187,15 +1223,15 @@ mod glow_integration {
focused_viewport,
);
if let Some(viewport) = self.viewports.get(&viewport_id) {
if let Some(window) = &viewport.window {
let is_viewport_focused = focused_viewport == Some(viewport_id);
egui_winit::process_viewport_commands(
commands,
window,
is_viewport_focused,
);
}
if let Some(window) = &viewport.window {
let is_viewport_focused = focused_viewport == Some(viewport_id);
egui_winit::process_viewport_commands(
&mut viewport.info,
commands,
window,
is_viewport_focused,
&mut viewport.screenshot_requested,
);
}
}
@ -1234,6 +1270,8 @@ mod glow_integration {
ids,
class,
builder,
info: Default::default(),
screenshot_requested: false,
viewport_ui_cb,
window: None,
egui_winit: None,
@ -1261,7 +1299,13 @@ mod glow_integration {
viewport.egui_winit = None;
} else if let Some(window) = &viewport.window {
let is_viewport_focused = focused_viewport == Some(ids.this);
process_viewport_commands(delta_commands, window, is_viewport_focused);
process_viewport_commands(
&mut viewport.info,
delta_commands,
window,
is_viewport_focused,
&mut viewport.screenshot_requested,
);
}
entry.into_mut()
@ -1378,7 +1422,7 @@ mod glow_integration {
let system_theme = system_theme(&glutin.window(ViewportId::ROOT), &self.native_options);
let mut integration = epi_integration::EpiIntegration::new(
let mut integration = EpiIntegration::new(
&glutin.window(ViewportId::ROOT),
system_theme,
&self.app_name,
@ -1548,7 +1592,7 @@ mod glow_integration {
let Some(viewport) = glutin.viewports.get_mut(&ids.this) else {
return;
};
viewport.update_viewport_info();
let Some(winit_state) = &mut viewport.egui_winit else {
return;
};
@ -1556,9 +1600,14 @@ mod glow_integration {
return;
};
let mut input = winit_state.take_egui_input(window, ids);
input.time = Some(beginning.elapsed().as_secs_f64());
input
let mut raw_input = winit_state.take_egui_input(window, ids);
raw_input.viewports = glutin
.viewports
.iter()
.map(|(id, viewport)| (*id, viewport.info.clone()))
.collect();
raw_input.time = Some(beginning.elapsed().as_secs_f64());
raw_input
};
// ---------------------------------------------------
@ -1813,18 +1862,20 @@ mod wgpu_integration {
use egui::{
DeferredViewportUiCallback, FullOutput, ImmediateViewport, ViewportClass, ViewportIdMap,
ViewportIdPair, ViewportIdSet, ViewportOutput,
ViewportIdPair, ViewportIdSet, ViewportInfo, ViewportOutput,
};
use egui_winit::{create_winit_window_builder, process_viewport_commands};
use crate::native::epi_integration::EpiIntegration;
use super::*;
pub struct Viewport {
ids: ViewportIdPair,
class: ViewportClass,
builder: ViewportBuilder,
info: ViewportInfo,
screenshot_requested: bool,
/// `None` for sync viewports.
viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,
@ -1849,28 +1900,42 @@ mod wgpu_integration {
let viewport_id = self.ids.this;
match create_winit_window_builder(&self.builder).build(event_loop) {
Ok(new_window) => {
windows_id.insert(new_window.id(), viewport_id);
Ok(window) => {
windows_id.insert(window.id(), viewport_id);
if let Err(err) =
pollster::block_on(painter.set_window(viewport_id, Some(&new_window)))
pollster::block_on(painter.set_window(viewport_id, Some(&window)))
{
log::error!("on set_window: viewport_id {viewport_id:?} {err}");
}
self.egui_winit = Some(egui_winit::State::new(
event_loop,
Some(new_window.scale_factor() as f32),
Some(window.scale_factor() as f32),
painter.max_texture_side(),
));
self.window = Some(Rc::new(new_window));
self.info.minimized = window.is_minimized();
self.info.maximized = Some(window.is_maximized());
self.window = Some(Rc::new(window));
}
Err(err) => {
log::error!("Failed to create window: {err}");
}
}
}
/// Update the stored `ViewportInfo`.
pub fn update_viewport_info(&mut self) {
let Some(window) = &self.window else {
return;
};
let Some(egui_winit) = &self.egui_winit else {
return;
};
egui_winit.update_viewport_info(&mut self.info, window);
}
}
pub type Viewports = ViewportIdMap<Viewport>;
@ -1888,7 +1953,7 @@ mod wgpu_integration {
/// a Resumed event. On Android this ensures that any graphics state is only
/// initialized once the application has an associated `SurfaceView`.
struct WgpuWinitRunning {
integration: epi_integration::EpiIntegration,
integration: EpiIntegration,
/// The users application.
app: Box<dyn epi::App>,
@ -1988,7 +2053,7 @@ mod wgpu_integration {
let wgpu_render_state = painter.render_state();
let system_theme = system_theme(&window, &self.native_options);
let mut integration = epi_integration::EpiIntegration::new(
let mut integration = EpiIntegration::new(
&window,
system_theme,
&self.app_name,
@ -2066,6 +2131,12 @@ mod wgpu_integration {
ids: ViewportIdPair::ROOT,
class: ViewportClass::Root,
builder,
info: ViewportInfo {
minimized: window.is_minimized(),
maximized: Some(window.is_maximized()),
..Default::default()
},
screenshot_requested: false,
viewport_ui_cb: None,
window: Some(Rc::new(window)),
egui_winit: Some(egui_winit),
@ -2155,6 +2226,7 @@ mod wgpu_integration {
painter,
viewport_from_window,
} = &mut *shared.borrow_mut();
let viewport = initialize_or_update_viewport(
viewports,
ids,
@ -2163,10 +2235,10 @@ mod wgpu_integration {
None,
None,
);
if viewport.window.is_none() {
viewport.init_window(viewport_from_window, painter, event_loop);
}
viewport.update_viewport_info();
let (Some(window), Some(winit_state)) = (&viewport.window, &mut viewport.egui_winit)
else {
@ -2174,6 +2246,10 @@ mod wgpu_integration {
};
let mut input = winit_state.take_egui_input(window, ids);
input.viewports = viewports
.iter()
.map(|(id, viewport)| (*id, viewport.info.clone()))
.collect();
input.time = Some(beginning.elapsed().as_secs_f64());
input
};
@ -2308,20 +2384,6 @@ mod wgpu_integration {
Ok(match event {
winit::event::Event::Resumed => {
let running = if let Some(running) = &self.running {
if !running
.shared
.borrow()
.viewports
.contains_key(&ViewportId::ROOT)
{
create_window(
event_loop,
running.integration.frame.storage(),
&self.app_name,
&mut self.native_options,
)?;
running.set_window(ViewportId::ROOT)?;
}
running
} else {
let storage = epi_integration::create_storage(
@ -2394,18 +2456,6 @@ mod wgpu_integration {
}
impl WgpuWinitRunning {
fn set_window(&self, id: ViewportId) -> Result<(), egui_wgpu::WgpuError> {
crate::profile_function!();
let mut shared = self.shared.borrow_mut();
let SharedState {
viewports, painter, ..
} = &mut *shared;
if let Some(Viewport { window, .. }) = viewports.get(&id) {
return pollster::block_on(painter.set_window(id, window.as_deref()));
}
Ok(())
}
fn save_and_destroy(&mut self) {
crate::profile_function!();
@ -2475,6 +2525,7 @@ mod wgpu_integration {
let Some(viewport) = viewports.get_mut(&viewport_id) else {
return EventResult::Wait;
};
viewport.update_viewport_info();
let Viewport {
ids,
@ -2483,6 +2534,7 @@ mod wgpu_integration {
egui_winit,
..
} = viewport;
let viewport_ui_cb = viewport_ui_cb.clone();
let Some(window) = window else {
return EventResult::Wait;
@ -2493,14 +2545,20 @@ mod wgpu_integration {
log::warn!("Failed to set window: {err}");
}
let raw_input = egui_winit.as_mut().unwrap().take_egui_input(
let mut raw_input = egui_winit.as_mut().unwrap().take_egui_input(
window,
ViewportIdPair::from_self_and_parent(viewport_id, ids.parent),
);
integration.pre_update(window);
integration.pre_update();
(viewport_ui_cb.clone(), raw_input)
raw_input.time = Some(integration.beginning.elapsed().as_secs_f64());
raw_input.viewports = viewports
.iter()
.map(|(id, viewport)| (*id, viewport.info.clone()))
.collect();
(viewport_ui_cb, raw_input)
};
// ------------------------------------------------------------
@ -2533,7 +2591,7 @@ mod wgpu_integration {
return EventResult::Wait;
};
integration.post_update(app.as_mut(), window);
integration.post_update();
let FullOutput {
platform_output,
@ -2548,21 +2606,27 @@ mod wgpu_integration {
{
let clipped_primitives = integration.egui_ctx.tessellate(shapes, pixels_per_point);
let screenshot_requested = &mut integration.frame.output.screenshot_requested;
let screenshot_requested = std::mem::take(&mut viewport.screenshot_requested);
let screenshot = painter.paint_and_update_textures(
viewport_id,
pixels_per_point,
app.clear_color(&integration.egui_ctx.style().visuals),
&clipped_primitives,
&textures_delta,
*screenshot_requested,
screenshot_requested,
);
*screenshot_requested = false;
integration.frame.screenshot.set(screenshot);
if let Some(screenshot) = screenshot {
egui_winit
.egui_input_mut()
.events
.push(egui::Event::Screenshot {
viewport_id,
image: screenshot.into(),
});
}
}
integration.post_rendering(app.as_mut(), window);
integration.post_present(window);
integration.post_rendering(window);
let active_viewports_ids: ViewportIdSet = viewport_output.keys().copied().collect();
@ -2630,6 +2694,7 @@ mod wgpu_integration {
winit::event::WindowEvent::Focused(new_focused) => {
*focused_viewport = new_focused.then(|| viewport_id).flatten();
}
winit::event::WindowEvent::Resized(physical_size) => {
// Resize with 0 width and height is used by winit to signal a minimize event on Windows.
// See: https://github.com/rust-windowing/winit/issues/208
@ -2645,6 +2710,7 @@ mod wgpu_integration {
}
}
}
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
use std::num::NonZeroU32;
if let (Some(width), Some(height), Some(viewport_id)) = (
@ -2656,10 +2722,12 @@ mod wgpu_integration {
shared.painter.on_window_resized(viewport_id, width, height);
}
}
winit::event::WindowEvent::CloseRequested if integration.should_close() => {
log::debug!("Received WindowEvent::CloseRequested");
return EventResult::Exit;
}
_ => {}
};
@ -2709,7 +2777,7 @@ mod wgpu_integration {
{
let ids = ViewportIdPair::from_self_and_parent(viewport_id, parent);
initialize_or_update_viewport(
let viewport = initialize_or_update_viewport(
viewports,
ids,
class,
@ -2718,12 +2786,15 @@ mod wgpu_integration {
focused_viewport,
);
if let Some(window) = viewports
.get(&viewport_id)
.and_then(|vp| vp.window.as_ref())
{
if let Some(window) = viewport.window.as_ref() {
let is_viewport_focused = focused_viewport == Some(viewport_id);
egui_winit::process_viewport_commands(commands, window, is_viewport_focused);
egui_winit::process_viewport_commands(
&mut viewport.info,
commands,
window,
is_viewport_focused,
&mut viewport.screenshot_requested,
);
}
}
}
@ -2751,6 +2822,8 @@ mod wgpu_integration {
ids,
class,
builder,
info: Default::default(),
screenshot_requested: false,
viewport_ui_cb,
window: None,
egui_winit: None,
@ -2777,7 +2850,13 @@ mod wgpu_integration {
viewport.egui_winit = None;
} else if let Some(window) = &viewport.window {
let is_viewport_focused = focused_viewport == Some(ids.this);
process_viewport_commands(delta_commands, window, is_viewport_focused);
process_viewport_commands(
&mut viewport.info,
delta_commands,
window,
is_viewport_focused,
&mut viewport.screenshot_requested,
);
}
entry.into_mut()

View File

@ -49,7 +49,6 @@ impl AppRunner {
},
system_theme,
cpu_usage: None,
native_pixels_per_point: Some(super::native_pixels_per_point()),
};
let storage = LocalStorage::default();
@ -78,7 +77,6 @@ impl AppRunner {
let frame = epi::Frame {
info,
output: Default::default(),
storage: Some(Box::new(storage)),
#[cfg(feature = "glow")]
@ -114,6 +112,7 @@ impl AppRunner {
};
runner.input.raw.max_texture_side = Some(runner.painter.max_texture_side());
runner.input.raw.native_pixels_per_point = Some(super::native_pixels_per_point());
Ok(runner)
}
@ -192,17 +191,19 @@ impl AppRunner {
if viewport_output.len() > 1 {
log::warn!("Multiple viewports not yet supported on the web");
}
// TODO(emilk): handle some of the command in `viewport_output`, like setting the title and icon?
for viewport_output in viewport_output.values() {
for command in &viewport_output.commands {
// TODO(emilk): handle some of the commands
log::warn!(
"Unhandled egui viewport command: {command:?} - not implemented in web backend"
);
}
}
self.handle_platform_output(platform_output);
self.textures_delta.append(textures_delta);
let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point);
{
let app_output = self.frame.take_app_output();
let epi::backend::AppOutput {} = app_output;
}
self.frame.info.cpu_usage = Some((now_sec() - frame_start) as f32);
clipped_primitives

View File

@ -14,7 +14,9 @@ pub use accesskit_winit;
pub use egui;
#[cfg(feature = "accesskit")]
use egui::accesskit;
use egui::{Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair};
use egui::{
Pos2, Rect, Vec2, ViewportBuilder, ViewportCommand, ViewportId, ViewportIdPair, ViewportInfo,
};
pub use winit;
pub mod clipboard;
@ -24,11 +26,11 @@ pub use window_settings::WindowSettings;
use raw_window_handle::HasRawDisplayHandle;
pub fn native_pixels_per_point(window: &winit::window::Window) -> f32 {
pub fn native_pixels_per_point(window: &Window) -> f32 {
window.scale_factor() as f32
}
pub fn screen_size_in_pixels(window: &winit::window::Window) -> egui::Vec2 {
pub fn screen_size_in_pixels(window: &Window) -> egui::Vec2 {
let size = window.inner_size();
egui::vec2(size.width as f32, size.height as f32)
}
@ -133,7 +135,7 @@ impl State {
#[cfg(feature = "accesskit")]
pub fn init_accesskit<T: From<accesskit_winit::ActionRequestEvent> + Send>(
&mut self,
window: &winit::window::Window,
window: &Window,
event_loop_proxy: winit::event_loop::EventLoopProxy<T>,
initial_tree_update_factory: impl 'static + FnOnce() -> accesskit::TreeUpdate + Send,
) {
@ -178,13 +180,28 @@ impl State {
&self.egui_input
}
/// The current input state.
/// This is changed by [`Self::on_event`] and cleared by [`Self::take_egui_input`].
#[inline]
pub fn egui_input_mut(&mut self) -> &mut egui::RawInput {
&mut self.egui_input
}
/// Update the given viewport info with the current state of the window.
///
/// Call before [`Self::update_viewport_info`]
pub fn update_viewport_info(&self, info: &mut ViewportInfo, window: &Window) {
update_viewport_info(info, window, self.pixels_per_point());
}
/// Prepare for a new frame by extracting the accumulated input,
///
/// as well as setting [the time](egui::RawInput::time) and [screen rectangle](egui::RawInput::screen_rect).
pub fn take_egui_input(
&mut self,
window: &winit::window::Window,
ids: ViewportIdPair,
) -> egui::RawInput {
///
/// You need to set [`egui::RawInput::viewports`] yourself though.
/// Use [`Self::update_viewport_info`] to update the info for each
/// viewport.
pub fn take_egui_input(&mut self, window: &Window, ids: ViewportIdPair) -> egui::RawInput {
crate::profile_function!();
let pixels_per_point = self.pixels_per_point();
@ -207,58 +224,9 @@ impl State {
&& screen_size_in_points.y > 0.0)
.then(|| Rect::from_min_size(Pos2::ZERO, screen_size_in_points));
let has_a_position = match window.is_minimized() {
None | Some(true) => false,
Some(false) => true,
};
let inner_pos_px = if has_a_position {
window
.inner_position()
.map(|pos| Pos2::new(pos.x as f32, pos.y as f32))
.ok()
} else {
None
};
let outer_pos_px = if has_a_position {
window
.outer_position()
.map(|pos| Pos2::new(pos.x as f32, pos.y as f32))
.ok()
} else {
None
};
let inner_size_px = if has_a_position {
let size = window.inner_size();
Some(Vec2::new(size.width as f32, size.height as f32))
} else {
None
};
let outer_size_px = if has_a_position {
let size = window.outer_size();
Some(Vec2::new(size.width as f32, size.height as f32))
} else {
None
};
self.egui_input.viewport.ids = ids;
self.egui_input.viewport.inner_rect_px =
if let (Some(pos), Some(size)) = (inner_pos_px, inner_size_px) {
Some(Rect::from_min_size(pos, size))
} else {
None
};
self.egui_input.viewport.outer_rect_px =
if let (Some(pos), Some(size)) = (outer_pos_px, outer_size_px) {
Some(Rect::from_min_size(pos, size))
} else {
None
};
// Tell egui which viewport is now active:
self.egui_input.viewport_ids = ids;
self.egui_input.native_pixels_per_point = Some(native_pixels_per_point(window));
self.egui_input.take()
}
@ -269,6 +237,7 @@ impl State {
&mut self,
egui_ctx: &egui::Context,
event: &winit::event::WindowEvent<'_>,
viewport_id: ViewportId,
) -> EventResponse {
crate::profile_function!();
@ -453,7 +422,9 @@ impl State {
// Things that may require repaint:
WindowEvent::CloseRequested => {
self.egui_input.viewport.close_requested = true;
if let Some(viewport_info) = self.egui_input.viewports.get_mut(&viewport_id) {
viewport_info.close_requested = true;
}
EventResponse {
consumed: true,
repaint: true,
@ -724,7 +695,7 @@ impl State {
/// *
pub fn handle_platform_output(
&mut self,
window: &winit::window::Window,
window: &Window,
viewport_id: ViewportId,
egui_ctx: &egui::Context,
platform_output: egui::PlatformOutput,
@ -772,7 +743,7 @@ impl State {
}
}
fn set_cursor_icon(&mut self, window: &winit::window::Window, cursor_icon: egui::CursorIcon) {
fn set_cursor_icon(&mut self, window: &Window, cursor_icon: egui::CursorIcon) {
if self.current_cursor_icon == Some(cursor_icon) {
// Prevent flickering near frame boundary when Windows OS tries to control cursor icon for window resizing.
// On other platforms: just early-out to save CPU.
@ -796,6 +767,82 @@ impl State {
}
}
fn update_viewport_info(viewport_info: &mut ViewportInfo, window: &Window, pixels_per_point: f32) {
crate::profile_function!();
let has_a_position = match window.is_minimized() {
None | Some(true) => false,
Some(false) => true,
};
let inner_pos_px = if has_a_position {
window
.inner_position()
.map(|pos| Pos2::new(pos.x as f32, pos.y as f32))
.ok()
} else {
None
};
let outer_pos_px = if has_a_position {
window
.outer_position()
.map(|pos| Pos2::new(pos.x as f32, pos.y as f32))
.ok()
} else {
None
};
let inner_size_px = if has_a_position {
let size = window.inner_size();
Some(Vec2::new(size.width as f32, size.height as f32))
} else {
None
};
let outer_size_px = if has_a_position {
let size = window.outer_size();
Some(Vec2::new(size.width as f32, size.height as f32))
} else {
None
};
let inner_rect_px = if let (Some(pos), Some(size)) = (inner_pos_px, inner_size_px) {
Some(Rect::from_min_size(pos, size))
} else {
None
};
let outer_rect_px = if let (Some(pos), Some(size)) = (outer_pos_px, outer_size_px) {
Some(Rect::from_min_size(pos, size))
} else {
None
};
let inner_rect = inner_rect_px.map(|r| r / pixels_per_point);
let outer_rect = outer_rect_px.map(|r| r / pixels_per_point);
let monitor = window.current_monitor().is_some();
let monitor_size = if monitor {
let size = window
.current_monitor()
.unwrap()
.size()
.to_logical::<f32>(pixels_per_point.into());
Some(egui::vec2(size.width, size.height))
} else {
None
};
viewport_info.title = Some(window.title());
viewport_info.pixels_per_point = pixels_per_point;
viewport_info.monitor_size = monitor_size;
viewport_info.inner_rect = inner_rect;
viewport_info.outer_rect = outer_rect;
viewport_info.fullscreen = Some(window.fullscreen().is_some());
viewport_info.focused = Some(window.has_focus());
}
fn open_url_in_browser(_url: &str) {
#[cfg(feature = "webbrowser")]
if let Err(err) = webbrowser::open(_url) {
@ -995,9 +1042,11 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::Curs
// ---------------------------------------------------------------------------
pub fn process_viewport_commands(
info: &mut ViewportInfo,
commands: impl IntoIterator<Item = ViewportCommand>,
window: &winit::window::Window,
window: &Window,
is_viewport_focused: bool,
screenshot_requested: &mut bool,
) {
crate::profile_function!();
@ -1005,20 +1054,27 @@ pub fn process_viewport_commands(
for command in commands {
match command {
egui::ViewportCommand::StartDrag => {
// if this is not checked on x11 the input will be permanently taken until the app is killed!
ViewportCommand::Close => {
info.close_requested = true;
}
ViewportCommand::StartDrag => {
// If `is_viewport_focused` is not checked on x11 the input will be permanently taken until the app is killed!
// TODO: check that the left mouse-button was pressed down recently,
// or we will have bugs on Windows.
// See https://github.com/emilk/egui/pull/1108
if is_viewport_focused {
if let Err(err) = window.drag_window() {
log::warn!("{command:?}: {err}");
}
}
}
egui::ViewportCommand::InnerSize(size) => {
ViewportCommand::InnerSize(size) => {
let width = size.x.max(1.0);
let height = size.y.max(1.0);
window.set_inner_size(LogicalSize::new(width, height));
}
egui::ViewportCommand::BeginResize(direction) => {
ViewportCommand::BeginResize(direction) => {
if let Err(err) = window.drag_resize_window(match direction {
egui::viewport::ResizeDirection::North => ResizeDirection::North,
egui::viewport::ResizeDirection::South => ResizeDirection::South,
@ -1031,7 +1087,9 @@ pub fn process_viewport_commands(
log::warn!("{command:?}: {err}");
}
}
ViewportCommand::Title(title) => window.set_title(&title),
ViewportCommand::Title(title) => {
window.set_title(&title);
}
ViewportCommand::Transparent(v) => window.set_transparent(v),
ViewportCommand::Visible(v) => window.set_visible(v),
ViewportCommand::OuterPosition(pos) => {
@ -1070,8 +1128,14 @@ pub fn process_viewport_commands(
WindowButtons::empty()
},
),
ViewportCommand::Minimized(v) => window.set_minimized(v),
ViewportCommand::Maximized(v) => window.set_maximized(v),
ViewportCommand::Minimized(v) => {
window.set_minimized(v);
info.minimized = Some(v);
}
ViewportCommand::Maximized(v) => {
window.set_maximized(v);
info.maximized = Some(v);
}
ViewportCommand::Fullscreen(v) => {
window.set_fullscreen(v.then_some(winit::window::Fullscreen::Borderless(None)));
}
@ -1100,15 +1164,21 @@ pub fn process_viewport_commands(
egui::viewport::IMEPurpose::Terminal => winit::window::ImePurpose::Terminal,
egui::viewport::IMEPurpose::Normal => winit::window::ImePurpose::Normal,
}),
ViewportCommand::Focus => {
if !window.has_focus() {
window.focus_window();
}
}
ViewportCommand::RequestUserAttention(a) => {
window.request_user_attention(a.map(|a| match a {
egui::viewport::UserAttentionType::Critical => {
winit::window::UserAttentionType::Critical
window.request_user_attention(match a {
egui::UserAttentionType::Reset => None,
egui::UserAttentionType::Critical => {
Some(winit::window::UserAttentionType::Critical)
}
egui::viewport::UserAttentionType::Informational => {
winit::window::UserAttentionType::Informational
egui::UserAttentionType::Informational => {
Some(winit::window::UserAttentionType::Informational)
}
}));
});
}
ViewportCommand::SetTheme(t) => window.set_theme(match t {
egui::SystemTheme::Light => Some(winit::window::Theme::Light),
@ -1136,6 +1206,9 @@ pub fn process_viewport_commands(
log::warn!("{command:?}: {err}");
}
}
ViewportCommand::Screenshot => {
*screenshot_requested = true;
}
}
}
}
@ -1268,5 +1341,5 @@ mod profiling_scopes {
pub(crate) use profiling_scopes::*;
use winit::{
dpi::{LogicalPosition, LogicalSize},
window::{CursorGrabMode, WindowButtons, WindowLevel},
window::{CursorGrabMode, Window, WindowButtons, WindowLevel},
};

View File

@ -227,7 +227,7 @@ struct ContextImpl {
impl ContextImpl {
fn begin_frame_mut(&mut self, mut new_raw_input: RawInput) {
let ids = new_raw_input.viewport.ids;
let ids = new_raw_input.viewport_ids;
let viewport_id = ids.this;
self.viewport_stack.push(ids);
let viewport = self.viewports.entry(viewport_id).or_default();
@ -2555,15 +2555,16 @@ impl Context {
/// Send a command to the current viewport.
///
/// This lets you affect the current viewport, e.g. resizing the window.
pub fn send_viewport_command(&self, command: ViewportCommand) {
self.send_viewport_command_to(self.viewport_id(), command);
pub fn send_viewport_cmd(&self, command: ViewportCommand) {
self.send_viewport_cmd_to(self.viewport_id(), command);
}
/// Send a command to a speicfic viewport.
///
/// This lets you affect another viewport, e.g. resizing its window.
pub fn send_viewport_command_to(&self, id: ViewportId, command: ViewportCommand) {
pub fn send_viewport_cmd_to(&self, id: ViewportId, command: ViewportCommand) {
self.write(|ctx| ctx.viewport_for(id).commands.push(command));
self.request_repaint_of(id);
}
/// This creates a new native window, if possible.
@ -2572,6 +2573,9 @@ impl Context {
///
/// You need to call this each frame when the child viewport should exist.
///
/// You can check if the user wants to close the viewport by checking the
/// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewport`].
///
/// The given callback will be called whenever the child viewport needs repainting,
/// e.g. on an event or when [`Self::request_repaint`] is called.
/// This means it may be called multiple times, for instance while the
@ -2629,6 +2633,9 @@ impl Context {
///
/// You need to call this each frame when the child viewport should exist.
///
/// You can check if the user wants to close the viewport by checking the
/// [`crate::ViewportInfo::close_requested`] flags found in [`crate::InputState::viewport`].
///
/// The given ui function will be called immediately.
/// This may only be called on the main thread.
/// This call will pause the current viewport and render the child viewport in its own window.

View File

@ -1,6 +1,8 @@
//! The input needed by egui.
use crate::{emath::*, ViewportIdPair};
use epaint::ColorImage;
use crate::{emath::*, ViewportIdMap, ViewportIdPair};
/// What the integrations provides to egui at the start of each frame.
///
@ -13,8 +15,11 @@ use crate::{emath::*, ViewportIdPair};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct RawInput {
/// Information about the viwport the input is part of.
pub viewport: ViewportInfo,
/// The id of the active viewport, and out parent.
pub viewport_ids: ViewportIdPair,
/// Information about all egui viewports.
pub viewports: ViewportIdMap<ViewportInfo>,
/// Position and size of the area that egui should use, in points.
/// Usually you would set this to
@ -27,10 +32,19 @@ pub struct RawInput {
pub screen_rect: Option<Rect>,
/// Also known as device pixel ratio, > 1 for high resolution screens.
///
/// If text looks blurry you probably forgot to set this.
/// Set this the first frame, whenever it changes, or just on every frame.
pub pixels_per_point: Option<f32>,
/// The OS native pixels-per-point.
///
/// This should always be set, if known.
///
/// On web this takes browser scaling into account,
/// and orresponds to [`window.devicePixelRatio`](https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio) in JavaScript.
pub native_pixels_per_point: Option<f32>,
/// Maximum size of one side of the font texture.
///
/// Ask your graphics drivers about this. This corresponds to `GL_MAX_TEXTURE_SIZE`.
@ -75,9 +89,11 @@ pub struct RawInput {
impl Default for RawInput {
fn default() -> Self {
Self {
viewport: ViewportInfo::default(),
viewport_ids: Default::default(),
viewports: Default::default(),
screen_rect: None,
pixels_per_point: None,
native_pixels_per_point: None,
max_texture_side: None,
time: None,
predicted_dt: 1.0 / 60.0,
@ -97,9 +113,11 @@ impl RawInput {
/// * [`Self::dropped_files`] is moved.
pub fn take(&mut self) -> RawInput {
RawInput {
viewport: self.viewport.take(),
viewport_ids: self.viewport_ids,
viewports: self.viewports.clone(),
screen_rect: self.screen_rect.take(),
pixels_per_point: self.pixels_per_point.take(),
pixels_per_point: self.pixels_per_point.take(), // take the diff
native_pixels_per_point: self.native_pixels_per_point, // copy
max_texture_side: self.max_texture_side.take(),
time: self.time.take(),
predicted_dt: self.predicted_dt,
@ -114,9 +132,11 @@ impl RawInput {
/// Add on new input.
pub fn append(&mut self, newer: Self) {
let Self {
viewport,
viewport_ids,
viewports,
screen_rect,
pixels_per_point,
native_pixels_per_point,
max_texture_side,
time,
predicted_dt,
@ -127,9 +147,11 @@ impl RawInput {
focused,
} = newer;
self.viewport = viewport;
self.viewport_ids = viewport_ids;
self.viewports = viewports;
self.screen_rect = screen_rect.or(self.screen_rect);
self.pixels_per_point = pixels_per_point.or(self.pixels_per_point);
self.native_pixels_per_point = native_pixels_per_point.or(self.native_pixels_per_point);
self.max_texture_side = max_texture_side.or(self.max_texture_side);
self.time = time; // use latest time
self.predicted_dt = predicted_dt; // use latest dt
@ -143,23 +165,50 @@ impl RawInput {
/// Information about the current viewport,
/// given as input each frame.
#[derive(Clone, Debug, Default, PartialEq, Eq)]
///
/// `None` means "unknown".
#[derive(Clone, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct ViewportInfo {
/// Id of us and our parent.
pub ids: ViewportIdPair,
/// Parent viewport, if known.
pub parent: Option<crate::ViewportId>,
/// Viewport inner position and size, only the drowable area
/// unit = physical pixels
pub inner_rect_px: Option<Rect>,
/// Viewport outer position and size, drowable area + decorations
/// unit = physical pixels
pub outer_rect_px: Option<Rect>,
/// Name of the viewport, if known.
pub title: Option<String>,
/// The user requested the viewport should close,
/// e.g. by pressing the close button in the window decoration.
pub close_requested: bool,
/// Number of physical pixels per ui point.
pub pixels_per_point: f32,
/// Current monitor size in egui points.
pub monitor_size: Option<Vec2>,
/// The inner rectangle of the native window, in monitor space and ui points scale.
///
/// This is the content rectangle of the viewport.
pub inner_rect: Option<Rect>,
/// The outer rectangle of the native window, in monitor space and ui points scale.
///
/// This is the content rectangle plus decoration chrome.
pub outer_rect: Option<Rect>,
/// Are we minimized?
pub minimized: Option<bool>,
/// Are we maximized?
pub maximized: Option<bool>,
/// Are we in fullscreen mode?
pub fullscreen: Option<bool>,
/// Is the window focused and able to receive input?
///
/// This should be the same as [`RawInput::focused`].
pub focused: Option<bool>,
}
impl ViewportInfo {
@ -169,15 +218,74 @@ impl ViewportInfo {
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
ids,
inner_rect_px,
outer_rect_px,
parent,
title,
close_requested,
pixels_per_point,
monitor_size,
inner_rect,
outer_rect,
minimized,
maximized,
fullscreen,
focused,
} = self;
ui.label(format!("ids: {ids:?}"));
ui.label(format!("inner_rect_px: {inner_rect_px:?}"));
ui.label(format!("outer_rect_px: {outer_rect_px:?}"));
ui.label(format!("close_requested: {close_requested:?}"));
crate::Grid::new("viewport_info").show(ui, |ui| {
ui.label("Parent:");
ui.label(opt_as_str(parent));
ui.end_row();
ui.label("Title:");
ui.label(opt_as_str(title));
ui.end_row();
ui.label("Close requested:");
ui.label(close_requested.to_string());
ui.end_row();
ui.label("Pixels per point:");
ui.label(pixels_per_point.to_string());
ui.end_row();
ui.label("Monitor size:");
ui.label(opt_as_str(monitor_size));
ui.end_row();
ui.label("Inner rect:");
ui.label(opt_rect_as_string(inner_rect));
ui.end_row();
ui.label("Outer rect:");
ui.label(opt_rect_as_string(outer_rect));
ui.end_row();
ui.label("Minimized:");
ui.label(opt_as_str(minimized));
ui.end_row();
ui.label("Maximized:");
ui.label(opt_as_str(maximized));
ui.end_row();
ui.label("Fullscreen:");
ui.label(opt_as_str(fullscreen));
ui.end_row();
ui.label("Focused:");
ui.label(opt_as_str(focused));
ui.end_row();
fn opt_rect_as_string(v: &Option<Rect>) -> String {
v.as_ref().map_or(String::new(), |r| {
format!("Pos: {:?}, size: {:?}", r.min, r.size())
})
}
fn opt_as_str<T: std::fmt::Debug>(v: &Option<T>) -> String {
v.as_ref().map_or(String::new(), |v| format!("{v:?}"))
}
});
}
}
@ -352,6 +460,12 @@ pub enum Event {
/// An assistive technology (e.g. screen reader) requested an action.
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit::ActionRequest),
/// The reply of a screenshot requested with [`crate::ViewportCommand::Screenshot`].
Screenshot {
viewport_id: crate::ViewportId,
image: std::sync::Arc<ColorImage>,
},
}
/// Mouse button (or similar for touch input)
@ -980,8 +1094,11 @@ fn format_kb_shortcut() {
impl RawInput {
pub fn ui(&self, ui: &mut crate::Ui) {
let Self {
viewport_ids,
viewports,
screen_rect,
pixels_per_point,
native_pixels_per_point,
max_texture_side,
time,
predicted_dt,
@ -990,15 +1107,31 @@ impl RawInput {
hovered_files,
dropped_files,
focused,
viewport,
} = self;
viewport.ui(ui);
ui.label(format!(
"Active viwport: {:?}, parent: {:?}",
viewport_ids.this, viewport_ids.parent,
));
for (id, viewport) in viewports {
ui.group(|ui| {
ui.label(format!("Viewport {id:?}"));
ui.push_id(id, |ui| {
viewport.ui(ui);
});
});
}
ui.label(format!("screen_rect: {screen_rect:?} points"));
ui.label(format!("pixels_per_point: {pixels_per_point:?}"))
.on_hover_text(
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
);
ui.label(format!(
"native_pixels_per_point: {native_pixels_per_point:?}"
))
.on_hover_text(
"Also called HDPI factor.\nNumber of physical pixels per each logical pixel.",
);
ui.label(format!("max_texture_side: {max_texture_side:?}"));
if let Some(time) = time {
ui.label(format!("time: {time:.3} s"));

View File

@ -210,6 +210,7 @@ impl OpenUrl {
///
/// [user_attention_type]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum UserAttentionType {
/// Request an elevated amount of animations and flair for the window and the task bar or dock icon.
Critical,

View File

@ -15,19 +15,15 @@ pub mod kb_shortcuts {
/// Let the user scale the GUI (change `Context::pixels_per_point`) by pressing
/// Cmd+Plus, Cmd+Minus or Cmd+0, just like in a browser.
///
/// When using [`eframe`](https://github.com/emilk/egui/tree/master/crates/eframe), you want to call this as:
/// ```ignore
/// // On web, the browser controls the gui zoom.
/// if !frame.is_web() {
/// egui::gui_zoom::zoom_with_keyboard_shortcuts(
/// ctx,
/// frame.info().native_pixels_per_point,
/// );
/// }
/// ```
pub fn zoom_with_keyboard_shortcuts(ctx: &Context, native_pixels_per_point: Option<f32>) {
/// # let ctx = &egui::Context::default();
/// // On web, the browser controls the gui zoom.
/// #[cfg(not(target_arch = "wasm32"))]
/// egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx);
/// ```
pub fn zoom_with_keyboard_shortcuts(ctx: &Context) {
if ctx.input_mut(|i| i.consume_shortcut(&kb_shortcuts::ZOOM_RESET)) {
if let Some(native_pixels_per_point) = native_pixels_per_point {
if let Some(native_pixels_per_point) = ctx.input(|i| i.raw.native_pixels_per_point) {
ctx.set_pixels_per_point(native_pixels_per_point);
}
} else {

View File

@ -231,6 +231,11 @@ impl InputState {
}
}
/// Info about the active viewport
pub fn viewport(&self) -> &ViewportInfo {
self.raw.viewports.get(&self.raw.viewport_ids.this).expect("Failed to find current viewport in egui RawInput. This is the fault of the egui backend")
}
#[inline(always)]
pub fn screen_rect(&self) -> Rect {
self.screen_rect

View File

@ -559,7 +559,7 @@ impl Memory {
self.window_interactions
.retain(|id, _| viewports.contains(id));
self.viewport_id = new_input.viewport.ids.this;
self.viewport_id = new_input.viewport_ids.this;
self.interactions
.entry(self.viewport_id)
.or_default()

View File

@ -38,12 +38,26 @@
//!
//! ## Using the viewports
//! Only one viewport is active at any one time, identified with [`Context::viewport_id`].
//! You can send commands to other viewports using [`Context::send_viewport_command_to`].
//! You can modify the current (change the title, resize the window, etc) by sending
//! a [`ViewportCommand`] to it using [`Context::send_viewport_cmd`].
//! You can interact with other viewports using [`Context::send_viewport_cmd_to`].
//!
//! There is an example in <https://github.com/emilk/egui/tree/master/examples/multiple_viewports/src/main.rs>.
//!
//! You can find all available viewports in [`crate::RawInput::viewports`] and the active viewport in
//! [`crate::InputState::viewport`]:
//!
//! ```no_run
//! # let ctx = &egui::Context::default();
//! ctx.input(|i| {
//! dbg!(&i.viewport()); // Current viewport
//! dbg!(&i.raw.viewports); // All viewports
//! });
//! ```
//!
//! ## For integrations
//! * There is a [`crate::RawInput::viewport`] with information about the current viewport.
//! * There is a [`crate::InputState::viewport`] with information about the current viewport.
//! * There is a [`crate::RawInput::viewports`] with information about all viewports.
//! * The repaint callback set by [`Context::set_request_repaint_callback`] points to which viewport should be repainted.
//! * [`crate::FullOutput::viewport_output`] is a list of viewports which should result in their own independent windows.
//! * To support immediate viewports you need to call [`Context::set_immediate_viewport_renderer`].
@ -636,13 +650,6 @@ pub enum CursorGrab {
Locked,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum UserAttentionType {
Informational,
Critical,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ResizeDirection {
@ -655,7 +662,7 @@ pub enum ResizeDirection {
SouthWest,
}
/// You can send a [`ViewportCommand`] to the viewport with [`Context::send_viewport_command`].
/// You can send a [`ViewportCommand`] to the viewport with [`Context::send_viewport_cmd`].
///
/// All coordinates are in logical points.
///
@ -663,7 +670,13 @@ pub enum ResizeDirection {
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ViewportCommand {
/// Set the title
/// Request this viewport to be closed.
///
/// For the root viewport, this usually results in the application shutting down.
/// For other viewports, the [`crate::ViewportInfo::close_requested`] flag will be set.
Close,
/// Set the window title.
Title(String),
/// Turn the window transparent or not.
@ -709,21 +722,45 @@ pub enum ViewportCommand {
maximize: bool,
},
Minimized(bool),
/// Maximize or unmaximize window.
Maximized(bool),
/// Turn borderless fullscreen on/off.
Fullscreen(bool),
/// Show window decorations, i.e. the chrome around the content
/// with the title bar, close buttons, resize handles, etc.
Decorations(bool),
/// Set window to be always-on-top, always-on-bottom, or neither.
WindowLevel(WindowLevel),
/// The the window icon.
WindowIcon(Option<Arc<ColorImage>>),
IMEPosition(Pos2),
IMEAllowed(bool),
IMEPurpose(IMEPurpose),
RequestUserAttention(Option<UserAttentionType>),
/// Bring the window into focus (native only).
///
/// This command puts the window on top of other applications and takes input focus away from them,
/// which, if unexpected, will disturb the user.
///
/// Has no effect on Wayland, or if the window is minimized or invisible.
Focus,
/// If the window is unfocused, attract the user's attention (native only).
///
/// Typically, this means that the window will flash on the taskbar, or bounce, until it is interacted with.
///
/// When the window comes into focus, or if `None` is passed, the attention request will be automatically reset.
///
/// See [winit's documentation][user_attention_details] for platform-specific effect details.
///
/// [user_attention_details]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html
RequestUserAttention(crate::UserAttentionType),
SetTheme(SystemTheme),
@ -737,6 +774,29 @@ pub enum ViewportCommand {
CursorVisible(bool),
CursorHitTest(bool),
/// Take a screenshot.
///
/// The results are returned in `crate::Event::Screenshot`.
Screenshot,
}
impl ViewportCommand {
/// Construct a command to center the viewport on the monitor, if possible.
pub fn center_on_screen(ctx: &crate::Context) -> Option<Self> {
ctx.input(|i| {
let outer_rect = i.viewport().outer_rect?;
let size = outer_rect.size();
let monitor_size = i.viewport().monitor_size?;
if 1.0 < monitor_size.x && 1.0 < monitor_size.y {
let x = (monitor_size.x - size.x) / 2.0;
let y = (monitor_size.y - size.y) / 2.0;
Some(Self::OuterPosition([x, y].into()))
} else {
None
}
})
}
}
/// Describes a viewport, i.e. a native window.

View File

@ -117,7 +117,7 @@ impl BackendPanel {
{
ui.separator();
if ui.button("Quit").clicked() {
frame.close();
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
}
@ -151,20 +151,21 @@ impl BackendPanel {
// On web, the browser controls `pixels_per_point`.
let integration_controls_pixels_per_point = frame.is_web();
if !integration_controls_pixels_per_point {
self.pixels_per_point_ui(ui, frame.info());
self.pixels_per_point_ui(ui);
}
#[cfg(not(target_arch = "wasm32"))]
{
ui.horizontal(|ui| {
{
let mut fullscreen = frame.info().window_info.fullscreen;
let mut fullscreen = ui.input(|i| i.viewport().fullscreen.unwrap_or(false));
if ui
.checkbox(&mut fullscreen, "🗖 Fullscreen (F11)")
.on_hover_text("Fullscreen the window")
.changed()
{
frame.set_fullscreen(fullscreen);
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(fullscreen));
}
}
@ -173,29 +174,29 @@ impl BackendPanel {
.on_hover_text("Resize the window to be small like a phone.")
.clicked()
{
// frame.set_window_size(egui::vec2(375.0, 812.0)); // iPhone 12 mini
frame.set_window_size(egui::vec2(375.0, 667.0)); // iPhone SE 2nd gen
frame.set_fullscreen(false);
// let size = egui::vec2(375.0, 812.0); // iPhone 12 mini
let size = egui::vec2(375.0, 667.0); // iPhone SE 2nd gen
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::InnerSize(size));
ui.ctx()
.send_viewport_cmd(egui::ViewportCommand::Fullscreen(false));
ui.close_menu();
}
});
if !frame.info().window_info.fullscreen
let fullscreen = ui.input(|i| i.viewport().fullscreen.unwrap_or(false));
if !fullscreen
&& ui
.button("Drag me to drag window")
.is_pointer_button_down_on()
{
frame.drag_window();
ui.ctx().send_viewport_cmd(egui::ViewportCommand::StartDrag);
}
ui.button("Native window info (hover me)")
.on_hover_ui(|ui| {
window_info_ui(ui, &frame.info().window_info);
});
}
}
fn pixels_per_point_ui(&mut self, ui: &mut egui::Ui, info: &eframe::IntegrationInfo) {
fn pixels_per_point_ui(&mut self, ui: &mut egui::Ui) {
let pixels_per_point = self
.pixels_per_point
.get_or_insert_with(|| ui.ctx().pixels_per_point());
@ -223,7 +224,7 @@ impl BackendPanel {
reset = true;
}
if let Some(native_pixels_per_point) = info.native_pixels_per_point {
if let Some(native_pixels_per_point) = ui.input(|i| i.raw.native_pixels_per_point) {
let enabled = ui.ctx().pixels_per_point() != native_pixels_per_point;
if ui
.add_enabled(enabled, egui::Button::new("Reset"))
@ -285,55 +286,6 @@ impl BackendPanel {
}
}
#[cfg(not(target_arch = "wasm32"))]
fn window_info_ui(ui: &mut egui::Ui, window_info: &eframe::WindowInfo) {
let eframe::WindowInfo {
position,
fullscreen,
minimized,
maximized,
focused,
size,
monitor_size,
} = window_info;
egui::Grid::new("window_info_grid")
.num_columns(2)
.show(ui, |ui| {
if let Some(egui::Pos2 { x, y }) = position {
ui.label("Position:");
ui.monospace(format!("{x:.0}, {y:.0}"));
ui.end_row();
}
ui.label("Fullscreen:");
ui.label(fullscreen.to_string());
ui.end_row();
ui.label("Minimized:");
ui.label(minimized.to_string());
ui.end_row();
ui.label("Maximized:");
ui.label(maximized.to_string());
ui.end_row();
ui.label("Focused:");
ui.label(focused.to_string());
ui.end_row();
ui.label("Window size:");
ui.monospace(format!("{x:.0} x {y:.0}", x = size.x, y = size.y));
ui.end_row();
if let Some(egui::Vec2 { x, y }) = monitor_size {
ui.label("Monitor size:");
ui.monospace(format!("{x:.0} x {y:.0}"));
ui.end_row();
}
});
}
// ----------------------------------------------------------------------------
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]

View File

@ -259,7 +259,8 @@ impl eframe::App for WrapApp {
#[cfg(not(target_arch = "wasm32"))]
if ctx.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::F11)) {
frame.set_fullscreen(!frame.info().window_info.fullscreen);
let fullscreen = ctx.input(|i| i.viewport().fullscreen.unwrap_or(false));
ctx.send_viewport_cmd(egui::ViewportCommand::Fullscreen(!fullscreen));
}
let mut cmd = Command::Nothing;
@ -284,7 +285,7 @@ impl eframe::App for WrapApp {
// On web, the browser controls `pixels_per_point`.
if !frame.is_web() {
egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx, frame.info().native_pixels_per_point);
egui::gui_zoom::zoom_with_keyboard_shortcuts(ctx);
}
self.run_cmd(ctx, cmd);

View File

@ -27,6 +27,7 @@ impl Default for Demos {
Box::<super::context_menu::ContextMenus>::default(),
Box::<super::dancing_strings::DancingStrings>::default(),
Box::<super::drag_and_drop::DragAndDropDemo>::default(),
Box::<super::extra_viewport::ExtraViewport>::default(),
Box::<super::font_book::FontBook>::default(),
Box::<super::MiscDemoWindow>::default(),
Box::<super::multi_touch::MultiTouch>::default(),
@ -61,9 +62,11 @@ impl Demos {
pub fn checkboxes(&mut self, ui: &mut Ui) {
let Self { demos, open } = self;
for demo in demos {
let mut is_open = open.contains(demo.name());
ui.toggle_value(&mut is_open, demo.name());
set_open(open, demo.name(), is_open);
if demo.is_enabled(ui.ctx()) {
let mut is_open = open.contains(demo.name());
ui.toggle_value(&mut is_open, demo.name());
set_open(open, demo.name(), is_open);
}
}
}

View File

@ -0,0 +1,72 @@
#[derive(Default)]
pub struct ExtraViewport {}
impl super::Demo for ExtraViewport {
fn is_enabled(&self, ctx: &egui::Context) -> bool {
!ctx.embed_viewports()
}
fn name(&self) -> &'static str {
"🗖 Extra Viewport"
}
fn show(&mut self, ctx: &egui::Context, open: &mut bool) {
if !*open {
return;
}
let id = egui::Id::new(self.name());
ctx.show_viewport_immediate(
egui::ViewportId(id),
egui::ViewportBuilder::default()
.with_title(self.name())
.with_inner_size([400.0, 512.0]),
|ctx, class| {
if class == egui::ViewportClass::Embedded {
// Not a real viewport
egui::Window::new(self.name())
.id(id)
.open(open)
.show(ctx, |ui| {
ui.label("This egui integration does not support multiple viewports");
});
} else {
egui::CentralPanel::default().show(ctx, |ui| {
ui.push_id(id, |ui| {
viewport_content(ui, ctx, open);
})
});
}
},
);
}
}
fn viewport_content(ui: &mut egui::Ui, ctx: &egui::Context, open: &mut bool) {
ui.label("egui and eframe supports having multiple native windows like this, which egui calls 'viewports'.");
ui.label(format!(
"This viewport has id: {:?}, child of viewport {:?}",
ctx.viewport_id(),
ctx.parent_viewport_id()
));
ui.label("Here you can see all the open viewports:");
egui::ScrollArea::vertical().show(ui, |ui| {
let viewports = ui.input(|i| i.raw.viewports.clone());
for (id, viewport) in viewports {
ui.group(|ui| {
ui.label(format!("viewport {id:?}"));
ui.push_id(id, |ui| {
viewport.ui(ui);
});
});
}
});
if ui.input(|i| i.viewport().close_requested) {
*open = false;
}
}

View File

@ -11,6 +11,7 @@ pub mod context_menu;
pub mod dancing_strings;
pub mod demo_app_windows;
pub mod drag_and_drop;
pub mod extra_viewport;
pub mod font_book;
pub mod highlighting;
pub mod layout_test;
@ -46,6 +47,11 @@ pub trait View {
/// Something to view
pub trait Demo {
/// Is the demo enabled for this integraton?
fn is_enabled(&self, _ctx: &egui::Context) -> bool {
true
}
/// `&'static` so we can also use it as a key to store open/close state.
fn name(&self) -> &'static str;

View File

@ -12,6 +12,8 @@ pub struct EguiGlow {
pub egui_winit: egui_winit::State,
pub painter: crate::Painter,
viewport_info: egui::ViewportInfo,
// output from the last update:
shapes: Vec<egui::epaint::ClippedShape>,
pixels_per_point: f32,
@ -43,6 +45,7 @@ impl EguiGlow {
egui_ctx: Default::default(),
egui_winit,
painter,
viewport_info: Default::default(),
shapes: Default::default(),
pixels_per_point,
textures_delta: Default::default(),
@ -50,7 +53,8 @@ impl EguiGlow {
}
pub fn on_event(&mut self, event: &winit::event::WindowEvent<'_>) -> EventResponse {
self.egui_winit.on_event(&self.egui_ctx, event)
self.egui_winit
.on_event(&self.egui_ctx, event, ViewportId::ROOT)
}
/// Call [`Self::paint`] later to paint.
@ -71,7 +75,17 @@ impl EguiGlow {
log::warn!("Multiple viewports not yet supported by EguiGlow");
}
for (_, ViewportOutput { commands, .. }) in viewport_output {
egui_winit::process_viewport_commands(commands, window, true);
let mut screenshot_requested = false;
egui_winit::process_viewport_commands(
&mut self.viewport_info,
commands,
window,
true,
&mut screenshot_requested,
);
if screenshot_requested {
log::warn!("Screenshot not yet supported by EguiGlow");
}
}
self.egui_winit.handle_platform_output(

View File

@ -280,6 +280,42 @@ impl Sub<Vec2> for Pos2 {
}
}
impl Mul<f32> for Pos2 {
type Output = Pos2;
#[inline(always)]
fn mul(self, factor: f32) -> Pos2 {
Pos2 {
x: self.x * factor,
y: self.y * factor,
}
}
}
impl Mul<Pos2> for f32 {
type Output = Pos2;
#[inline(always)]
fn mul(self, vec: Pos2) -> Pos2 {
Pos2 {
x: self * vec.x,
y: self * vec.y,
}
}
}
impl Div<f32> for Pos2 {
type Output = Pos2;
#[inline(always)]
fn div(self, factor: f32) -> Pos2 {
Pos2 {
x: self.x / factor,
y: self.y / factor,
}
}
}
impl std::fmt::Debug for Pos2 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{:.1} {:.1}]", self.x, self.y)

View File

@ -622,3 +622,39 @@ impl From<[Pos2; 2]> for Rect {
Self { min, max }
}
}
impl Mul<f32> for Rect {
type Output = Rect;
#[inline]
fn mul(self, factor: f32) -> Rect {
Rect {
min: self.min * factor,
max: self.max * factor,
}
}
}
impl Mul<Rect> for f32 {
type Output = Rect;
#[inline]
fn mul(self, vec: Rect) -> Rect {
Rect {
min: self * vec.min,
max: self * vec.max,
}
}
}
impl Div<f32> for Rect {
type Output = Rect;
#[inline]
fn div(self, factor: f32) -> Rect {
Rect {
min: self.min / factor,
max: self.max / factor,
}
}
}

View File

@ -27,7 +27,7 @@ impl eframe::App for MyApp {
self.allowed_to_close
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Try to close the window");
});
@ -45,7 +45,7 @@ impl eframe::App for MyApp {
if ui.button("Yes!").clicked() {
self.allowed_to_close = true;
frame.close();
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
});
});

View File

@ -2,7 +2,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use eframe::egui::{self, ViewportCommand};
fn main() -> Result<(), eframe::Error> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
@ -30,8 +30,8 @@ impl eframe::App for MyApp {
egui::Rgba::TRANSPARENT.to_array() // Make sure we don't paint anything behind the rounded corners
}
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
custom_window_frame(ctx, frame, "egui with custom frame", |ui| {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
custom_window_frame(ctx, "egui with custom frame", |ui| {
ui.label("This is just the contents of the window.");
ui.horizontal(|ui| {
ui.label("egui theme:");
@ -41,12 +41,7 @@ impl eframe::App for MyApp {
}
}
fn custom_window_frame(
ctx: &egui::Context,
frame: &mut eframe::Frame,
title: &str,
add_contents: impl FnOnce(&mut egui::Ui),
) {
fn custom_window_frame(ctx: &egui::Context, title: &str, add_contents: impl FnOnce(&mut egui::Ui)) {
use egui::*;
let panel_frame = egui::Frame {
@ -66,7 +61,7 @@ fn custom_window_frame(
rect.max.y = rect.min.y + title_bar_height;
rect
};
title_bar_ui(ui, frame, title_bar_rect, title);
title_bar_ui(ui, title_bar_rect, title);
// Add the contents:
let content_rect = {
@ -80,12 +75,7 @@ fn custom_window_frame(
});
}
fn title_bar_ui(
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
title_bar_rect: eframe::epaint::Rect,
title: &str,
) {
fn title_bar_ui(ui: &mut egui::Ui, title_bar_rect: eframe::epaint::Rect, title: &str) {
use egui::*;
let painter = ui.painter();
@ -112,9 +102,11 @@ fn title_bar_ui(
// Interact with the title bar (drag to move window):
if title_bar_response.double_clicked() {
frame.set_maximized(!frame.info().window_info.maximized);
let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
ui.ctx()
.send_viewport_cmd(ViewportCommand::Maximized(!is_maximized));
} else if title_bar_response.is_pointer_button_down_on() {
frame.drag_window();
ui.ctx().send_viewport_cmd(ViewportCommand::StartDrag);
}
ui.allocate_ui_at_rect(title_bar_rect, |ui| {
@ -122,13 +114,13 @@ fn title_bar_ui(
ui.spacing_mut().item_spacing.x = 0.0;
ui.visuals_mut().button_frame = false;
ui.add_space(8.0);
close_maximize_minimize(ui, frame);
close_maximize_minimize(ui);
});
});
}
/// Show some close/maximize/minimize buttons for the native window.
fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) {
fn close_maximize_minimize(ui: &mut egui::Ui) {
use egui::{Button, RichText};
let button_height = 12.0;
@ -137,22 +129,24 @@ fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) {
.add(Button::new(RichText::new("").size(button_height)))
.on_hover_text("Close the window");
if close_response.clicked() {
frame.close();
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
if frame.info().window_info.maximized {
let is_maximized = ui.input(|i| i.viewport().maximized.unwrap_or(false));
if is_maximized {
let maximized_response = ui
.add(Button::new(RichText::new("🗗").size(button_height)))
.on_hover_text("Restore window");
if maximized_response.clicked() {
frame.set_maximized(false);
ui.ctx()
.send_viewport_cmd(ViewportCommand::Maximized(false));
}
} else {
let maximized_response = ui
.add(Button::new(RichText::new("🗗").size(button_height)))
.on_hover_text("Maximize window");
if maximized_response.clicked() {
frame.set_maximized(true);
ui.ctx().send_viewport_cmd(ViewportCommand::Maximized(true));
}
}
@ -160,6 +154,6 @@ fn close_maximize_minimize(ui: &mut egui::Ui, frame: &mut eframe::Frame) {
.add(Button::new(RichText::new("🗕").size(button_height)))
.on_hover_text("Minimize the window");
if minimized_response.clicked() {
frame.set_minimized(true);
ui.ctx().send_viewport_cmd(ViewportCommand::Minimized(true));
}
}

View File

@ -65,7 +65,7 @@ impl eframe::App for MyApp {
ui.label("Hello from immediate viewport");
});
if ctx.input(|i| i.raw.viewport.close_requested) {
if ctx.input(|i| i.viewport().close_requested) {
// Tell parent viewport that we should not show next frame:
self.show_immediate_viewport = false;
ctx.request_repaint(); // make sure there is a next frame
@ -90,7 +90,7 @@ impl eframe::App for MyApp {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Hello from deferred viewport");
});
if ctx.input(|i| i.raw.viewport.close_requested) {
if ctx.input(|i| i.viewport().close_requested) {
// Tell parent to close us.
show_deferred_viewport.store(false, Ordering::Relaxed);
ctx.request_repaint(); // make sure there is a next frame

View File

@ -1,7 +1,6 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use eframe::egui;
use eframe::egui::ColorImage;
use egui_plot::{Legend, Line, Plot, PlotPoints};
fn main() -> Result<(), eframe::Error> {
@ -19,16 +18,14 @@ fn main() -> Result<(), eframe::Error> {
}
#[derive(Default)]
struct MyApp {
screenshot: Option<ColorImage>,
}
struct MyApp {}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let mut plot_rect = None;
egui::CentralPanel::default().show(ctx, |ui| {
if ui.button("Save Plot").clicked() {
frame.request_screenshot();
ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot);
}
let my_plot = Plot::new("My Plot").legend(Legend::default());
@ -42,15 +39,25 @@ impl eframe::App for MyApp {
plot_rect = Some(inner.response.rect);
});
if let (Some(screenshot), Some(plot_location)) = (self.screenshot.take(), plot_rect) {
// Check for returned screenshot:
let screenshot = ctx.input(|i| {
for event in &i.raw.events {
if let egui::Event::Screenshot { image, .. } = event {
return Some(image.clone());
}
}
None
});
if let (Some(screenshot), Some(plot_location)) = (screenshot, plot_rect) {
if let Some(mut path) = rfd::FileDialog::new().save_file() {
path.set_extension("png");
// for a full size application, we should put this in a different thread,
// so that the GUI doesn't lag during saving
let pixels_per_point = frame.info().native_pixels_per_point;
let plot = screenshot.region(&plot_location, pixels_per_point);
let pixels_per_point = ctx.pixels_per_point();
let plot = screenshot.region(&plot_location, Some(pixels_per_point));
// save the plot to png
image::save_buffer(
&path,
@ -60,14 +67,8 @@ impl eframe::App for MyApp {
image::ColorType::Rgba8,
)
.unwrap();
eprintln!("Image saved to {path:?}.");
}
}
}
fn post_rendering(&mut self, _screen_size_px: [u32; 2], frame: &eframe::Frame) {
// this is inspired by the Egui screenshot example
if let Some(screenshot) = frame.screenshot() {
self.screenshot = Some(screenshot);
}
}
}

View File

@ -1,5 +1,7 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
use std::sync::Arc;
use eframe::egui::{self, ColorImage};
fn main() -> Result<(), eframe::Error> {
@ -19,12 +21,12 @@ fn main() -> Result<(), eframe::Error> {
struct MyApp {
continuously_take_screenshots: bool,
texture: Option<egui::TextureHandle>,
screenshot: Option<ColorImage>,
screenshot: Option<Arc<ColorImage>>,
save_to_file: bool,
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if let Some(screenshot) = self.screenshot.take() {
self.texture = Some(ui.ctx().load_texture(
@ -42,7 +44,7 @@ impl eframe::App for MyApp {
if ui.button("save to 'top_left.png'").clicked() {
self.save_to_file = true;
frame.request_screenshot();
ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot);
}
ui.with_layout(egui::Layout::top_down(egui::Align::RIGHT), |ui| {
@ -55,9 +57,9 @@ impl eframe::App for MyApp {
} else {
ctx.set_visuals(egui::Visuals::light());
};
frame.request_screenshot();
ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot);
} else if ui.button("take screenshot!").clicked() {
frame.request_screenshot();
ctx.send_viewport_cmd(egui::ViewportCommand::Screenshot);
}
});
});
@ -68,28 +70,33 @@ impl eframe::App for MyApp {
ui.spinner();
}
// Check for returned screenshot:
ui.input(|i| {
for event in &i.raw.events {
if let egui::Event::Screenshot { image, .. } = event {
if self.save_to_file {
let pixels_per_point = i.pixels_per_point();
let region = egui::Rect::from_two_pos(
egui::Pos2::ZERO,
egui::Pos2 { x: 100., y: 100. },
);
let top_left_corner = image.region(&region, Some(pixels_per_point));
image::save_buffer(
"top_left.png",
top_left_corner.as_raw(),
top_left_corner.width() as u32,
top_left_corner.height() as u32,
image::ColorType::Rgba8,
)
.unwrap();
self.save_to_file = false;
}
self.screenshot = Some(image.clone());
}
}
});
ctx.request_repaint();
});
}
fn post_rendering(&mut self, _window_size: [u32; 2], frame: &eframe::Frame) {
if let Some(screenshot) = frame.screenshot() {
if self.save_to_file {
let pixels_per_point = frame.info().native_pixels_per_point;
let region =
egui::Rect::from_two_pos(egui::Pos2::ZERO, egui::Pos2 { x: 100., y: 100. });
let top_left_corner = screenshot.region(&region, pixels_per_point);
image::save_buffer(
"top_left.png",
top_left_corner.as_raw(),
top_left_corner.width() as u32,
top_left_corner.height() as u32,
image::ColorType::Rgba8,
)
.unwrap();
self.save_to_file = false;
}
self.screenshot = Some(screenshot);
}
}
}

View File

@ -46,7 +46,7 @@ struct MyApp {
}
impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
let label_text = if self.has_next {
"When this window is closed the next will be opened after a short delay"
@ -56,7 +56,7 @@ impl eframe::App for MyApp {
ui.label(label_text);
if ui.button("Close").clicked() {
eprintln!("Pressed Close button");
frame.close();
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
});
}

View File

@ -221,14 +221,14 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc<RwLock<ViewportState>>]) {
ui.add_space(8.0);
if let Some(inner_rect) = ctx.input(|i| i.raw.viewport.inner_rect_px) {
if let Some(inner_rect) = ctx.input(|i| i.viewport().inner_rect) {
ui.label(format!(
"Inner Rect: Pos: {:?}, Size: {:?}",
inner_rect.min,
inner_rect.size()
));
}
if let Some(outer_rect) = ctx.input(|i| i.raw.viewport.outer_rect_px) {
if let Some(outer_rect) = ctx.input(|i| i.viewport().outer_rect) {
ui.label(format!(
"Outer Rect: Pos: {:?}, Size: {:?}",
outer_rect.min,
@ -258,12 +258,12 @@ fn generic_ui(ui: &mut egui::Ui, children: &[Arc<RwLock<ViewportState>>]) {
data.insert_temp(container_id.with("pixels_per_point"), tmp_pixels_per_point);
});
}
egui::gui_zoom::zoom_with_keyboard_shortcuts(&ctx, None);
egui::gui_zoom::zoom_with_keyboard_shortcuts(&ctx);
if ctx.viewport_id() != ctx.parent_viewport_id() {
let parent = ctx.parent_viewport_id();
if ui.button("Set parent pos 0,0").clicked() {
ctx.send_viewport_command_to(
ctx.send_viewport_cmd_to(
parent,
egui::ViewportCommand::OuterPosition(egui::pos2(0.0, 0.0)),
);

View File

@ -1,14 +1,12 @@
use eframe::{
egui::{Button, CentralPanel, Context, UserAttentionType},
CreationContext, NativeOptions,
};
use eframe::{egui, CreationContext, NativeOptions};
use egui::{Button, CentralPanel, Context, UserAttentionType};
use std::time::{Duration, SystemTime};
fn main() -> eframe::Result<()> {
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
let native_options = NativeOptions {
initial_window_size: Some(eframe::egui::vec2(400., 200.)),
initial_window_size: Some(egui::vec2(400., 200.)),
..Default::default()
};
eframe::run_native(
@ -54,11 +52,11 @@ impl Application {
}
impl eframe::App for Application {
fn update(&mut self, ctx: &Context, frame: &mut eframe::Frame) {
fn update(&mut self, ctx: &Context, _frame: &mut eframe::Frame) {
if let Some(request_at) = self.request_at {
if request_at < SystemTime::now() {
self.request_at = None;
frame.request_user_attention(self.attention);
ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention(self.attention));
if self.auto_reset {
self.auto_reset = false;
self.reset_at = Some(SystemTime::now() + Self::attention_reset_timeout());
@ -69,7 +67,9 @@ impl eframe::App for Application {
if let Some(reset_at) = self.reset_at {
if reset_at < SystemTime::now() {
self.reset_at = None;
frame.request_user_attention(UserAttentionType::Reset);
ctx.send_viewport_cmd(egui::ViewportCommand::RequestUserAttention(
UserAttentionType::Reset,
));
}
}
@ -77,7 +77,7 @@ impl eframe::App for Application {
ui.vertical(|ui| {
ui.horizontal(|ui| {
ui.label("Attention type:");
eframe::egui::ComboBox::new("attention", "")
egui::ComboBox::new("attention", "")
.selected_text(repr(self.attention))
.show_ui(ui, |ui| {
for kind in [

View File

@ -16,17 +16,22 @@ cargo install cargo-cranky # Uses lints defined in Cranky.toml. See https://gith
export RUSTFLAGS="--cfg=web_sys_unstable_apis -D warnings"
export RUSTDOCFLAGS="-D warnings" # https://github.com/emilk/egui/pull/1454
# Fast checks first:
typos
./scripts/lint.py
cargo fmt --all -- --check
cargo doc --lib --no-deps --all-features
cargo doc --document-private-items --no-deps --all-features
cargo cranky --all-targets --all-features -- -D warnings
./scripts/clippy_wasm.sh
cargo check --all-targets
cargo check --all-targets --all-features
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown
cargo check -p egui_demo_app --lib --target wasm32-unknown-unknown --all-features
cargo cranky --all-targets --all-features -- -D warnings
cargo test --all-targets --all-features
cargo test --doc # slow - checks all doc-tests
cargo fmt --all -- --check
cargo doc --lib --no-deps --all-features
cargo doc --document-private-items --no-deps --all-features
(cd crates/eframe && cargo check --no-default-features --features "glow")
(cd crates/eframe && cargo check --no-default-features --features "wgpu")
@ -53,8 +58,6 @@ cargo doc --document-private-items --no-deps --all-features
./scripts/wasm_bindgen_check.sh
cargo cranky --target wasm32-unknown-unknown --all-features -p egui_demo_app --lib -- -D warnings
./scripts/cargo_deny.sh
# TODO(emilk): consider using https://github.com/taiki-e/cargo-hack or https://github.com/frewsxcv/cargo-all-features