diff --git a/Cargo.lock b/Cargo.lock
index 1a2de3d90b24..02603be8660b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1288,7 +1288,7 @@ checksum = "4f94fa09c2aeea5b8839e414b7b841bf429fd25b9c522116ac97ee87856d88b2"
 [[package]]
 name = "ecolor"
 version = "0.20.0"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "bytemuck",
  "serde",
@@ -1297,7 +1297,7 @@ dependencies = [
 [[package]]
 name = "eframe"
 version = "0.20.1"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "bytemuck",
  "directories-next",
@@ -1324,7 +1324,7 @@ dependencies = [
 [[package]]
 name = "egui"
 version = "0.20.1"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "accesskit",
  "ahash 0.8.2",
@@ -1337,9 +1337,8 @@ dependencies = [
 
 [[package]]
 name = "egui-notify"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60cb7e66ecf925bd16a52e4bb3abde9211d0fbe21df6e84be7a9a61f4aa68ed4"
+version = "0.5.1"
+source = "git+https://github.com/rerun-io/egui-notify?rev=a158c2b81ca69ac78e3c61a705f478e8af76fd7d#a158c2b81ca69ac78e3c61a705f478e8af76fd7d"
 dependencies = [
  "egui",
 ]
@@ -1347,7 +1346,7 @@ dependencies = [
 [[package]]
 name = "egui-wgpu"
 version = "0.20.0"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "bytemuck",
  "epaint",
@@ -1361,7 +1360,7 @@ dependencies = [
 [[package]]
 name = "egui-winit"
 version = "0.20.1"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "arboard",
  "egui",
@@ -1376,9 +1375,8 @@ dependencies = [
 
 [[package]]
 name = "egui_dock"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f37e5eba9330fb607e0fcb8dae0b6ed0ea23d3645841cc53b8ead5d544600486"
+version = "0.3.1"
+source = "git+https://github.com/rerun-io/egui_dock?rev=1355a9f24e99518ef7b9546872a0f25ffe1f91d2#1355a9f24e99518ef7b9546872a0f25ffe1f91d2"
 dependencies = [
  "egui",
  "serde",
@@ -1387,7 +1385,7 @@ dependencies = [
 [[package]]
 name = "egui_extras"
 version = "0.20.0"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "egui",
  "serde",
@@ -1397,7 +1395,7 @@ dependencies = [
 [[package]]
 name = "egui_glow"
 version = "0.20.1"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "bytemuck",
  "egui",
@@ -1419,7 +1417,7 @@ checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
 [[package]]
 name = "emath"
 version = "0.20.0"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "bytemuck",
  "serde",
@@ -1465,7 +1463,7 @@ dependencies = [
 [[package]]
 name = "epaint"
 version = "0.20.0"
-source = "git+https://github.com/emilk/egui?rev=eee4cf6a824e21141bb893aae2aea7849c0e9e03#eee4cf6a824e21141bb893aae2aea7849c0e9e03"
+source = "git+https://github.com/emilk/egui?rev=8ce0e1c5206780e76234842b94ceb0edf5bb8b75#8ce0e1c5206780e76234842b94ceb0edf5bb8b75"
 dependencies = [
  "ab_glyph",
  "ahash 0.8.2",
diff --git a/Cargo.toml b/Cargo.toml
index 598dbe8c0c33..b043413756c6 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -67,15 +67,14 @@ arrow2_convert = { git = "https://github.com/rerun-io/arrow2-convert", rev = "7e
 # arrow2 = { path = "../arrow2" }
 # arrow2_convert = { path = "../arrow2-convert/arrow2_convert" }
 
-# 2022-01-24: allow hiding button backgrounds
-ecolor = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-eframe = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-egui = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-egui_extras = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-egui-wgpu = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-emath = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-epaint = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893aae2aea7849c0e9e03" }
-
+# 2023-01-25 - egui deadlock fix
+ecolor = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
+eframe = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
+egui = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
+egui_extras = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
+egui-wgpu = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
+emath = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
+epaint = { git = "https://github.com/emilk/egui", rev = "8ce0e1c5206780e76234842b94ceb0edf5bb8b75" }
 # ecolor = { path = "../../egui/crates/ecolor" }
 # eframe = { path = "../../egui/crates/eframe" }
 # egui = { path = "../../egui/crates/egui" }
@@ -84,7 +83,13 @@ epaint = { git = "https://github.com/emilk/egui", rev = "eee4cf6a824e21141bb893a
 # emath = { path = "../../egui/crates/emath" }
 # epaint = { path = "../../egui/crates/epaint" }
 
-# 2022-10-12 - Alpha to coverage support for GLES
+# Forks of 3rd party egui crates, tracking latest egui/master:
+egui_dock = { git = "https://github.com/rerun-io/egui_dock", rev = "1355a9f24e99518ef7b9546872a0f25ffe1f91d2" }     # https://github.com/Adanos020/egui_dock/pull/93
+egui-notify = { git = "https://github.com/rerun-io/egui-notify", rev = "a158c2b81ca69ac78e3c61a705f478e8af76fd7d" } # https://github.com/ItsEthra/egui-notify/pull/10
+# egui_dock = { path = "../../forks/egui_dock" }
+# egui-notify = { path = "../../forks/egui-notify" }
+
+# 2023-10-12 - Alpha to coverage support for GLES
 wgpu = { git = "https://github.com/gfx-rs/wgpu.git", ref = "a377ae2b7fe6c1c9412751166f0917e617164e49" }
 wgpu-core = { git = "https://github.com/gfx-rs/wgpu.git", ref = "a377ae2b7fe6c1c9412751166f0917e617164e49" }
 # wgpu = { path = "../wgpu/wgpu" }
diff --git a/crates/re_ui/src/command.rs b/crates/re_ui/src/command.rs
index 6fcf3b747131..e6e4c3d152d4 100644
--- a/crates/re_ui/src/command.rs
+++ b/crates/re_ui/src/command.rs
@@ -167,20 +167,21 @@ impl Command {
     pub fn listen_for_kb_shortcut(egui_ctx: &egui::Context) -> Option<Command> {
         use strum::IntoEnumIterator as _;
 
-        let anything_has_focus = egui_ctx.memory().focus().is_some();
+        let anything_has_focus = egui_ctx.memory(|mem| mem.focus().is_some());
         if anything_has_focus {
             return None; // e.g. we're typing in a TextField
         }
 
-        let mut input = egui_ctx.input_mut();
-        for command in Command::iter() {
-            if let Some(kb_shortcut) = command.kb_shortcut() {
-                if input.consume_shortcut(&kb_shortcut) {
-                    return Some(command);
+        egui_ctx.input_mut(|input| {
+            for command in Command::iter() {
+                if let Some(kb_shortcut) = command.kb_shortcut() {
+                    if input.consume_shortcut(&kb_shortcut) {
+                        return Some(command);
+                    }
                 }
             }
-        }
-        None
+            None
+        })
     }
 
     /// Show this command as a menu-button.
diff --git a/crates/re_ui/src/command_palette.rs b/crates/re_ui/src/command_palette.rs
index cce2e16d4da3..d78016b161fa 100644
--- a/crates/re_ui/src/command_palette.rs
+++ b/crates/re_ui/src/command_palette.rs
@@ -19,15 +19,13 @@ impl CommandPalette {
     /// Show the command palette, if it is visible.
     #[must_use = "Returns the command that was selected"]
     pub fn show(&mut self, egui_ctx: &egui::Context) -> Option<Command> {
-        self.visible &= !egui_ctx
-            .input_mut()
-            .consume_key(Default::default(), Key::Escape);
+        self.visible &= !egui_ctx.input_mut(|i| i.consume_key(Default::default(), Key::Escape));
         if !self.visible {
             self.query.clear();
             return None;
         }
 
-        let screen_rect = egui_ctx.input().screen_rect();
+        let screen_rect = egui_ctx.screen_rect();
         let width = 300.0;
         let max_height = 320.0.at_most(screen_rect.height());
 
@@ -43,7 +41,7 @@ impl CommandPalette {
     #[must_use = "Returns the command that was selected"]
     fn window_content_ui(&mut self, ui: &mut egui::Ui) -> Option<Command> {
         // Check _before_ we add the `TextEdit`, so it doesn't steal it.
-        let enter_pressed = ui.input_mut().consume_key(Default::default(), Key::Enter);
+        let enter_pressed = ui.input_mut(|i| i.consume_key(Default::default(), Key::Enter));
 
         let text_response = ui.add(
             egui::TextEdit::singleline(&mut self.query)
@@ -78,8 +76,8 @@ impl CommandPalette {
         enter_pressed: bool,
         mut scroll_to_selected_alternative: bool,
     ) -> Option<Command> {
-        scroll_to_selected_alternative |= ui.input().key_pressed(Key::ArrowUp);
-        scroll_to_selected_alternative |= ui.input().key_pressed(Key::ArrowDown);
+        scroll_to_selected_alternative |= ui.input(|i| i.key_pressed(Key::ArrowUp));
+        scroll_to_selected_alternative |= ui.input(|i| i.key_pressed(Key::ArrowDown));
 
         let query = self.query.to_lowercase();
 
@@ -159,12 +157,10 @@ impl CommandPalette {
 
         // Move up/down in the list:
         self.selected_alternative = self.selected_alternative.saturating_sub(
-            ui.input_mut()
-                .count_and_consume_key(Default::default(), Key::ArrowUp),
+            ui.input_mut(|i| i.count_and_consume_key(Default::default(), Key::ArrowUp)),
         );
         self.selected_alternative = self.selected_alternative.saturating_add(
-            ui.input_mut()
-                .count_and_consume_key(Default::default(), Key::ArrowDown),
+            ui.input_mut(|i| i.count_and_consume_key(Default::default(), Key::ArrowDown)),
         );
 
         self.selected_alternative = self
diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs
index 3d2e89459989..1f2b3f5e9c79 100644
--- a/crates/re_ui/src/lib.rs
+++ b/crates/re_ui/src/lib.rs
@@ -158,7 +158,7 @@ impl ReUi {
     /// Paint a watermark
     pub fn paint_watermark(&self) {
         let logo = self.rerun_logo();
-        let screen_rect = self.egui_ctx.input().screen_rect;
+        let screen_rect = self.egui_ctx.screen_rect();
         let size = logo.size_vec2();
         let rect = Align2::RIGHT_BOTTOM
             .align_size_within_rect(size, screen_rect)
diff --git a/crates/re_viewer/src/app.rs b/crates/re_viewer/src/app.rs
index 89e156b4f2c5..ead291c723e7 100644
--- a/crates/re_viewer/src/app.rs
+++ b/crates/re_viewer/src/app.rs
@@ -492,8 +492,10 @@ impl eframe::App for App {
 
         self.run_pending_commands(egui_ctx, frame);
 
-        self.frame_time_history
-            .add(egui_ctx.input().time, frame_start.elapsed().as_secs_f32());
+        self.frame_time_history.add(
+            egui_ctx.input(|i| i.time),
+            frame_start.elapsed().as_secs_f32(),
+        );
     }
 }
 
@@ -667,7 +669,7 @@ impl App {
 
         // Keep the style:
         let style = egui_ctx.style();
-        *egui_ctx.memory() = Default::default();
+        egui_ctx.memory_mut(|mem| *mem = Default::default());
         egui_ctx.set_style((*style).clone());
     }
 
@@ -684,13 +686,13 @@ impl App {
         preview_files_being_dropped(egui_ctx);
 
         // Collect dropped files:
-        if egui_ctx.input().raw.dropped_files.len() > 2 {
+        if egui_ctx.input(|i| i.raw.dropped_files.len()) > 2 {
             rfd::MessageDialog::new()
                 .set_level(rfd::MessageLevel::Error)
                 .set_description("Can only load one file at a time")
                 .show();
         }
-        if let Some(file) = egui_ctx.input().raw.dropped_files.first() {
+        if let Some(file) = egui_ctx.input(|i| i.raw.dropped_files.first().cloned()) {
             if let Some(bytes) = &file.bytes {
                 let mut bytes: &[u8] = &(*bytes)[..];
                 if let Some(log_db) = load_file_contents(&file.name, &mut bytes) {
@@ -715,22 +717,24 @@ fn preview_files_being_dropped(egui_ctx: &egui::Context) {
     use egui::{Align2, Color32, Id, LayerId, Order, TextStyle};
 
     // Preview hovering files:
-    if !egui_ctx.input().raw.hovered_files.is_empty() {
+    if !egui_ctx.input(|i| i.raw.hovered_files.is_empty()) {
         use std::fmt::Write as _;
 
         let mut text = "Drop to load:\n".to_owned();
-        for file in &egui_ctx.input().raw.hovered_files {
-            if let Some(path) = &file.path {
-                write!(text, "\n{}", path.display()).ok();
-            } else if !file.mime.is_empty() {
-                write!(text, "\n{}", file.mime).ok();
+        egui_ctx.input(|input| {
+            for file in &input.raw.hovered_files {
+                if let Some(path) = &file.path {
+                    write!(text, "\n{}", path.display()).ok();
+                } else if !file.mime.is_empty() {
+                    write!(text, "\n{}", file.mime).ok();
+                }
             }
-        }
+        });
 
         let painter =
             egui_ctx.layer_painter(LayerId::new(Order::Foreground, Id::new("file_drop_target")));
 
-        let screen_rect = egui_ctx.input().screen_rect();
+        let screen_rect = egui_ctx.screen_rect();
         painter.rect_filled(screen_rect, 0.0, Color32::from_black_alpha(192));
         painter.text(
             screen_rect.center(),
diff --git a/crates/re_viewer/src/misc/time_control.rs b/crates/re_viewer/src/misc/time_control.rs
index ec833ef25ad6..ac3bc9de4418 100644
--- a/crates/re_viewer/src/misc/time_control.rs
+++ b/crates/re_viewer/src/misc/time_control.rs
@@ -106,7 +106,7 @@ impl TimeControl {
             return;
         };
 
-        let dt = egui_ctx.input().stable_dt.at_most(0.1) * self.speed;
+        let dt = egui_ctx.input(|i| i.stable_dt).at_most(0.1) * self.speed;
 
         let state = self
             .states
diff --git a/crates/re_viewer/src/misc/viewer_context.rs b/crates/re_viewer/src/misc/viewer_context.rs
index e72aad838648..e421dbca2539 100644
--- a/crates/re_viewer/src/misc/viewer_context.rs
+++ b/crates/re_viewer/src/misc/viewer_context.rs
@@ -241,7 +241,7 @@ impl<'a> ViewerContext<'a> {
     pub fn select_hovered_on_click(&mut self, response: &egui::Response) {
         if response.clicked() {
             let hovered = self.rec_cfg.selection_state.hovered().clone();
-            if response.ctx.input().modifiers.command {
+            if response.ctx.input(|i| i.modifiers.command) {
                 self.rec_cfg
                     .selection_state
                     .toggle_selection(hovered.into_iter());
diff --git a/crates/re_viewer/src/remote_viewer_app.rs b/crates/re_viewer/src/remote_viewer_app.rs
index b0c2e928087e..a077fa0183ce 100644
--- a/crates/re_viewer/src/remote_viewer_app.rs
+++ b/crates/re_viewer/src/remote_viewer_app.rs
@@ -78,7 +78,7 @@ impl eframe::App for RemoteViewerApp {
             ui.horizontal(|ui| {
                 ui.label("URL:");
                 if ui.text_edit_singleline(&mut self.url).lost_focus()
-                    && ui.input().key_pressed(egui::Key::Enter)
+                    && ui.input(|i| i.key_pressed(egui::Key::Enter))
                 {
                     if let Some(storage) = frame.storage_mut() {
                         if let Some((_, mut app)) = self.app.take() {
diff --git a/crates/re_viewer/src/ui/memory_panel.rs b/crates/re_viewer/src/ui/memory_panel.rs
index d8a625115be9..6fcdaa413448 100644
--- a/crates/re_viewer/src/ui/memory_panel.rs
+++ b/crates/re_viewer/src/ui/memory_panel.rs
@@ -290,7 +290,9 @@ impl MemoryPanel {
                                 .on_hover_text("Click to copy callstack to clipboard")
                                 .clicked()
                             {
-                                ui.output().copied_text = callstack.readable_backtrace.to_string();
+                                ui.output_mut(|o| {
+                                    o.copied_text = callstack.readable_backtrace.to_string();
+                                });
                             }
                         }
                     });
diff --git a/crates/re_viewer/src/ui/time_panel.rs b/crates/re_viewer/src/ui/time_panel.rs
index eafb11241688..1f9f2b663325 100644
--- a/crates/re_viewer/src/ui/time_panel.rs
+++ b/crates/re_viewer/src/ui/time_panel.rs
@@ -576,7 +576,7 @@ fn show_data_over_time(
     let points_per_time = time_ranges_ui.points_per_time().unwrap_or(f32::INFINITY);
     let max_stretch_length_in_time = 1.0 / points_per_time as f64; // TODO(emilk)
 
-    let pointer_pos = ui.input().pointer.hover_pos();
+    let pointer_pos = ui.input(|i| i.pointer.hover_pos());
 
     let hovered_color = ui.visuals().widgets.hovered.text_color();
     let inactive_color = if is_selected {
@@ -715,7 +715,7 @@ fn show_data_over_time(
                 ctx.rec_cfg.time_ctrl.set_time(hovered_time);
                 ctx.rec_cfg.time_ctrl.pause();
             }
-        } else if !ui.ctx().memory().is_anything_being_dragged() {
+        } else if !ui.ctx().memory(|mem| mem.is_anything_being_dragged()) {
             show_msg_ids_tooltip(ctx, ui.ctx(), &hovered_messages);
         }
     }
@@ -957,7 +957,7 @@ fn interact_with_streams_rect(
     full_rect: &Rect,
     streams_rect: &Rect,
 ) -> egui::Response {
-    let pointer_pos = ui.input().pointer.hover_pos();
+    let pointer_pos = ui.input(|i| i.pointer.hover_pos());
 
     let mut delta_x = 0.0;
     let mut zoom_factor = 1.0;
@@ -967,8 +967,10 @@ fn interact_with_streams_rect(
     let full_rect_hovered =
         pointer_pos.map_or(false, |pointer_pos| full_rect.contains(pointer_pos));
     if full_rect_hovered {
-        delta_x += ui.input().scroll_delta.x;
-        zoom_factor *= ui.input().zoom_delta_2d().x;
+        ui.input(|input| {
+            delta_x += input.scroll_delta.x;
+            zoom_factor *= input.zoom_delta_2d().x;
+        });
     }
 
     // We only check for drags in the streams rect,
@@ -981,7 +983,7 @@ fn interact_with_streams_rect(
     );
     if response.dragged_by(PointerButton::Primary) {
         delta_x += response.drag_delta().x;
-        ui.output().cursor_icon = CursorIcon::AllScroll;
+        ui.ctx().set_cursor_icon(CursorIcon::AllScroll);
     }
     if response.dragged_by(PointerButton::Secondary) {
         zoom_factor *= (response.drag_delta().y * 0.01).exp();
@@ -1078,7 +1080,7 @@ fn loop_selection_ui(
 
     let is_active = time_ctrl.looping == Looping::Selection;
 
-    let pointer_pos = ui.input().pointer.hover_pos();
+    let pointer_pos = ui.input(|i| i.pointer.hover_pos());
     let is_pointer_in_timeline =
         pointer_pos.map_or(false, |pointer_pos| timeline_rect.contains(pointer_pos));
 
@@ -1152,7 +1154,7 @@ fn loop_selection_ui(
                     .on_hover_and_drag_cursor(CursorIcon::ResizeEast);
 
                 // Use "smart_aim" to find a natural length of the time interval
-                let aim_radius = ui.input().aim_radius();
+                let aim_radius = ui.input(|i| i.aim_radius());
                 use egui::emath::smart_aim::best_in_range_f64;
 
                 if left_response.dragged() {
@@ -1172,7 +1174,7 @@ fn loop_selection_ui(
 
                         if selected_range.min > selected_range.max {
                             std::mem::swap(&mut selected_range.min, &mut selected_range.max);
-                            ui.memory().set_dragged_id(right_edge_id);
+                            ui.memory_mut(|mem| mem.set_dragged_id(right_edge_id));
                         }
 
                         time_ctrl.set_loop_selection(selected_range);
@@ -1197,7 +1199,7 @@ fn loop_selection_ui(
 
                         if selected_range.min > selected_range.max {
                             std::mem::swap(&mut selected_range.min, &mut selected_range.max);
-                            ui.memory().set_dragged_id(left_edge_id);
+                            ui.memory_mut(|mem| mem.set_dragged_id(left_edge_id));
                         }
 
                         time_ctrl.set_loop_selection(selected_range);
@@ -1219,8 +1221,9 @@ fn loop_selection_ui(
                         let min_x = time_ranges_ui.x_from_time(selected_range.min)?;
                         let max_x = time_ranges_ui.x_from_time(selected_range.max)?;
 
-                        let min_x = min_x + ui.input().pointer.delta().x;
-                        let max_x = max_x + ui.input().pointer.delta().x;
+                        let pointer_delta = ui.input(|i| i.pointer.delta());
+                        let min_x = min_x + pointer_delta.x;
+                        let max_x = max_x + pointer_delta.x;
 
                         let min_time = time_ranges_ui.time_from_x(min_x)?;
                         let max_time = time_ranges_ui.time_from_x(max_x)?;
@@ -1237,7 +1240,7 @@ fn loop_selection_ui(
                         }
 
                         time_ctrl.set_loop_selection(new_range);
-                        if ui.input().pointer.is_moving() {
+                        if ui.input(|i| i.pointer.is_moving()) {
                             time_ctrl.looping = Looping::Selection;
                         }
                         Some(())
@@ -1249,16 +1252,15 @@ fn loop_selection_ui(
 
     // Start new selection?
     if let Some(pointer_pos) = pointer_pos {
-        let is_anything_being_dragged = ui.memory().is_anything_being_dragged();
+        let is_anything_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged());
         if is_pointer_in_timeline
             && !is_anything_being_dragged
-            && ui.input().pointer.primary_down()
-            && ui.input().modifiers.shift_only()
+            && ui.input(|i| i.pointer.primary_down() && i.modifiers.shift_only())
         {
             if let Some(time) = time_ranges_ui.time_from_x(pointer_pos.x) {
                 time_ctrl.set_loop_selection(TimeRangeF::point(time));
                 time_ctrl.looping = Looping::Selection;
-                ui.memory().set_dragged_id(right_edge_id);
+                ui.memory_mut(|mem| mem.set_dragged_id(right_edge_id));
             }
         }
     }
@@ -1346,7 +1348,7 @@ fn time_marker_ui(
 ) {
     // timeline_rect: top part with the second ticks and time marker
 
-    let pointer_pos = ui.input().pointer.hover_pos();
+    let pointer_pos = ui.input(|i| i.pointer.hover_pos());
 
     // ------------------------------------------------
 
@@ -1356,9 +1358,9 @@ fn time_marker_ui(
 
     let timeline_cursor_icon = CursorIcon::ResizeHorizontal;
 
-    let is_hovering_the_loop_selection = ui.output().cursor_icon != CursorIcon::Default; // A kind of hacky proxy
+    let is_hovering_the_loop_selection = ui.output(|o| o.cursor_icon) != CursorIcon::Default; // A kind of hacky proxy
 
-    let is_anything_being_dragged = ui.memory().is_anything_being_dragged();
+    let is_anything_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged());
 
     let interact_radius = ui.style().interaction.resize_grab_radius_side;
 
@@ -1416,11 +1418,11 @@ fn time_marker_ui(
                 timeline_rect.top()..=ui.max_rect().bottom(),
                 ui.visuals().widgets.noninteractive.bg_stroke,
             );
-            ui.output().cursor_icon = timeline_cursor_icon; // preview!
+            ui.ctx().set_cursor_icon(timeline_cursor_icon); // preview!
         }
 
         // Click to move time here:
-        if ui.input().pointer.primary_down()
+        if ui.input(|i| i.pointer.primary_down())
             && is_pointer_in_timeline_rect
             && !is_anything_being_dragged
             && !is_hovering_the_loop_selection
@@ -1429,7 +1431,7 @@ fn time_marker_ui(
                 let time = time_ranges_ui.clamp_time(time);
                 time_ctrl.set_time(time);
                 time_ctrl.pause();
-                ui.memory().set_dragged_id(time_drag_id);
+                ui.memory_mut(|mem| mem.set_dragged_id(time_drag_id));
             }
         }
     }
@@ -1847,7 +1849,7 @@ fn paint_time_range_ticks(
             }
 
             paint_ticks(
-                &ui.fonts(),
+                ui.ctx(),
                 ui.visuals().dark_mode,
                 &font_id,
                 rect,
@@ -1862,7 +1864,7 @@ fn paint_time_range_ticks(
                 i * 10
             }
             paint_ticks(
-                &ui.fonts(),
+                ui.ctx(),
                 ui.visuals().dark_mode,
                 &font_id,
                 rect,
@@ -1877,7 +1879,7 @@ fn paint_time_range_ticks(
 
 #[allow(clippy::too_many_arguments)]
 fn paint_ticks(
-    fonts: &egui::epaint::Fonts,
+    egui_ctx: &egui::Context,
     dark_mode: bool,
     font_id: &egui::FontId,
     canvas: &Rect,
@@ -1983,14 +1985,16 @@ fn paint_ticks(
                 let text = format_tick(current_time);
                 let text_x = line_x + 4.0;
 
-                shapes.push(egui::Shape::text(
-                    fonts,
-                    pos2(text_x, lerp(canvas.y_range(), 0.5)),
-                    Align2::LEFT_CENTER,
-                    &text,
-                    font_id.clone(),
-                    text_color,
-                ));
+                egui_ctx.fonts(|fonts| {
+                    shapes.push(egui::Shape::text(
+                        fonts,
+                        pos2(text_x, lerp(canvas.y_range(), 0.5)),
+                        Align2::LEFT_CENTER,
+                        &text,
+                        font_id.clone(),
+                        text_color,
+                    ));
+                });
             }
         }
 
diff --git a/crates/re_viewer/src/ui/view_spatial/eye.rs b/crates/re_viewer/src/ui/view_spatial/eye.rs
index e9bd64a937ab..8fa7916bf219 100644
--- a/crates/re_viewer/src/ui/view_spatial/eye.rs
+++ b/crates/re_viewer/src/ui/view_spatial/eye.rs
@@ -282,9 +282,9 @@ impl OrbitEye {
 
         if response.hovered() {
             self.keyboard_navigation(&response.ctx);
-            let input = response.ctx.input();
-
-            let factor = input.zoom_delta() * (input.scroll_delta.y / 200.0).exp();
+            let factor = response
+                .ctx
+                .input(|i| i.zoom_delta() * (i.scroll_delta.y / 200.0).exp());
             if factor != 1.0 {
                 self.orbit_radius /= factor;
                 did_interact = true;
@@ -296,37 +296,38 @@ impl OrbitEye {
 
     /// Listen to WSAD and QE to move the eye.
     fn keyboard_navigation(&mut self, egui_ctx: &egui::Context) {
-        let anything_has_focus = egui_ctx.memory().focus().is_some();
+        let anything_has_focus = egui_ctx.memory(|mem| mem.focus().is_some());
         if anything_has_focus {
             return; // e.g. we're typing in a TextField
         }
 
-        let input = egui_ctx.input();
-        let dt = input.stable_dt.at_most(0.1);
-
-        // X=right, Y=up, Z=back
-        let mut local_movement = Vec3::ZERO;
-        local_movement.z -= input.key_down(egui::Key::W) as i32 as f32;
-        local_movement.z += input.key_down(egui::Key::S) as i32 as f32;
-        local_movement.x -= input.key_down(egui::Key::A) as i32 as f32;
-        local_movement.x += input.key_down(egui::Key::D) as i32 as f32;
-        local_movement.y -= input.key_down(egui::Key::Q) as i32 as f32;
-        local_movement.y += input.key_down(egui::Key::E) as i32 as f32;
-        local_movement = local_movement.normalize_or_zero();
-
-        let speed = self.orbit_radius
-            * (if input.modifiers.shift { 10.0 } else { 1.0 })
-            * (if input.modifiers.ctrl { 0.1 } else { 1.0 });
-        let world_movement = self.world_from_view_rot * (speed * local_movement);
-
-        self.velocity = egui::lerp(
-            self.velocity..=world_movement,
-            egui::emath::exponential_smooth_factor(0.90, 0.2, dt),
-        );
-        self.orbit_center += self.velocity * dt;
-
-        drop(input); // avoid deadlock on request_repaint
-        if local_movement != Vec3::ZERO || self.velocity.length() > 0.01 * speed {
+        let requires_repaint = egui_ctx.input(|input| {
+            let dt = input.stable_dt.at_most(0.1);
+
+            // X=right, Y=up, Z=back
+            let mut local_movement = Vec3::ZERO;
+            local_movement.z -= input.key_down(egui::Key::W) as i32 as f32;
+            local_movement.z += input.key_down(egui::Key::S) as i32 as f32;
+            local_movement.x -= input.key_down(egui::Key::A) as i32 as f32;
+            local_movement.x += input.key_down(egui::Key::D) as i32 as f32;
+            local_movement.y -= input.key_down(egui::Key::Q) as i32 as f32;
+            local_movement.y += input.key_down(egui::Key::E) as i32 as f32;
+            local_movement = local_movement.normalize_or_zero();
+
+            let speed = self.orbit_radius
+                * (if input.modifiers.shift { 10.0 } else { 1.0 })
+                * (if input.modifiers.ctrl { 0.1 } else { 1.0 });
+            let world_movement = self.world_from_view_rot * (speed * local_movement);
+
+            self.velocity = egui::lerp(
+                self.velocity..=world_movement,
+                egui::emath::exponential_smooth_factor(0.90, 0.2, dt),
+            );
+            self.orbit_center += self.velocity * dt;
+            local_movement != Vec3::ZERO || self.velocity.length() > 0.01 * speed
+        });
+
+        if requires_repaint {
             egui_ctx.request_repaint();
         }
     }
diff --git a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs
index 20bc40c60476..34472b345655 100644
--- a/crates/re_viewer/src/ui/view_spatial/ui_2d.rs
+++ b/crates/re_viewer/src/ui/view_spatial/ui_2d.rs
@@ -106,7 +106,7 @@ impl View2DState {
         available_size: Vec2,
     ) {
         // Determine if we are zooming
-        let zoom_delta = response.ctx.input().zoom_delta();
+        let zoom_delta = response.ctx.input(|i| i.zoom_delta());
         let hovered_zoom = if response.hovered() && zoom_delta != 1.0 {
             Some(zoom_delta)
         } else {
@@ -141,7 +141,7 @@ impl View2DState {
                     let new_scale = scale * input_zoom;
 
                     // Adjust for mouse location while executing zoom
-                    if let Some(hover_pos) = response.ctx.input().pointer.hover_pos() {
+                    if let Some(hover_pos) = response.ctx.input(|i| i.pointer.hover_pos()) {
                         let zoom_loc = ui_to_space.transform_pos(hover_pos);
 
                         // Space-units under the cursor will shift based on distance from center
@@ -479,22 +479,24 @@ fn create_labels(
         };
 
         let font_id = TextStyle::Body.resolve(parent_ui.style());
-        let galley = parent_ui.fonts().layout_job({
-            egui::text::LayoutJob {
-                sections: vec![egui::text::LayoutSection {
-                    leading_space: 0.0,
-                    byte_range: 0..label.text.len(),
-                    format: TextFormat::simple(font_id, label.color),
-                }],
-                text: label.text.clone(),
-                wrap: TextWrapping {
-                    max_width: wrap_width,
+        let galley = parent_ui.fonts(|fonts| {
+            fonts.layout_job({
+                egui::text::LayoutJob {
+                    sections: vec![egui::text::LayoutSection {
+                        leading_space: 0.0,
+                        byte_range: 0..label.text.len(),
+                        format: TextFormat::simple(font_id, label.color),
+                    }],
+                    text: label.text.clone(),
+                    wrap: TextWrapping {
+                        max_width: wrap_width,
+                        ..Default::default()
+                    },
+                    break_on_newline: true,
+                    halign: Align::Center,
                     ..Default::default()
-                },
-                break_on_newline: true,
-                halign: Align::Center,
-                ..Default::default()
-            }
+                }
+            })
         });
 
         let text_rect =
@@ -607,7 +609,8 @@ fn show_projections_from_3d_space(
 
                     let text = format!("Depth: {:.3} m", pos_2d.z);
                     let font_id = egui::TextStyle::Body.resolve(ui.style());
-                    let galley = ui.fonts().layout_no_wrap(text, font_id, Color32::WHITE);
+                    let galley =
+                        ui.fonts(|fonts| fonts.layout_no_wrap(text, font_id, Color32::WHITE));
                     let rect = Align2::CENTER_TOP.anchor_rect(Rect::from_min_size(
                         pos_in_ui + vec2(0.0, 5.0),
                         galley.size(),
diff --git a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs
index a66abf081b62..00e67e3123a3 100644
--- a/crates/re_viewer/src/ui/view_spatial/ui_3d.rs
+++ b/crates/re_viewer/src/ui/view_spatial/ui_3d.rs
@@ -106,14 +106,14 @@ impl View3DState {
 
         if self.spin {
             orbit_camera.rotate(egui::vec2(
-                -response.ctx.input().stable_dt.at_most(0.1) * 150.0,
+                -response.ctx.input(|i| i.stable_dt).at_most(0.1) * 150.0,
                 0.0,
             ));
             response.ctx.request_repaint();
         }
 
         if let Some(cam_interpolation) = &mut self.eye_interpolation {
-            cam_interpolation.elapsed_time += response.ctx.input().stable_dt.at_most(0.1);
+            cam_interpolation.elapsed_time += response.ctx.input(|i| i.stable_dt).at_most(0.1);
 
             let t = cam_interpolation.elapsed_time / cam_interpolation.target_time;
             let t = t.clamp(0.0, 1.0);
@@ -318,7 +318,7 @@ pub fn view_3d(
     let eye = orbit_eye.to_eye();
 
     if did_interact_with_eye {
-        state.state_3d.last_eye_interact_time = ui.input().time;
+        state.state_3d.last_eye_interact_time = ui.input(|i| i.time);
         state.state_3d.eye_interpolation = None;
         state.state_3d.tracked_camera = None;
     }
@@ -453,7 +453,7 @@ pub fn view_3d(
 
     {
         let orbit_center_alpha = egui::remap_clamp(
-            ui.input().time - state.state_3d.last_eye_interact_time,
+            ui.input(|i| i.time) - state.state_3d.last_eye_interact_time,
             0.0..=0.4,
             0.7..=0.0,
         ) as f32;
@@ -530,12 +530,14 @@ fn paint_view(
 
             let font_id = egui::TextStyle::Monospace.resolve(ui.style());
 
-            let galley = ui.fonts().layout(
-                (*label.text).to_owned(),
-                font_id,
-                ui.style().visuals.text_color(),
-                100.0,
-            );
+            let galley = ui.fonts(|fonts| {
+                fonts.layout(
+                    (*label.text).to_owned(),
+                    font_id,
+                    ui.style().visuals.text_color(),
+                    100.0,
+                )
+            });
 
             let text_rect = egui::Align2::CENTER_TOP.anchor_rect(egui::Rect::from_min_size(
                 egui::pos2(pos_in_ui.x, pos_in_ui.y),
diff --git a/crates/re_viewer/src/ui/view_tensor/tensor_dimension_mapper.rs b/crates/re_viewer/src/ui/view_tensor/tensor_dimension_mapper.rs
index 5bbfa063e54f..4fdff41e8889 100644
--- a/crates/re_viewer/src/ui/view_tensor/tensor_dimension_mapper.rs
+++ b/crates/re_viewer/src/ui/view_tensor/tensor_dimension_mapper.rs
@@ -78,21 +78,21 @@ fn tensor_dimension_ui(
                 ui.colored_label(ui.visuals().widgets.inactive.fg_stroke.color, label_text);
             });
 
-            if ui.memory().is_being_dragged(dim_ui_id) {
+            if ui.memory(|mem| mem.is_being_dragged(dim_ui_id)) {
                 *drop_source = location;
             }
         }
     })
     .response;
 
-    let is_being_dragged = ui.memory().is_anything_being_dragged();
+    let is_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged());
     if is_being_dragged && response.hovered() {
         *drop_target = location;
     }
 }
 
 fn drag_source_ui(ui: &mut egui::Ui, id: egui::Id, body: impl FnOnce(&mut egui::Ui)) {
-    let is_being_dragged = ui.memory().is_being_dragged(id);
+    let is_being_dragged = ui.memory(|mem| mem.is_being_dragged(id));
 
     if !is_being_dragged {
         let response = ui.scope(body).response;
@@ -100,10 +100,10 @@ fn drag_source_ui(ui: &mut egui::Ui, id: egui::Id, body: impl FnOnce(&mut egui::
         // Check for drags:
         let response = ui.interact(response.rect, id, egui::Sense::drag());
         if response.hovered() {
-            ui.output().cursor_icon = egui::CursorIcon::Grab;
+            ui.ctx().set_cursor_icon(egui::CursorIcon::Grab);
         }
     } else {
-        ui.output().cursor_icon = egui::CursorIcon::Grabbing;
+        ui.ctx().set_cursor_icon(egui::CursorIcon::Grabbing);
 
         // Paint the body to a new layer:
         let layer_id = egui::LayerId::new(egui::Order::Tooltip, id);
@@ -129,7 +129,7 @@ fn drop_target_ui<R>(
     can_accept_dragged: bool,
     body: impl FnOnce(&mut egui::Ui) -> R,
 ) -> egui::InnerResponse<R> {
-    let is_being_dragged = ui.memory().is_anything_being_dragged();
+    let is_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged());
 
     let margin = egui::Vec2::splat(4.0);
 
@@ -182,8 +182,7 @@ pub fn dimension_mapping_ui(
 
     let drag_context_id = ui.id();
     let can_accept_dragged = (0..shape.len()).any(|dim_idx| {
-        ui.memory()
-            .is_being_dragged(drag_source_ui_id(drag_context_id, dim_idx))
+        ui.memory(|mem| mem.is_being_dragged(drag_source_ui_id(drag_context_id, dim_idx)))
     });
 
     ui.horizontal(|ui| {
@@ -262,7 +261,7 @@ pub fn dimension_mapping_ui(
     });
 
     // persist drag/drop
-    if drop_target.is_some() && drop_source.is_some() && ui.input().pointer.any_released() {
+    if drop_target.is_some() && drop_source.is_some() && ui.input(|i| i.pointer.any_released()) {
         let previous_value_source = drop_source.read_from_address(dim_mapping);
         let previous_value_target = drop_target.read_from_address(dim_mapping);
         drop_source.write_to_address(dim_mapping, previous_value_target);