vizia_winit/
window.rs

1use crate::window_modifiers::WindowModifiers;
2use glutin::context::GlProfile;
3use vizia_core::context::TreeProps;
4use vizia_window::AnchorTarget;
5#[cfg(target_os = "windows")]
6use winit::platform::windows::WindowExtWindows;
7#[cfg(target_os = "windows")]
8use winit::{platform::windows::WindowAttributesExtWindows, raw_window_handle::RawWindowHandle};
9
10use crate::convert::cursor_icon_to_cursor_icon;
11use hashbrown::HashMap;
12use std::error::Error;
13use std::num::NonZeroU32;
14use std::{ffi::CString, sync::Arc};
15use winit::raw_window_handle::HasWindowHandle;
16
17use gl_rs as gl;
18use glutin::config::Config;
19use glutin_winit::DisplayBuilder;
20
21use gl::types::*;
22
23use glutin::{
24    config::ConfigTemplateBuilder,
25    context::{ContextApi, ContextAttributesBuilder},
26    display::GetGlDisplay,
27    prelude::*,
28    surface::{SurfaceAttributesBuilder, WindowSurface},
29};
30
31use skia_safe::{
32    gpu::{
33        self, backend_render_targets, ganesh::context_options, gl::FramebufferInfo, ContextOptions,
34        SurfaceOrigin,
35    },
36    ColorSpace, ColorType, PixelGeometry, Surface, SurfaceProps, SurfacePropsFlags,
37};
38
39use vizia_core::prelude::*;
40use winit::event_loop::ActiveEventLoop;
41use winit::window::{CursorGrabMode, CursorIcon, CustomCursor, WindowAttributes, WindowLevel};
42use winit::{dpi::*, window::WindowId};
43
44pub struct WinState {
45    pub entity: Entity,
46    gl_config: Config,
47    gl_context: glutin::context::PossiblyCurrentContext,
48    pub gl_surface: glutin::surface::Surface<glutin::surface::WindowSurface>,
49    pub id: WindowId,
50    pub gr_context: skia_safe::gpu::DirectContext,
51    pub window: Arc<winit::window::Window>,
52    pub surface: skia_safe::Surface,
53    pub dirty_surface: skia_safe::Surface,
54    pub should_close: bool,
55    #[cfg(target_os = "windows")]
56    pub is_initially_cloaked: bool,
57}
58
59impl Drop for WinState {
60    fn drop(&mut self) {
61        self.gl_context.make_current(&self.gl_surface).unwrap();
62    }
63}
64
65impl WinState {
66    pub fn new(
67        event_loop: &ActiveEventLoop,
68        entity: Entity,
69        #[allow(unused_mut)] mut window_attributes: WindowAttributes,
70        #[allow(unused_variables)] owner: Option<Arc<winit::window::Window>>,
71    ) -> Result<Self, Box<dyn Error>> {
72        #[cfg(target_os = "windows")]
73        let (window, gl_config) = {
74            if let Some(owner) = owner {
75                let RawWindowHandle::Win32(handle) = owner.window_handle().unwrap().as_raw() else {
76                    unreachable!();
77                };
78                window_attributes = window_attributes.with_owner_window(handle.hwnd.get());
79            }
80
81            // The current version of winit spawns new windows with unspecified position/size.
82            // As a workaround, we'll hide the window during creation and reveal it afterward.
83            let visible = window_attributes.visible;
84            let window_attributes = window_attributes.with_visible(false);
85
86            let (window, config) = build_window(event_loop, window_attributes);
87
88            let window = window.expect("Could not create window with OpenGL context");
89            // Another problem is the white background that briefly flashes on window creation.
90            // To avoid this one we must wait until the first draw is complete before revealing
91            // our window. The visible property won't work in this case as it prevents drawing.
92            // Instead we use the "cloak" attribute, which hides the window without that issue.
93            set_cloak(&window, true);
94            window.set_visible(visible);
95
96            (window, config)
97        };
98
99        #[cfg(not(target_os = "windows"))]
100        let (window, gl_config) = {
101            let (window, config) = build_window(event_loop, window_attributes);
102            let window = window.expect("Could not create window with OpenGL context");
103            (window, config)
104        };
105
106        window.set_ime_allowed(true);
107        window.set_visible(true);
108
109        let raw_window_handle = window.window_handle().unwrap().as_raw();
110
111        let gl_display = gl_config.display();
112
113        let context_attributes = ContextAttributesBuilder::new()
114            .with_profile(GlProfile::Core)
115            .with_context_api(ContextApi::OpenGl(None))
116            .build(Some(raw_window_handle));
117
118        let fallback_context_attributes = ContextAttributesBuilder::new()
119            .with_profile(GlProfile::Core)
120            .with_context_api(ContextApi::Gles(None))
121            .build(Some(raw_window_handle));
122
123        let not_current_gl_context = unsafe {
124            gl_display.create_context(&gl_config, &context_attributes).unwrap_or_else(|_| {
125                gl_display
126                    .create_context(&gl_config, &fallback_context_attributes)
127                    .expect("failed to create context")
128            })
129        };
130
131        let (width, height): (u32, u32) = window.inner_size().into();
132
133        let attrs = SurfaceAttributesBuilder::<WindowSurface>::new().with_srgb(Some(true)).build(
134            raw_window_handle,
135            NonZeroU32::new(width.max(1)).unwrap(),
136            NonZeroU32::new(height.max(1)).unwrap(),
137        );
138
139        let gl_surface =
140            unsafe { gl_config.display().create_window_surface(&gl_config, &attrs).unwrap() };
141
142        let gl_context = not_current_gl_context.make_current(&gl_surface).unwrap();
143
144        // if window_description.vsync {
145        //     gl_surface
146        //         .set_swap_interval(&gl_context, SwapInterval::Wait(NonZeroU32::new(1).unwrap()))
147        //         .expect("Failed to set vsync");
148        // }
149
150        // Build skia renderer
151        gl::load_with(|s| {
152            gl_config.display().get_proc_address(CString::new(s).unwrap().as_c_str())
153        });
154
155        let interface = skia_safe::gpu::gl::Interface::new_load_with(|name| {
156            if name == "eglGetCurrentDisplay" {
157                return std::ptr::null();
158            }
159            gl_config.display().get_proc_address(CString::new(name).unwrap().as_c_str())
160        })
161        .expect("Could not create interface");
162
163        // https://github.com/rust-skia/rust-skia/issues/476
164        let mut context_options = ContextOptions::new();
165        context_options.skip_gl_error_checks = context_options::Enable::Yes;
166
167        let mut gr_context = skia_safe::gpu::direct_contexts::make_gl(interface, &context_options)
168            .expect("Could not create direct context");
169
170        let fb_info = {
171            let mut fboid: GLint = 0;
172            unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
173
174            FramebufferInfo {
175                fboid: fboid.try_into().unwrap(),
176                format: skia_safe::gpu::gl::Format::RGBA8.into(),
177                ..Default::default()
178            }
179        };
180
181        let num_samples = gl_config.num_samples() as usize;
182        let stencil_size = gl_config.stencil_size() as usize;
183
184        let mut surface =
185            create_surface(&window, fb_info, &mut gr_context, num_samples, stencil_size);
186
187        let inner_size = window.inner_size();
188
189        let dirty_surface = surface
190            .new_surface_with_dimensions((inner_size.width as i32, inner_size.height as i32))
191            .unwrap();
192
193        // Build our window
194        Ok(WinState {
195            entity,
196            gl_config,
197            gl_context,
198            id: window.id(),
199            gr_context,
200            gl_surface,
201            window: Arc::new(window),
202            surface,
203            dirty_surface,
204            should_close: false,
205            #[cfg(target_os = "windows")]
206            is_initially_cloaked: true,
207        })
208    }
209
210    // Returns a reference to the winit window
211    pub fn window(&self) -> &winit::window::Window {
212        &self.window
213    }
214
215    pub fn make_current(&mut self) {
216        self.gl_context.make_current(&self.gl_surface).unwrap();
217    }
218
219    pub fn resize(&mut self, size: PhysicalSize<u32>) {
220        self.gl_context.make_current(&self.gl_surface).unwrap();
221        let (width, height): (u32, u32) = size.into();
222
223        if width == 0 || height == 0 {
224            return;
225        }
226
227        let fb_info = {
228            let mut fboid: GLint = 0;
229            unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
230
231            FramebufferInfo {
232                fboid: fboid.try_into().unwrap(),
233                format: skia_safe::gpu::gl::Format::RGBA8.into(),
234                ..Default::default()
235            }
236        };
237
238        self.surface = create_surface(
239            &self.window,
240            fb_info,
241            &mut self.gr_context,
242            self.gl_config.num_samples() as usize,
243            self.gl_config.stencil_size() as usize,
244        );
245
246        self.dirty_surface = self
247            .surface
248            .new_surface_with_dimensions((width.max(1) as i32, height.max(1) as i32))
249            .unwrap();
250
251        self.gl_surface.resize(
252            &self.gl_context,
253            NonZeroU32::new(width.max(1)).unwrap(),
254            NonZeroU32::new(height.max(1)).unwrap(),
255        );
256    }
257
258    pub fn swap_buffers(&mut self) {
259        self.gr_context.flush_and_submit();
260        self.gl_surface.swap_buffers(&self.gl_context).expect("Failed to swap buffers");
261    }
262}
263
264fn build_window(
265    event_loop: &ActiveEventLoop,
266    window_attributes: WindowAttributes,
267) -> (Option<winit::window::Window>, Config) {
268    let template = ConfigTemplateBuilder::new().with_alpha_size(8).with_transparency(true);
269    let display_builder = DisplayBuilder::new().with_window_attributes(Some(window_attributes));
270
271    display_builder
272        .build(event_loop, template, |configs| {
273            // Find the config with the maximum number of samples, so our triangle will
274            // be smooth.
275            configs
276                .reduce(|accum, config| {
277                    let transparency_check = config.supports_transparency().unwrap_or(false)
278                        & !accum.supports_transparency().unwrap_or(false);
279
280                    if transparency_check || config.num_samples() < accum.num_samples() {
281                        config
282                    } else {
283                        accum
284                    }
285                })
286                .unwrap()
287        })
288        .unwrap()
289}
290
291/// Cloaks the window such that it is not visible to the user, but will still be composited.
292///
293/// <https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute>
294///
295#[cfg(target_os = "windows")]
296pub fn set_cloak(window: &winit::window::Window, state: bool) -> bool {
297    use windows_sys::Win32::{
298        Foundation::{BOOL, FALSE, HWND, TRUE},
299        Graphics::Dwm::{DwmSetWindowAttribute, DWMWA_CLOAK},
300    };
301
302    let RawWindowHandle::Win32(handle) = window.window_handle().unwrap().as_raw() else {
303        unreachable!();
304    };
305
306    let value = if state { TRUE } else { FALSE };
307
308    let result = unsafe {
309        DwmSetWindowAttribute(
310            handle.hwnd.get() as HWND,
311            DWMWA_CLOAK as u32,
312            std::ptr::from_ref(&value).cast(),
313            std::mem::size_of::<BOOL>() as u32,
314        )
315    };
316
317    result == 0 // success
318}
319
320pub fn create_surface(
321    window: &winit::window::Window,
322    fb_info: FramebufferInfo,
323    gr_context: &mut skia_safe::gpu::DirectContext,
324    num_samples: usize,
325    stencil_size: usize,
326) -> Surface {
327    let size = window.inner_size();
328    let size = (
329        size.width.try_into().expect("Could not convert width"),
330        size.height.try_into().expect("Could not convert height"),
331    );
332
333    let backend_render_target =
334        backend_render_targets::make_gl(size, num_samples, stencil_size, fb_info);
335
336    let surface_props = SurfaceProps::new_with_text_properties(
337        SurfacePropsFlags::default(),
338        PixelGeometry::default(),
339        0.5,
340        0.0,
341    );
342
343    gpu::surfaces::wrap_backend_render_target(
344        gr_context,
345        &backend_render_target,
346        SurfaceOrigin::BottomLeft,
347        ColorType::RGBA8888,
348        ColorSpace::new_srgb(),
349        Some(surface_props).as_ref(),
350        // None,
351    )
352    .expect("Could not create skia surface")
353}
354
355type WindowCallback = Option<Box<dyn Fn(&mut EventContext)>>;
356
357pub struct Window {
358    pub window: Option<Arc<winit::window::Window>>,
359    pub on_close: WindowCallback,
360    pub on_create: WindowCallback,
361    pub should_close: bool,
362    pub(crate) custom_cursors: Arc<HashMap<CursorIcon, CustomCursor>>,
363}
364
365impl Window {
366    fn window(&self) -> &winit::window::Window {
367        self.window.as_ref().unwrap()
368    }
369
370    pub fn new(cx: &mut Context, content: impl 'static + Fn(&mut Context)) -> Handle<Self> {
371        Self {
372            window: None,
373            on_close: None,
374            on_create: None,
375            should_close: false,
376            custom_cursors: Default::default(),
377        }
378        .build(cx, |cx| {
379            cx.windows.insert(
380                cx.current(),
381                WindowState { content: Some(Arc::new(content)), ..Default::default() },
382            );
383            cx.tree.set_window(cx.current(), true);
384        })
385    }
386
387    pub fn popup(
388        cx: &mut Context,
389        is_modal: bool,
390        content: impl 'static + Fn(&mut Context),
391    ) -> Handle<Self> {
392        Self {
393            window: None,
394            on_close: None,
395            on_create: None,
396            should_close: false,
397            custom_cursors: Default::default(),
398        }
399        .build(cx, |cx| {
400            let parent_window = cx.parent_window();
401            if is_modal {
402                cx.emit_to(parent_window, WindowEvent::SetEnabled(false));
403            }
404
405            cx.windows.insert(
406                cx.current(),
407                WindowState {
408                    owner: Some(parent_window),
409                    is_modal: true,
410                    content: Some(Arc::new(content)),
411                    ..Default::default()
412                },
413            );
414            cx.tree.set_window(cx.current(), true);
415        })
416        .anchor_target(AnchorTarget::Window)
417        .lock_focus_to_within()
418    }
419}
420
421impl View for Window {
422    fn element(&self) -> Option<&'static str> {
423        Some("window")
424    }
425
426    fn event(&mut self, cx: &mut EventContext, event: &mut Event) {
427        event.map(|window_event, meta| match window_event {
428            WindowEvent::Destroyed => {
429                let parent_window = cx.parent_window();
430                cx.emit_to(parent_window, WindowEvent::SetEnabled(true));
431            }
432
433            WindowEvent::GrabCursor(flag) => {
434                let grab_mode = if *flag { CursorGrabMode::Locked } else { CursorGrabMode::None };
435                self.window().set_cursor_grab(grab_mode).expect("Failed to set cursor grab");
436            }
437
438            WindowEvent::SetCursorPosition(x, y) => {
439                self.window()
440                    .set_cursor_position(winit::dpi::Position::Physical(PhysicalPosition::new(
441                        *x as i32, *y as i32,
442                    )))
443                    .expect("Failed to set cursor position");
444            }
445
446            WindowEvent::SetCursor(cursor) => {
447                let Some(icon) = cursor_icon_to_cursor_icon(*cursor) else {
448                    self.window().set_cursor_visible(false);
449                    return;
450                };
451
452                if let Some(custom_icon) = self.custom_cursors.get(&icon) {
453                    self.window().set_cursor(custom_icon.clone());
454                } else {
455                    self.window().set_cursor(icon);
456                }
457
458                self.window().set_cursor_visible(true);
459            }
460
461            WindowEvent::SetTitle(title) => {
462                self.window().set_title(title);
463            }
464
465            WindowEvent::SetSize(size) => {
466                let _ = self.window().request_inner_size(LogicalSize::new(size.width, size.height));
467            }
468
469            WindowEvent::SetMinSize(size) => {
470                self.window()
471                    .set_min_inner_size(size.map(|size| LogicalSize::new(size.width, size.height)));
472            }
473
474            WindowEvent::SetMaxSize(size) => {
475                self.window()
476                    .set_max_inner_size(size.map(|size| LogicalSize::new(size.width, size.height)));
477            }
478
479            WindowEvent::SetPosition(pos) => {
480                self.window().set_outer_position(LogicalPosition::new(pos.x, pos.y));
481                meta.consume();
482            }
483
484            WindowEvent::SetResizable(flag) => {
485                self.window().set_resizable(*flag);
486            }
487
488            WindowEvent::SetMinimized(flag) => {
489                self.window().set_minimized(*flag);
490            }
491
492            WindowEvent::SetMaximized(flag) => {
493                self.window().set_maximized(*flag);
494            }
495
496            WindowEvent::SetVisible(flag) => {
497                self.window().set_visible(*flag);
498
499                meta.consume();
500            }
501
502            WindowEvent::SetDecorations(flag) => {
503                self.window().set_decorations(*flag);
504            }
505
506            WindowEvent::ReloadStyles => {
507                cx.reload_styles().unwrap();
508            }
509
510            WindowEvent::WindowClose => {
511                self.should_close = true;
512
513                cx.close_window();
514
515                if let Some(callback) = &self.on_close {
516                    callback(cx);
517                }
518
519                meta.consume();
520            }
521
522            WindowEvent::FocusNext => {
523                cx.focus_next();
524            }
525
526            WindowEvent::FocusPrev => {
527                cx.focus_prev();
528            }
529
530            WindowEvent::Redraw => {
531                self.window().request_redraw();
532            }
533
534            #[allow(unused_variables)]
535            WindowEvent::SetEnabled(flag) => {
536                #[cfg(target_os = "windows")]
537                self.window().set_enable(*flag);
538
539                self.window().focus_window();
540            }
541
542            WindowEvent::DragWindow => {
543                self.window().drag_window().expect("Failed to init drag window");
544                meta.consume();
545            }
546
547            WindowEvent::SetAlwaysOnTop(flag) => {
548                self.window().set_window_level(if *flag {
549                    WindowLevel::AlwaysOnTop
550                } else {
551                    WindowLevel::Normal
552                });
553            }
554
555            WindowEvent::SetImeCursorArea(position, size) => {
556                let position = PhysicalPosition::new(position.0 as i32, position.1 as i32);
557                let size = PhysicalSize::new(size.0, size.1);
558                self.window().set_ime_cursor_area(position, size);
559            }
560
561            _ => {}
562        })
563    }
564}
565
566impl WindowModifiers for Handle<'_, Window> {
567    fn on_close(self, callback: impl Fn(&mut EventContext) + 'static) -> Self {
568        self.modify(|window| window.on_close = Some(Box::new(callback)))
569    }
570
571    fn on_create(self, callback: impl Fn(&mut EventContext) + 'static) -> Self {
572        self.modify(|window| window.on_create = Some(Box::new(callback)))
573    }
574
575    fn title<T: ToString>(mut self, title: impl Res<T>) -> Self {
576        let entity = self.entity();
577        let title = title.get(&self).to_string();
578        if let Some(win_state) = self.context().windows.get_mut(&entity) {
579            win_state.window_description.title = title;
580        }
581
582        self
583    }
584
585    fn inner_size<S: Into<WindowSize>>(mut self, size: impl Res<S>) -> Self {
586        let entity = self.entity();
587        let size = size.get(&self).into();
588        if let Some(win_state) = self.context().windows.get_mut(&entity) {
589            win_state.window_description.inner_size = size;
590        }
591
592        self
593    }
594
595    fn min_inner_size<S: Into<WindowSize>>(mut self, size: impl Res<Option<S>>) -> Self {
596        let entity = self.entity();
597        let size = size.get(&self).map(|size| size.into());
598        if let Some(win_state) = self.context().windows.get_mut(&entity) {
599            win_state.window_description.min_inner_size = size;
600        }
601
602        self
603    }
604
605    fn max_inner_size<S: Into<WindowSize>>(mut self, size: impl Res<Option<S>>) -> Self {
606        let entity = self.entity();
607        let size = size.get(&self).map(|size| size.into());
608        if let Some(win_state) = self.context().windows.get_mut(&entity) {
609            win_state.window_description.max_inner_size = size;
610        }
611
612        self
613    }
614
615    fn position<P: Into<vizia_window::WindowPosition>>(mut self, position: impl Res<P>) -> Self {
616        let entity = self.entity();
617        let pos = Some(position.get(&self).into());
618        if let Some(win_state) = self.context().windows.get_mut(&entity) {
619            win_state.window_description.position = pos;
620        }
621
622        self
623    }
624
625    fn offset<P: Into<vizia_window::WindowPosition>>(mut self, offset: impl Res<P>) -> Self {
626        let entity = self.entity();
627        let offset = Some(offset.get(&self).into());
628        if let Some(win_state) = self.context().windows.get_mut(&entity) {
629            win_state.window_description.offset = offset;
630        }
631
632        self
633    }
634
635    fn anchor<P: Into<vizia_window::Anchor>>(mut self, anchor: impl Res<P>) -> Self {
636        let entity = self.entity();
637        let anchor = Some(anchor.get(&self).into());
638        if let Some(win_state) = self.context().windows.get_mut(&entity) {
639            win_state.window_description.anchor = anchor;
640        }
641
642        self
643    }
644
645    fn anchor_target<P: Into<vizia_window::AnchorTarget>>(
646        mut self,
647        anchor_target: impl Res<P>,
648    ) -> Self {
649        let entity = self.entity();
650        let anchor_target = Some(anchor_target.get(&self).into());
651        if let Some(win_state) = self.context().windows.get_mut(&entity) {
652            win_state.window_description.anchor_target = anchor_target;
653        }
654
655        self
656    }
657
658    fn parent_anchor<P: Into<Anchor>>(mut self, parent_anchor: impl Res<P>) -> Self {
659        let entity = self.entity();
660        let parent_anchor = Some(parent_anchor.get(&self).into());
661        if let Some(win_state) = self.context().windows.get_mut(&entity) {
662            win_state.window_description.parent_anchor = parent_anchor;
663        }
664
665        self
666    }
667
668    fn resizable(mut self, flag: impl Res<bool>) -> Self {
669        let entity = self.entity();
670        let flag = flag.get(&self);
671        if let Some(win_state) = self.context().windows.get_mut(&entity) {
672            win_state.window_description.resizable = flag;
673        }
674
675        self
676    }
677
678    fn minimized(mut self, flag: impl Res<bool>) -> Self {
679        let entity = self.entity();
680        let flag = flag.get(&self);
681        if let Some(win_state) = self.context().windows.get_mut(&entity) {
682            win_state.window_description.minimized = flag;
683        }
684
685        self
686    }
687
688    fn maximized(mut self, flag: impl Res<bool>) -> Self {
689        let entity = self.entity();
690        let flag = flag.get(&self);
691        if let Some(win_state) = self.context().windows.get_mut(&entity) {
692            win_state.window_description.maximized = flag;
693        }
694
695        self
696    }
697
698    fn visible(mut self, flag: impl Res<bool>) -> Self {
699        let entity = self.entity();
700        let flag = flag.get(&self);
701        if let Some(win_state) = self.context().windows.get_mut(&entity) {
702            win_state.window_description.visible = flag
703        }
704
705        self
706    }
707
708    fn transparent(mut self, flag: bool) -> Self {
709        let entity = self.entity();
710        if let Some(win_state) = self.context().windows.get_mut(&entity) {
711            win_state.window_description.transparent = flag
712        }
713
714        self
715    }
716
717    fn decorations(mut self, flag: bool) -> Self {
718        let entity = self.entity();
719        if let Some(win_state) = self.context().windows.get_mut(&entity) {
720            win_state.window_description.decorations = flag
721        }
722
723        self
724    }
725
726    fn always_on_top(mut self, flag: bool) -> Self {
727        let entity = self.entity();
728        if let Some(win_state) = self.context().windows.get_mut(&entity) {
729            win_state.window_description.always_on_top = flag
730        }
731
732        self
733    }
734
735    fn vsync(mut self, flag: bool) -> Self {
736        let entity = self.entity();
737        if let Some(win_state) = self.context().windows.get_mut(&entity) {
738            win_state.window_description.vsync = flag
739        }
740
741        self
742    }
743
744    fn icon(mut self, width: u32, height: u32, image: Vec<u8>) -> Self {
745        let entity = self.entity();
746        if let Some(win_state) = self.context().windows.get_mut(&entity) {
747            win_state.window_description.icon = Some(image);
748            win_state.window_description.icon_width = width;
749            win_state.window_description.icon_height = height;
750        }
751
752        self
753    }
754
755    fn enabled_window_buttons(mut self, window_buttons: WindowButtons) -> Self {
756        let entity = self.entity();
757        if let Some(win_state) = self.context().windows.get_mut(&entity) {
758            win_state.window_description.enabled_window_buttons = window_buttons;
759        }
760
761        self
762    }
763}