1
+ use web_sys:: EventTarget ;
2
+
3
+ use crate :: web:: string_from_js_value;
4
+
1
5
use super :: {
2
6
button_from_mouse_event, location_hash, modifiers_from_kb_event, modifiers_from_mouse_event,
3
- modifiers_from_wheel_event, pos_from_mouse_event, prefers_color_scheme_dark, primary_touch_pos,
4
- push_touches, text_from_keyboard_event, theme_from_dark_mode, translate_key, AppRunner ,
5
- Closure , JsCast , JsValue , WebRunner ,
7
+ modifiers_from_wheel_event, native_pixels_per_point, pos_from_mouse_event,
8
+ prefers_color_scheme_dark, primary_touch_pos, push_touches, text_from_keyboard_event,
9
+ theme_from_dark_mode, translate_key, AppRunner , Closure , JsCast , JsValue , WebRunner ,
10
+ DEBUG_RESIZE ,
6
11
} ;
7
- use web_sys:: EventTarget ;
8
12
9
13
// TODO(emilk): there are more calls to `prevent_default` and `stop_propagation`
10
14
// than what is probably needed.
@@ -363,10 +367,17 @@ fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result
363
367
runner. save ( ) ;
364
368
} ) ?;
365
369
366
- // NOTE: resize is handled by `ResizeObserver` below
370
+ // We want to handle the case of dragging the browser from one monitor to another,
371
+ // which can cause the DPR to change without any resize event (e.g. Safari).
372
+ install_dpr_change_event ( runner_ref) ?;
373
+
374
+ // No need to subscribe to "resize": we already subscribe to the canvas
375
+ // size using a ResizeObserver, and we also subscribe to DPR changes of the monitor.
367
376
for event_name in & [ "load" , "pagehide" , "pageshow" ] {
368
377
runner_ref. add_event_listener ( window, event_name, move |_: web_sys:: Event , runner| {
369
- // log::debug!("{event_name:?}");
378
+ if DEBUG_RESIZE {
379
+ log:: debug!( "{event_name:?}" ) ;
380
+ }
370
381
runner. needs_repaint . repaint_asap ( ) ;
371
382
} ) ?;
372
383
}
@@ -380,6 +391,48 @@ fn install_window_events(runner_ref: &WebRunner, window: &EventTarget) -> Result
380
391
Ok ( ( ) )
381
392
}
382
393
394
+ fn install_dpr_change_event ( web_runner : & WebRunner ) -> Result < ( ) , JsValue > {
395
+ let original_dpr = native_pixels_per_point ( ) ;
396
+
397
+ let window = web_sys:: window ( ) . unwrap ( ) ;
398
+ let Some ( media_query_list) =
399
+ window. match_media ( & format ! ( "(resolution: {original_dpr}dppx)" ) ) ?
400
+ else {
401
+ log:: error!(
402
+ "Failed to create MediaQueryList: eframe won't be able to detect changes in DPR"
403
+ ) ;
404
+ return Ok ( ( ) ) ;
405
+ } ;
406
+
407
+ let closure = move |_: web_sys:: Event , app_runner : & mut AppRunner , web_runner : & WebRunner | {
408
+ let new_dpr = native_pixels_per_point ( ) ;
409
+ log:: debug!( "Device Pixel Ratio changed from {original_dpr} to {new_dpr}" ) ;
410
+
411
+ if true {
412
+ // Explicitly resize canvas to match the new DPR.
413
+ // This is a bit ugly, but I haven't found a better way to do it.
414
+ let canvas = app_runner. canvas ( ) ;
415
+ canvas. set_width ( ( canvas. width ( ) as f32 * new_dpr / original_dpr) . round ( ) as _ ) ;
416
+ canvas. set_height ( ( canvas. height ( ) as f32 * new_dpr / original_dpr) . round ( ) as _ ) ;
417
+ log:: debug!( "Resized canvas to {}x{}" , canvas. width( ) , canvas. height( ) ) ;
418
+ }
419
+
420
+ // It may be tempting to call `resize_observer.observe(&canvas)` here,
421
+ // but unfortunately this has no effect.
422
+
423
+ if let Err ( err) = install_dpr_change_event ( web_runner) {
424
+ log:: error!(
425
+ "Failed to install DPR change event: {}" ,
426
+ string_from_js_value( & err)
427
+ ) ;
428
+ }
429
+ } ;
430
+
431
+ let options = web_sys:: AddEventListenerOptions :: default ( ) ;
432
+ options. set_once ( true ) ;
433
+ web_runner. add_event_listener_ex ( & media_query_list, "change" , & options, closure)
434
+ }
435
+
383
436
fn install_color_scheme_change_event (
384
437
runner_ref : & WebRunner ,
385
438
window : & web_sys:: Window ,
@@ -813,53 +866,79 @@ fn install_drag_and_drop(runner_ref: &WebRunner, target: &EventTarget) -> Result
813
866
Ok ( ( ) )
814
867
}
815
868
816
- /// Install a `ResizeObserver` to observe changes to the size of the canvas.
817
- ///
818
- /// This is the only way to ensure a canvas size change without an associated window `resize` event
819
- /// actually results in a resize of the canvas.
869
+ /// A `ResizeObserver` is used to observe changes to the size of the canvas.
820
870
///
821
871
/// The resize observer is called the by the browser at `observe` time, instead of just on the first actual resize.
822
872
/// We use that to trigger the first `request_animation_frame` _after_ updating the size of the canvas to the correct dimensions,
823
873
/// to avoid [#4622](https://github.com/emilk/egui/issues/4622).
824
- pub ( crate ) fn install_resize_observer ( runner_ref : & WebRunner ) -> Result < ( ) , JsValue > {
825
- let closure = Closure :: wrap ( Box :: new ( {
826
- let runner_ref = runner_ref. clone ( ) ;
827
- move |entries : js_sys:: Array | {
828
- // Only call the wrapped closure if the egui code has not panicked
829
- if let Some ( mut runner_lock) = runner_ref. try_lock ( ) {
830
- let canvas = runner_lock. canvas ( ) ;
831
- let ( width, height) = match get_display_size ( & entries) {
832
- Ok ( v) => v,
833
- Err ( err) => {
834
- log:: error!( "{}" , super :: string_from_js_value( & err) ) ;
835
- return ;
874
+ pub struct ResizeObserverContext {
875
+ observer : web_sys:: ResizeObserver ,
876
+
877
+ // Kept so it is not dropped until we are done with it.
878
+ _closure : Closure < dyn FnMut ( js_sys:: Array ) > ,
879
+ }
880
+
881
+ impl Drop for ResizeObserverContext {
882
+ fn drop ( & mut self ) {
883
+ self . observer . disconnect ( ) ;
884
+ }
885
+ }
886
+
887
+ impl ResizeObserverContext {
888
+ pub fn new ( runner_ref : & WebRunner ) -> Result < Self , JsValue > {
889
+ let closure = Closure :: wrap ( Box :: new ( {
890
+ let runner_ref = runner_ref. clone ( ) ;
891
+ move |entries : js_sys:: Array | {
892
+ if DEBUG_RESIZE {
893
+ // log::info!("ResizeObserverContext callback");
894
+ }
895
+ // Only call the wrapped closure if the egui code has not panicked
896
+ if let Some ( mut runner_lock) = runner_ref. try_lock ( ) {
897
+ let canvas = runner_lock. canvas ( ) ;
898
+ let ( width, height) = match get_display_size ( & entries) {
899
+ Ok ( v) => v,
900
+ Err ( err) => {
901
+ log:: error!( "{}" , super :: string_from_js_value( & err) ) ;
902
+ return ;
903
+ }
904
+ } ;
905
+ if DEBUG_RESIZE {
906
+ log:: info!(
907
+ "ResizeObserver: new canvas size: {width}x{height}, DPR: {}" ,
908
+ web_sys:: window( ) . unwrap( ) . device_pixel_ratio( )
909
+ ) ;
836
910
}
837
- } ;
838
- canvas. set_width ( width ) ;
839
- canvas . set_height ( height ) ;
840
-
841
- // force an immediate repaint
842
- runner_lock . needs_repaint . repaint_asap ( ) ;
843
- paint_if_needed ( & mut runner_lock) ;
844
- drop ( runner_lock ) ;
845
- // we rely on the resize observer to trigger the first ` request_animation_frame`:
846
- if let Err ( err ) = runner_ref . request_animation_frame ( ) {
847
- log :: error! ( "{}" , super :: string_from_js_value ( & err ) ) ;
848
- } ;
911
+ canvas . set_width ( width ) ;
912
+ canvas. set_height ( height ) ;
913
+
914
+ // force an immediate repaint
915
+ runner_lock . needs_repaint . repaint_asap ( ) ;
916
+ paint_if_needed ( & mut runner_lock ) ;
917
+ drop ( runner_lock) ;
918
+ // we rely on the resize observer to trigger the first `request_animation_frame`:
919
+ if let Err ( err ) = runner_ref . request_animation_frame ( ) {
920
+ log :: error! ( "{}" , super :: string_from_js_value ( & err ) ) ;
921
+ } ;
922
+ }
849
923
}
850
- }
851
- } ) as Box < dyn FnMut ( js_sys:: Array ) > ) ;
924
+ } ) as Box < dyn FnMut ( js_sys:: Array ) > ) ;
852
925
853
- let observer = web_sys:: ResizeObserver :: new ( closure. as_ref ( ) . unchecked_ref ( ) ) ?;
854
- let options = web_sys:: ResizeObserverOptions :: new ( ) ;
855
- options. set_box ( web_sys:: ResizeObserverBoxOptions :: ContentBox ) ;
856
- if let Some ( runner_lock) = runner_ref. try_lock ( ) {
857
- observer. observe_with_options ( runner_lock. canvas ( ) , & options) ;
858
- drop ( runner_lock) ;
859
- runner_ref. set_resize_observer ( observer, closure) ;
926
+ let observer = web_sys:: ResizeObserver :: new ( closure. as_ref ( ) . unchecked_ref ( ) ) ?;
927
+
928
+ Ok ( Self {
929
+ observer,
930
+ _closure : closure,
931
+ } )
860
932
}
861
933
862
- Ok ( ( ) )
934
+ pub fn observe ( & self , canvas : & web_sys:: HtmlCanvasElement ) {
935
+ if DEBUG_RESIZE {
936
+ log:: info!( "Calling observe on canvas…" ) ;
937
+ }
938
+ let options = web_sys:: ResizeObserverOptions :: new ( ) ;
939
+ options. set_box ( web_sys:: ResizeObserverBoxOptions :: ContentBox ) ;
940
+ self . observer . observe_with_options ( canvas, & options) ;
941
+ }
863
942
}
864
943
865
944
// Code ported to Rust from:
@@ -878,6 +957,10 @@ fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32
878
957
width = size. inline_size ( ) ;
879
958
height = size. block_size ( ) ;
880
959
dpr = 1.0 ; // no need to apply
960
+
961
+ if DEBUG_RESIZE {
962
+ // log::info!("devicePixelContentBoxSize {width}x{height}");
963
+ }
881
964
} else if JsValue :: from_str ( "contentBoxSize" ) . js_in ( entry. as_ref ( ) ) {
882
965
let content_box_size = entry. content_box_size ( ) ;
883
966
let idx0 = content_box_size. at ( 0 ) ;
@@ -892,6 +975,9 @@ fn get_display_size(resize_observer_entries: &js_sys::Array) -> Result<(u32, u32
892
975
width = size. inline_size ( ) ;
893
976
height = size. block_size ( ) ;
894
977
}
978
+ if DEBUG_RESIZE {
979
+ log:: info!( "contentBoxSize {width}x{height}" ) ;
980
+ }
895
981
} else {
896
982
// legacy
897
983
let content_rect = entry. content_rect ( ) ;
0 commit comments