WIP

Nathan Sobo created

Change summary

crates/diagnostics/src/diagnostics.rs       |   2 
crates/editor/src/element.rs                |   4 
crates/gpui/src/app.rs                      | 340 ++----------
crates/gpui/src/app/test_app_context.rs     |  12 
crates/gpui/src/app/window.rs               | 573 ++++++++++++++--------
crates/gpui/src/app/window_input_handler.rs |   7 
crates/gpui/src/elements.rs                 |   8 
crates/gpui/src/elements/align.rs           |   2 
crates/gpui/src/elements/canvas.rs          |   2 
crates/gpui/src/elements/constrained_box.rs |   2 
crates/gpui/src/elements/container.rs       |   2 
crates/gpui/src/elements/empty.rs           |   2 
crates/gpui/src/elements/expanded.rs        |   2 
crates/gpui/src/elements/flex.rs            |   2 
crates/gpui/src/elements/hook.rs            |   2 
crates/gpui/src/elements/image.rs           |   5 
crates/gpui/src/elements/label.rs           |   2 
crates/gpui/src/elements/list.rs            |   6 
crates/gpui/src/elements/overlay.rs         |   2 
crates/gpui/src/elements/stack.rs           |   2 
crates/gpui/src/elements/svg.rs             |   5 
crates/gpui/src/elements/text.rs            |  14 
crates/gpui/src/elements/tooltip.rs         |   2 
crates/gpui/src/elements/uniform_list.rs    |   2 
crates/gpui/src/gpui.rs                     |   3 
crates/gpui/src/platform/test.rs            |   2 
26 files changed, 492 insertions(+), 515 deletions(-)

Detailed changes

crates/diagnostics/src/diagnostics.rs 🔗

