]> gitweb.factorcode.org Git - factor.git/blob - basis/ui/backend/cocoa/views/views.factor
ui.backend.cocoa: change theme automatically if we haven't set it yet.
[factor.git] / basis / ui / backend / cocoa / views / views.factor
1 ! Copyright (C) 2006, 2010 Slava Pestov
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: accessors alien alien.c-types alien.data alien.strings
4 arrays assocs classes cocoa cocoa.application cocoa.classes
5 cocoa.pasteboard cocoa.runtime cocoa.subclassing cocoa.touchbar
6 cocoa.types cocoa.views combinators core-foundation.strings
7 core-graphics core-graphics.types core-text io.encodings.string
8 io.encodings.utf16n io.encodings.utf8 kernel literals math
9 math.order math.parser math.rectangles math.vectors namespaces
10 opengl sequences splitting threads
11 ui.backend.cocoa.input-methods ui.commands ui.gadgets
12 ui.gadgets.editors ui.gadgets.line-support ui.gadgets.private
13 ui.gadgets.worlds ui.gestures ui.private ui.theme
14 ui.theme.switching words ;
15 IN: ui.backend.cocoa.views
16
17 SLOT: window
18
19 : send-mouse-moved ( view event -- )
20     [ mouse-location ] [ drop window ] 2bi
21     [ move-hand fire-motion yield ] [ drop ] if* ;
22
23 ! Issue #1453
24 : button ( event -- n )
25     ! Cocoa -> Factor UI button mapping
26     -> buttonNumber {
27         { 0 [ 1 ] }
28         { 1 [ 3 ] }
29         { 2 [ 2 ] }
30         [ ]
31     } case ;
32
33 CONSTANT: NSAlphaShiftKeyMask 0x10000
34 CONSTANT: NSShiftKeyMask      0x20000
35 CONSTANT: NSControlKeyMask    0x40000
36 CONSTANT: NSAlternateKeyMask  0x80000
37 CONSTANT: NSCommandKeyMask    0x100000
38 CONSTANT: NSNumericPadKeyMask 0x200000
39 CONSTANT: NSHelpKeyMask       0x400000
40 CONSTANT: NSFunctionKeyMask   0x800000
41
42 CONSTANT: modifiers {
43         { S+ $ NSShiftKeyMask }
44         { C+ $ NSControlKeyMask }
45         { M+ $ NSCommandKeyMask }
46         { A+ $ NSAlternateKeyMask }
47     }
48
49 CONSTANT: key-codes
50     H{
51         { 71 "CLEAR" }
52         { 36 "RET" }
53         { 76 "ENTER" }
54         { 53 "ESC" }
55         { 48 "TAB" }
56         { 51 "BACKSPACE" }
57         { 115 "HOME" }
58         { 117 "DELETE" }
59         { 119 "END" }
60         { 122 "F1" }
61         { 120 "F2" }
62         { 99 "F3" }
63         { 118 "F4" }
64         { 96 "F5" }
65         { 97 "F6" }
66         { 98 "F7" }
67         { 100 "F8" }
68         { 123 "LEFT" }
69         { 124 "RIGHT" }
70         { 125 "DOWN" }
71         { 126 "UP" }
72         { 116 "PAGE_UP" }
73         { 121 "PAGE_DOWN" }
74     }
75
76 : key-code ( event -- string ? )
77     dup -> keyCode key-codes at
78     [ t ] [ -> charactersIgnoringModifiers CF>string f ] ?if ;
79
80 : event-modifiers ( event -- modifiers )
81     -> modifierFlags modifiers modifier ;
82
83 : key-event>gesture ( event -- modifiers keycode action? )
84     [ event-modifiers ] [ key-code ] bi ;
85
86 : send-key-event ( view gesture -- )
87     swap window [ propagate-key-gesture ] [ drop ] if* ;
88
89 : interpret-key-event ( view event -- )
90     NSArray swap -> arrayWithObject: -> interpretKeyEvents: ;
91
92 : send-key-down-event ( view event -- )
93     [ key-event>gesture <key-down> send-key-event ]
94     [ interpret-key-event ]
95     2bi ;
96
97 : send-key-up-event ( view event -- )
98     key-event>gesture <key-up> send-key-event ;
99
100 : mouse-event>gesture ( event -- modifiers button )
101     [ event-modifiers ] [ button ] bi ;
102
103 : send-button-down$ ( view event -- )
104     [ nip mouse-event>gesture <button-down> ]
105     [ mouse-location ]
106     [ drop window ]
107     2tri
108     [ send-button-down ] [ 2drop ] if* ;
109
110 : send-button-up$ ( view event -- )
111     [ nip mouse-event>gesture <button-up> ]
112     [ mouse-location ]
113     [ drop window ]
114     2tri
115     [ send-button-up ] [ 2drop ] if* ;
116
117 : send-scroll$ ( view event -- )
118     [ nip [ -> deltaX ] [ -> deltaY ] bi [ neg ] bi@ 2array ]
119     [ mouse-location ]
120     [ drop window ]
121     2tri
122     [ send-scroll ] [ 2drop ] if* ;
123
124 : send-action$ ( view event gesture -- )
125     [ drop window ] dip over [ send-action ] [ 2drop ] if ;
126
127 : string-or-nil? ( NSString -- ? )
128     [ CF>string NSStringPboardType = ] [ t ] if* ;
129
130 : valid-service? ( gadget send-type return-type -- ? )
131     2dup [ string-or-nil? ] [ string-or-nil? ] bi* and
132     [ drop [ gadget-selection? ] [ drop t ] if ] [ 3drop f ] if ;
133
134 : NSRect>rect ( NSRect world -- rect )
135     [ [ [ CGRect-x ] [ CGRect-y ] bi ] [ dim>> second ] bi* swap - 2array ]
136     [ drop [ CGRect-w ] [ CGRect-h ] bi 2array ]
137     2bi <rect> ;
138
139 : rect>NSRect ( rect world -- NSRect )
140     [ [ loc>> first2 ] [ dim>> second ] bi* swap - ]
141     [ drop dim>> first2 ]
142     2bi <CGRect> ;
143
144 CONSTANT: selector>action H{
145     { "undo:" undo-action }
146     { "redo:" redo-action }
147     { "cut:" cut-action }
148     { "copy:" copy-action }
149     { "paste:" paste-action }
150     { "delete:" delete-action }
151     { "selectAll:" select-all-action }
152     { "newDocument:" new-action }
153     { "openDocument:" open-action }
154     { "saveDocument:" save-action }
155     { "saveDocumentAs:" save-as-action }
156     { "revertDocumentToSaved:" revert-action }
157 }
158
159 : validate-action ( world selector -- ? validated? )
160     selector>action at
161     [ swap world-focus parents-handle-gesture? t ] [ drop f f ] if* ;
162
163 : touchbar-commands ( -- commands/f gadget )
164     world get-global [
165         children>> [
166             class-of "commands" word-prop
167             "touchbar" of dup [ commands>> ] when
168         ] map-find
169     ] [ f f ] if* ;
170
171 TUPLE: send-touchbar-command target command ;
172
173 M: send-touchbar-command send-queued-gesture
174     [ target>> ] [ command>> ] bi invoke-command ;
175
176 : touchbar-invoke-command ( n -- )
177     [ touchbar-commands ] dip over [
178         rot nth second
179         send-touchbar-command queue-gesture notify-ui-thread
180         yield
181     ] [ 3drop ] if ;
182
183 IMPORT: NSAttributedString
184
185 <PRIVATE
186
187 :: >codepoint-index ( str utf16-index -- codepoint-index )
188     0 utf16-index 2 * str utf16n encode subseq utf16n decode length ;
189
190 :: >utf16-index ( str codepoint-index -- utf16-index )
191     0 codepoint-index str subseq utf16n encode length 2 /i ;
192
193 :: earlier-caret/mark ( editor -- loc )
194     editor editor-caret :> caret
195     editor editor-mark :> mark
196     caret first mark first = [
197         caret second mark second < [ caret ] [ mark ] if
198     ] [
199         caret first mark first < [ caret ] [ mark ] if
200     ] if ;
201
202 :: make-preedit-underlines ( gadget text range -- underlines )
203     f gadget preedit-selection-mode?<<
204     { } clone :> underlines!
205     text -> length :> text-length
206     0 0 <NSRange> :> effective-range
207     text -> string CF>string :> str
208     str utf16n encode :> byte-16n
209     0 :> cp-loc!
210     "NSMarkedClauseSegment" <NSString> :> segment-attr
211     [ effective-range [ location>> ] [ length>> ] bi + text-length < ] [
212         text
213         segment-attr
214         effective-range [ location>> ] [ length>> ] bi +
215         effective-range >c-ptr
216         -> attribute:atIndex:effectiveRange: drop
217         1 :> thickness!
218         range location>> effective-range location>> = [
219             2 thickness!
220             t gadget preedit-selection-mode?<<
221         ] when
222         underlines
223         effective-range [ location>> ] [ length>> ] bi over +
224         [ str swap >codepoint-index ] bi@ swap - :> len
225         cp-loc cp-loc len + dup cp-loc!
226         2array thickness 2array
227         suffix underlines!
228     ] while 
229     underlines length 1 = [
230         underlines first first 2 2array 1array  ! thickness: 2
231     ] [ underlines ] if ;
232
233 :: update-marked-text ( gadget str selectedRange replacementRange -- )
234     replacementRange location>> NSNotFound = [
235         gadget editor-caret first
236         dup gadget editor-line
237         [ 
238             replacementRange location>>
239             >codepoint-index
240             2array gadget set-caret
241         ] [
242             replacementRange [ location>> ] [ length>> ] bi +
243             >codepoint-index
244             2array gadget set-mark
245         ] 2bi
246         gadget earlier-caret/mark dup
247         gadget preedit-start<<
248         0 1 2array v+ gadget preedit-end<<
249     ] unless
250
251     gadget preedit? [
252         gadget remove-preedit-text
253     ] when
254
255     gadget earlier-caret/mark dup
256     gadget preedit-start<<
257     0 str length 2array v+ gadget preedit-end<<
258     str gadget temp-im-input drop
259     gadget preedit-start>>
260     0 str selectedRange location>> >codepoint-index 2array v+
261     dup gadget preedit-selected-start<<
262     0
263     selectedRange [ location>> ] [ length>> ] bi + selectedRange location>>
264     [ str swap >codepoint-index ] bi@ -
265     2array v+
266     dup gadget preedit-selected-end<<
267     dup gadget set-caret gadget set-mark
268     gadget preedit-start>> gadget preedit-end>> = [
269         gadget remove-preedit-info 
270     ] when ;
271
272 PRIVATE>
273
274 <CLASS: FactorView < NSOpenGLView
275     COCOA-PROTOCOL: NSTextInputClient
276
277     METHOD: void prepareOpenGL [
278
279         self SEL: setWantsBestResolutionOpenGLSurface:
280         -> respondsToSelector: c-bool> [
281
282             self 1 { void { id SEL char } } ?-> setWantsBestResolutionOpenGLSurface:
283
284             self { double { id SEL } } ?-> backingScaleFactor
285
286             dup 1.0 > [
287                 gl-scale-factor set-global t retina? set-global
288                 cached-lines get-global clear-assoc
289             ] [ drop ] if
290
291             self -> update
292         ] when
293     ] ;
294
295     METHOD: void reshape [
296         self window :> window
297         window [
298             self view-dim window dim<<
299         ] when
300     ] ;
301
302     ! TouchBar
303     METHOD: void touchBarCommand0 [ 0 touchbar-invoke-command ] ;
304     METHOD: void touchBarCommand1 [ 1 touchbar-invoke-command ] ;
305     METHOD: void touchBarCommand2 [ 2 touchbar-invoke-command ] ;
306     METHOD: void touchBarCommand3 [ 3 touchbar-invoke-command ] ;
307     METHOD: void touchBarCommand4 [ 4 touchbar-invoke-command ] ;
308     METHOD: void touchBarCommand5 [ 5 touchbar-invoke-command ] ;
309     METHOD: void touchBarCommand6 [ 6 touchbar-invoke-command ] ;
310     METHOD: void touchBarCommand7 [ 7 touchbar-invoke-command ] ;
311
312     METHOD: id makeTouchBar [
313         touchbar-commands drop [
314             length 8 min <iota> [ number>string ] map
315         ] [ { } ] if* self make-touchbar
316     ] ;
317
318     METHOD: id touchBar: id touchbar makeItemForIdentifier: id string [
319         touchbar-commands drop [
320             [ self string CF>string dup string>number ] dip nth
321             second name>> "com-" ?head drop over
322             "touchBarCommand" prepend make-NSTouchBar-button
323         ] [ f ] if*
324     ] ;
325
326     ! Rendering
327     METHOD: void drawRect: NSRect rect [
328         self window [
329             draw-world yield
330         ] when*
331     ] ;
332
333     ! Light/Dark Mode
334
335     METHOD: void viewDidChangeEffectiveAppearance [
336         default-theme? get [
337             self -> effectiveAppearance -> name [
338                 CF>string "NSAppearanceNameDarkAqua" =
339                 dark-theme light-theme ? switch-theme
340                 t default-theme? set-global
341             ] when*
342         ] when
343     ] ;
344
345     ! Events
346     METHOD: char acceptsFirstMouse: id event [ 0 ] ;
347
348     METHOD: void mouseEntered: id event [ self event send-mouse-moved ] ;
349
350     METHOD: void mouseExited: id event [ forget-rollover ] ;
351
352     METHOD: void mouseMoved: id event [ self event send-mouse-moved ] ;
353
354     METHOD: void mouseDragged: id event [ self event send-mouse-moved ] ;
355
356     METHOD: void rightMouseDragged: id event [ self event send-mouse-moved ] ;
357
358     METHOD: void otherMouseDragged: id event [ self event send-mouse-moved ] ;
359
360     METHOD: void mouseDown: id event [ self event send-button-down$ ] ;
361
362     METHOD: void mouseUp: id event [ self event send-button-up$ ] ;
363
364     METHOD: void rightMouseDown: id event [ self event send-button-down$ ] ;
365
366     METHOD: void rightMouseUp: id event [ self event send-button-up$ ] ;
367
368     METHOD: void otherMouseDown: id event [ self event send-button-down$ ] ;
369
370     METHOD: void otherMouseUp: id event [ self event send-button-up$ ] ;
371
372     METHOD: void scrollWheel: id event [ self event send-scroll$ ] ;
373
374     METHOD: void keyDown: id event [ self event send-key-down-event ] ;
375
376     METHOD: void keyUp: id event [ self event send-key-up-event ] ;
377
378     METHOD: char validateUserInterfaceItem: id event
379     [
380         self window :> window
381         window [
382             window world-focus :> gadget
383             gadget [
384                 gadget preedit? not [
385                     window event -> action utf8 alien>string validate-action
386                     [ >c-bool ] [ drop self event SUPER-> validateUserInterfaceItem: ] if
387                 ] [ 0 ] if
388             ] [ 0 ] if
389         ] [ 0 ] if
390     ] ;
391
392     METHOD: void undo: id event [ self event undo-action send-action$ ] ;
393
394     METHOD: void redo: id event [ self event redo-action send-action$ ] ;
395
396     METHOD: void cut: id event [ self event cut-action send-action$ ] ;
397
398     METHOD: void copy: id event [ self event copy-action send-action$ ] ;
399
400     METHOD: void paste: id event [ self event paste-action send-action$ ] ;
401
402     METHOD: void delete: id event [ self event delete-action send-action$ ] ;
403
404     METHOD: void selectAll: id event [ self event select-all-action send-action$ ] ;
405
406     METHOD: void newDocument: id event [ self event new-action send-action$ ] ;
407
408     METHOD: void openDocument: id event [ self event open-action send-action$ ] ;
409
410     METHOD: void saveDocument: id event [ self event save-action send-action$ ] ;
411
412     METHOD: void saveDocumentAs: id event [ self event save-as-action send-action$ ] ;
413
414     METHOD: void revertDocumentToSaved: id event [ self event revert-action send-action$ ] ;
415
416     ! Multi-touch gestures
417     METHOD: void magnifyWithEvent: id event
418     [
419         self event
420         dup -> deltaZ sgn {
421             {  1 [ zoom-in-action send-action$ ] }
422             { -1 [ zoom-out-action send-action$ ] }
423             {  0 [ 2drop ] }
424         } case
425     ] ;
426
427     METHOD: void swipeWithEvent: id event
428     [
429         self event
430         dup -> deltaX sgn {
431             {  1 [ left-action send-action$ ] }
432             { -1 [ right-action send-action$ ] }
433             {  0
434                 [
435                     dup -> deltaY sgn {
436                         {  1 [ up-action send-action$ ] }
437                         { -1 [ down-action send-action$ ] }
438                         {  0 [ 2drop ] }
439                     } case
440                 ]
441             }
442         } case
443     ] ;
444
445     METHOD: char acceptsFirstResponder [ 1 ] ;
446
447     ! Services
448     METHOD: id validRequestorForSendType: id sendType returnType: id returnType
449     [
450         ! We return either self or nil
451         self window [
452             world-focus sendType returnType
453             valid-service? [ self ] [ f ] if
454         ] [ f ] if*
455     ] ;
456
457     METHOD: char writeSelectionToPasteboard: id pboard types: id types
458     [
459         NSStringPboardType types CF>string-array member? [
460             self window [
461                 world-focus gadget-selection
462                 [ pboard set-pasteboard-string 1 ] [ 0 ] if*
463             ] [ 0 ] if*
464         ] [ 0 ] if
465     ] ;
466
467     METHOD: char readSelectionFromPasteboard: id pboard
468     [
469         self window :> window
470         window [
471             pboard pasteboard-string
472             [ window user-input 1 ] [ 0 ] if*
473         ] [ 0 ] if
474     ] ;
475
476     ! Text input
477     METHOD: void insertText: id text replacementRange: NSRange replacementRange [
478         self window :> window
479         window [
480             "" clone :> str!
481             text NSString -> class -> isKindOfClass: 0 = not [
482                 text CF>string str!
483             ] [
484                 text -> string CF>string str!
485             ] if
486             window world-focus :> gadget
487             gadget [
488                 gadget support-input-methods? [
489                     replacementRange location>> NSNotFound = [
490                         gadget editor-caret first
491                         dup gadget editor-line
492                         [ 
493                             replacementRange location>> >codepoint-index
494                             2array gadget set-caret
495                         ] [
496                             replacementRange [ location>> ] [ length>> ] bi +
497                             >codepoint-index
498                             2array gadget set-mark
499                         ] 2bi
500                     ] unless
501                     gadget preedit? [
502                         gadget remove-preedit-text
503                         gadget remove-preedit-info
504                         str gadget user-input* drop
505                         f gadget preedit-selection-mode?<<
506                     ] [
507                         str window user-input
508                     ] if
509                 ] [ 
510                     str window user-input
511                 ] if
512             ] when
513         ] when
514     ] ;
515
516     METHOD: char hasMarkedText [
517         self window :> window
518         window [
519             window world-focus :> gadget
520             gadget [
521                 gadget preedit? 1 0 ?
522             ] [ 0 ] if
523         ] [ 0 ] if
524     ] ;
525
526     METHOD: NSRange markedRange [
527         self window :> window
528         window [
529             window world-focus :> gadget
530             gadget [
531                 gadget preedit? [
532                     gadget preedit-start>> second
533                     gadget preedit-end>> second < [
534                         gadget preedit-start>> first gadget editor-line :> str
535                         gadget preedit-start>> second           ! location
536                         gadget preedit-end>> second
537                         [ str swap >utf16-index ] bi@ over -    ! length
538                     ] [ NSNotFound 0 ] if
539                 ] [ NSNotFound 0 ] if
540             ] [ NSNotFound 0 ] if
541         ] [ NSNotFound 0 ] if
542         <NSRange>
543     ] ;
544
545     METHOD: NSRange selectedRange [
546         self window :> window
547         window [
548             window world-focus :> gadget
549             gadget [
550                 gadget support-input-methods? [
551                     gadget editor-caret first gadget editor-line :> str
552                     gadget preedit? [
553                         str
554                         gadget preedit-selected-start>> second
555                         gadget preedit-start>> second
556                         - >utf16-index                        ! location
557                         gadget preedit-selected-end>> second
558                         gadget preedit-selected-start>> second
559                         [ str swap >utf16-index ] bi@ -       ! length
560                     ] [
561                         str gadget editor-caret second >utf16-index 0
562                     ] if
563                 ] [ 0 0 ] if
564             ] [ 0 0 ] if
565         ] [ 0 0 ] if 
566         <NSRange>
567     ] ;
568
569     METHOD: void setMarkedText: id text selectedRange: NSRange selectedRange
570                                      replacementRange: NSRange replacementRange [
571         self window :> window
572         window [
573             window world-focus :> gadget
574             gadget [
575                 { } clone :> underlines!
576                 "" clone :> str!
577                 text NSString -> class -> isKindOfClass: 0 = not [
578                     text CF>string str!
579                 ] [
580                     text -> string CF>string str!
581                     gadget support-input-methods? [
582                         gadget text selectedRange make-preedit-underlines underlines!
583                     ] when
584                 ] if
585                 gadget support-input-methods? [
586                     gadget str selectedRange replacementRange update-marked-text
587                     underlines gadget preedit-underlines<<
588                 ] when
589             ] when
590         ] when
591     ] ;
592
593     METHOD: void unmarkText [
594         self window :> window
595         window [
596             window world-focus :> gadget
597             gadget [
598                 gadget support-input-methods? [
599                     gadget preedit? [
600                         gadget {
601                             [ preedit-start>> second ]
602                             [ preedit-end>> second ]
603                             [ preedit-start>> first ]
604                             [ editor-line ]
605                         } cleave subseq
606                         gadget remove-preedit-text
607                         gadget remove-preedit-info
608                         gadget user-input* drop
609                     ] when
610                     f gadget preedit-selection-mode?<<
611                 ] when
612             ] when
613         ] when
614     ] ;
615
616     METHOD: id validAttributesForMarkedText [
617         NSArray "NSMarkedClauseSegment" <NSString> -> arrayWithObject:
618     ] ;
619
620     METHOD: id attributedSubstringForProposedRange: NSRange aRange
621                                        actualRange: id actualRange [ f ] ;
622
623     METHOD: NSUInteger characterIndexForPoint: NSPoint point [ 0 ] ;
624
625     METHOD: NSRect firstRectForCharacterRange: NSRange aRange
626                                   actualRange: NSRange actualRange [
627         self window :> window
628         window [
629             window world-focus :> gadget
630             gadget [
631                 gadget support-input-methods? [
632                     gadget editor-caret first gadget editor-line :> str
633                     str aRange location>> >codepoint-index :> start-pos
634                     gadget editor-caret first start-pos 2array gadget loc>x
635                     gadget caret-loc second gadget caret-dim second + 
636                     2array                     ! character pos
637                     gadget screen-loc v+       ! + gadget pos
638                     { 1 -1 } v*
639                     window handle>> window>> dup -> frame -> contentRectForFrameRect:
640                     CGRect-top-left 2array v+  ! + window pos
641                     first2 [ >fixnum ] bi@ 0 gadget line-height >fixnum
642                 ] [ 0 0 0 0 ] if
643             ] [ 0 0 0 0 ] if
644         ] [ 0 0 0 0 ] if
645         <CGRect>
646     ] ;
647
648     METHOD: void doCommandBySelector: SEL selector [ ] ;
649
650     ! Initialization
651     METHOD: id initWithFrame: NSRect frame pixelFormat: id pixelFormat
652     [
653         self frame pixelFormat SUPER-> initWithFrame:pixelFormat:
654     ] ;
655
656     METHOD: char isOpaque [ 0 ] ;
657
658     METHOD: void dealloc
659     [
660         self remove-observer
661         self SUPER-> dealloc
662     ] ;
663 ;CLASS>
664
665 : sync-refresh-to-screen ( GLView -- )
666     -> openGLContext -> CGLContextObj NSOpenGLCPSwapInterval 1 int <ref>
667     CGLSetParameter drop ;
668
669 : <FactorView> ( dim pixel-format -- view )
670     [ FactorView ] 2dip <GLView> [ sync-refresh-to-screen ] keep ;
671
672 : save-position ( world window -- )
673     -> frame CGRect-top-left 2array >>window-loc drop ;
674
675 <CLASS: FactorWindowDelegate < NSObject
676
677     METHOD: void windowDidMove: id notification
678     [
679         notification -> object -> contentView window
680         [ notification -> object save-position ] when*
681     ] ;
682
683     METHOD: void windowDidBecomeKey: id notification
684     [
685         notification -> object -> contentView window
686         [ focus-world ] when*
687     ] ;
688
689     METHOD: void windowDidResignKey: id notification
690     [
691         forget-rollover
692         notification -> object -> contentView :> view
693         view window :> window
694         window [
695             view -> isInFullScreenMode 0 =
696             [ window unfocus-world ] when
697         ] when
698     ] ;
699
700     METHOD: char windowShouldClose: id notification [ 1 ] ;
701
702     METHOD: void windowWillClose: id notification
703     [
704         notification -> object -> contentView
705         [ window ungraft ] [ unregister-window ] bi
706     ] ;
707
708     METHOD: void windowDidChangeBackingProperties: id notification
709     [
710
711         notification -> object dup SEL: backingScaleFactor
712         -> respondsToSelector: c-bool> [
713             { double { id SEL } } ?-> backingScaleFactor
714
715             [ [ 1.0 > ] keep f ? gl-scale-factor set-global ]
716             [ 1.0 > retina? set-global ] bi
717         ] [ drop ] if
718     ] ;
719 ;CLASS>
720
721 : install-window-delegate ( window -- )
722     FactorWindowDelegate install-delegate ;