1
0
mirror of https://github.com/emilk/egui.git synced 2026-08-31 05:40: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

@@ -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},
};