Update to winit 0.29 (#3649)

* Closes https://github.com/emilk/egui/issues/3542
* Closes https://github.com/emilk/egui/issues/2977
* Closes https://github.com/emilk/egui/issues/3303

---------

Co-authored-by: Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
This commit is contained in:
Fredrik Fornwall 2023-12-18 14:53:14 +01:00 committed by GitHub
parent 8503a85113
commit 8e5959d55d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 1306 additions and 827 deletions

View File

@ -3,8 +3,7 @@
# run: typos
[default.extend-words]
nknown = "nknown" # part of @55nknown username
Prefence = "Prefence" # typo in glutin_winit API
nknown = "nknown" # part of @55nknown username
[files]
extend-exclude = ["web_demo/egui_demo_app.js"] # auto-generated

643
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -131,7 +131,7 @@ image = { version = "0.24", default-features = false, features = [
"png",
] } # Needed for app icon
raw-window-handle.workspace = true
winit = { version = "0.28.1", default-features = false }
winit = { version = "0.29.4", default-features = false, features = ["rwh_05"] }
# optional native:
directories-next = { version = "2", optional = true }
@ -142,14 +142,14 @@ pollster = { version = "0.3", optional = true } # needed for wgpu
# we can expose these to user so that they can select which backends they want to enable to avoid compiling useless deps.
# this can be done at the same time we expose x11/wayland features of winit crate.
glutin = { version = "0.30", optional = true }
glutin-winit = { version = "0.3.0", optional = true }
glutin = { version = "0.31", optional = true }
glutin-winit = { version = "0.4", optional = true }
puffin = { workspace = true, optional = true }
wgpu = { workspace = true, optional = true }
# mac:
[target.'cfg(any(target_os = "macos"))'.dependencies]
cocoa = "0.24.1" # Stuck on old version until we update to winit 0.29
cocoa = "0.25.0"
objc = "0.2.7"
# windows:

View File

@ -298,7 +298,7 @@ pub struct NativeOptions {
///
/// This feature was introduced in <https://github.com/emilk/egui/pull/1889>.
///
/// When `true`, [`winit::platform::run_return::EventLoopExtRunReturn::run_return`] is used.
/// When `true`, [`winit::platform::run_on_demand::EventLoopExtRunOnDemand`] is used.
/// When `false`, [`winit::event_loop::EventLoop::run`] is used.
pub run_and_return: bool,

View File

@ -325,6 +325,11 @@ pub enum Error {
#[error("winit error: {0}")]
Winit(#[from] winit::error::OsError),
/// An error from [`winit::event_loop::EventLoop`].
#[cfg(not(target_arch = "wasm32"))]
#[error("winit EventLoopError: {0}")]
WinitEventLoop(#[from] winit::error::EventLoopError),
/// An error from [`glutin`] when using [`glow`].
#[cfg(all(feature = "glow", not(target_arch = "wasm32")))]
#[error("glutin error: {0}")]

View File

@ -231,7 +231,7 @@ impl EpiIntegration {
&mut self,
window: &winit::window::Window,
egui_winit: &mut egui_winit::State,
event: &winit::event::WindowEvent<'_>,
event: &winit::event::WindowEvent,
) -> EventResponse {
crate::profile_function!(egui_winit::short_window_event_description(event));

View File

@ -1,8 +1,17 @@
//! Note that this file contains code very similar to [`wgpu_integration`].
//! When making changes to one you often also want to apply it to the other.
//!
//! This is also very complex code, and not very pretty.
//! There is a bunch of improvements we could do,
//! like removing a bunch of `unwraps`.
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant};
use glutin::{
config::GlConfig,
context::NotCurrentGlContext,
display::GetGlDisplay,
prelude::{GlDisplay, NotCurrentGlContextSurfaceAccessor, PossiblyCurrentGlContext},
prelude::{GlDisplay, PossiblyCurrentGlContext},
surface::GlSurface,
};
use raw_window_handle::{HasRawDisplayHandle as _, HasRawWindowHandle as _};
@ -112,6 +121,8 @@ struct Viewport {
/// None for immediate viewports.
viewport_ui_cb: Option<Arc<DeferredViewportUiCallback>>,
// These three live and die together.
// TODO(emilk): clump them together into one struct!
gl_surface: Option<glutin::surface::Surface<glutin::surface::WindowSurface>>,
window: Option<Rc<Window>>,
egui_winit: Option<egui_winit::State>,
@ -160,12 +171,12 @@ impl GlowWinitApp {
};
// Creates the window - must come before we create our glow context
glutin_window_context.on_resume(event_loop)?;
glutin_window_context.initialize_window(ViewportId::ROOT, event_loop)?;
if let Some(viewport) = glutin_window_context.viewports.get(&ViewportId::ROOT) {
if let Some(window) = &viewport.window {
epi_integration::apply_window_settings(window, window_settings);
}
{
let viewport = &glutin_window_context.viewports[&ViewportId::ROOT];
let window = viewport.window.as_ref().unwrap(); // Can't fail - we just called `initialize_all_viewports`
epi_integration::apply_window_settings(window, window_settings);
}
let gl = unsafe {
@ -390,9 +401,13 @@ impl WinitApp for GlowWinitApp {
}
}
fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
window_id: WindowId,
) -> EventResult {
if let Some(running) = &mut self.running {
running.run_ui_and_paint(window_id)
running.run_ui_and_paint(event_loop, window_id)
} else {
EventResult::Wait
}
@ -401,29 +416,27 @@ impl WinitApp for GlowWinitApp {
fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &winit::event::Event<'_, UserEvent>,
event: &winit::event::Event<UserEvent>,
) -> Result<EventResult> {
crate::profile_function!(winit_integration::short_event_description(event));
Ok(match event {
winit::event::Event::Resumed => {
log::debug!("Event::Resumed");
let running = if let Some(running) = &mut self.running {
// not the first resume event. create whatever you need.
running.glutin.borrow_mut().on_resume(event_loop)?;
// Not the first resume event. Create all outstanding windows.
running
.glutin
.borrow_mut()
.initialize_all_windows(event_loop);
running
} else {
// first resume event.
// we can actually move this outside of event loop.
// and just run the on_resume fn of gl_window
// First resume event. Created our root window etc.
self.init_run_state(event_loop)?
};
let window_id = running
.glutin
.borrow()
.window_from_viewport
.get(&ViewportId::ROOT)
.copied();
EventResult::RepaintNow(window_id.unwrap())
let window_id = running.glutin.borrow().window_from_viewport[&ViewportId::ROOT];
EventResult::RepaintNow(window_id)
}
winit::event::Event::Suspended => {
@ -433,15 +446,6 @@ impl WinitApp for GlowWinitApp {
EventResult::Wait
}
winit::event::Event::MainEventsCleared => {
if let Some(running) = &self.running {
if let Err(err) = running.glutin.borrow_mut().on_resume(event_loop) {
log::warn!("on_resume failed {err}");
}
}
EventResult::Wait
}
winit::event::Event::WindowEvent { event, window_id } => {
if let Some(running) = &mut self.running {
running.on_window_event(*window_id, event)
@ -477,7 +481,11 @@ impl WinitApp for GlowWinitApp {
}
impl GlowWinitRunning {
fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
window_id: WindowId,
) -> EventResult {
crate::profile_function!();
let Some(viewport_id) = self
@ -666,7 +674,7 @@ impl GlowWinitRunning {
std::thread::sleep(std::time::Duration::from_millis(10));
}
glutin.handle_viewport_output(&integration.egui_ctx, viewport_output);
glutin.handle_viewport_output(event_loop, &integration.egui_ctx, viewport_output);
if integration.should_close() {
EventResult::Exit
@ -678,7 +686,7 @@ impl GlowWinitRunning {
fn on_window_event(
&mut self,
window_id: WindowId,
event: &winit::event::WindowEvent<'_>,
event: &winit::event::WindowEvent,
) -> EventResult {
crate::profile_function!(egui_winit::short_window_event_description(event));
@ -717,13 +725,6 @@ impl GlowWinitRunning {
}
}
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
if let Some(viewport_id) = viewport_id {
repaint_asap = true;
glutin.resize(viewport_id, **new_inner_size);
}
}
winit::event::WindowEvent::CloseRequested => {
if viewport_id == Some(ViewportId::ROOT) && self.integration.should_close() {
log::debug!(
@ -768,7 +769,11 @@ impl GlowWinitRunning {
{
event_response = self.integration.on_window_event(window, egui_winit, event);
}
} else {
log::trace!("Ignoring event: no viewport for {viewport_id:?}");
}
} else {
log::trace!("Ignoring event: no viewport_id");
}
if event_response.repaint {
@ -855,7 +860,7 @@ impl GlutinWindowContext {
// Create GL display. This may probably create a window too on most platforms. Definitely on `MS windows`. Never on Android.
let display_builder = glutin_winit::DisplayBuilder::new()
// we might want to expose this option to users in the future. maybe using an env var or using native_options.
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_builder(Some(egui_winit::create_winit_window_builder(
egui_ctx,
event_loop,
@ -968,37 +973,29 @@ impl GlutinWindowContext {
focused_viewport: Some(ViewportId::ROOT),
};
slf.on_resume(event_loop)?;
slf.initialize_window(ViewportId::ROOT, event_loop)?;
Ok(slf)
}
/// This will be run after `new`. on android, it might be called multiple times over the course of the app's lifetime.
/// roughly,
/// 1. check if window already exists. otherwise, create one now.
/// 2. create attributes for surface creation.
/// 3. create surface.
/// 4. make surface and context current.
/// Create a surface, window, and winit integration for all viewports lacking any of that.
///
/// we presently assume that we will
fn on_resume(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) -> Result<()> {
/// Errors will be logged.
fn initialize_all_windows(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) {
crate::profile_function!();
let viewports: Vec<ViewportId> = self
.viewports
.iter()
.filter(|(_, viewport)| viewport.gl_surface.is_none())
.map(|(id, _)| *id)
.collect();
let viewports: Vec<ViewportId> = self.viewports.keys().copied().collect();
for viewport_id in viewports {
self.init_viewport(viewport_id, event_loop)?;
if let Err(err) = self.initialize_window(viewport_id, event_loop) {
log::error!("Failed to initialize a window for viewport {viewport_id:?}: {err}");
}
}
Ok(())
}
/// Create a surface, window, and winit integration for the viewport, if missing.
#[allow(unsafe_code)]
pub(crate) fn init_viewport(
pub(crate) fn initialize_window(
&mut self,
viewport_id: ViewportId,
event_loop: &EventLoopWindowTarget<UserEvent>,
@ -1013,12 +1010,16 @@ impl GlutinWindowContext {
let window = if let Some(window) = &mut viewport.window {
window
} else {
log::trace!("Window doesn't exist yet. Creating one now with finalize_window");
log::debug!("Creating a window for viewport {viewport_id:?}");
let window_builder = egui_winit::create_winit_window_builder(
&self.egui_ctx,
event_loop,
viewport.builder.clone(),
);
if window_builder.transparent() && self.gl_config.supports_transparency() == Some(false)
{
log::error!("Cannot create transparent window: the GL config does not support it");
}
let window =
glutin_winit::finalize_window(event_loop, window_builder, &self.gl_config)?;
egui_winit::apply_viewport_builder_to_window(
@ -1031,7 +1032,20 @@ impl GlutinWindowContext {
viewport.window.insert(Rc::new(window))
};
{
viewport.egui_winit.get_or_insert_with(|| {
log::debug!("Initializing egui_winit for viewport {viewport_id:?}");
egui_winit::State::new(
self.egui_ctx.clone(),
viewport_id,
event_loop,
Some(window.scale_factor() as f32),
self.max_texture_side,
)
});
if viewport.gl_surface.is_none() {
log::debug!("Creating a gl_surface for viewport {viewport_id:?}");
// surface attributes
let (width_px, height_px): (u32, u32) = window.inner_size().into();
let width_px = std::num::NonZeroU32::new(width_px.at_least(1)).unwrap();
@ -1071,24 +1085,14 @@ impl GlutinWindowContext {
// we will reach this point only once in most platforms except android.
// create window/surface/make context current once and just use them forever.
viewport.egui_winit.get_or_insert_with(|| {
egui_winit::State::new(
self.egui_ctx.clone(),
viewport_id,
event_loop,
Some(window.scale_factor() as f32),
self.max_texture_side,
)
});
viewport.gl_surface = Some(gl_surface);
self.current_gl_context = Some(current_gl_context);
self.viewport_from_window
.insert(window.id(), viewport.ids.this);
self.window_from_viewport
.insert(viewport.ids.this, window.id());
}
self.viewport_from_window.insert(window.id(), viewport_id);
self.window_from_viewport.insert(viewport_id, window.id());
Ok(())
}
@ -1153,6 +1157,7 @@ impl GlutinWindowContext {
fn handle_viewport_output(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
egui_ctx: &egui::Context,
viewport_output: ViewportIdMap<ViewportOutput>,
) {
@ -1197,6 +1202,9 @@ impl GlutinWindowContext {
}
}
// Create windows for any new viewports:
self.initialize_all_windows(event_loop);
// GC old viewports
self.viewports
.retain(|id, _| active_viewports_ids.contains(id));
@ -1295,10 +1303,12 @@ fn render_immediate_viewport(
viewport_ui_cb,
} = immediate_viewport;
let viewport_id = ids.this;
{
let mut glutin = glutin.borrow_mut();
let viewport = initialize_or_update_viewport(
initialize_or_update_viewport(
egui_ctx,
&mut glutin.viewports,
ids,
@ -1308,17 +1318,18 @@ fn render_immediate_viewport(
None,
);
if viewport.gl_surface.is_none() {
glutin
.init_viewport(ids.this, event_loop)
.expect("Failed to initialize window in egui::Context::show_viewport_immediate");
if let Err(err) = glutin.initialize_window(viewport_id, event_loop) {
log::error!(
"Failed to initialize a window for immediate viewport {viewport_id:?}: {err}"
);
return;
}
}
let input = {
let mut glutin = glutin.borrow_mut();
let Some(viewport) = glutin.viewports.get_mut(&ids.this) else {
let Some(viewport) = glutin.viewports.get_mut(&viewport_id) else {
return;
};
let (Some(egui_winit), Some(window)) = (&mut viewport.egui_winit, &viewport.window) else {
@ -1362,7 +1373,7 @@ fn render_immediate_viewport(
..
} = &mut *glutin;
let Some(viewport) = viewports.get_mut(&ids.this) else {
let Some(viewport) = viewports.get_mut(&viewport_id) else {
return;
};
@ -1423,7 +1434,7 @@ fn render_immediate_viewport(
egui_winit.handle_platform_output(window, platform_output);
glutin.handle_viewport_output(egui_ctx, viewport_output);
glutin.handle_viewport_output(event_loop, egui_ctx, viewport_output);
}
#[cfg(feature = "__screenshot")]

View File

@ -1,10 +1,3 @@
//! Note that this file contains two similar paths - one for [`glow`], one for [`wgpu`].
//! When making changes to one you often also want to apply it to the other.
//!
//! This is also very complex code, and not very pretty.
//! There is a bunch of improvements we could do,
//! like removing a bunch of `unwraps`.
use std::{cell::RefCell, time::Instant};
use winit::event_loop::{EventLoop, EventLoopBuilder};
@ -34,12 +27,12 @@ fn create_event_loop_builder(
event_loop_builder
}
fn create_event_loop(native_options: &mut epi::NativeOptions) -> EventLoop<UserEvent> {
fn create_event_loop(native_options: &mut epi::NativeOptions) -> Result<EventLoop<UserEvent>> {
crate::profile_function!();
let mut builder = create_event_loop_builder(native_options);
crate::profile_scope!("EventLoopBuilder::build");
builder.build()
Ok(builder.build()?)
}
/// Access a thread-local event loop.
@ -49,16 +42,20 @@ fn create_event_loop(native_options: &mut epi::NativeOptions) -> EventLoop<UserE
fn with_event_loop<R>(
mut native_options: epi::NativeOptions,
f: impl FnOnce(&mut EventLoop<UserEvent>, epi::NativeOptions) -> R,
) -> R {
) -> Result<R> {
thread_local!(static EVENT_LOOP: RefCell<Option<EventLoop<UserEvent>>> = RefCell::new(None));
EVENT_LOOP.with(|event_loop| {
// Since we want to reference NativeOptions when creating the EventLoop we can't
// do that as part of the lazy thread local storage initialization and so we instead
// create the event loop lazily here
let mut event_loop = event_loop.borrow_mut();
let event_loop = event_loop.get_or_insert_with(|| create_event_loop(&mut native_options));
f(event_loop, native_options)
let mut event_loop_lock = event_loop.borrow_mut();
let event_loop = if let Some(event_loop) = &mut *event_loop_lock {
event_loop
} else {
event_loop_lock.insert(create_event_loop(&mut native_options)?)
};
Ok(f(event_loop, native_options))
})
}
@ -67,31 +64,39 @@ fn run_and_return(
event_loop: &mut EventLoop<UserEvent>,
mut winit_app: impl WinitApp,
) -> Result<()> {
use winit::{event_loop::ControlFlow, platform::run_return::EventLoopExtRunReturn as _};
use winit::{event_loop::ControlFlow, platform::run_on_demand::EventLoopExtRunOnDemand};
log::debug!("Entering the winit event loop (run_return)…");
log::debug!("Entering the winit event loop (run_on_demand)…");
// When to repaint what window
let mut windows_next_repaint_times = HashMap::default();
let mut returned_result = Ok(());
event_loop.run_return(|event, event_loop, control_flow| {
event_loop.run_on_demand(|event, event_loop_window_target| {
crate::profile_scope!("winit_event", short_event_description(&event));
log::trace!("winit event: {event:?}");
if matches!(event, winit::event::Event::AboutToWait) {
return; // early-out: don't trigger another wait
}
let event_result = match &event {
winit::event::Event::LoopDestroyed => {
// On Mac, Cmd-Q we get here and then `run_return` doesn't return (despite its name),
winit::event::Event::LoopExiting => {
// On Mac, Cmd-Q we get here and then `run_on_demand` doesn't return (despite its name),
// so we need to save state now:
log::debug!("Received Event::LoopDestroyed - saving app state…");
log::debug!("Received Event::LoopExiting - saving app state…");
winit_app.save_and_destroy();
*control_flow = ControlFlow::Exit;
return;
}
winit::event::Event::RedrawRequested(window_id) => {
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
window_id,
} => {
windows_next_repaint_times.remove(window_id);
winit_app.run_ui_and_paint(*window_id)
winit_app.run_ui_and_paint(event_loop_window_target, *window_id)
}
winit::event::Event::UserEvent(UserEvent::RequestRepaint {
@ -120,8 +125,11 @@ fn run_and_return(
EventResult::Wait
}
event => match winit_app.on_event(event_loop, event) {
Ok(event_result) => event_result,
event => match winit_app.on_event(event_loop_window_target, event) {
Ok(event_result) => {
log::trace!("event_result: {event_result:?}");
event_result
}
Err(err) => {
log::error!("Exiting because of error: {err} during event {event:?}");
returned_result = Err(err);
@ -132,21 +140,28 @@ fn run_and_return(
match event_result {
EventResult::Wait => {
control_flow.set_wait();
event_loop_window_target.set_control_flow(ControlFlow::Wait);
}
EventResult::RepaintNow(window_id) => {
log::trace!("Repaint caused by {}", short_event_description(&event));
log::trace!(
"RepaintNow of {window_id:?} caused by {}",
short_event_description(&event)
);
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
windows_next_repaint_times.remove(&window_id);
winit_app.run_ui_and_paint(window_id);
winit_app.run_ui_and_paint(event_loop_window_target, window_id);
} else {
// Fix for https://github.com/emilk/egui/issues/2425
windows_next_repaint_times.insert(window_id, Instant::now());
}
}
EventResult::RepaintNext(window_id) => {
log::trace!(
"RepaintNext of {window_id:?} caused by {}",
short_event_description(&event)
);
windows_next_repaint_times.insert(window_id, Instant::now());
}
EventResult::RepaintAt(window_id, repaint_time) => {
@ -160,45 +175,35 @@ fn run_and_return(
EventResult::Exit => {
log::debug!("Asking to exit event loop…");
winit_app.save_and_destroy();
*control_flow = ControlFlow::Exit;
event_loop_window_target.exit();
return;
}
}
let mut next_repaint_time = windows_next_repaint_times.values().min().copied();
// This is for not duplicating redraw requests
use winit::event::Event;
if matches!(
event,
Event::RedrawEventsCleared | Event::RedrawRequested(_) | Event::Resumed
) {
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true;
};
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
};
next_repaint_time = None;
control_flow.set_poll();
next_repaint_time = None;
event_loop_window_target.set_control_flow(ControlFlow::Poll);
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
window.request_redraw();
true
} else {
false
}
});
}
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
window.request_redraw();
true
} else {
log::trace!("No window found for {window_id:?}");
false
}
});
if let Some(next_repaint_time) = next_repaint_time {
let time_until_next = next_repaint_time.saturating_duration_since(Instant::now());
if time_until_next < std::time::Duration::from_secs(10_000) {
log::trace!("WaitUntil {time_until_next:?}");
}
control_flow.set_wait_until(next_repaint_time);
event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
});
})?;
log::debug!("eframe window closed");
@ -211,32 +216,47 @@ fn run_and_return(
// we only apply this approach on Windows to minimize the affect.
#[cfg(target_os = "windows")]
{
event_loop.run_return(|_, _, control_flow| {
control_flow.set_exit();
});
event_loop
.run_on_demand(|_, event_loop_window_target| {
event_loop_window_target.exit();
})
.ok();
}
returned_result
}
fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp + 'static) -> ! {
fn run_and_exit(
event_loop: EventLoop<UserEvent>,
mut winit_app: impl WinitApp + 'static,
) -> Result<()> {
use winit::event_loop::ControlFlow;
log::debug!("Entering the winit event loop (run)…");
// When to repaint what window
let mut windows_next_repaint_times = HashMap::default();
event_loop.run(move |event, event_loop, control_flow| {
event_loop.run(move |event, event_loop_window_target| {
crate::profile_scope!("winit_event", short_event_description(&event));
log::trace!("winit event: {event:?}");
if matches!(event, winit::event::Event::AboutToWait) {
return; // early-out: don't trigger another wait
}
let event_result = match &event {
winit::event::Event::LoopDestroyed => {
log::debug!("Received Event::LoopDestroyed");
winit::event::Event::LoopExiting => {
log::debug!("Received Event::LoopExiting");
EventResult::Exit
}
winit::event::Event::RedrawRequested(window_id) => {
winit::event::Event::WindowEvent {
event: winit::event::WindowEvent::RedrawRequested,
window_id,
} => {
windows_next_repaint_times.remove(window_id);
winit_app.run_ui_and_paint(*window_id)
winit_app.run_ui_and_paint(event_loop_window_target, *window_id)
}
winit::event::Event::UserEvent(UserEvent::RequestRepaint {
@ -264,8 +284,11 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
EventResult::Wait
}
event => match winit_app.on_event(event_loop, event) {
Ok(event_result) => event_result,
event => match winit_app.on_event(event_loop_window_target, event) {
Ok(event_result) => {
log::trace!("event_result: {event_result:?}");
event_result
}
Err(err) => {
panic!("eframe encountered a fatal error: {err} during event {event:?}");
}
@ -274,22 +297,22 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
match event_result {
EventResult::Wait => {
control_flow.set_wait();
event_loop_window_target.set_control_flow(ControlFlow::Wait);
}
EventResult::RepaintNow(window_id) => {
log::trace!("Repaint caused by {}", short_event_description(&event));
log::trace!("RepaintNow caused by {}", short_event_description(&event));
if cfg!(target_os = "windows") {
// Fix flickering on Windows, see https://github.com/emilk/egui/pull/2280
windows_next_repaint_times.remove(&window_id);
winit_app.run_ui_and_paint(window_id);
winit_app.run_ui_and_paint(event_loop_window_target, window_id);
} else {
// Fix for https://github.com/emilk/egui/issues/2425
windows_next_repaint_times.insert(window_id, Instant::now());
}
}
EventResult::RepaintNext(window_id) => {
log::trace!("Repaint caused by {}", short_event_description(&event));
log::trace!("RepaintNext caused by {}", short_event_description(&event));
windows_next_repaint_times.insert(window_id, Instant::now());
}
EventResult::RepaintAt(window_id, repaint_time) => {
@ -303,6 +326,8 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
EventResult::Exit => {
log::debug!("Quitting - saving app state…");
winit_app.save_and_destroy();
log::debug!("Exiting with return code 0");
#[allow(clippy::exit)]
std::process::exit(0);
}
@ -310,36 +335,25 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
let mut next_repaint_time = windows_next_repaint_times.values().min().copied();
// This is for not duplicating redraw requests
use winit::event::Event;
if matches!(
event,
Event::RedrawEventsCleared | Event::RedrawRequested(_) | Event::Resumed
) {
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true;
}
next_repaint_time = None;
control_flow.set_poll();
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
window.request_redraw();
true
} else {
false
}
});
}
if let Some(next_repaint_time) = next_repaint_time {
let time_until_next = next_repaint_time.saturating_duration_since(Instant::now());
if time_until_next < std::time::Duration::from_secs(10_000) {
log::trace!("WaitUntil {time_until_next:?}");
windows_next_repaint_times.retain(|window_id, repaint_time| {
if Instant::now() < *repaint_time {
return true; // not yet ready
}
next_repaint_time = None;
event_loop_window_target.set_control_flow(ControlFlow::Poll);
if let Some(window) = winit_app.window(*window_id) {
log::trace!("request_redraw for {window_id:?}");
window.request_redraw();
true
} else {
log::trace!("No window found for {window_id:?}");
false
}
});
if let Some(next_repaint_time) = next_repaint_time {
// WaitUntil seems to not work on iOS
#[cfg(target_os = "ios")]
winit_app
@ -350,9 +364,13 @@ fn run_and_exit(event_loop: EventLoop<UserEvent>, mut winit_app: impl WinitApp +
.map(|window| window.request_redraw())
});
control_flow.set_wait_until(next_repaint_time);
event_loop_window_target.set_control_flow(ControlFlow::WaitUntil(next_repaint_time));
};
})
})?;
log::debug!("winit event loop unexpectedly returned");
Ok(())
}
// ----------------------------------------------------------------------------
@ -370,12 +388,12 @@ pub fn run_glow(
return with_event_loop(native_options, |event_loop, native_options| {
let glow_eframe = GlowWinitApp::new(event_loop, app_name, native_options, app_creator);
run_and_return(event_loop, glow_eframe)
});
})?;
}
let event_loop = create_event_loop(&mut native_options);
let event_loop = create_event_loop(&mut native_options)?;
let glow_eframe = GlowWinitApp::new(&event_loop, app_name, native_options, app_creator);
run_and_exit(event_loop, glow_eframe);
run_and_exit(event_loop, glow_eframe)
}
// ----------------------------------------------------------------------------
@ -393,10 +411,10 @@ pub fn run_wgpu(
return with_event_loop(native_options, |event_loop, native_options| {
let wgpu_eframe = WgpuWinitApp::new(event_loop, app_name, native_options, app_creator);
run_and_return(event_loop, wgpu_eframe)
});
})?;
}
let event_loop = create_event_loop(&mut native_options);
let event_loop = create_event_loop(&mut native_options)?;
let wgpu_eframe = WgpuWinitApp::new(&event_loop, app_name, native_options, app_creator);
run_and_exit(event_loop, wgpu_eframe);
run_and_exit(event_loop, wgpu_eframe)
}

View File

@ -1,3 +1,10 @@
//! Note that this file contains code very similar to [`glow_integration`].
//! When making changes to one you often also want to apply it to the other.
//!
//! This is also very complex code, and not very pretty.
//! There is a bunch of improvements we could do,
//! like removing a bunch of `unwraps`.
use std::{cell::RefCell, rc::Rc, sync::Arc, time::Instant};
use parking_lot::Mutex;
@ -109,7 +116,8 @@ impl WgpuWinitApp {
}
}
fn build_windows(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) {
/// Create a window for all viewports lacking one.
fn initialized_all_windows(&mut self, event_loop: &EventLoopWindowTarget<UserEvent>) {
let Some(running) = &mut self.running else {
return;
};
@ -122,14 +130,12 @@ impl WgpuWinitApp {
} = &mut *shared;
for viewport in viewports.values_mut() {
if viewport.window.is_none() {
viewport.init_window(
&running.integration.egui_ctx,
viewport_from_window,
painter,
event_loop,
);
}
viewport.initialize_window(
event_loop,
&running.integration.egui_ctx,
viewport_from_window,
painter,
);
}
}
@ -350,7 +356,13 @@ impl WinitApp for WgpuWinitApp {
}
}
fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult {
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
window_id: WindowId,
) -> EventResult {
self.initialized_all_windows(event_loop);
if let Some(running) = &mut self.running {
running.run_ui_and_paint(window_id)
} else {
@ -361,14 +373,16 @@ impl WinitApp for WgpuWinitApp {
fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &winit::event::Event<'_, UserEvent>,
event: &winit::event::Event<UserEvent>,
) -> Result<EventResult> {
crate::profile_function!(winit_integration::short_event_description(event));
self.build_windows(event_loop);
self.initialized_all_windows(event_loop);
Ok(match event {
winit::event::Event::Resumed => {
log::debug!("Event::Resumed");
let running = if let Some(running) = &self.running {
running
} else {
@ -660,7 +674,7 @@ impl WgpuWinitRunning {
fn on_window_event(
&mut self,
window_id: WindowId,
event: &winit::event::WindowEvent<'_>,
event: &winit::event::WindowEvent,
) -> EventResult {
crate::profile_function!(egui_winit::short_window_event_description(event));
@ -709,18 +723,6 @@ impl WgpuWinitRunning {
}
}
winit::event::WindowEvent::ScaleFactorChanged { new_inner_size, .. } => {
use std::num::NonZeroU32;
if let (Some(width), Some(height), Some(viewport_id)) = (
NonZeroU32::new(new_inner_size.width),
NonZeroU32::new(new_inner_size.height),
viewport_id,
) {
repaint_asap = true;
shared.painter.on_window_resized(viewport_id, width, height);
}
}
winit::event::WindowEvent::CloseRequested => {
if viewport_id == Some(ViewportId::ROOT) && integration.should_close() {
log::debug!(
@ -775,13 +777,18 @@ impl WgpuWinitRunning {
}
impl Viewport {
fn init_window(
/// Create winit window, if needed.
fn initialize_window(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
egui_ctx: &egui::Context,
windows_id: &mut HashMap<WindowId, ViewportId>,
painter: &mut egui_wgpu::winit::Painter,
event_loop: &EventLoopWindowTarget<UserEvent>,
) {
if self.window.is_some() {
return; // we already have one
}
crate::profile_function!();
let viewport_id = self.ids.this;
@ -870,7 +877,7 @@ fn render_immediate_viewport(
None,
);
if viewport.window.is_none() {
viewport.init_window(egui_ctx, viewport_from_window, painter, event_loop);
viewport.initialize_window(event_loop, egui_ctx, viewport_from_window, painter);
}
let (Some(window), Some(egui_winit)) = (&viewport.window, &mut viewport.egui_winit) else {

View File

@ -74,12 +74,16 @@ pub trait WinitApp {
fn save_and_destroy(&mut self);
fn run_ui_and_paint(&mut self, window_id: WindowId) -> EventResult;
fn run_ui_and_paint(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
window_id: WindowId,
) -> EventResult;
fn on_event(
&mut self,
event_loop: &EventLoopWindowTarget<UserEvent>,
event: &winit::event::Event<'_, UserEvent>,
event: &winit::event::Event<UserEvent>,
) -> crate::Result<EventResult>;
}
@ -117,11 +121,9 @@ pub fn system_theme(window: &Window, options: &crate::NativeOptions) -> Option<c
/// Short and fast description of an event.
/// Useful for logging and profiling.
pub fn short_event_description(event: &winit::event::Event<'_, UserEvent>) -> &'static str {
use winit::event::Event;
pub fn short_event_description(event: &winit::event::Event<UserEvent>) -> &'static str {
match event {
Event::UserEvent(user_event) => match user_event {
winit::event::Event::UserEvent(user_event) => match user_event {
UserEvent::RequestRepaint { .. } => "UserEvent::RequestRepaint",
#[cfg(feature = "accesskit")]
UserEvent::AccessKitActionRequest(_) => "UserEvent::AccessKitActionRequest",

View File

@ -13,7 +13,7 @@ pub struct AppRunner {
app: Box<dyn epi::App>,
pub(crate) needs_repaint: std::sync::Arc<NeedRepaint>,
last_save_time: f64,
pub(crate) text_cursor_pos: Option<egui::Pos2>,
pub(crate) ime: Option<egui::output::IMEOutput>,
pub(crate) mutable_text_under_cursor: bool,
// Output for the last run:
@ -112,7 +112,7 @@ impl AppRunner {
app,
needs_repaint,
last_save_time: now_sec(),
text_cursor_pos: None,
ime: None,
mutable_text_under_cursor: false,
textures_delta: Default::default(),
clipped_primitives: None,
@ -244,7 +244,7 @@ impl AppRunner {
copied_text,
events: _, // already handled
mutable_text_under_cursor,
text_cursor_pos,
ime,
#[cfg(feature = "accesskit")]
accesskit_update: _, // not currently implemented
} = platform_output;
@ -264,9 +264,9 @@ impl AppRunner {
self.mutable_text_under_cursor = mutable_text_under_cursor;
if self.text_cursor_pos != text_cursor_pos {
super::text_agent::move_text_cursor(text_cursor_pos, self.canvas_id());
self.text_cursor_pos = text_cursor_pos;
if self.ime != ime {
super::text_agent::move_text_cursor(ime, self.canvas_id());
self.ime = ime;
}
}
}

View File

@ -91,6 +91,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
if let Some(key) = egui_key {
runner.input.raw.events.push(egui::Event::Key {
key,
physical_key: None, // TODO
pressed: true,
repeat: false, // egui will fill this in for us!
modifiers,
@ -157,6 +158,7 @@ pub(crate) fn install_document_events(runner_ref: &WebRunner) -> Result<(), JsVa
if let Some(key) = translate_key(&event.key()) {
runner.input.raw.events.push(egui::Event::Key {
key,
physical_key: None, // TODO
pressed: false,
repeat: false,
modifiers,

View File

@ -112,91 +112,7 @@ pub fn should_ignore_key(key: &str) -> bool {
/// Web sends all keys as strings, so it is up to us to figure out if it is
/// a real text input or the name of a key.
pub fn translate_key(key: &str) -> Option<egui::Key> {
use egui::Key;
match key {
"ArrowDown" => Some(Key::ArrowDown),
"ArrowLeft" => Some(Key::ArrowLeft),
"ArrowRight" => Some(Key::ArrowRight),
"ArrowUp" => Some(Key::ArrowUp),
"Esc" | "Escape" => Some(Key::Escape),
"Tab" => Some(Key::Tab),
"Backspace" => Some(Key::Backspace),
"Enter" => Some(Key::Enter),
"Space" | " " => Some(Key::Space),
"Help" | "Insert" => Some(Key::Insert),
"Delete" => Some(Key::Delete),
"Home" => Some(Key::Home),
"End" => Some(Key::End),
"PageUp" => Some(Key::PageUp),
"PageDown" => Some(Key::PageDown),
"-" => Some(Key::Minus),
"+" | "=" => Some(Key::PlusEquals),
"0" => Some(Key::Num0),
"1" => Some(Key::Num1),
"2" => Some(Key::Num2),
"3" => Some(Key::Num3),
"4" => Some(Key::Num4),
"5" => Some(Key::Num5),
"6" => Some(Key::Num6),
"7" => Some(Key::Num7),
"8" => Some(Key::Num8),
"9" => Some(Key::Num9),
"a" | "A" => Some(Key::A),
"b" | "B" => Some(Key::B),
"c" | "C" => Some(Key::C),
"d" | "D" => Some(Key::D),
"e" | "E" => Some(Key::E),
"f" | "F" => Some(Key::F),
"g" | "G" => Some(Key::G),
"h" | "H" => Some(Key::H),
"i" | "I" => Some(Key::I),
"j" | "J" => Some(Key::J),
"k" | "K" => Some(Key::K),
"l" | "L" => Some(Key::L),
"m" | "M" => Some(Key::M),
"n" | "N" => Some(Key::N),
"o" | "O" => Some(Key::O),
"p" | "P" => Some(Key::P),
"q" | "Q" => Some(Key::Q),
"r" | "R" => Some(Key::R),
"s" | "S" => Some(Key::S),
"t" | "T" => Some(Key::T),
"u" | "U" => Some(Key::U),
"v" | "V" => Some(Key::V),
"w" | "W" => Some(Key::W),
"x" | "X" => Some(Key::X),
"y" | "Y" => Some(Key::Y),
"z" | "Z" => Some(Key::Z),
"F1" => Some(Key::F1),
"F2" => Some(Key::F2),
"F3" => Some(Key::F3),
"F4" => Some(Key::F4),
"F5" => Some(Key::F5),
"F6" => Some(Key::F6),
"F7" => Some(Key::F7),
"F8" => Some(Key::F8),
"F9" => Some(Key::F9),
"F10" => Some(Key::F10),
"F11" => Some(Key::F11),
"F12" => Some(Key::F12),
"F13" => Some(Key::F13),
"F14" => Some(Key::F14),
"F15" => Some(Key::F15),
"F16" => Some(Key::F16),
"F17" => Some(Key::F17),
"F18" => Some(Key::F18),
"F19" => Some(Key::F19),
"F20" => Some(Key::F20),
_ => None,
}
egui::Key::from_name(key)
}
pub fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers {

View File

@ -205,11 +205,13 @@ fn is_mobile() -> Option<bool> {
// candidate window moves following text element (agent),
// so it appears that the IME candidate window moves with text cursor.
// On mobile devices, there is no need to do that.
pub fn move_text_cursor(cursor: Option<egui::Pos2>, canvas_id: &str) -> Option<()> {
pub fn move_text_cursor(ime: Option<egui::output::IMEOutput>, canvas_id: &str) -> Option<()> {
let style = text_agent().style();
// Note: movint agent on mobile devices will lead to unpredictable scroll.
// Note: moving agent on mobile devices will lead to unpredictable scroll.
if is_mobile() == Some(false) {
cursor.as_ref().and_then(|&egui::Pos2 { x, y }| {
ime.as_ref().and_then(|ime| {
let egui::Pos2 { x, y } = ime.cursor_rect.left_top();
let canvas = canvas_element(canvas_id)?;
let bounding_rect = text_agent().get_bounding_client_rect();
let y = (y + (canvas.scroll_top() + canvas.offset_top()) as f32)

View File

@ -51,7 +51,7 @@ wgpu.workspace = true
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
winit = { version = "0.28", default-features = false, optional = true }
winit = { version = "0.29.4", default-features = false, optional = true, features = ["rwh_05"] }
# Native:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]

View File

@ -61,12 +61,12 @@ egui = { version = "0.24.1", path = "../egui", default-features = false, feature
log = { version = "0.4", features = ["std"] }
raw-window-handle.workspace = true
web-time = { version = "0.2" } # We use web-time so we can (maybe) compile for web
winit = { version = "0.28", default-features = false }
winit = { version = "0.29.4", default-features = false, features = ["rwh_05"] }
#! ### Optional dependencies
# feature accesskit
accesskit_winit = { version = "0.15.0", optional = true }
accesskit_winit = { version = "0.16.0", optional = true }
## Enable this when generating docs.
document-features = { version = "0.2", optional = true }
@ -76,7 +76,7 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
webbrowser = { version = "0.8.3", optional = true }
[target.'cfg(any(target_os="linux", target_os="dragonfly", target_os="freebsd", target_os="netbsd", target_os="openbsd"))'.dependencies]
smithay-clipboard = { version = "0.6.3", optional = true }
smithay-clipboard = { version = "0.7.0", optional = true }
[target.'cfg(not(target_os = "android"))'.dependencies]
arboard = { version = "3.2", optional = true, default-features = false }

View File

@ -232,7 +232,7 @@ impl State {
pub fn on_window_event(
&mut self,
window: &Window,
event: &winit::event::WindowEvent<'_>,
event: &winit::event::WindowEvent,
) -> EventResponse {
crate::profile_function!(short_window_event_description(event));
@ -295,25 +295,7 @@ impl State {
consumed,
}
}
WindowEvent::ReceivedCharacter(ch) => {
// On Mac we get here when the user presses Cmd-C (copy), ctrl-W, etc.
// We need to ignore these characters that are side-effects of commands.
let is_mac_cmd = cfg!(target_os = "macos")
&& (self.egui_input.modifiers.ctrl || self.egui_input.modifiers.mac_cmd);
let consumed = if is_printable_char(*ch) && !is_mac_cmd {
self.egui_input
.events
.push(egui::Event::Text(ch.to_string()));
self.egui_ctx.wants_keyboard_input()
} else {
false
};
EventResponse {
repaint: true,
consumed,
}
}
WindowEvent::Ime(ime) => {
// on Mac even Cmd-C is pressed during ime, a `c` is pushed to Preedit.
// So no need to check is_mac_cmd.
@ -353,11 +335,12 @@ impl State {
consumed: self.egui_ctx.wants_keyboard_input(),
}
}
WindowEvent::KeyboardInput { input, .. } => {
self.on_keyboard_input(input);
WindowEvent::KeyboardInput { event, .. } => {
// When pressing the Tab key, egui focuses the first focusable element, hence Tab always consumes.
let consumed = self.egui_ctx.wants_keyboard_input()
|| input.virtual_keycode == Some(winit::event::VirtualKeyCode::Tab);
let consumed = self.on_keyboard_input(event)
|| self.egui_ctx.wants_keyboard_input()
|| event.logical_key
== winit::keyboard::Key::Named(winit::keyboard::NamedKey::Tab);
EventResponse {
repaint: true,
consumed,
@ -405,15 +388,23 @@ impl State {
}
}
WindowEvent::ModifiersChanged(state) => {
self.egui_input.modifiers.alt = state.alt();
self.egui_input.modifiers.ctrl = state.ctrl();
self.egui_input.modifiers.shift = state.shift();
self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && state.logo();
let state = state.state();
let alt = state.alt_key();
let ctrl = state.control_key();
let shift = state.shift_key();
let super_ = state.super_key();
self.egui_input.modifiers.alt = alt;
self.egui_input.modifiers.ctrl = ctrl;
self.egui_input.modifiers.shift = shift;
self.egui_input.modifiers.mac_cmd = cfg!(target_os = "macos") && super_;
self.egui_input.modifiers.command = if cfg!(target_os = "macos") {
state.logo()
super_
} else {
state.ctrl()
ctrl
};
EventResponse {
repaint: true,
consumed: false,
@ -421,7 +412,8 @@ impl State {
}
// Things that may require repaint:
WindowEvent::CursorEntered { .. }
WindowEvent::RedrawRequested
| WindowEvent::CursorEntered { .. }
| WindowEvent::Destroyed
| WindowEvent::Occluded(_)
| WindowEvent::Resized(_)
@ -434,7 +426,8 @@ impl State {
},
// Things we completely ignore:
WindowEvent::AxisMotion { .. }
WindowEvent::ActivationTokenDone { .. }
| WindowEvent::AxisMotion { .. }
| WindowEvent::SmartMagnify { .. }
| WindowEvent::TouchpadRotate { .. } => EventResponse {
repaint: false,
@ -655,36 +648,92 @@ impl State {
}
}
fn on_keyboard_input(&mut self, input: &winit::event::KeyboardInput) {
if let Some(keycode) = input.virtual_keycode {
let pressed = input.state == winit::event::ElementState::Pressed;
fn on_keyboard_input(&mut self, event: &winit::event::KeyEvent) -> bool {
let winit::event::KeyEvent {
// Represents the position of a key independent of the currently active layout.
//
// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode).
// The most prevalent use case for this is games. For example the default keys for the player
// to move around might be the W, A, S, and D keys on a US layout. The position of these keys
// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY"
// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.)
physical_key,
// Represents the results of a keymap, i.e. what character a certain key press represents.
// When telling users "Press Ctrl-F to find", this is where we should
// look for the "F" key, because they may have a dvorak layout on
// a qwerty keyboard, and so the logical "F" character may not be located on the physical `KeyCode::KeyF` position.
logical_key,
text,
state,
location: _, // e.g. is it on the numpad?
repeat: _, // egui will figure this out for us
..
} = event;
let pressed = *state == winit::event::ElementState::Pressed;
let physical_key = if let winit::keyboard::PhysicalKey::Code(keycode) = *physical_key {
key_from_key_code(keycode)
} else {
None
};
let logical_key = key_from_winit_key(logical_key);
if let Some(logical_key) = logical_key {
if pressed {
// VirtualKeyCode::Paste etc in winit are broken/untrustworthy,
// KeyCode::Paste etc in winit are broken/untrustworthy,
// so we detect these things manually:
if is_cut_command(self.egui_input.modifiers, keycode) {
if is_cut_command(self.egui_input.modifiers, logical_key) {
self.egui_input.events.push(egui::Event::Cut);
} else if is_copy_command(self.egui_input.modifiers, keycode) {
return true;
} else if is_copy_command(self.egui_input.modifiers, logical_key) {
self.egui_input.events.push(egui::Event::Copy);
} else if is_paste_command(self.egui_input.modifiers, keycode) {
return true;
} else if is_paste_command(self.egui_input.modifiers, logical_key) {
if let Some(contents) = self.clipboard.get() {
let contents = contents.replace("\r\n", "\n");
if !contents.is_empty() {
self.egui_input.events.push(egui::Event::Paste(contents));
}
}
return true;
}
}
if let Some(key) = translate_virtual_key_code(keycode) {
self.egui_input.events.push(egui::Event::Key {
key,
pressed,
repeat: false, // egui will fill this in for us!
modifiers: self.egui_input.modifiers,
});
self.egui_input.events.push(egui::Event::Key {
key: logical_key,
physical_key,
pressed,
repeat: false, // egui will fill this in for us!
modifiers: self.egui_input.modifiers,
});
}
if let Some(text) = &text {
// Make sure there is text, and that it is not control characters
// (e.g. delete is sent as "\u{f728}" on macOS).
if !text.is_empty() && text.chars().all(is_printable_char) {
// On some platforms we get here when the user presses Cmd-C (copy), ctrl-W, etc.
// We need to ignore these characters that are side-effects of commands.
// Also make sure the key is pressed (not released). On Linux, text might
// contain some data even when the key is released.
let is_cmd = self.egui_input.modifiers.ctrl
|| self.egui_input.modifiers.command
|| self.egui_input.modifiers.mac_cmd;
if pressed && !is_cmd {
self.egui_input
.events
.push(egui::Event::Text(text.to_string()));
}
}
}
false
}
/// Call with the output given by `egui`.
@ -708,7 +757,7 @@ impl State {
copied_text,
events: _, // handled elsewhere
mutable_text_under_cursor: _, // only used in eframe web
text_cursor_pos,
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
} = platform_output;
@ -723,14 +772,25 @@ impl State {
self.clipboard.set(copied_text);
}
let allow_ime = text_cursor_pos.is_some();
let allow_ime = ime.is_some();
if self.allow_ime != allow_ime {
self.allow_ime = allow_ime;
window.set_ime_allowed(allow_ime);
}
if let Some(egui::Pos2 { x, y }) = text_cursor_pos {
window.set_ime_position(winit::dpi::LogicalPosition { x, y });
if let Some(ime) = ime {
let rect = ime.rect;
let pixels_per_point = pixels_per_point(&self.egui_ctx, window);
window.set_ime_cursor_area(
winit::dpi::PhysicalPosition {
x: pixels_per_point * rect.min.x,
y: pixels_per_point * rect.min.y,
},
winit::dpi::PhysicalSize {
width: pixels_per_point * rect.width(),
height: pixels_per_point * rect.height(),
},
);
}
#[cfg(feature = "accesskit")]
@ -880,25 +940,19 @@ fn is_printable_char(chr: char) -> bool {
!is_in_private_use_area && !chr.is_ascii_control()
}
fn is_cut_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
(modifiers.command && keycode == winit::event::VirtualKeyCode::X)
|| (cfg!(target_os = "windows")
&& modifiers.shift
&& keycode == winit::event::VirtualKeyCode::Delete)
fn is_cut_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
(modifiers.command && keycode == egui::Key::X)
|| (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Delete)
}
fn is_copy_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
(modifiers.command && keycode == winit::event::VirtualKeyCode::C)
|| (cfg!(target_os = "windows")
&& modifiers.ctrl
&& keycode == winit::event::VirtualKeyCode::Insert)
fn is_copy_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
(modifiers.command && keycode == egui::Key::C)
|| (cfg!(target_os = "windows") && modifiers.ctrl && keycode == egui::Key::Insert)
}
fn is_paste_command(modifiers: egui::Modifiers, keycode: winit::event::VirtualKeyCode) -> bool {
(modifiers.command && keycode == winit::event::VirtualKeyCode::V)
|| (cfg!(target_os = "windows")
&& modifiers.shift
&& keycode == winit::event::VirtualKeyCode::Insert)
fn is_paste_command(modifiers: egui::Modifiers, keycode: egui::Key) -> bool {
(modifiers.command && keycode == egui::Key::V)
|| (cfg!(target_os = "windows") && modifiers.shift && keycode == egui::Key::Insert)
}
fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::PointerButton> {
@ -906,100 +960,159 @@ fn translate_mouse_button(button: winit::event::MouseButton) -> Option<egui::Poi
winit::event::MouseButton::Left => Some(egui::PointerButton::Primary),
winit::event::MouseButton::Right => Some(egui::PointerButton::Secondary),
winit::event::MouseButton::Middle => Some(egui::PointerButton::Middle),
winit::event::MouseButton::Other(1) => Some(egui::PointerButton::Extra1),
winit::event::MouseButton::Other(2) => Some(egui::PointerButton::Extra2),
winit::event::MouseButton::Back => Some(egui::PointerButton::Extra1),
winit::event::MouseButton::Forward => Some(egui::PointerButton::Extra2),
winit::event::MouseButton::Other(_) => None,
}
}
fn translate_virtual_key_code(key: winit::event::VirtualKeyCode) -> Option<egui::Key> {
fn key_from_winit_key(key: &winit::keyboard::Key) -> Option<egui::Key> {
match key {
winit::keyboard::Key::Named(named_key) => key_from_named_key(*named_key),
winit::keyboard::Key::Character(str) => egui::Key::from_name(str.as_str()),
winit::keyboard::Key::Unidentified(_) | winit::keyboard::Key::Dead(_) => None,
}
}
fn key_from_named_key(named_key: winit::keyboard::NamedKey) -> Option<egui::Key> {
use egui::Key;
use winit::event::VirtualKeyCode;
use winit::keyboard::NamedKey;
match named_key {
NamedKey::Enter => Some(Key::Enter),
NamedKey::Tab => Some(Key::Tab),
NamedKey::Space => Some(Key::Space),
NamedKey::ArrowDown => Some(Key::ArrowDown),
NamedKey::ArrowLeft => Some(Key::ArrowLeft),
NamedKey::ArrowRight => Some(Key::ArrowRight),
NamedKey::ArrowUp => Some(Key::ArrowUp),
NamedKey::End => Some(Key::End),
NamedKey::Home => Some(Key::Home),
NamedKey::PageDown => Some(Key::PageDown),
NamedKey::PageUp => Some(Key::PageUp),
NamedKey::Backspace => Some(Key::Backspace),
NamedKey::Delete => Some(Key::Delete),
NamedKey::Insert => Some(Key::Insert),
NamedKey::Escape => Some(Key::Escape),
NamedKey::F1 => Some(Key::F1),
NamedKey::F2 => Some(Key::F2),
NamedKey::F3 => Some(Key::F3),
NamedKey::F4 => Some(Key::F4),
NamedKey::F5 => Some(Key::F5),
NamedKey::F6 => Some(Key::F6),
NamedKey::F7 => Some(Key::F7),
NamedKey::F8 => Some(Key::F8),
NamedKey::F9 => Some(Key::F9),
NamedKey::F10 => Some(Key::F10),
NamedKey::F11 => Some(Key::F11),
NamedKey::F12 => Some(Key::F12),
NamedKey::F13 => Some(Key::F13),
NamedKey::F14 => Some(Key::F14),
NamedKey::F15 => Some(Key::F15),
NamedKey::F16 => Some(Key::F16),
NamedKey::F17 => Some(Key::F17),
NamedKey::F18 => Some(Key::F18),
NamedKey::F19 => Some(Key::F19),
NamedKey::F20 => Some(Key::F20),
_ => {
log::trace!("Unknown key: {named_key:?}");
None
}
}
}
fn key_from_key_code(key: winit::keyboard::KeyCode) -> Option<egui::Key> {
use egui::Key;
use winit::keyboard::KeyCode;
Some(match key {
VirtualKeyCode::Down => Key::ArrowDown,
VirtualKeyCode::Left => Key::ArrowLeft,
VirtualKeyCode::Right => Key::ArrowRight,
VirtualKeyCode::Up => Key::ArrowUp,
KeyCode::ArrowDown => Key::ArrowDown,
KeyCode::ArrowLeft => Key::ArrowLeft,
KeyCode::ArrowRight => Key::ArrowRight,
KeyCode::ArrowUp => Key::ArrowUp,
VirtualKeyCode::Escape => Key::Escape,
VirtualKeyCode::Tab => Key::Tab,
VirtualKeyCode::Back => Key::Backspace,
VirtualKeyCode::Return | VirtualKeyCode::NumpadEnter => Key::Enter,
VirtualKeyCode::Space => Key::Space,
KeyCode::Escape => Key::Escape,
KeyCode::Tab => Key::Tab,
KeyCode::Backspace => Key::Backspace,
KeyCode::Enter | KeyCode::NumpadEnter => Key::Enter,
KeyCode::Space => Key::Space,
VirtualKeyCode::Insert => Key::Insert,
VirtualKeyCode::Delete => Key::Delete,
VirtualKeyCode::Home => Key::Home,
VirtualKeyCode::End => Key::End,
VirtualKeyCode::PageUp => Key::PageUp,
VirtualKeyCode::PageDown => Key::PageDown,
KeyCode::Insert => Key::Insert,
KeyCode::Delete => Key::Delete,
KeyCode::Home => Key::Home,
KeyCode::End => Key::End,
KeyCode::PageUp => Key::PageUp,
KeyCode::PageDown => Key::PageDown,
KeyCode::Comma => Key::Comma,
KeyCode::Period => Key::Period,
// KeyCode::Colon => Key::Colon, // NOTE: there is no physical colon key on an american keyboard
KeyCode::Semicolon => Key::Semicolon,
KeyCode::Minus | KeyCode::NumpadSubtract => Key::Minus,
VirtualKeyCode::Minus | VirtualKeyCode::NumpadSubtract => Key::Minus,
// Using Mac the key with the Plus sign on it is reported as the Equals key
// (with both English and Swedish keyboard).
VirtualKeyCode::Equals | VirtualKeyCode::Plus | VirtualKeyCode::NumpadAdd => {
Key::PlusEquals
}
KeyCode::Equal | KeyCode::NumpadAdd => Key::PlusEquals,
VirtualKeyCode::Key0 | VirtualKeyCode::Numpad0 => Key::Num0,
VirtualKeyCode::Key1 | VirtualKeyCode::Numpad1 => Key::Num1,
VirtualKeyCode::Key2 | VirtualKeyCode::Numpad2 => Key::Num2,
VirtualKeyCode::Key3 | VirtualKeyCode::Numpad3 => Key::Num3,
VirtualKeyCode::Key4 | VirtualKeyCode::Numpad4 => Key::Num4,
VirtualKeyCode::Key5 | VirtualKeyCode::Numpad5 => Key::Num5,
VirtualKeyCode::Key6 | VirtualKeyCode::Numpad6 => Key::Num6,
VirtualKeyCode::Key7 | VirtualKeyCode::Numpad7 => Key::Num7,
VirtualKeyCode::Key8 | VirtualKeyCode::Numpad8 => Key::Num8,
VirtualKeyCode::Key9 | VirtualKeyCode::Numpad9 => Key::Num9,
KeyCode::Digit0 | KeyCode::Numpad0 => Key::Num0,
KeyCode::Digit1 | KeyCode::Numpad1 => Key::Num1,
KeyCode::Digit2 | KeyCode::Numpad2 => Key::Num2,
KeyCode::Digit3 | KeyCode::Numpad3 => Key::Num3,
KeyCode::Digit4 | KeyCode::Numpad4 => Key::Num4,
KeyCode::Digit5 | KeyCode::Numpad5 => Key::Num5,
KeyCode::Digit6 | KeyCode::Numpad6 => Key::Num6,
KeyCode::Digit7 | KeyCode::Numpad7 => Key::Num7,
KeyCode::Digit8 | KeyCode::Numpad8 => Key::Num8,
KeyCode::Digit9 | KeyCode::Numpad9 => Key::Num9,
VirtualKeyCode::A => Key::A,
VirtualKeyCode::B => Key::B,
VirtualKeyCode::C => Key::C,
VirtualKeyCode::D => Key::D,
VirtualKeyCode::E => Key::E,
VirtualKeyCode::F => Key::F,
VirtualKeyCode::G => Key::G,
VirtualKeyCode::H => Key::H,
VirtualKeyCode::I => Key::I,
VirtualKeyCode::J => Key::J,
VirtualKeyCode::K => Key::K,
VirtualKeyCode::L => Key::L,
VirtualKeyCode::M => Key::M,
VirtualKeyCode::N => Key::N,
VirtualKeyCode::O => Key::O,
VirtualKeyCode::P => Key::P,
VirtualKeyCode::Q => Key::Q,
VirtualKeyCode::R => Key::R,
VirtualKeyCode::S => Key::S,
VirtualKeyCode::T => Key::T,
VirtualKeyCode::U => Key::U,
VirtualKeyCode::V => Key::V,
VirtualKeyCode::W => Key::W,
VirtualKeyCode::X => Key::X,
VirtualKeyCode::Y => Key::Y,
VirtualKeyCode::Z => Key::Z,
KeyCode::KeyA => Key::A,
KeyCode::KeyB => Key::B,
KeyCode::KeyC => Key::C,
KeyCode::KeyD => Key::D,
KeyCode::KeyE => Key::E,
KeyCode::KeyF => Key::F,
KeyCode::KeyG => Key::G,
KeyCode::KeyH => Key::H,
KeyCode::KeyI => Key::I,
KeyCode::KeyJ => Key::J,
KeyCode::KeyK => Key::K,
KeyCode::KeyL => Key::L,
KeyCode::KeyM => Key::M,
KeyCode::KeyN => Key::N,
KeyCode::KeyO => Key::O,
KeyCode::KeyP => Key::P,
KeyCode::KeyQ => Key::Q,
KeyCode::KeyR => Key::R,
KeyCode::KeyS => Key::S,
KeyCode::KeyT => Key::T,
KeyCode::KeyU => Key::U,
KeyCode::KeyV => Key::V,
KeyCode::KeyW => Key::W,
KeyCode::KeyX => Key::X,
KeyCode::KeyY => Key::Y,
KeyCode::KeyZ => Key::Z,
VirtualKeyCode::F1 => Key::F1,
VirtualKeyCode::F2 => Key::F2,
VirtualKeyCode::F3 => Key::F3,
VirtualKeyCode::F4 => Key::F4,
VirtualKeyCode::F5 => Key::F5,
VirtualKeyCode::F6 => Key::F6,
VirtualKeyCode::F7 => Key::F7,
VirtualKeyCode::F8 => Key::F8,
VirtualKeyCode::F9 => Key::F9,
VirtualKeyCode::F10 => Key::F10,
VirtualKeyCode::F11 => Key::F11,
VirtualKeyCode::F12 => Key::F12,
VirtualKeyCode::F13 => Key::F13,
VirtualKeyCode::F14 => Key::F14,
VirtualKeyCode::F15 => Key::F15,
VirtualKeyCode::F16 => Key::F16,
VirtualKeyCode::F17 => Key::F17,
VirtualKeyCode::F18 => Key::F18,
VirtualKeyCode::F19 => Key::F19,
VirtualKeyCode::F20 => Key::F20,
KeyCode::F1 => Key::F1,
KeyCode::F2 => Key::F2,
KeyCode::F3 => Key::F3,
KeyCode::F4 => Key::F4,
KeyCode::F5 => Key::F5,
KeyCode::F6 => Key::F6,
KeyCode::F7 => Key::F7,
KeyCode::F8 => Key::F8,
KeyCode::F9 => Key::F9,
KeyCode::F10 => Key::F10,
KeyCode::F11 => Key::F11,
KeyCode::F12 => Key::F12,
KeyCode::F13 => Key::F13,
KeyCode::F14 => Key::F14,
KeyCode::F15 => Key::F15,
KeyCode::F16 => Key::F16,
KeyCode::F17 => Key::F17,
KeyCode::F18 => Key::F18,
KeyCode::F19 => Key::F19,
KeyCode::F20 => Key::F20,
_ => {
return None;
@ -1024,7 +1137,7 @@ fn translate_cursor(cursor_icon: egui::CursorIcon) -> Option<winit::window::Curs
egui::CursorIcon::Move => Some(winit::window::CursorIcon::Move),
egui::CursorIcon::NoDrop => Some(winit::window::CursorIcon::NoDrop),
egui::CursorIcon::NotAllowed => Some(winit::window::CursorIcon::NotAllowed),
egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Hand),
egui::CursorIcon::PointingHand => Some(winit::window::CursorIcon::Pointer),
egui::CursorIcon::Progress => Some(winit::window::CursorIcon::Progress),
egui::CursorIcon::ResizeHorizontal => Some(winit::window::CursorIcon::EwResize),
@ -1112,7 +1225,12 @@ fn process_viewport_command(
ViewportCommand::InnerSize(size) => {
let width_px = pixels_per_point * size.x.max(1.0);
let height_px = pixels_per_point * size.y.max(1.0);
window.set_inner_size(PhysicalSize::new(width_px, height_px));
if window
.request_inner_size(PhysicalSize::new(width_px, height_px))
.is_some()
{
log::debug!("ViewportCommand::InnerSize ignored by winit");
}
}
ViewportCommand::BeginResize(direction) => {
if let Err(err) = window.drag_resize_window(match direction {
@ -1196,11 +1314,14 @@ fn process_viewport_command(
.expect("Invalid ICON data!")
}));
}
ViewportCommand::IMEPosition(pos) => {
window.set_ime_position(PhysicalPosition::new(
pixels_per_point * pos.x,
pixels_per_point * pos.y,
));
ViewportCommand::IMERect(rect) => {
window.set_ime_cursor_area(
PhysicalPosition::new(pixels_per_point * rect.min.x, pixels_per_point * rect.min.y),
PhysicalSize::new(
pixels_per_point * rect.size().x,
pixels_per_point * rect.size().y,
),
);
}
ViewportCommand::IMEAllowed(v) => window.set_ime_allowed(v),
ViewportCommand::IMEPurpose(p) => window.set_ime_purpose(match p {
@ -1448,10 +1569,15 @@ pub fn apply_viewport_builder_to_window(
let pixels_per_point = pixels_per_point(egui_ctx, window);
if let Some(size) = builder.inner_size {
window.set_inner_size(PhysicalSize::new(
pixels_per_point * size.x,
pixels_per_point * size.y,
));
if window
.request_inner_size(PhysicalSize::new(
pixels_per_point * size.x,
pixels_per_point * size.y,
))
.is_some()
{
log::debug!("Failed to set window size");
}
}
if let Some(size) = builder.min_inner_size {
window.set_min_inner_size(Some(PhysicalSize::new(
@ -1476,16 +1602,15 @@ pub fn apply_viewport_builder_to_window(
/// Short and fast description of an event.
/// Useful for logging and profiling.
pub fn short_generic_event_description<T>(event: &winit::event::Event<'_, T>) -> &'static str {
pub fn short_generic_event_description<T>(event: &winit::event::Event<T>) -> &'static str {
use winit::event::{DeviceEvent, Event, StartCause};
match event {
Event::AboutToWait => "Event::AboutToWait",
Event::LoopExiting => "Event::LoopExiting",
Event::Suspended => "Event::Suspended",
Event::Resumed => "Event::Resumed",
Event::MainEventsCleared => "Event::MainEventsCleared",
Event::RedrawRequested(_) => "Event::RedrawRequested",
Event::RedrawEventsCleared => "Event::RedrawEventsCleared",
Event::LoopDestroyed => "Event::LoopDestroyed",
Event::MemoryWarning => "Event::MemoryWarning",
Event::UserEvent(_) => "UserEvent",
Event::DeviceEvent { event, .. } => match event {
DeviceEvent::Added { .. } => "DeviceEvent::Added",
@ -1495,7 +1620,6 @@ pub fn short_generic_event_description<T>(event: &winit::event::Event<'_, T>) ->
DeviceEvent::Motion { .. } => "DeviceEvent::Motion",
DeviceEvent::Button { .. } => "DeviceEvent::Button",
DeviceEvent::Key { .. } => "DeviceEvent::Key",
DeviceEvent::Text { .. } => "DeviceEvent::Text",
},
Event::NewEvents(start_cause) => match start_cause {
StartCause::ResumeTimeReached { .. } => "NewEvents::ResumeTimeReached",
@ -1509,10 +1633,11 @@ pub fn short_generic_event_description<T>(event: &winit::event::Event<'_, T>) ->
/// Short and fast description of an event.
/// Useful for logging and profiling.
pub fn short_window_event_description(event: &winit::event::WindowEvent<'_>) -> &'static str {
pub fn short_window_event_description(event: &winit::event::WindowEvent) -> &'static str {
use winit::event::WindowEvent;
match event {
WindowEvent::ActivationTokenDone { .. } => "WindowEvent::ActivationTokenDone",
WindowEvent::Resized { .. } => "WindowEvent::Resized",
WindowEvent::Moved { .. } => "WindowEvent::Moved",
WindowEvent::CloseRequested { .. } => "WindowEvent::CloseRequested",
@ -1520,7 +1645,6 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent<'_>) ->
WindowEvent::DroppedFile { .. } => "WindowEvent::DroppedFile",
WindowEvent::HoveredFile { .. } => "WindowEvent::HoveredFile",
WindowEvent::HoveredFileCancelled { .. } => "WindowEvent::HoveredFileCancelled",
WindowEvent::ReceivedCharacter { .. } => "WindowEvent::ReceivedCharacter",
WindowEvent::Focused { .. } => "WindowEvent::Focused",
WindowEvent::KeyboardInput { .. } => "WindowEvent::KeyboardInput",
WindowEvent::ModifiersChanged { .. } => "WindowEvent::ModifiersChanged",
@ -1531,6 +1655,7 @@ pub fn short_window_event_description(event: &winit::event::WindowEvent<'_>) ->
WindowEvent::MouseWheel { .. } => "WindowEvent::MouseWheel",
WindowEvent::MouseInput { .. } => "WindowEvent::MouseInput",
WindowEvent::TouchpadMagnify { .. } => "WindowEvent::TouchpadMagnify",
WindowEvent::RedrawRequested { .. } => "WindowEvent::RedrawRequested",
WindowEvent::SmartMagnify { .. } => "WindowEvent::SmartMagnify",
WindowEvent::TouchpadRotate { .. } => "WindowEvent::TouchpadRotate",
WindowEvent::TouchpadPressure { .. } => "WindowEvent::TouchpadPressure",

View File

@ -361,8 +361,20 @@ pub enum Event {
/// A key was pressed or released.
Key {
/// The logical key, heeding the users keymap.
///
/// For instance, if the user is using Dvorak keyboard layout,
/// this will take that into account.
key: Key,
/// The physical key, corresponding to the actual position on the keyboard.
///
/// This ignored keymaps, so it is not recommended to use this.
/// The only thing it makes sense for is things like games,
/// where e.g. the physical location of WSAD on QWERTY should always map to movement,
/// even if the user is using Dvorak or AZERTY.
physical_key: Option<Key>,
/// Was it pressed or released?
pressed: bool,
@ -844,11 +856,8 @@ impl<'a> ModifierNames<'a> {
/// Keyboard keys.
///
/// Includes all keys egui is interested in (such as `Home` and `End`)
/// plus a few that are useful for detecting keyboard shortcuts.
///
/// Many keys are omitted because they are not always physical keys (depending on keyboard language), e.g. `;` and `§`,
/// and are therefore unsuitable as keyboard shortcuts if you want your app to be portable.
/// egui usually uses logical keys, i.e. after applying any user keymap.
// TODO(emilk): split into `LogicalKey` and `PhysicalKey`
#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Key {
@ -870,12 +879,28 @@ pub enum Key {
PageUp,
PageDown,
/// The virtual keycode for the Minus key.
// ----------------------------------------------
// Punctuation:
/// `:`
Colon,
/// `,`
Comma,
/// `-`
Minus,
/// The virtual keycode for the Plus/Equals key.
/// `.`
Period,
/// The for the Plus/Equals key.
PlusEquals,
/// `;`
Semicolon,
// ----------------------------------------------
// Digits:
/// Either from the main row or from the numpad.
Num0,
@ -906,6 +931,8 @@ pub enum Key {
/// Either from the main row or from the numpad.
Num9,
// ----------------------------------------------
// Letters:
A, // Used for cmd+A (select All)
B,
C, // |CMD COPY|
@ -933,7 +960,8 @@ pub enum Key {
Y,
Z, // |CMD UNDO|
// The function keys:
// ----------------------------------------------
// Function keys:
F1,
F2,
F3,
@ -954,9 +982,196 @@ pub enum Key {
F18,
F19,
F20,
// When adding keys, remember to also update `crates/egui-winit/src/lib.rs`
// and [`Self::ALL`].
// Also: don't add keys last; add them to the group they best belong to.
}
impl Key {
/// All egui keys
pub const ALL: &'static [Self] = &[
Self::ArrowDown,
Self::ArrowLeft,
Self::ArrowRight,
Self::ArrowUp,
Self::Escape,
Self::Tab,
Self::Backspace,
Self::Enter,
Self::Space,
Self::Insert,
Self::Delete,
Self::Home,
Self::End,
Self::PageUp,
Self::PageDown,
// Punctuation:
Self::Colon,
Self::Comma,
Self::Minus,
Self::Period,
Self::PlusEquals,
Self::Semicolon,
// Digits:
Self::Num0,
Self::Num1,
Self::Num2,
Self::Num3,
Self::Num4,
Self::Num5,
Self::Num6,
Self::Num7,
Self::Num8,
Self::Num9,
// Letters:
Self::A,
Self::B,
Self::C,
Self::D,
Self::E,
Self::F,
Self::G,
Self::H,
Self::I,
Self::J,
Self::K,
Self::L,
Self::M,
Self::N,
Self::O,
Self::P,
Self::Q,
Self::R,
Self::S,
Self::T,
Self::U,
Self::V,
Self::W,
Self::X,
Self::Y,
Self::Z,
// Function keys:
Self::F1,
Self::F2,
Self::F3,
Self::F4,
Self::F5,
Self::F6,
Self::F7,
Self::F8,
Self::F9,
Self::F10,
Self::F11,
Self::F12,
Self::F13,
Self::F14,
Self::F15,
Self::F16,
Self::F17,
Self::F18,
Self::F19,
Self::F20,
];
/// Converts `"A"` to `Key::A`, `Space` to `Key::Space`, etc.
///
/// Makes sense for logical keys.
///
/// This will parse the output of both [`Self::name`] and [`Self::symbol_or_name`],
/// but will also parse single characters, so that both `"-"` and `"Minus"` will return `Key::Minus`.
///
/// This should support both the names generated in a web browser,
/// and by winit. Please test on both with `eframe`.
pub fn from_name(key: &str) -> Option<Self> {
Some(match key {
"ArrowDown" | "Down" | "" => Self::ArrowDown,
"ArrowLeft" | "Left" | "" => Self::ArrowLeft,
"ArrowRight" | "Right" | "" => Self::ArrowRight,
"ArrowUp" | "Up" | "" => Self::ArrowUp,
"Escape" | "Esc" => Self::Escape,
"Tab" => Self::Tab,
"Backspace" => Self::Backspace,
"Enter" | "Return" => Self::Enter,
"Space" | " " => Self::Space,
"Help" | "Insert" => Self::Insert,
"Delete" => Self::Delete,
"Home" => Self::Home,
"End" => Self::End,
"PageUp" => Self::PageUp,
"PageDown" => Self::PageDown,
"Colon" | ":" => Self::Colon,
"Comma" | "," => Self::Comma,
"Minus" | "-" | "" => Self::Minus,
"Period" | "." => Self::Period,
"Plus" | "+" | "Equals" | "=" => Self::PlusEquals,
"Semicolon" | ";" => Self::Semicolon,
"0" => Self::Num0,
"1" => Self::Num1,
"2" => Self::Num2,
"3" => Self::Num3,
"4" => Self::Num4,
"5" => Self::Num5,
"6" => Self::Num6,
"7" => Self::Num7,
"8" => Self::Num8,
"9" => Self::Num9,
"a" | "A" => Self::A,
"b" | "B" => Self::B,
"c" | "C" => Self::C,
"d" | "D" => Self::D,
"e" | "E" => Self::E,
"f" | "F" => Self::F,
"g" | "G" => Self::G,
"h" | "H" => Self::H,
"i" | "I" => Self::I,
"j" | "J" => Self::J,
"k" | "K" => Self::K,
"l" | "L" => Self::L,
"m" | "M" => Self::M,
"n" | "N" => Self::N,
"o" | "O" => Self::O,
"p" | "P" => Self::P,
"q" | "Q" => Self::Q,
"r" | "R" => Self::R,
"s" | "S" => Self::S,
"t" | "T" => Self::T,
"u" | "U" => Self::U,
"v" | "V" => Self::V,
"w" | "W" => Self::W,
"x" | "X" => Self::X,
"y" | "Y" => Self::Y,
"z" | "Z" => Self::Z,
"F1" => Self::F1,
"F2" => Self::F2,
"F3" => Self::F3,
"F4" => Self::F4,
"F5" => Self::F5,
"F6" => Self::F6,
"F7" => Self::F7,
"F8" => Self::F8,
"F9" => Self::F9,
"F10" => Self::F10,
"F11" => Self::F11,
"F12" => Self::F12,
"F13" => Self::F13,
"F14" => Self::F14,
"F15" => Self::F15,
"F16" => Self::F16,
"F17" => Self::F17,
"F18" => Self::F18,
"F19" => Self::F19,
"F20" => Self::F20,
_ => return None,
})
}
/// Emoji or name representing the key
pub fn symbol_or_name(self) -> &'static str {
// TODO(emilk): add support for more unicode symbols (see for instance https://wincent.com/wiki/Unicode_representations_of_modifier_keys).
@ -980,19 +1195,27 @@ impl Key {
Key::ArrowLeft => "Left",
Key::ArrowRight => "Right",
Key::ArrowUp => "Up",
Key::Escape => "Escape",
Key::Tab => "Tab",
Key::Backspace => "Backspace",
Key::Enter => "Enter",
Key::Space => "Space",
Key::Insert => "Insert",
Key::Delete => "Delete",
Key::Home => "Home",
Key::End => "End",
Key::PageUp => "PageUp",
Key::PageDown => "PageDown",
Key::Colon => "Colon",
Key::Comma => "Comma",
Key::Minus => "Minus",
Key::Period => "Period",
Key::PlusEquals => "Plus",
Key::Semicolon => "Semicolon",
Key::Num0 => "0",
Key::Num1 => "1",
Key::Num2 => "2",
@ -1003,6 +1226,7 @@ impl Key {
Key::Num7 => "7",
Key::Num8 => "8",
Key::Num9 => "9",
Key::A => "A",
Key::B => "B",
Key::C => "C",
@ -1053,6 +1277,31 @@ impl Key {
}
}
#[test]
fn test_key_from_name() {
assert_eq!(
Key::ALL.len(),
Key::F20 as usize + 1,
"Some keys are missing in Key::ALL"
);
for &key in Key::ALL {
let name = key.name();
assert_eq!(
Key::from_name(name),
Some(key),
"Failed to roundtrip {key:?} from name {name:?}"
);
let symbol = key.symbol_or_name();
assert_eq!(
Key::from_name(symbol),
Some(key),
"Failed to roundtrip {key:?} from symbol {symbol:?}"
);
}
}
// ----------------------------------------------------------------------------
/// A keyboard shortcut, e.g. `Ctrl+Alt+W`.

View File

@ -64,6 +64,21 @@ impl FullOutput {
}
}
/// Information about text being edited.
///
/// Useful for IME.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct IMEOutput {
/// Where the [`crate::TextEdit`] is located on screen.
pub rect: crate::Rect,
/// Where the cursor is.
///
/// This is a very thin rectangle.
pub cursor_rect: crate::Rect,
}
/// The non-rendering part of what egui emits each frame.
///
/// You can access (and modify) this with [`crate::Context::output`].
@ -98,10 +113,10 @@ pub struct PlatformOutput {
/// Use by `eframe` web to show/hide mobile keyboard and IME agent.
pub mutable_text_under_cursor: bool,
/// Screen-space position of text edit cursor (used for IME).
/// This is et if, and only if, the user is currently editing text.
///
/// Iff `Some`, the user is editing text.
pub text_cursor_pos: Option<crate::Pos2>,
/// Useful for IME.
pub ime: Option<IMEOutput>,
/// The difference in the widget tree since last frame.
///
@ -137,7 +152,7 @@ impl PlatformOutput {
copied_text,
mut events,
mutable_text_under_cursor,
text_cursor_pos,
ime,
#[cfg(feature = "accesskit")]
accesskit_update,
} = newer;
@ -151,7 +166,7 @@ impl PlatformOutput {
}
self.events.append(&mut events);
self.mutable_text_under_cursor = mutable_text_under_cursor;
self.text_cursor_pos = text_cursor_pos.or(self.text_cursor_pos);
self.ime = ime.or(self.ime);
#[cfg(feature = "accesskit")]
{

View File

@ -899,7 +899,8 @@ pub enum ViewportCommand {
/// The the window icon.
Icon(Option<Arc<IconData>>),
IMEPosition(Pos2),
/// Set the IME cursor editing area.
IMERect(crate::Rect),
IMEAllowed(bool),
IMEPurpose(IMEPurpose),

View File

@ -683,7 +683,7 @@ impl<'t> TextEdit<'t> {
paint_cursor_selection(ui, &painter, text_draw_pos, &galley, &cursor_range);
if text.is_mutable() {
let cursor_pos = paint_cursor_end(
let cursor_rect = paint_cursor_end(
ui,
row_height,
&painter,
@ -694,23 +694,14 @@ impl<'t> TextEdit<'t> {
let is_fully_visible = ui.clip_rect().contains_rect(rect); // TODO: remove this HACK workaround for https://github.com/emilk/egui/issues/1531
if (response.changed || selection_changed) && !is_fully_visible {
ui.scroll_to_rect(cursor_pos, None); // keep cursor in view
ui.scroll_to_rect(cursor_rect, None); // keep cursor in view
}
if interactive {
// eframe web uses `text_cursor_pos` when showing IME,
// so only set it when text is editable and visible!
// But `winit` and `egui_web` differs in how to set the
// position of IME.
if cfg!(target_arch = "wasm32") {
ui.ctx().output_mut(|o| {
o.text_cursor_pos = Some(cursor_pos.left_top());
});
} else {
ui.ctx().output_mut(|o| {
o.text_cursor_pos = Some(cursor_pos.left_bottom());
});
}
// For IME, so only set it when text is editable and visible!
ui.ctx().output_mut(|o| {
o.ime = Some(crate::output::IMEOutput { rect, cursor_rect });
});
}
}
}

View File

@ -53,7 +53,7 @@ image = ["dep:image"]
puffin = ["dep:puffin", "egui/puffin"]
## Support loading svg images.
svg = ["resvg", "tiny-skia", "usvg"]
svg = ["resvg"]
## Enable better syntax highlighting using [`syntect`](https://docs.rs/syntect).
syntect = ["dep:syntect"]
@ -93,8 +93,6 @@ syntect = { version = "5", optional = true, default-features = false, features =
# svg feature
resvg = { version = "0.28", optional = true, default-features = false }
tiny-skia = { version = "0.8", optional = true, default-features = false } # must be updated in lock-step with resvg
usvg = { version = "0.28", optional = true, default-features = false }
# http feature
ehttp = { version = "0.3.1", optional = true, default-features = false }

View File

@ -2,6 +2,9 @@
use egui::{mutex::Mutex, TextureOptions};
#[cfg(feature = "svg")]
use resvg::{tiny_skia, usvg};
#[cfg(feature = "svg")]
pub use usvg::FitTo;

View File

@ -1,10 +1,13 @@
use std::{mem::size_of, path::Path, sync::Arc};
use egui::{
ahash::HashMap,
load::{BytesPoll, ImageLoadResult, ImageLoader, ImagePoll, LoadError, SizeHint},
mutex::Mutex,
ColorImage,
};
use std::{mem::size_of, path::Path, sync::Arc};
use resvg::usvg;
type Entry = Result<Arc<ColorImage>, String>;

View File

@ -69,9 +69,9 @@ wasm-bindgen = "0.2"
[dev-dependencies]
glutin = "0.30" # examples/pure_glow
glutin = "0.31" # examples/pure_glow
raw-window-handle.workspace = true
glutin-winit = "0.3.0"
glutin-winit = "0.4.0"
[[example]]

View File

@ -19,7 +19,7 @@ impl GlutinWindowContext {
#[allow(unsafe_code)]
unsafe fn new(event_loop: &winit::event_loop::EventLoopWindowTarget<UserEvent>) -> Self {
use egui::NumExt;
use glutin::context::NotCurrentGlContextSurfaceAccessor;
use glutin::context::NotCurrentGlContext;
use glutin::display::GetGlDisplay;
use glutin::display::GlDisplay;
use glutin::prelude::GlSurface;
@ -42,7 +42,7 @@ impl GlutinWindowContext {
log::debug!("trying to get gl_config");
let (mut window, gl_config) =
glutin_winit::DisplayBuilder::new() // let glutin-winit helper crate handle the complex parts of opengl context creation
.with_preference(glutin_winit::ApiPrefence::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_preference(glutin_winit::ApiPreference::FallbackEgl) // https://github.com/emilk/egui/issues/2520#issuecomment-1367841150
.with_window_builder(Some(winit_window_builder.clone()))
.build(
event_loop,
@ -150,7 +150,9 @@ pub enum UserEvent {
fn main() {
let mut clear_color = [0.1, 0.1, 0.1];
let event_loop = winit::event_loop::EventLoopBuilder::<UserEvent>::with_user_event().build();
let event_loop = winit::event_loop::EventLoopBuilder::<UserEvent>::with_user_event()
.build()
.unwrap();
let (gl_window, gl) = create_display(&event_loop);
let gl = std::sync::Arc::new(gl);
@ -168,7 +170,7 @@ fn main() {
let mut repaint_delay = std::time::Duration::MAX;
event_loop.run(move |event, _, control_flow| {
let _ = event_loop.run(move |event, event_loop_window_target| {
let mut redraw = || {
let mut quit = false;
@ -182,18 +184,20 @@ fn main() {
});
});
*control_flow = if quit {
winit::event_loop::ControlFlow::Exit
} else if repaint_delay.is_zero() {
gl_window.window().request_redraw();
winit::event_loop::ControlFlow::Poll
} else if let Some(repaint_delay_instant) =
std::time::Instant::now().checked_add(repaint_delay)
{
winit::event_loop::ControlFlow::WaitUntil(repaint_delay_instant)
if quit {
event_loop_window_target.exit();
} else {
winit::event_loop::ControlFlow::Wait
};
event_loop_window_target.set_control_flow(if repaint_delay.is_zero() {
gl_window.window().request_redraw();
winit::event_loop::ControlFlow::Poll
} else if let Some(repaint_after_instant) =
std::time::Instant::now().checked_add(repaint_delay)
{
winit::event_loop::ControlFlow::WaitUntil(repaint_after_instant)
} else {
winit::event_loop::ControlFlow::Wait
});
}
{
unsafe {
@ -214,25 +218,20 @@ fn main() {
};
match event {
// Platform-dependent event handlers to workaround a winit bug
// See: https://github.com/rust-windowing/winit/issues/987
// See: https://github.com/rust-windowing/winit/issues/1619
winit::event::Event::RedrawEventsCleared if cfg!(target_os = "windows") => redraw(),
winit::event::Event::RedrawRequested(_) if !cfg!(target_os = "windows") => redraw(),
winit::event::Event::WindowEvent { event, .. } => {
use winit::event::WindowEvent;
if matches!(event, WindowEvent::CloseRequested | WindowEvent::Destroyed) {
*control_flow = winit::event_loop::ControlFlow::Exit;
event_loop_window_target.exit();
return;
}
if matches!(event, WindowEvent::RedrawRequested) {
redraw();
return;
}
if let winit::event::WindowEvent::Resized(physical_size) = &event {
gl_window.resize(*physical_size);
} else if let winit::event::WindowEvent::ScaleFactorChanged {
new_inner_size, ..
} = &event
{
gl_window.resize(**new_inner_size);
}
let event_response = egui_glow.on_window_event(gl_window.window(), &event);
@ -245,7 +244,7 @@ fn main() {
winit::event::Event::UserEvent(UserEvent::Redraw(delay)) => {
repaint_delay = delay;
}
winit::event::Event::LoopDestroyed => {
winit::event::Event::LoopExiting => {
egui_glow.destroy();
}
winit::event::Event::NewEvents(winit::event::StartCause::ResumeTimeReached {

View File

@ -58,7 +58,7 @@ impl EguiGlow {
pub fn on_window_event(
&mut self,
window: &winit::window::Window,
event: &winit::event::WindowEvent<'_>,
event: &winit::event::WindowEvent,
) -> EventResponse {
self.egui_winit.on_window_event(window, event)
}

View File

@ -34,28 +34,26 @@ deny = [
]
skip = [
{ name = "arrayvec" }, # old version via tiny-skiaz
{ name = "base64" }, # small crate, old version from usvg
{ name = "bitflags" }, # old 1.0 version via glutin, png, spirv, …
{ name = "glow" }, # TODO(@wumpf): Old version use for glow backend right now, newer for wgpu. Updating this trickles out to updating winit.
{ name = "glutin_wgl_sys" }, # TODO(@wumpf): Old version use for glow backend right now, newer for wgpu. Updating this trickles out to updating winit.
{ name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7
{ name = "memoffset" }, # tiny dependency
{ name = "nix" }, # old version via winit
{ name = "redox_syscall" }, # old version via winit
{ name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu.
{ name = "time" }, # old version pulled in by unmaintianed crate 'chrono'
{ name = "tiny-skia" }, # winit uses a different version from egui_extras (TODO(emilk): update egui_extras!)
{ name = "ttf-parser" }, # different versions pulled in by ab_glyph and usvg
{ name = "wayland-sys" }, # old version via winit
{ name = "windows_x86_64_msvc" }, # old version via glutin
{ name = "windows-sys" }, # old version via glutin
{ name = "windows" }, # old version via accesskit
{ name = "arrayvec" }, # old version via tiny-skiaz
{ name = "base64" }, # small crate, old version from usvg
{ name = "bitflags" }, # old 1.0 version via glutin, png, spirv, …
{ name = "glow" }, # TODO(@wumpf): updatere glow
{ name = "glutin_wgl_sys" }, # TODO(@wumpf): updatere glow
{ name = "libloading" }, # wgpu-hal itself depends on 0.8 while some of its dependencies, like ash and d3d12, depend on 0.7
{ name = "memoffset" }, # tiny dependency
{ name = "quick-xml" }, # old version via wayland-scanner
{ name = "redox_syscall" }, # old version via directories-next
{ name = "spin" }, # old version via ring through rusttls and other libraries, newer for wgpu.
{ name = "time" }, # old version pulled in by unmaintianed crate 'chrono'
{ name = "ttf-parser" }, # different versions pulled in by ab_glyph and usvg
{ name = "windows" }, # old version via accesskit_windows
]
skip-tree = [
{ name = "criterion" }, # dev-dependency
{ name = "foreign-types" }, # small crate. Old version via cocoa and core-graphics (winit).
{ name = "objc2" }, # old version via accesskit_macos
{ name = "rfd" }, # example dependency
{ name = "tiny-skia" }, # old version via old resvg in egui_extras - see https://github.com/emilk/egui/issues/3652
]

View File

@ -7,6 +7,8 @@ edition = "2021"
rust-version = "1.72"
publish = false
[features]
wgpu = ["eframe/wgpu"]
[dependencies]
eframe = { path = "../../crates/eframe", features = [