1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 22:30:03 -04:00

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
29 changed files with 905 additions and 915 deletions

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.