1use crate::display::WebDisplay;
2use crate::events::{ClickState, WebEventListeners, is_mac_platform};
3use std::sync::Arc;
4use std::{cell::Cell, cell::RefCell, rc::Rc};
5
6use gpui::{
7 AnyWindowHandle, Bounds, Capslock, Decorations, DevicePixels, DispatchEventResult, GpuSpecs,
8 Modifiers, MouseButton, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
9 PlatformInputHandler, PlatformWindow, Point, PromptButton, PromptLevel, RequestFrameOptions,
10 ResizeEdge, Scene, Size, WindowAppearance, WindowBackgroundAppearance, WindowBounds,
11 WindowControlArea, WindowControls, WindowDecorations, WindowParams, px,
12};
13use gpui_wgpu::{WgpuContext, WgpuRenderer, WgpuSurfaceConfig};
14use wasm_bindgen::prelude::*;
15
16#[derive(Default)]
17pub(crate) struct WebWindowCallbacks {
18 pub(crate) request_frame: Option<Box<dyn FnMut(RequestFrameOptions)>>,
19 pub(crate) input: Option<Box<dyn FnMut(PlatformInput) -> DispatchEventResult>>,
20 pub(crate) active_status_change: Option<Box<dyn FnMut(bool)>>,
21 pub(crate) hover_status_change: Option<Box<dyn FnMut(bool)>>,
22 pub(crate) resize: Option<Box<dyn FnMut(Size<Pixels>, f32)>>,
23 pub(crate) moved: Option<Box<dyn FnMut()>>,
24 pub(crate) should_close: Option<Box<dyn FnMut() -> bool>>,
25 pub(crate) close: Option<Box<dyn FnOnce()>>,
26 pub(crate) appearance_changed: Option<Box<dyn FnMut()>>,
27 pub(crate) hit_test_window_control: Option<Box<dyn FnMut() -> Option<WindowControlArea>>>,
28}
29
30pub(crate) struct WebWindowMutableState {
31 pub(crate) renderer: WgpuRenderer,
32 pub(crate) bounds: Bounds<Pixels>,
33 pub(crate) scale_factor: f32,
34 pub(crate) max_texture_dimension: u32,
35 pub(crate) title: String,
36 pub(crate) input_handler: Option<PlatformInputHandler>,
37 pub(crate) is_fullscreen: bool,
38 pub(crate) is_active: bool,
39 pub(crate) is_hovered: bool,
40 pub(crate) mouse_position: Point<Pixels>,
41 pub(crate) modifiers: Modifiers,
42 pub(crate) capslock: Capslock,
43}
44
45pub(crate) struct WebWindowInner {
46 pub(crate) browser_window: web_sys::Window,
47 pub(crate) canvas: web_sys::HtmlCanvasElement,
48 pub(crate) has_device_pixel_support: bool,
49 pub(crate) is_mac: bool,
50 pub(crate) state: RefCell<WebWindowMutableState>,
51 pub(crate) callbacks: RefCell<WebWindowCallbacks>,
52 pub(crate) click_state: RefCell<ClickState>,
53 pub(crate) pressed_button: Cell<Option<MouseButton>>,
54 pub(crate) last_physical_size: Cell<(u32, u32)>,
55 pub(crate) notify_scale: Cell<bool>,
56 mql_handle: RefCell<Option<MqlHandle>>,
57 pending_physical_size: Cell<Option<(u32, u32)>>,
58}
59
60pub struct WebWindow {
61 inner: Rc<WebWindowInner>,
62 display: Rc<dyn PlatformDisplay>,
63 #[allow(dead_code)]
64 handle: AnyWindowHandle,
65 _raf_closure: Closure<dyn FnMut()>,
66 _resize_observer: Option<web_sys::ResizeObserver>,
67 _resize_observer_closure: Closure<dyn FnMut(js_sys::Array)>,
68 _event_listeners: WebEventListeners,
69}
70
71impl WebWindow {
72 pub fn new(
73 handle: AnyWindowHandle,
74 _params: WindowParams,
75 context: &WgpuContext,
76 browser_window: web_sys::Window,
77 ) -> anyhow::Result<Self> {
78 let document = browser_window
79 .document()
80 .ok_or_else(|| anyhow::anyhow!("No `document` found on window"))?;
81
82 let canvas: web_sys::HtmlCanvasElement = document
83 .create_element("canvas")
84 .map_err(|e| anyhow::anyhow!("Failed to create canvas element: {e:?}"))?
85 .dyn_into()
86 .map_err(|e| anyhow::anyhow!("Created element is not a canvas: {e:?}"))?;
87
88 let dpr = browser_window.device_pixel_ratio() as f32;
89 let max_texture_dimension = context.device.limits().max_texture_dimension_2d;
90 let has_device_pixel_support = check_device_pixel_support();
91
92 canvas.set_tab_index(0);
93
94 let style = canvas.style();
95 style
96 .set_property("width", "100%")
97 .map_err(|e| anyhow::anyhow!("Failed to set canvas width style: {e:?}"))?;
98 style
99 .set_property("height", "100%")
100 .map_err(|e| anyhow::anyhow!("Failed to set canvas height style: {e:?}"))?;
101 style
102 .set_property("display", "block")
103 .map_err(|e| anyhow::anyhow!("Failed to set canvas display style: {e:?}"))?;
104 style
105 .set_property("outline", "none")
106 .map_err(|e| anyhow::anyhow!("Failed to set canvas outline style: {e:?}"))?;
107 style
108 .set_property("touch-action", "none")
109 .map_err(|e| anyhow::anyhow!("Failed to set touch-action style: {e:?}"))?;
110
111 let body = document
112 .body()
113 .ok_or_else(|| anyhow::anyhow!("No `body` found on document"))?;
114 body.append_child(&canvas)
115 .map_err(|e| anyhow::anyhow!("Failed to append canvas to body: {e:?}"))?;
116
117 canvas.focus().ok();
118
119 let device_size = Size {
120 width: DevicePixels(0),
121 height: DevicePixels(0),
122 };
123
124 let renderer_config = WgpuSurfaceConfig {
125 size: device_size,
126 transparent: false,
127 };
128
129 let renderer = WgpuRenderer::new_from_canvas(context, &canvas, renderer_config)?;
130
131 let display: Rc<dyn PlatformDisplay> = Rc::new(WebDisplay::new(browser_window.clone()));
132
133 let initial_bounds = Bounds {
134 origin: Point::default(),
135 size: Size::default(),
136 };
137
138 let mutable_state = WebWindowMutableState {
139 renderer,
140 bounds: initial_bounds,
141 scale_factor: dpr,
142 max_texture_dimension,
143 title: String::new(),
144 input_handler: None,
145 is_fullscreen: false,
146 is_active: true,
147 is_hovered: false,
148 mouse_position: Point::default(),
149 modifiers: Modifiers::default(),
150 capslock: Capslock::default(),
151 };
152
153 let is_mac = is_mac_platform(&browser_window);
154
155 let inner = Rc::new(WebWindowInner {
156 browser_window,
157 canvas,
158 has_device_pixel_support,
159 is_mac,
160 state: RefCell::new(mutable_state),
161 callbacks: RefCell::new(WebWindowCallbacks::default()),
162 click_state: RefCell::new(ClickState::default()),
163 pressed_button: Cell::new(None),
164 last_physical_size: Cell::new((0, 0)),
165 notify_scale: Cell::new(false),
166 mql_handle: RefCell::new(None),
167 pending_physical_size: Cell::new(None),
168 });
169
170 let raf_closure = inner.create_raf_closure();
171 inner.schedule_raf(&raf_closure);
172
173 let resize_observer_closure = Self::create_resize_observer_closure(Rc::clone(&inner));
174 let resize_observer =
175 web_sys::ResizeObserver::new(resize_observer_closure.as_ref().unchecked_ref()).ok();
176
177 if let Some(ref observer) = resize_observer {
178 inner.observe_canvas(observer);
179 inner.watch_dpr_changes(observer);
180 }
181
182 let event_listeners = inner.register_event_listeners();
183
184 Ok(Self {
185 inner,
186 display,
187 handle,
188 _raf_closure: raf_closure,
189 _resize_observer: resize_observer,
190 _resize_observer_closure: resize_observer_closure,
191 _event_listeners: event_listeners,
192 })
193 }
194
195 fn create_resize_observer_closure(
196 inner: Rc<WebWindowInner>,
197 ) -> Closure<dyn FnMut(js_sys::Array)> {
198 Closure::new(move |entries: js_sys::Array| {
199 let entry: web_sys::ResizeObserverEntry = match entries.get(0).dyn_into().ok() {
200 Some(entry) => entry,
201 None => return,
202 };
203
204 let dpr = inner.browser_window.device_pixel_ratio();
205 let dpr_f32 = dpr as f32;
206
207 let (physical_width, physical_height, logical_width, logical_height) =
208 if inner.has_device_pixel_support {
209 let size: web_sys::ResizeObserverSize = entry
210 .device_pixel_content_box_size()
211 .get(0)
212 .unchecked_into();
213 let pw = size.inline_size() as u32;
214 let ph = size.block_size() as u32;
215 let lw = pw as f64 / dpr;
216 let lh = ph as f64 / dpr;
217 (pw, ph, lw as f32, lh as f32)
218 } else {
219 // Safari fallback: use contentRect (always CSS px).
220 let rect = entry.content_rect();
221 let lw = rect.width() as f32;
222 let lh = rect.height() as f32;
223 let pw = (lw as f64 * dpr).round() as u32;
224 let ph = (lh as f64 * dpr).round() as u32;
225 (pw, ph, lw, lh)
226 };
227
228 let scale_changed = inner.notify_scale.replace(false);
229 let prev = inner.last_physical_size.get();
230 let size_changed = prev != (physical_width, physical_height);
231
232 if !scale_changed && !size_changed {
233 return;
234 }
235 inner
236 .last_physical_size
237 .set((physical_width, physical_height));
238
239 // Skip rendering to a zero-size canvas (e.g. display:none).
240 if physical_width == 0 || physical_height == 0 {
241 let mut s = inner.state.borrow_mut();
242 s.bounds.size = Size::default();
243 s.scale_factor = dpr_f32;
244 // Still fire the callback so GPUI knows the window is gone.
245 drop(s);
246 let mut cbs = inner.callbacks.borrow_mut();
247 if let Some(ref mut callback) = cbs.resize {
248 callback(Size::default(), dpr_f32);
249 }
250 return;
251 }
252
253 let max_texture_dimension = inner.state.borrow().max_texture_dimension;
254 let clamped_width = physical_width.min(max_texture_dimension);
255 let clamped_height = physical_height.min(max_texture_dimension);
256
257 inner
258 .pending_physical_size
259 .set(Some((clamped_width, clamped_height)));
260
261 {
262 let mut s = inner.state.borrow_mut();
263 s.bounds.size = Size {
264 width: px(logical_width),
265 height: px(logical_height),
266 };
267 s.scale_factor = dpr_f32;
268 }
269
270 let new_size = Size {
271 width: px(logical_width),
272 height: px(logical_height),
273 };
274
275 let mut cbs = inner.callbacks.borrow_mut();
276 if let Some(ref mut callback) = cbs.resize {
277 callback(new_size, dpr_f32);
278 }
279 })
280 }
281}
282
283impl WebWindowInner {
284 fn create_raf_closure(self: &Rc<Self>) -> Closure<dyn FnMut()> {
285 let raf_handle: Rc<RefCell<Option<js_sys::Function>>> = Rc::new(RefCell::new(None));
286 let raf_handle_inner = Rc::clone(&raf_handle);
287
288 let this = Rc::clone(self);
289 let closure = Closure::new(move || {
290 {
291 let mut callbacks = this.callbacks.borrow_mut();
292 if let Some(ref mut callback) = callbacks.request_frame {
293 callback(RequestFrameOptions {
294 require_presentation: true,
295 force_render: false,
296 });
297 }
298 }
299
300 // Re-schedule for the next frame
301 if let Some(ref func) = *raf_handle_inner.borrow() {
302 this.browser_window.request_animation_frame(func).ok();
303 }
304 });
305
306 let js_func: js_sys::Function =
307 closure.as_ref().unchecked_ref::<js_sys::Function>().clone();
308 *raf_handle.borrow_mut() = Some(js_func);
309
310 closure
311 }
312
313 fn schedule_raf(&self, closure: &Closure<dyn FnMut()>) {
314 self.browser_window
315 .request_animation_frame(closure.as_ref().unchecked_ref())
316 .ok();
317 }
318
319 fn observe_canvas(&self, observer: &web_sys::ResizeObserver) {
320 observer.unobserve(&self.canvas);
321 if self.has_device_pixel_support {
322 let options = web_sys::ResizeObserverOptions::new();
323 options.set_box(web_sys::ResizeObserverBoxOptions::DevicePixelContentBox);
324 observer.observe_with_options(&self.canvas, &options);
325 } else {
326 observer.observe(&self.canvas);
327 }
328 }
329
330 fn watch_dpr_changes(self: &Rc<Self>, observer: &web_sys::ResizeObserver) {
331 let current_dpr = self.browser_window.device_pixel_ratio();
332 let media_query =
333 format!("(resolution: {current_dpr}dppx), (-webkit-device-pixel-ratio: {current_dpr})");
334 let Some(mql) = self.browser_window.match_media(&media_query).ok().flatten() else {
335 return;
336 };
337
338 let this = Rc::clone(self);
339 let observer = observer.clone();
340
341 let closure = Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
342 this.notify_scale.set(true);
343 this.observe_canvas(&observer);
344 this.watch_dpr_changes(&observer);
345 });
346
347 mql.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())
348 .ok();
349
350 *self.mql_handle.borrow_mut() = Some(MqlHandle {
351 mql,
352 _closure: closure,
353 });
354 }
355
356 pub(crate) fn register_visibility_change(
357 self: &Rc<Self>,
358 ) -> Option<Closure<dyn FnMut(JsValue)>> {
359 let document = self.browser_window.document()?;
360 let this = Rc::clone(self);
361
362 let closure = Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
363 let is_visible = this
364 .browser_window
365 .document()
366 .map(|doc| {
367 let state_str: String = js_sys::Reflect::get(&doc, &"visibilityState".into())
368 .ok()
369 .and_then(|v| v.as_string())
370 .unwrap_or_default();
371 state_str == "visible"
372 })
373 .unwrap_or(true);
374
375 {
376 let mut state = this.state.borrow_mut();
377 state.is_active = is_visible;
378 }
379 let mut callbacks = this.callbacks.borrow_mut();
380 if let Some(ref mut callback) = callbacks.active_status_change {
381 callback(is_visible);
382 }
383 });
384
385 document
386 .add_event_listener_with_callback("visibilitychange", closure.as_ref().unchecked_ref())
387 .ok();
388
389 Some(closure)
390 }
391
392 pub(crate) fn register_appearance_change(
393 self: &Rc<Self>,
394 ) -> Option<Closure<dyn FnMut(JsValue)>> {
395 let mql = self
396 .browser_window
397 .match_media("(prefers-color-scheme: dark)")
398 .ok()??;
399
400 let this = Rc::clone(self);
401 let closure = Closure::<dyn FnMut(JsValue)>::new(move |_event: JsValue| {
402 let mut callbacks = this.callbacks.borrow_mut();
403 if let Some(ref mut callback) = callbacks.appearance_changed {
404 callback();
405 }
406 });
407
408 mql.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())
409 .ok();
410
411 Some(closure)
412 }
413}
414
415fn current_appearance(browser_window: &web_sys::Window) -> WindowAppearance {
416 let is_dark = browser_window
417 .match_media("(prefers-color-scheme: dark)")
418 .ok()
419 .flatten()
420 .map(|mql| mql.matches())
421 .unwrap_or(false);
422
423 if is_dark {
424 WindowAppearance::Dark
425 } else {
426 WindowAppearance::Light
427 }
428}
429
430struct MqlHandle {
431 mql: web_sys::MediaQueryList,
432 _closure: Closure<dyn FnMut(JsValue)>,
433}
434
435impl Drop for MqlHandle {
436 fn drop(&mut self) {
437 self.mql
438 .remove_event_listener_with_callback("change", self._closure.as_ref().unchecked_ref())
439 .ok();
440 }
441}
442
443// Safari does not support `devicePixelContentBoxSize`, so detect whether it's available.
444fn check_device_pixel_support() -> bool {
445 let global: JsValue = js_sys::global().into();
446 let Ok(constructor) = js_sys::Reflect::get(&global, &"ResizeObserverEntry".into()) else {
447 return false;
448 };
449 let Ok(prototype) = js_sys::Reflect::get(&constructor, &"prototype".into()) else {
450 return false;
451 };
452 let descriptor = js_sys::Object::get_own_property_descriptor(
453 &prototype.unchecked_into::<js_sys::Object>(),
454 &"devicePixelContentBoxSize".into(),
455 );
456 !descriptor.is_undefined()
457}
458
459impl raw_window_handle::HasWindowHandle for WebWindow {
460 fn window_handle(
461 &self,
462 ) -> Result<raw_window_handle::WindowHandle<'_>, raw_window_handle::HandleError> {
463 let canvas_ref: &JsValue = self.inner.canvas.as_ref();
464 let obj = std::ptr::NonNull::from(canvas_ref).cast::<std::ffi::c_void>();
465 let handle = raw_window_handle::WebCanvasWindowHandle::new(obj);
466 Ok(unsafe { raw_window_handle::WindowHandle::borrow_raw(handle.into()) })
467 }
468}
469
470impl raw_window_handle::HasDisplayHandle for WebWindow {
471 fn display_handle(
472 &self,
473 ) -> Result<raw_window_handle::DisplayHandle<'_>, raw_window_handle::HandleError> {
474 Ok(raw_window_handle::DisplayHandle::web())
475 }
476}
477
478impl PlatformWindow for WebWindow {
479 fn bounds(&self) -> Bounds<Pixels> {
480 self.inner.state.borrow().bounds
481 }
482
483 fn is_maximized(&self) -> bool {
484 false
485 }
486
487 fn window_bounds(&self) -> WindowBounds {
488 WindowBounds::Windowed(self.bounds())
489 }
490
491 fn content_size(&self) -> Size<Pixels> {
492 self.inner.state.borrow().bounds.size
493 }
494
495 fn resize(&mut self, size: Size<Pixels>) {
496 let style = self.inner.canvas.style();
497 style
498 .set_property("width", &format!("{}px", f32::from(size.width)))
499 .ok();
500 style
501 .set_property("height", &format!("{}px", f32::from(size.height)))
502 .ok();
503 }
504
505 fn scale_factor(&self) -> f32 {
506 self.inner.state.borrow().scale_factor
507 }
508
509 fn appearance(&self) -> WindowAppearance {
510 current_appearance(&self.inner.browser_window)
511 }
512
513 fn display(&self) -> Option<Rc<dyn PlatformDisplay>> {
514 Some(self.display.clone())
515 }
516
517 fn mouse_position(&self) -> Point<Pixels> {
518 self.inner.state.borrow().mouse_position
519 }
520
521 fn modifiers(&self) -> Modifiers {
522 self.inner.state.borrow().modifiers
523 }
524
525 fn capslock(&self) -> Capslock {
526 self.inner.state.borrow().capslock
527 }
528
529 fn set_input_handler(&mut self, input_handler: PlatformInputHandler) {
530 self.inner.state.borrow_mut().input_handler = Some(input_handler);
531 }
532
533 fn take_input_handler(&mut self) -> Option<PlatformInputHandler> {
534 self.inner.state.borrow_mut().input_handler.take()
535 }
536
537 fn prompt(
538 &self,
539 _level: PromptLevel,
540 _msg: &str,
541 _detail: Option<&str>,
542 _answers: &[PromptButton],
543 ) -> Option<futures::channel::oneshot::Receiver<usize>> {
544 None
545 }
546
547 fn activate(&self) {
548 self.inner.state.borrow_mut().is_active = true;
549 }
550
551 fn is_active(&self) -> bool {
552 self.inner.state.borrow().is_active
553 }
554
555 fn is_hovered(&self) -> bool {
556 self.inner.state.borrow().is_hovered
557 }
558
559 fn background_appearance(&self) -> WindowBackgroundAppearance {
560 WindowBackgroundAppearance::Opaque
561 }
562
563 fn set_title(&mut self, title: &str) {
564 self.inner.state.borrow_mut().title = title.to_owned();
565 if let Some(document) = self.inner.browser_window.document() {
566 document.set_title(title);
567 }
568 }
569
570 fn set_background_appearance(&self, _background: WindowBackgroundAppearance) {}
571
572 fn minimize(&self) {
573 log::warn!("WebWindow::minimize is not supported in the browser");
574 }
575
576 fn zoom(&self) {
577 log::warn!("WebWindow::zoom is not supported in the browser");
578 }
579
580 fn toggle_fullscreen(&self) {
581 let mut state = self.inner.state.borrow_mut();
582 state.is_fullscreen = !state.is_fullscreen;
583
584 if state.is_fullscreen {
585 let canvas: &web_sys::Element = self.inner.canvas.as_ref();
586 canvas.request_fullscreen().ok();
587 } else {
588 if let Some(document) = self.inner.browser_window.document() {
589 document.exit_fullscreen();
590 }
591 }
592 }
593
594 fn is_fullscreen(&self) -> bool {
595 self.inner.state.borrow().is_fullscreen
596 }
597
598 fn on_request_frame(&self, callback: Box<dyn FnMut(RequestFrameOptions)>) {
599 self.inner.callbacks.borrow_mut().request_frame = Some(callback);
600 }
601
602 fn on_input(&self, callback: Box<dyn FnMut(PlatformInput) -> DispatchEventResult>) {
603 self.inner.callbacks.borrow_mut().input = Some(callback);
604 }
605
606 fn on_active_status_change(&self, callback: Box<dyn FnMut(bool)>) {
607 self.inner.callbacks.borrow_mut().active_status_change = Some(callback);
608 }
609
610 fn on_hover_status_change(&self, callback: Box<dyn FnMut(bool)>) {
611 self.inner.callbacks.borrow_mut().hover_status_change = Some(callback);
612 }
613
614 fn on_resize(&self, callback: Box<dyn FnMut(Size<Pixels>, f32)>) {
615 self.inner.callbacks.borrow_mut().resize = Some(callback);
616 }
617
618 fn on_moved(&self, callback: Box<dyn FnMut()>) {
619 self.inner.callbacks.borrow_mut().moved = Some(callback);
620 }
621
622 fn on_should_close(&self, callback: Box<dyn FnMut() -> bool>) {
623 self.inner.callbacks.borrow_mut().should_close = Some(callback);
624 }
625
626 fn on_close(&self, callback: Box<dyn FnOnce()>) {
627 self.inner.callbacks.borrow_mut().close = Some(callback);
628 }
629
630 fn on_hit_test_window_control(&self, callback: Box<dyn FnMut() -> Option<WindowControlArea>>) {
631 self.inner.callbacks.borrow_mut().hit_test_window_control = Some(callback);
632 }
633
634 fn on_appearance_changed(&self, callback: Box<dyn FnMut()>) {
635 self.inner.callbacks.borrow_mut().appearance_changed = Some(callback);
636 }
637
638 fn draw(&self, scene: &Scene) {
639 if let Some((width, height)) = self.inner.pending_physical_size.take() {
640 if self.inner.canvas.width() != width || self.inner.canvas.height() != height {
641 self.inner.canvas.set_width(width);
642 self.inner.canvas.set_height(height);
643 }
644
645 let mut state = self.inner.state.borrow_mut();
646 state.renderer.update_drawable_size(Size {
647 width: DevicePixels(width as i32),
648 height: DevicePixels(height as i32),
649 });
650 drop(state);
651 }
652
653 self.inner.state.borrow_mut().renderer.draw(scene);
654 }
655
656 fn completed_frame(&self) {
657 // On web, presentation happens automatically via wgpu surface present
658 }
659
660 fn sprite_atlas(&self) -> Arc<dyn PlatformAtlas> {
661 self.inner.state.borrow().renderer.sprite_atlas().clone()
662 }
663
664 fn is_subpixel_rendering_supported(&self) -> bool {
665 self.inner
666 .state
667 .borrow()
668 .renderer
669 .supports_dual_source_blending()
670 }
671
672 fn gpu_specs(&self) -> Option<GpuSpecs> {
673 Some(self.inner.state.borrow().renderer.gpu_specs())
674 }
675
676 fn update_ime_position(&self, _bounds: Bounds<Pixels>) {}
677
678 fn request_decorations(&self, _decorations: WindowDecorations) {}
679
680 fn show_window_menu(&self, _position: Point<Pixels>) {}
681
682 fn start_window_move(&self) {}
683
684 fn start_window_resize(&self, _edge: ResizeEdge) {}
685
686 fn window_decorations(&self) -> Decorations {
687 Decorations::Server
688 }
689
690 fn set_app_id(&mut self, _app_id: &str) {}
691
692 fn window_controls(&self) -> WindowControls {
693 WindowControls {
694 fullscreen: true,
695 maximize: false,
696 minimize: false,
697 window_menu: false,
698 }
699 }
700
701 fn set_client_inset(&self, _inset: Pixels) {}
702}