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