@@ -1177,7 +1177,7 @@ mod tests {
     }
 
     fn editor_blocks(editor: &ViewHandle<Editor>, cx: &mut AppContext) -> Vec<(u32, String)> {
-        let mut presenter = cx.build_presenter(editor.id(), 0., Default::default());
+        let mut presenter = cx.build_window(editor.id(), 0., Default::default());
         let mut cx = presenter.build_layout_context(Default::default(), false, cx);
         cx.render(editor, |editor, cx| {
             let snapshot = editor.snapshot(cx);

crates/editor/src/element.rs 🔗

@@ -2531,7 +2531,7 @@ mod tests {
 
         let layouts = editor.update(cx, |editor, cx| {
             let snapshot = editor.snapshot(cx);
-            let mut presenter = cx.build_presenter(window_id, 30., Default::default());
+            let mut presenter = cx.build_window(window_id, 30., Default::default());
             let layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
             element
                 .layout_line_numbers(0..6, &Default::default(), false, &snapshot, &layout_cx)
@@ -2568,7 +2568,7 @@ mod tests {
         let mut element = EditorElement::new(editor.downgrade(), editor.read(cx).style(cx));
 
         let mut scene = SceneBuilder::new(1.0);
-        let mut presenter = cx.build_presenter(window_id, 30., Default::default());
+        let mut presenter = cx.build_window(window_id, 30., Default::default());
         let mut layout_cx = presenter.build_layout_context(Vector2F::zero(), false, cx);
         let (size, mut state) = element.layout(
             SizeConstraint::new(vec2f(500., 500.), vec2f(500., 500.)),

crates/gpui/src/app.rs 🔗

@@ -4,6 +4,7 @@ mod menu;
 pub(crate) mod ref_counts;
 #[cfg(any(test, feature = "test-support"))]
 pub mod test_app_context;
+pub(crate) mod window;
 mod window_input_handler;
 
 use std::{
@@ -23,7 +24,6 @@ use std::{
 
 use anyhow::{anyhow, Context, Result};
 use parking_lot::Mutex;
-use pathfinder_geometry::vector::Vector2F;
 use postage::oneshot;
 use smallvec::SmallVec;
 use smol::prelude::*;
@@ -48,8 +48,8 @@ use crate::{
         self, Appearance, KeyDownEvent, KeyUpEvent, ModifiersChangedEvent, MouseButton,
         PathPromptOptions, Platform, PromptLevel, WindowBounds, WindowOptions,
     },
-    presenter::Presenter,
     util::post_inc,
+    window::{Window, WindowContext},
     AssetCache, AssetSource, ClipboardItem, FontCache, MouseRegionId, TextLayoutCache,
 };
 
@@ -1622,25 +1622,10 @@ impl AppContext {
             let platform_window =
                 this.platform
                     .open_window(window_id, window_options, this.foreground.clone());
-            let presenter = self.build_presenter(
-                window_id,
-                platform_window.titlebar_height(),
-                platform_window.appearance(),
-            );
-            this.register_platform_window(window_id, &mut presenter, platform_window.as_mut());
+            let window =
+                this.build_window(window_id, root_view.clone().into_any(), platform_window);
 
-            this.windows.insert(
-                window_id,
-                Window {
-                    root_view: root_view.clone().into_any(),
-                    focused_view_id: Some(root_view.id()),
-                    is_active: false,
-                    invalidation: None,
-                    is_fullscreen: false,
-                    platform_window,
-                    presenter,
-                },
-            );
+            this.windows.insert(window_id, window);
             root_view.update(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
 
             (window_id, root_view)
@@ -1658,27 +1643,11 @@ impl AppContext {
                 .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
                 .unwrap();
 
-            let mut platform_window = this.platform.add_status_item();
-            let mut presenter = self.build_presenter(
-                window_id,
-                platform_window.titlebar_height(),
-                platform_window.appearance(),
-            );
-            this.register_platform_window(window_id, &mut presenter, platform_window.as_mut());
+            let platform_window = this.platform.add_status_item();
+            let window =
+                this.build_window(window_id, root_view.clone().into_any(), platform_window);
 
-            let focused_view_id = root_view.id();
-            this.windows.insert(
-                window_id,
-                Window {
-                    root_view: root_view.clone().into_any(),
-                    focused_view_id: Some(focused_view_id),
-                    is_active: false,
-                    invalidation: None,
-                    is_fullscreen: false,
-                    platform_window,
-                    presenter,
-                },
-            );
+            this.windows.insert(window_id, window);
             root_view.update(this, |view, cx| view.focus_in(cx.handle().into_any(), cx));
 
             (window_id, root_view)
@@ -1689,12 +1658,33 @@ impl AppContext {
         self.remove_window(id);
     }
 
-    fn register_platform_window(
+    pub fn replace_root_view<T, F>(&mut self, window_id: usize, build_root_view: F) -> ViewHandle<T>
+    where
+        T: View,
+        F: FnOnce(&mut ViewContext<T>) -> T,
+    {
+        self.update(|this| {
+            let root_view = this
+                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
+                .unwrap();
+            let window = this.windows.get_mut(&window_id).unwrap();
+            window.root_view = root_view.clone().into_any();
+            window.focused_view_id = Some(root_view.id());
+            root_view
+        })
+    }
+
+    pub fn remove_window(&mut self, window_id: usize) {
+        self.windows.remove(&window_id);
+        self.flush_effects();
+    }
+
+    pub fn build_window(
         &mut self,
         window_id: usize,
-        presenter: &mut Presenter,
-        platform_window: &mut dyn platform::Window,
-    ) {
+        root_view: AnyViewHandle,
+        mut platform_window: Box<dyn platform::Window>,
+    ) -> Window {
         {
             let mut app = self.upgrade();
 
@@ -1758,51 +1748,19 @@ impl AppContext {
             window_id,
         }));
 
-        let scene = presenter.build_scene(
-            platform_window.content_size(),
-            platform_window.scale_factor(),
-            false,
-            self,
-        );
-        platform_window.present_scene(scene);
-    }
-
-    pub fn replace_root_view<T, F>(&mut self, window_id: usize, build_root_view: F) -> ViewHandle<T>
-    where
-        T: View,
-        F: FnOnce(&mut ViewContext<T>) -> T,
-    {
-        self.update(|this| {
-            let root_view = this
-                .build_and_insert_view(window_id, ParentId::Root, |cx| Some(build_root_view(cx)))
-                .unwrap();
-            let window = this.windows.get_mut(&window_id).unwrap();
-            window.root_view = root_view.clone().into_any();
-            window.focused_view_id = Some(root_view.id());
-            root_view
-        })
-    }
-
-    pub fn remove_window(&mut self, window_id: usize) {
-        self.windows.remove(&window_id);
-        self.flush_effects();
-    }
-
-    pub fn build_presenter(
-        &mut self,
-        window_id: usize,
-        titlebar_height: f32,
-        appearance: Appearance,
-    ) -> Presenter {
-        Presenter::new(
+        let mut window = Window::new(
             window_id,
-            titlebar_height,
-            appearance,
+            root_view,
+            platform_window,
             self.font_cache.clone(),
             TextLayoutCache::new(self.platform.fonts()),
             self.assets.clone(),
             self,
-        )
+        );
+
+        let scene = WindowContext::new(self, &mut window, window_id).build_scene(false);
+        window.platform_window.present_scene(scene);
+        window
     }
 
     pub fn add_view<T, F>(&mut self, parent_handle: &AnyViewHandle, build_view: F) -> ViewHandle<T>
@@ -2144,21 +2102,16 @@ impl AppContext {
     }
 
     fn update_windows(&mut self) {
-        for (window_id, window) in &mut self.windows {
-            if let Some(mut invalidation) = window.invalidation.take() {
-                window.presenter.invalidate(
-                    &mut invalidation,
-                    window.platform_window.appearance(),
-                    self,
-                );
-                let scene = window.presenter.build_scene(
-                    window.platform_window.content_size(),
-                    window.platform_window.scale_factor(),
-                    false,
-                    self,
-                );
-                window.platform_window.present_scene(scene);
-            }
+        let window_ids = self.windows.keys().cloned().collect::<Vec<_>>();
+        for window_id in window_ids {
+            self.update_window(window_id, |cx| {
+                if let Some(mut invalidation) = cx.window.invalidation.take() {
+                    let appearance = cx.window.platform_window.appearance();
+                    cx.invalidate(&mut invalidation, appearance);
+                    let scene = cx.build_scene(false);
+                    cx.window.platform_window.present_scene(scene);
+                }
+            });
         }
     }
 
@@ -2223,20 +2176,14 @@ impl AppContext {
     }
 
     fn perform_window_refresh(&mut self) {
-        for window in self.windows.values_mut() {
-            let mut invalidation = window.invalidation.take().unwrap_or_default();
-            window.presenter.invalidate(
-                &mut invalidation,
-                window.platform_window.appearance(),
-                self,
-            );
-            let scene = window.presenter.build_scene(
-                window.platform_window.content_size(),
-                window.platform_window.scale_factor(),
-                true,
-                self,
-            );
-            window.platform_window.present_scene(scene);
+        let window_ids = self.windows.keys().cloned().collect::<Vec<_>>();
+        for window_id in window_ids {
+            self.update_window(window_id, |cx| {
+                let mut invalidation = cx.window.invalidation.take().unwrap_or_default();
+                cx.invalidate(&mut invalidation, cx.window.platform_window.appearance());
+                let scene = cx.build_scene(true);
+                cx.window.platform_window.present_scene(scene);
+            });
         }
     }
 
@@ -2478,14 +2425,12 @@ impl AppContext {
         self.update_window(window_id, |cx| {
             if let Some(display) = cx.window_display_uuid() {
                 let bounds = cx.window_bounds();
-                cx.window_bounds_observations.clone().emit(
-                    window_id,
-                    self,
-                    move |callback, this| {
+                cx.window_bounds_observations
+                    .clone()
+                    .emit(window_id, cx, move |callback, this| {
                         callback(bounds, display, this);
                         true
-                    },
-                );
+                    });
             }
         });
     }
@@ -2708,16 +2653,6 @@ pub enum ParentId {
     Root,
 }
 
-pub struct Window {
-    root_view: AnyViewHandle,
-    focused_view_id: Option<usize>,
-    is_active: bool,
-    is_fullscreen: bool,
-    invalidation: Option<WindowInvalidation>,
-    presenter: Presenter,
-    platform_window: Box<dyn platform::Window>,
-}
-
 #[derive(Default, Clone)]
 pub struct WindowInvalidation {
     pub updated: HashSet<usize>,
@@ -3494,138 +3429,6 @@ impl<M> DerefMut for ModelContext<'_, M> {
     }
 }
 
-pub struct WindowContext<'a: 'b, 'b> {
-    app_context: &'a mut AppContext,
-    window: &'b mut Window,
-    window_id: usize,
-}
-
-impl Deref for WindowContext<'_, '_> {
-    type Target = AppContext;
-
-    fn deref(&self) -> &Self::Target {
-        self.app_context
-    }
-}
-
-impl DerefMut for WindowContext<'_, '_> {
-    fn deref_mut(&mut self) -> &mut Self::Target {
-        &mut self.app_context
-    }
-}
-
-impl WindowContext<'_, '_> {
-    pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
-        let window_id = self.window_id;
-        if let Some(focused_view_id) = self.focused_view_id(window_id) {
-            let dispatch_path = self
-                .ancestors(window_id, focused_view_id)
-                .filter_map(|view_id| {
-                    self.views
-                        .get(&(window_id, view_id))
-                        .map(|view| (view_id, view.keymap_context(self)))
-                })
-                .collect();
-
-            let match_result = self
-                .keystroke_matcher
-                .push_keystroke(keystroke.clone(), dispatch_path);
-            let mut handled_by = None;
-
-            let keystroke_handled = match &match_result {
-                MatchResult::None => false,
-                MatchResult::Pending => true,
-                MatchResult::Matches(matches) => {
-                    for (view_id, action) in matches {
-                        if self.handle_dispatch_action_from_effect(
-                            window_id,
-                            Some(*view_id),
-                            action.as_ref(),
-                        ) {
-                            self.keystroke_matcher.clear_pending();
-                            handled_by = Some(action.boxed_clone());
-                            break;
-                        }
-                    }
-                    handled_by.is_some()
-                }
-            };
-
-            self.keystroke(
-                window_id,
-                keystroke.clone(),
-                handled_by,
-                match_result.clone(),
-            );
-            keystroke_handled
-        } else {
-            self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
-            false
-        }
-    }
-
-    pub fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
-        self.window
-            .presenter
-            .dispatch_event(event, event_reused, self)
-    }
-
-    pub fn set_window_title(&mut self, title: &str) {
-        self.window.platform_window.set_title(title);
-    }
-
-    pub fn set_window_edited(&mut self, edited: bool) {
-        self.window.platform_window.set_edited(edited);
-    }
-
-    pub fn is_topmost_window_for_position(&self, position: Vector2F) -> bool {
-        self.window
-            .platform_window
-            .is_topmost_for_position(position)
-    }
-
-    pub fn activate_window(&self) {
-        self.window.platform_window.activate();
-    }
-
-    pub fn window_bounds(&self) -> WindowBounds {
-        self.window.platform_window.bounds()
-    }
-
-    pub fn window_display_uuid(&self) -> Option<Uuid> {
-        self.window.platform_window.screen().display_uuid()
-    }
-
-    pub fn debug_elements(&self) -> Option<crate::json::Value> {
-        self.window.presenter.debug_elements(self)
-    }
-
-    fn show_character_palette(&self) {
-        self.window.platform_window.show_character_palette();
-    }
-
-    pub fn minimize_window(&self) {
-        self.window.platform_window.minimize();
-    }
-
-    pub fn zoom_window(&self) {
-        self.window.platform_window.zoom();
-    }
-
-    pub fn toggle_window_full_screen(&self) {
-        self.window.platform_window.toggle_full_screen();
-    }
-
-    pub fn prompt(
-        &self,
-        level: PromptLevel,
-        msg: &str,
-        answers: &[&str],
-    ) -> oneshot::Receiver<usize> {
-        self.window.platform_window.prompt(level, msg, answers)
-    }
-}
-
 pub struct ViewContext<'a, T: ?Sized> {
     app: &'a mut AppContext,
     window_id: usize,
@@ -5139,6 +4942,7 @@ mod tests {
         elements::*,
         impl_actions,
         platform::{MouseButton, MouseButtonEvent},
+        window::ChildView,
     };
     use itertools::Itertools;
     use postage::{sink::Sink, stream::Stream};
@@ -6847,7 +6651,7 @@ mod tests {
         let (window_id, root_view) = cx.add_window(Default::default(), |_| View(0));
         cx.update_window(window_id, |cx| {
             assert_eq!(
-                cx.window.presenter.rendered_views[&root_view.id()].name(),
+                cx.window.rendered_views[&root_view.id()].name(),
                 Some("render count: 0")
             );
         });
@@ -6859,11 +6663,11 @@ mod tests {
 
         cx.update_window(window_id, |cx| {
             assert_eq!(
-                cx.window.presenter.rendered_views[&root_view.id()].name(),
+                cx.window.rendered_views[&root_view.id()].name(),
                 Some("render count: 1")
             );
             assert_eq!(
-                cx.window.presenter.rendered_views[&view.id()].name(),
+                cx.window.rendered_views[&view.id()].name(),
                 Some("render count: 0")
             );
         });
@@ -6872,11 +6676,11 @@ mod tests {
 
         cx.update_window(window_id, |cx| {
             assert_eq!(
-                cx.window.presenter.rendered_views[&root_view.id()].name(),
+                cx.window.rendered_views[&root_view.id()].name(),
                 Some("render count: 2")
             );
             assert_eq!(
-                cx.window.presenter.rendered_views[&view.id()].name(),
+                cx.window.rendered_views[&view.id()].name(),
                 Some("render count: 1")
             );
         });
@@ -6888,10 +6692,10 @@ mod tests {
 
         cx.update_window(window_id, |cx| {
             assert_eq!(
-                cx.window.presenter.rendered_views[&root_view.id()].name(),
+                cx.window.rendered_views[&root_view.id()].name(),
                 Some("render count: 3")
             );
-            assert_eq!(cx.window.presenter.rendered_views.len(), 1);
+            assert_eq!(cx.window.rendered_views.len(), 1);
         });
     }
 

crates/gpui/src/app/test_app_context.rs 🔗

@@ -92,13 +92,12 @@ impl TestAppContext {
                     return true;
                 }
 
-                if cx.window.presenter.dispatch_event(
+                if cx.dispatch_event(
                     Event::KeyDown(KeyDownEvent {
                         keystroke: keystroke.clone(),
                         is_held,
                     }),
                     false,
-                    cx,
                 ) {
                     return true;
                 }
@@ -286,12 +285,15 @@ impl TestAppContext {
 
     pub fn simulate_window_activation(&self, to_activate: Option<usize>) {
         self.cx.borrow_mut().update(|cx| {
-            for window_id in cx
+            let other_window_ids = cx
                 .windows
                 .keys()
                 .filter(|window_id| Some(**window_id) != to_activate)
-            {
-                cx.window_changed_active_status(*window_id, false)
+                .copied()
+                .collect::<Vec<_>>();
+
+            for window_id in other_window_ids {
+                cx.window_changed_active_status(window_id, false)
             }
 
             if let Some(to_activate) = to_activate {

crates/gpui/src/presenter.rs → crates/gpui/src/app/window.rs 🔗

@@ -4,7 +4,11 @@ use crate::{
     font_cache::FontCache,
     geometry::rect::RectF,
     json::{self, ToJson},
-    platform::{Appearance, CursorStyle, Event, FontSystem, MouseButton, MouseMovedEvent},
+    keymap_matcher::{Keystroke, MatchResult},
+    platform::{
+        self, Appearance, CursorStyle, Event, FontSystem, MouseButton, MouseMovedEvent,
+        PromptLevel, WindowBounds,
+    },
     scene::{
         CursorRegion, MouseClick, MouseDown, MouseDownOut, MouseDrag, MouseEvent, MouseHover,
         MouseMove, MouseMoveOut, MouseScrollWheel, MouseUp, MouseUpOut, Scene,
@@ -18,6 +22,7 @@ use crate::{
 use anyhow::bail;
 use collections::{HashMap, HashSet};
 use pathfinder_geometry::vector::{vec2f, Vector2F};
+use postage::oneshot;
 use serde_json::json;
 use smallvec::SmallVec;
 use sqlez::{
@@ -29,9 +34,16 @@ use std::{
     ops::{Deref, DerefMut, Range},
     sync::Arc,
 };
+use uuid::Uuid;
 
-pub struct Presenter {
+pub struct Window {
     window_id: usize,
+    pub(crate) root_view: AnyViewHandle,
+    pub(crate) focused_view_id: Option<usize>,
+    pub(crate) is_active: bool,
+    pub(crate) is_fullscreen: bool,
+    pub(crate) invalidation: Option<WindowInvalidation>,
+    pub(crate) platform_window: Box<dyn platform::Window>,
     pub(crate) rendered_views: HashMap<usize, ElementBox>,
     cursor_regions: Vec<CursorRegion>,
     mouse_regions: Vec<(MouseRegion, usize)>,
@@ -47,18 +59,27 @@ pub struct Presenter {
     appearance: Appearance,
 }
 
-impl Presenter {
+impl Window {
     pub fn new(
         window_id: usize,
-        titlebar_height: f32,
-        appearance: Appearance,
+        root_view: AnyViewHandle,
+        platform_window: Box<dyn platform::Window>,
         font_cache: Arc<FontCache>,
         text_layout_cache: TextLayoutCache,
         asset_cache: Arc<AssetCache>,
         cx: &mut AppContext,
     ) -> Self {
+        let focused_view_id = Some(root_view.id());
+        let titlebar_height = platform_window.titlebar_height();
+        let appearance = platform_window.appearance();
         Self {
             window_id,
+            root_view,
+            focused_view_id,
+            is_active: false,
+            invalidation: None,
+            is_fullscreen: false,
+            platform_window,
             rendered_views: cx.render_views(window_id, titlebar_height, appearance),
             cursor_regions: Default::default(),
             mouse_regions: Default::default(),
@@ -74,180 +95,101 @@ impl Presenter {
             appearance,
         }
     }
+}
 
-    pub fn invalidate(
-        &mut self,
-        invalidation: &mut WindowInvalidation,
-        appearance: Appearance,
-        cx: &mut AppContext,
-    ) {
-        cx.start_frame();
-        self.appearance = appearance;
-        for view_id in &invalidation.removed {
-            invalidation.updated.remove(view_id);
-            self.rendered_views.remove(view_id);
-        }
-        for view_id in &invalidation.updated {
-            self.rendered_views.insert(
-                *view_id,
-                cx.render_view(RenderParams {
-                    window_id: self.window_id,
-                    view_id: *view_id,
-                    titlebar_height: self.titlebar_height,
-                    hovered_region_ids: self.hovered_region_ids.clone(),
-                    clicked_region_ids: self
-                        .clicked_button
-                        .map(|button| (self.clicked_region_ids.clone(), button)),
-                    refreshing: false,
-                    appearance,
-                })
-                .unwrap(),
-            );
-        }
-    }
-
-    pub fn refresh(
-        &mut self,
-        invalidation: &mut WindowInvalidation,
-        appearance: Appearance,
-        cx: &mut AppContext,
-    ) {
-        self.invalidate(invalidation, appearance, cx);
-        for (view_id, view) in &mut self.rendered_views {
-            if !invalidation.updated.contains(view_id) {
-                *view = cx
-                    .render_view(RenderParams {
-                        window_id: self.window_id,
-                        view_id: *view_id,
-                        titlebar_height: self.titlebar_height,
-                        hovered_region_ids: self.hovered_region_ids.clone(),
-                        clicked_region_ids: self
-                            .clicked_button
-                            .map(|button| (self.clicked_region_ids.clone(), button)),
-                        refreshing: true,
-                        appearance,
-                    })
-                    .unwrap();
-            }
-        }
-    }
-
-    pub fn build_scene(
-        &mut self,
-        window_size: Vector2F,
-        scale_factor: f32,
-        refreshing: bool,
-        cx: &mut AppContext,
-    ) -> Scene {
-        let mut scene_builder = SceneBuilder::new(scale_factor);
-
-        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
-            self.layout(window_size, refreshing, cx);
-            let mut paint_cx = self.build_paint_context(&mut scene_builder, window_size, cx);
-            paint_cx.paint(
-                root_view_id,
-                Vector2F::zero(),
-                RectF::new(Vector2F::zero(), window_size),
-            );
-            self.text_layout_cache.finish_frame();
-            let scene = scene_builder.build();
-            self.cursor_regions = scene.cursor_regions();
-            self.mouse_regions = scene.mouse_regions();
-
-            // window.is_topmost for the mouse moved event's postion?
-            if cx.window_is_active(self.window_id) {
-                if let Some(event) = self.last_mouse_moved_event.clone() {
-                    self.dispatch_event(event, true, cx);
-                }
-            }
+pub struct WindowContext<'a: 'b, 'b> {
+    app_context: &'a mut AppContext,
+    pub(crate) window: &'b mut Window, // TODO: make this private?
+    window_id: usize,
+}
 
-            scene
-        } else {
-            log::error!("could not find root_view_id for window {}", self.window_id);
-            scene_builder.build()
-        }
-    }
+impl Deref for WindowContext<'_, '_> {
+    type Target = AppContext;
 
-    fn layout(&mut self, window_size: Vector2F, refreshing: bool, cx: &mut AppContext) {
-        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
-            self.build_layout_context(window_size, refreshing, cx)
-                .layout(root_view_id, SizeConstraint::strict(window_size));
-        }
+    fn deref(&self) -> &Self::Target {
+        self.app_context
     }
+}
 
-    pub fn build_layout_context<'a>(
-        &'a mut self,
-        window_size: Vector2F,
-        refreshing: bool,
-        cx: &'a mut AppContext,
-    ) -> LayoutContext<'a> {
-        LayoutContext {
-            window_id: self.window_id,
-            rendered_views: &mut self.rendered_views,
-            font_cache: &self.font_cache,
-            font_system: cx.platform().fonts(),
-            text_layout_cache: &self.text_layout_cache,
-            asset_cache: &self.asset_cache,
-            view_stack: Vec::new(),
-            refreshing,
-            hovered_region_ids: self.hovered_region_ids.clone(),
-            clicked_region_ids: self
-                .clicked_button
-                .map(|button| (self.clicked_region_ids.clone(), button)),
-            titlebar_height: self.titlebar_height,
-            appearance: self.appearance,
-            window_size,
-            app: cx,
-        }
+impl DerefMut for WindowContext<'_, '_> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.app_context
     }
+}
 
-    pub fn build_paint_context<'a>(
-        &'a mut self,
-        scene: &'a mut SceneBuilder,
-        window_size: Vector2F,
-        cx: &'a mut AppContext,
-    ) -> PaintContext {
-        PaintContext {
-            scene,
-            window_size,
-            font_cache: &self.font_cache,
-            text_layout_cache: &self.text_layout_cache,
-            rendered_views: &mut self.rendered_views,
-            view_stack: Vec::new(),
-            app: cx,
+impl<'a: 'b, 'b> WindowContext<'a, 'b> {
+    pub fn new(app_context: &'a mut AppContext, window: &'b mut Window, window_id: usize) -> Self {
+        Self {
+            app_context,
+            window,
+            window_id,
         }
     }
 
-    pub fn rect_for_text_range(&self, range_utf16: Range<usize>, cx: &AppContext) -> Option<RectF> {
-        cx.focused_view_id(self.window_id).and_then(|view_id| {
-            let cx = MeasurementContext {
-                app: cx,
-                rendered_views: &self.rendered_views,
-                window_id: self.window_id,
+    pub fn dispatch_keystroke(&mut self, keystroke: &Keystroke) -> bool {
+        let window_id = self.window_id;
+        if let Some(focused_view_id) = self.focused_view_id(window_id) {
+            let dispatch_path = self
+                .ancestors(window_id, focused_view_id)
+                .filter_map(|view_id| {
+                    self.views
+                        .get(&(window_id, view_id))
+                        .map(|view| (view_id, view.keymap_context(self)))
+                })
+                .collect();
+
+            let match_result = self
+                .keystroke_matcher
+                .push_keystroke(keystroke.clone(), dispatch_path);
+            let mut handled_by = None;
+
+            let keystroke_handled = match &match_result {
+                MatchResult::None => false,
+                MatchResult::Pending => true,
+                MatchResult::Matches(matches) => {
+                    for (view_id, action) in matches {
+                        if self.handle_dispatch_action_from_effect(
+                            window_id,
+                            Some(*view_id),
+                            action.as_ref(),
+                        ) {
+                            self.keystroke_matcher.clear_pending();
+                            handled_by = Some(action.boxed_clone());
+                            break;
+                        }
+                    }
+                    handled_by.is_some()
+                }
             };
-            cx.rect_for_text_range(view_id, range_utf16)
-        })
+
+            self.keystroke(
+                window_id,
+                keystroke.clone(),
+                handled_by,
+                match_result.clone(),
+            );
+            keystroke_handled
+        } else {
+            self.keystroke(window_id, keystroke.clone(), None, MatchResult::None);
+            false
+        }
     }
 
-    pub fn dispatch_event(
-        &mut self,
-        event: Event,
-        event_reused: bool,
-        cx: &mut AppContext,
-    ) -> bool {
+    pub fn dispatch_event(&mut self, event: Event, event_reused: bool) -> bool {
         let mut mouse_events = SmallVec::<[_; 2]>::new();
         let mut notified_views: HashSet<usize> = Default::default();
+        let window_id = self.window_id;
 
         // 1. Handle platform event. Keyboard events get dispatched immediately, while mouse events
         //    get mapped into the mouse-specific MouseEvent type.
         //  -> These are usually small: [Mouse Down] or [Mouse up, Click] or [Mouse Moved, Mouse Dragged?]
         //  -> Also updates mouse-related state
         match &event {
-            Event::KeyDown(e) => return cx.dispatch_key_down(self.window_id, e),
+            Event::KeyDown(e) => return self.dispatch_key_down(window_id, e),
 
-            Event::KeyUp(e) => return cx.dispatch_key_up(self.window_id, e),
+            Event::KeyUp(e) => return self.dispatch_key_up(window_id, e),
 
-            Event::ModifiersChanged(e) => return cx.dispatch_modifiers_changed(self.window_id, e),
+            Event::ModifiersChanged(e) => return self.dispatch_modifiers_changed(window_id, e),
 
             Event::MouseDown(e) => {
                 // Click events are weird because they can be fired after a drag event.
@@ -256,8 +198,9 @@ impl Presenter {
                 // So we need to store the overlapping regions on mouse down.
 
                 // If there is already clicked_button stored, don't replace it.
-                if self.clicked_button.is_none() {
-                    self.clicked_region_ids = self
+                if self.window.clicked_button.is_none() {
+                    self.window.clicked_region_ids = self
+                        .window
                         .mouse_regions
                         .iter()
                         .filter_map(|(region, _)| {
@@ -269,7 +212,7 @@ impl Presenter {
                         })
                         .collect();
 
-                    self.clicked_button = Some(e.button);
+                    self.window.clicked_button = Some(e.button);
                 }
 
                 mouse_events.push(MouseEvent::Down(MouseDown {
@@ -308,26 +251,29 @@ impl Presenter {
                 },
             ) => {
                 let mut style_to_assign = CursorStyle::Arrow;
-                for region in self.cursor_regions.iter().rev() {
+                for region in self.window.cursor_regions.iter().rev() {
                     if region.bounds.contains_point(*position) {
                         style_to_assign = region.style;
                         break;
                     }
                 }
 
-                // TODO!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-                // if cx.is_topmost_window_for_position(self.window_id, *position) {
-                //     cx.platform().set_cursor_style(style_to_assign);
-                // }
+                if self
+                    .window
+                    .platform_window
+                    .is_topmost_for_position(*position)
+                {
+                    self.platform().set_cursor_style(style_to_assign);
+                }
 
                 if !event_reused {
                     if pressed_button.is_some() {
                         mouse_events.push(MouseEvent::Drag(MouseDrag {
                             region: Default::default(),
-                            prev_mouse_position: self.mouse_position,
+                            prev_mouse_position: self.window.mouse_position,
                             platform_event: e.clone(),
                         }));
-                    } else if let Some(clicked_button) = self.clicked_button {
+                    } else if let Some(clicked_button) = self.window.clicked_button {
                         // Mouse up event happened outside the current window. Simulate mouse up button event
                         let button_event = e.to_button_event(clicked_button);
                         mouse_events.push(MouseEvent::Up(MouseUp {
@@ -359,7 +305,7 @@ impl Presenter {
                     region: Default::default(),
                 }));
 
-                self.last_mouse_moved_event = Some(event.clone());
+                self.window.last_mouse_moved_event = Some(event.clone());
             }
 
             Event::MouseExited(event) => {
@@ -373,7 +319,6 @@ impl Presenter {
                         modifiers: event.modifiers,
                     }),
                     event_reused,
-                    cx,
                 );
             }
 
@@ -384,7 +329,7 @@ impl Presenter {
         }
 
         if let Some(position) = event.position() {
-            self.mouse_position = position;
+            self.window.mouse_position = position;
         }
 
         // 2. Dispatch mouse events on regions
@@ -398,8 +343,8 @@ impl Presenter {
             match &mouse_event {
                 MouseEvent::Hover(_) => {
                     let mut highest_z_index = None;
-                    let mouse_position = self.mouse_position.clone();
-                    for (region, z_index) in self.mouse_regions.iter().rev() {
+                    let mouse_position = self.window.mouse_position.clone();
+                    for (region, z_index) in self.window.mouse_regions.iter().rev() {
                         // Allow mouse regions to appear transparent to hovers
                         if !region.hoverable {
                             continue;
@@ -417,7 +362,7 @@ impl Presenter {
                         // highest_z_index is set.
                         if contains_mouse && z_index == highest_z_index.unwrap() {
                             //Ensure that hover entrance events aren't sent twice
-                            if self.hovered_region_ids.insert(region.id()) {
+                            if self.window.hovered_region_ids.insert(region.id()) {
                                 valid_regions.push(region.clone());
                                 if region.notify_on_hover {
                                     notified_views.insert(region.id().view_id());
@@ -425,7 +370,7 @@ impl Presenter {
                             }
                         } else {
                             // Ensure that hover exit events aren't sent twice
-                            if self.hovered_region_ids.remove(&region.id()) {
+                            if self.window.hovered_region_ids.remove(&region.id()) {
                                 valid_regions.push(region.clone());
                                 if region.notify_on_hover {
                                     notified_views.insert(region.id().view_id());
@@ -436,8 +381,8 @@ impl Presenter {
                 }
 
                 MouseEvent::Down(_) | MouseEvent::Up(_) => {
-                    for (region, _) in self.mouse_regions.iter().rev() {
-                        if region.bounds.contains_point(self.mouse_position) {
+                    for (region, _) in self.window.mouse_regions.iter().rev() {
+                        if region.bounds.contains_point(self.window.mouse_position) {
                             valid_regions.push(region.clone());
                             if region.notify_on_click {
                                 notified_views.insert(region.id().view_id());
@@ -449,19 +394,25 @@ impl Presenter {
                 MouseEvent::Click(e) => {
                     // Only raise click events if the released button is the same as the one stored
                     if self
+                        .window
                         .clicked_button
                         .map(|clicked_button| clicked_button == e.button)
                         .unwrap_or(false)
                     {
                         // Clear clicked regions and clicked button
-                        let clicked_region_ids =
-                            std::mem::replace(&mut self.clicked_region_ids, Default::default());
-                        self.clicked_button = None;
+                        let clicked_region_ids = std::mem::replace(
+                            &mut self.window.clicked_region_ids,
+                            Default::default(),
+                        );
+                        self.window.clicked_button = None;
 
                         // Find regions which still overlap with the mouse since the last MouseDown happened
-                        for (mouse_region, _) in self.mouse_regions.iter().rev() {
+                        for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
                             if clicked_region_ids.contains(&mouse_region.id()) {
-                                if mouse_region.bounds.contains_point(self.mouse_position) {
+                                if mouse_region
+                                    .bounds
+                                    .contains_point(self.window.mouse_position)
+                                {
                                     valid_regions.push(mouse_region.clone());
                                 }
                             }
@@ -470,26 +421,32 @@ impl Presenter {
                 }
 
                 MouseEvent::Drag(_) => {
-                    for (mouse_region, _) in self.mouse_regions.iter().rev() {
-                        if self.clicked_region_ids.contains(&mouse_region.id()) {
+                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
+                        if self.window.clicked_region_ids.contains(&mouse_region.id()) {
                             valid_regions.push(mouse_region.clone());
                         }
                     }
                 }
 
                 MouseEvent::MoveOut(_) | MouseEvent::UpOut(_) | MouseEvent::DownOut(_) => {
-                    for (mouse_region, _) in self.mouse_regions.iter().rev() {
+                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
                         // NOT contains
-                        if !mouse_region.bounds.contains_point(self.mouse_position) {
+                        if !mouse_region
+                            .bounds
+                            .contains_point(self.window.mouse_position)
+                        {
                             valid_regions.push(mouse_region.clone());
                         }
                     }
                 }
 
                 _ => {
-                    for (mouse_region, _) in self.mouse_regions.iter().rev() {
+                    for (mouse_region, _) in self.window.mouse_regions.iter().rev() {
                         // Contains
-                        if mouse_region.bounds.contains_point(self.mouse_position) {
+                        if mouse_region
+                            .bounds
+                            .contains_point(self.window.mouse_position)
+                        {
                             valid_regions.push(mouse_region.clone());
                         }
                     }
@@ -497,9 +454,9 @@ impl Presenter {
             }
 
             //3. Fire region events
-            let hovered_region_ids = self.hovered_region_ids.clone();
+            let hovered_region_ids = self.window.hovered_region_ids.clone();
             for valid_region in valid_regions.into_iter() {
-                let mut event_cx = self.build_event_context(&mut notified_views, cx);
+                let mut event_cx = self.build_event_context(&mut notified_views);
 
                 mouse_event.set_region(valid_region.bounds);
                 if let MouseEvent::Hover(e) = &mut mouse_event {
@@ -548,43 +505,253 @@ impl Presenter {
         }
 
         for view_id in notified_views {
-            cx.notify_view(self.window_id, view_id);
+            self.notify_view(window_id, view_id);
         }
 
         any_event_handled
     }
 
-    pub fn build_event_context<'a>(
-        &'a mut self,
-        notified_views: &'a mut HashSet<usize>,
-        cx: &'a mut AppContext,
-    ) -> EventContext<'a> {
+    pub fn build_event_context<'c>(
+        &'c mut self,
+        notified_views: &'c mut HashSet<usize>,
+    ) -> EventContext<'c> {
         EventContext {
-            font_cache: &self.font_cache,
-            text_layout_cache: &self.text_layout_cache,
+            font_cache: &self.window.font_cache,
+            text_layout_cache: &self.window.text_layout_cache,
             view_stack: Default::default(),
             notified_views,
             notify_count: 0,
             handled: false,
             window_id: self.window_id,
+            app: self,
+        }
+    }
+
+    pub fn invalidate(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) {
+        self.start_frame();
+        self.window.appearance = appearance;
+        for view_id in &invalidation.removed {
+            invalidation.updated.remove(view_id);
+            self.window.rendered_views.remove(view_id);
+        }
+        for view_id in &invalidation.updated {
+            let window_id = self.window_id;
+            let titlebar_height = self.window.titlebar_height;
+            let hovered_region_ids = self.window.hovered_region_ids.clone();
+            let clicked_region_ids = self
+                .window
+                .clicked_button
+                .map(|button| (self.window.clicked_region_ids.clone(), button));
+
+            let element = self
+                .render_view(RenderParams {
+                    window_id,
+                    view_id: *view_id,
+                    titlebar_height,
+                    hovered_region_ids,
+                    clicked_region_ids,
+                    refreshing: false,
+                    appearance,
+                })
+                .unwrap();
+            self.window.rendered_views.insert(*view_id, element);
+        }
+    }
+
+    pub fn refresh(&mut self, invalidation: &mut WindowInvalidation, appearance: Appearance) {
+        self.invalidate(invalidation, appearance);
+
+        let view_ids = self
+            .window
+            .rendered_views
+            .keys()
+            .copied()
+            .collect::<Vec<_>>();
+
+        for view_id in view_ids {
+            if !invalidation.updated.contains(&view_id) {
+                let window_id = self.window_id;
+                let titlebar_height = self.window.titlebar_height;
+                let hovered_region_ids = self.window.hovered_region_ids.clone();
+                let clicked_region_ids = self
+                    .window
+                    .clicked_button
+                    .map(|button| (self.window.clicked_region_ids.clone(), button));
+                let element = self
+                    .render_view(RenderParams {
+                        window_id,
+                        view_id,
+                        titlebar_height,
+                        hovered_region_ids,
+                        clicked_region_ids,
+                        refreshing: true,
+                        appearance,
+                    })
+                    .unwrap();
+                self.window.rendered_views.insert(view_id, element);
+            }
+        }
+    }
+
+    pub fn build_scene(&mut self, refreshing: bool) -> Scene {
+        let window_size = self.window.platform_window.content_size();
+        let scale_factor = self.window.platform_window.scale_factor();
+
+        let mut scene_builder = SceneBuilder::new(scale_factor);
+
+        if let Some(root_view_id) = self.root_view_id(self.window_id) {
+            self.layout(window_size, refreshing, self);
+            let mut paint_cx = self.build_paint_context(&mut scene_builder, window_size);
+            paint_cx.paint(
+                root_view_id,
+                Vector2F::zero(),
+                RectF::new(Vector2F::zero(), window_size),
+            );
+            self.window.text_layout_cache.finish_frame();
+            let scene = scene_builder.build();
+            self.window.cursor_regions = scene.cursor_regions();
+            self.window.mouse_regions = scene.mouse_regions();
+
+            // window.is_topmost for the mouse moved event's postion?
+            if self.window_is_active(self.window_id) {
+                if let Some(event) = self.window.last_mouse_moved_event.clone() {
+                    self.dispatch_event(event, true);
+                }
+            }
+
+            scene
+        } else {
+            log::error!("could not find root_view_id for window {}", self.window_id);
+            scene_builder.build()
+        }
+    }
+
+    fn layout(&mut self, window_size: Vector2F, refreshing: bool, cx: &mut AppContext) {
+        if let Some(root_view_id) = cx.root_view_id(self.window_id) {
+            self.build_layout_context(window_size, refreshing, cx)
+                .layout(root_view_id, SizeConstraint::strict(window_size));
+        }
+    }
+
+    pub fn build_layout_context<'c>(
+        &'c mut self,
+        window_size: Vector2F,
+        refreshing: bool,
+        cx: &'c mut AppContext,
+    ) -> LayoutContext<'c> {
+        LayoutContext {
+            window_id: self.window_id,
+            rendered_views: &mut self.window.rendered_views,
+            font_cache: &self.font_cache,
+            font_system: cx.platform().fonts(),
+            text_layout_cache: &self.window.text_layout_cache,
+            asset_cache: &self.window.asset_cache,
+            view_stack: Vec::new(),
+            refreshing,
+            hovered_region_ids: self.window.hovered_region_ids.clone(),
+            clicked_region_ids: self
+                .window
+                .clicked_button
+                .map(|button| (self.window.clicked_region_ids.clone(), button)),
+            titlebar_height: self.window.titlebar_height,
+            appearance: self.window.appearance,
+            window_size,
             app: cx,
         }
     }
 
-    pub fn debug_elements(&self, cx: &AppContext) -> Option<json::Value> {
-        let view = cx.root_view(self.window_id)?;
+    pub fn build_paint_context<'c>(
+        &'c mut self,
+        scene: &'c mut SceneBuilder,
+        window_size: Vector2F,
+    ) -> PaintContext {
+        PaintContext {
+            scene,
+            window_size,
+            font_cache: &self.font_cache,
+            text_layout_cache: &self.window.text_layout_cache,
+            rendered_views: &mut self.window.rendered_views,
+            view_stack: Vec::new(),
+            app: self,
+        }
+    }
+
+    pub fn rect_for_text_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
+        self.focused_view_id(self.window_id).and_then(|view_id| {
+            let cx = MeasurementContext {
+                app: self,
+                rendered_views: &self.window.rendered_views,
+                window_id: self.window_id,
+            };
+            cx.rect_for_text_range(view_id, range_utf16)
+        })
+    }
+
+    pub fn debug_elements(&self) -> Option<json::Value> {
+        let view = self.root_view(self.window_id)?;
         Some(json!({
-            "root_view": view.debug_json(cx),
-            "root_element": self.rendered_views.get(&view.id())
+            "root_view": view.debug_json(self),
+            "root_element": self.window.rendered_views.get(&view.id())
                 .map(|root_element| {
                     root_element.debug(&DebugContext {
-                        rendered_views: &self.rendered_views,
-                        font_cache: &self.font_cache,
-                        app: cx,
+                        rendered_views: &self.window.rendered_views,
+                        font_cache: &self.window.font_cache,
+                        app: self,
                     })
                 })
         }))
     }
+
+    pub fn set_window_title(&mut self, title: &str) {
+        self.window.platform_window.set_title(title);
+    }
+
+    pub fn set_window_edited(&mut self, edited: bool) {
+        self.window.platform_window.set_edited(edited);
+    }
+
+    pub fn is_topmost_window_for_position(&self, position: Vector2F) -> bool {
+        self.window
+            .platform_window
+            .is_topmost_for_position(position)
+    }
+
+    pub fn activate_window(&self) {
+        self.window.platform_window.activate();
+    }
+
+    pub fn window_bounds(&self) -> WindowBounds {
+        self.window.platform_window.bounds()
+    }
+
+    pub fn window_display_uuid(&self) -> Option<Uuid> {
+        self.window.platform_window.screen().display_uuid()
+    }
+
+    fn show_character_palette(&self) {
+        self.window.platform_window.show_character_palette();
+    }
+
+    pub fn minimize_window(&self) {
+        self.window.platform_window.minimize();
+    }
+
+    pub fn zoom_window(&self) {
+        self.window.platform_window.zoom();
+    }
+
+    pub fn toggle_window_full_screen(&self) {
+        self.window.platform_window.toggle_full_screen();
+    }
+
+    pub fn prompt(
+        &self,
+        level: PromptLevel,
+        msg: &str,
+        answers: &[&str],
+    ) -> oneshot::Receiver<usize> {
+        self.window.platform_window.prompt(level, msg, answers)
+    }
 }
 
 pub struct LayoutContext<'a> {

crates/gpui/src/app/window_input_handler.rs 🔗

@@ -90,8 +90,9 @@ impl InputHandler for WindowInputHandler {
     }
 
     fn rect_for_range(&self, range_utf16: Range<usize>) -> Option<RectF> {
-        let app = self.app.borrow();
-        let window = app.windows.get(&self.window_id)?;
-        window.presenter.rect_for_text_range(range_utf16, &app)
+        self.app
+            .borrow_mut()
+            .update_window(self.window_id, |cx| cx.rect_for_text_range(range_utf16))
+            .flatten()
     }
 }

crates/gpui/src/elements.rs 🔗

@@ -26,16 +26,14 @@ pub use self::{
     stack::*, svg::*, text::*, tooltip::*, uniform_list::*,
 };
 use self::{clipped::Clipped, expanded::Expanded};
-pub use crate::presenter::ChildView;
 use crate::{
+    app::window::MeasurementContext,
     geometry::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    json,
-    presenter::MeasurementContext,
-    Action, DebugContext, EventContext, LayoutContext, PaintContext, RenderContext, SizeConstraint,
-    View,
+    json, Action, DebugContext, EventContext, LayoutContext, PaintContext, RenderContext,
+    SizeConstraint, View,
 };
 use core::panic;
 use json::ToJson;

crates/gpui/src/elements/align.rs 🔗

@@ -1,7 +1,7 @@
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
 };
 use json::ToJson;

crates/gpui/src/elements/canvas.rs 🔗

@@ -1,7 +1,7 @@
 use super::Element;
 use crate::{
     json::{self, json},
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, PaintContext,
 };
 use json::ToJson;

crates/gpui/src/elements/constrained_box.rs 🔗

@@ -6,7 +6,7 @@ use serde_json::json;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
 };
 

crates/gpui/src/elements/container.rs 🔗

@@ -9,8 +9,8 @@ use crate::{
     },
     json::ToJson,
     platform::CursorStyle,
-    presenter::MeasurementContext,
     scene::{self, Border, CursorRegion, Quad},
+    window::MeasurementContext,
     Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
 };
 use serde::Deserialize;

crates/gpui/src/elements/empty.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext,
 };
 use crate::{Element, LayoutContext, PaintContext, SizeConstraint};

crates/gpui/src/elements/expanded.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
 };
 use serde_json::json;

crates/gpui/src/elements/flex.rs 🔗

@@ -2,7 +2,7 @@ use std::{any::Any, cell::Cell, f32::INFINITY, ops::Range, rc::Rc};
 
 use crate::{
     json::{self, ToJson, Value},
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     Axis, DebugContext, Element, ElementBox, ElementStateHandle, LayoutContext, PaintContext,
     RenderContext, SizeConstraint, Vector2FExt, View,
 };

crates/gpui/src/elements/hook.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
 };
 

crates/gpui/src/elements/image.rs 🔗

@@ -5,8 +5,9 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{json, ToJson},
-    presenter::MeasurementContext,
-    scene, Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
+    scene,
+    window::MeasurementContext,
+    Border, DebugContext, Element, ImageData, LayoutContext, PaintContext, SizeConstraint,
 };
 use serde::Deserialize;
 use std::{ops::Range, sync::Arc};

crates/gpui/src/elements/label.rs 🔗

@@ -7,8 +7,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{ToJson, Value},
-    presenter::MeasurementContext,
     text_layout::{Line, RunStyle},
+    window::MeasurementContext,
     DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
 };
 use serde::Deserialize;

crates/gpui/src/elements/list.rs 🔗

@@ -4,7 +4,7 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::json,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, Element, ElementBox, ElementRc, EventContext, LayoutContext, MouseRegion,
     PaintContext, RenderContext, SizeConstraint, View, ViewContext,
 };
@@ -631,7 +631,6 @@ mod tests {
 
     #[crate::test(self)]
     fn test_layout(cx: &mut crate::AppContext) {
-        let mut presenter = cx.build_presenter(0, 0., Default::default());
         let (_, view) = cx.add_window(Default::default(), |_| TestView);
         let constraint = SizeConstraint::new(vec2f(0., 0.), vec2f(100., 40.));
 
@@ -730,8 +729,7 @@ mod tests {
             .map(|i| i.parse().expect("invalid `OPERATIONS` variable"))
             .unwrap_or(10);
 
-        let (_, view) = cx.add_window(Default::default(), |_| TestView);
-        let mut presenter = cx.build_presenter(0, 0., Default::default());
+        let (window_id, view) = cx.add_window(Default::default(), |_| TestView);
         let mut next_id = 0;
         let elements = Rc::new(RefCell::new(
             (0..rng.gen_range(0..=20))

crates/gpui/src/elements/overlay.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::ToJson,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     Axis, DebugContext, Element, ElementBox, LayoutContext, MouseRegion, PaintContext,
     SizeConstraint,
 };

crates/gpui/src/elements/stack.rs 🔗

@@ -3,7 +3,7 @@ use std::ops::Range;
 use crate::{
     geometry::{rect::RectF, vector::Vector2F},
     json::{self, json, ToJson},
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
 };
 

crates/gpui/src/elements/svg.rs 🔗

@@ -8,8 +8,9 @@ use crate::{
         rect::RectF,
         vector::{vec2f, Vector2F},
     },
-    presenter::MeasurementContext,
-    scene, DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
+    scene,
+    window::MeasurementContext,
+    DebugContext, Element, LayoutContext, PaintContext, SizeConstraint,
 };
 
 pub struct Svg {

crates/gpui/src/elements/text.rs 🔗

@@ -6,8 +6,8 @@ use crate::{
         vector::{vec2f, Vector2F},
     },
     json::{ToJson, Value},
-    presenter::MeasurementContext,
     text_layout::{Line, RunStyle, ShapedBoundary},
+    window::MeasurementContext,
     DebugContext, Element, FontCache, LayoutContext, PaintContext, SizeConstraint, TextLayoutCache,
 };
 use log::warn;
@@ -271,12 +271,18 @@ pub fn layout_highlighted_chunks<'a>(
 #[cfg(test)]
 mod tests {
     use super::*;
-    use crate::{elements::Empty, fonts, AppContext, ElementBox, Entity, RenderContext, View};
+    use crate::{
+        elements::Empty, fonts, platform, AppContext, ElementBox, Entity, RenderContext, View,
+    };
 
     #[crate::test(self)]
     fn test_soft_wrapping_with_carriage_returns(cx: &mut AppContext) {
-        let (window_id, _) = cx.add_window(Default::default(), |_| TestView);
-        let mut presenter = cx.build_presenter(window_id, Default::default(), Default::default());
+        let (window_id, root_view) = cx.add_window(Default::default(), |_| TestView);
+        let mut presenter = cx.build_window(
+            window_id,
+            root_view.into_any(),
+            Box::new(platform::test::Window::new(Vector2F::new(800., 600.))),
+        );
         fonts::with_font_cache(cx.font_cache().clone(), || {
             let mut text = Text::new("Hello\r\n", Default::default()).with_soft_wrap(true);
             let (_, state) = text.layout(

crates/gpui/src/elements/tooltip.rs 🔗

@@ -6,7 +6,7 @@ use crate::{
     fonts::TextStyle,
     geometry::{rect::RectF, vector::Vector2F},
     json::json,
-    presenter::MeasurementContext,
+    window::MeasurementContext,
     Action, Axis, ElementStateHandle, LayoutContext, PaintContext, RenderContext, SizeConstraint,
     Task, View,
 };

crates/gpui/src/elements/uniform_list.rs 🔗

@@ -6,8 +6,8 @@ use crate::{
     },
     json::{self, json},
     platform::ScrollWheelEvent,
-    presenter::MeasurementContext,
     scene::MouseScrollWheel,
+    window::MeasurementContext,
     ElementBox, MouseRegion, RenderContext, View,
 };
 use json::ToJson;

crates/gpui/src/gpui.rs 🔗

@@ -14,7 +14,6 @@ mod clipboard;
 pub use clipboard::ClipboardItem;
 pub mod fonts;
 pub mod geometry;
-mod presenter;
 pub mod scene;
 pub use scene::{Border, CursorRegion, MouseRegion, MouseRegionId, Quad, Scene, SceneBuilder};
 pub mod text_layout;
@@ -28,7 +27,7 @@ pub mod json;
 pub mod keymap_matcher;
 pub mod platform;
 pub use gpui_macros::test;
-pub use presenter::{
+pub use window::{
     Axis, DebugContext, EventContext, LayoutContext, MeasurementContext, PaintContext,
     SizeConstraint, Vector2FExt,
 };

crates/gpui/src/platform/test.rs 🔗

@@ -261,7 +261,7 @@ pub struct Window {
 }
 
 impl Window {
-    fn new(size: Vector2F) -> Self {
+    pub fn new(size: Vector2F) -> Self {
         Self {
             size,
             event_handlers: Default::default(),