move_entity_between_windows.rs

  1//! An entity registers callbacks via the `_in` API family and then gets
  2//! re-hosted in a new window via a click. The point of the example is to
  3//! demonstrate that callbacks dispatched after the move correctly target the
  4//! entity's *current* window rather than the window it was in at
  5//! registration time.
  6//!
  7//! To run:  cargo run -p gpui --example move_entity_between_windows
  8
  9#![cfg_attr(target_family = "wasm", no_main)]
 10
 11use std::time::Duration;
 12
 13use gpui::{
 14    App, AppContext as _, Bounds, Context, EventEmitter, MouseButton, Render, SharedString,
 15    Subscription, Task, Window, WindowBounds, WindowOptions, div, prelude::*, px, rgb, size,
 16};
 17use gpui_platform::application;
 18
 19struct MoveToNewWindow;
 20
 21struct HelloWorld {
 22    text: SharedString,
 23    tick_count: u32,
 24    move_count: u32,
 25    _tasks: Vec<Task<()>>,
 26    _subscriptions: Vec<Subscription>,
 27}
 28
 29impl EventEmitter<MoveToNewWindow> for HelloWorld {}
 30
 31impl HelloWorld {
 32    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
 33        let self_entity = cx.entity();
 34
 35        let task = cx.spawn_in(window, async move |this, cx| {
 36            loop {
 37                cx.background_executor().timer(Duration::from_secs(1)).await;
 38                let result = this.update_in(cx, |this, window, _cx| {
 39                    this.tick_count += 1;
 40                    println!(
 41                        "tick #{} fired in entity's current window {}",
 42                        this.tick_count,
 43                        window.window_handle().window_id().as_u64(),
 44                    );
 45                });
 46                if let Err(err) = result {
 47                    println!("tick task giving up: {err}");
 48                    return;
 49                }
 50            }
 51        });
 52
 53        let subscription = cx.subscribe_in::<_, MoveToNewWindow>(
 54            &self_entity,
 55            window,
 56            move |this, _emitter, _event, window, cx| {
 57                let entered_window_id = window.window_handle().window_id().as_u64();
 58                println!(
 59                    "MoveToNewWindow handler fired in entity's current window {entered_window_id}",
 60                );
 61
 62                this.move_count += 1;
 63                cx.notify();
 64
 65                let entity = cx.entity();
 66                let old_window = window.window_handle();
 67                cx.defer(move |cx| {
 68                    let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
 69                    cx.open_window(
 70                        WindowOptions {
 71                            window_bounds: Some(WindowBounds::Windowed(bounds)),
 72                            ..Default::default()
 73                        },
 74                        move |_, _| entity,
 75                    )
 76                    .expect("failed to open new window");
 77                    old_window
 78                        .update(cx, |_, window, _| window.remove_window())
 79                        .ok();
 80                });
 81            },
 82        );
 83
 84        Self {
 85            text: "World".into(),
 86            tick_count: 0,
 87            move_count: 0,
 88            _tasks: vec![task],
 89            _subscriptions: vec![subscription],
 90        }
 91    }
 92}
 93
 94impl Render for HelloWorld {
 95    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 96        let window_id = window.window_handle().window_id().as_u64();
 97
 98        div()
 99            .flex()
100            .flex_col()
101            .gap_3()
102            .bg(rgb(0x505050))
103            .size(px(500.0))
104            .justify_center()
105            .items_center()
106            .text_xl()
107            .text_color(rgb(0xffffff))
108            .child(format!("Hello, {}!", &self.text))
109            .child(format!("Rendering in window: {window_id}"))
110            .child(format!("Ticks observed by entity: {}", self.tick_count))
111            .child(format!("Moves observed by entity: {}", self.move_count))
112            .child(
113                div()
114                    .px_4()
115                    .py_2()
116                    .bg(rgb(0x4040ff))
117                    .rounded_md()
118                    .child("Move me to a new window")
119                    .on_mouse_down(
120                        MouseButton::Left,
121                        cx.listener(|_this, _, _window, cx| {
122                            cx.emit(MoveToNewWindow);
123                        }),
124                    ),
125            )
126    }
127}
128
129fn run_example() {
130    application().run(|cx: &mut App| {
131        let bounds = Bounds::centered(None, size(px(500.0), px(500.0)), cx);
132        cx.open_window(
133            WindowOptions {
134                window_bounds: Some(WindowBounds::Windowed(bounds)),
135                ..Default::default()
136            },
137            |window, cx| cx.new(|cx| HelloWorld::new(window, cx)),
138        )
139        .unwrap();
140        cx.activate(true);
141    });
142}
143
144#[cfg(not(target_family = "wasm"))]
145fn main() {
146    run_example();
147}
148
149#[cfg(target_family = "wasm")]
150#[wasm_bindgen::prelude::wasm_bindgen(start)]
151pub fn start() {
152    gpui_platform::web_init();
153    run_example();
154}