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}