1# ----------------------------------------------------------------------------
2# pyglet
3# Copyright (c) 2006-2008 Alex Holkner
4# Copyright (c) 2008-2020 pyglet contributors
5# All rights reserved.
6#
7# Redistribution and use in source and binary forms, with or without
8# modification, are permitted provided that the following conditions
9# are met:
10#
11#  * Redistributions of source code must retain the above copyright
12#    notice, this list of conditions and the following disclaimer.
13#  * Redistributions in binary form must reproduce the above copyright
14#    notice, this list of conditions and the following disclaimer in
15#    the documentation and/or other materials provided with the
16#    distribution.
17#  * Neither the name of pyglet nor the names of its
18#    contributors may be used to endorse or promote products
19#    derived from this software without specific prior written
20#    permission.
21#
22# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
28# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33# POSSIBILITY OF SUCH DAMAGE.
34# ----------------------------------------------------------------------------
35
36"""Windowing and user-interface events.
37
38This module allows applications to create and display windows with an
39OpenGL context.  Windows can be created with a variety of border styles
40or set fullscreen.
41
42You can register event handlers for keyboard, mouse and window events.
43For games and kiosks you can also restrict the input to your windows,
44for example disabling users from switching away from the application
45with certain key combinations or capturing and hiding the mouse.
46
47Getting started
48---------------
49
50Call the Window constructor to create a new window::
51
52    from pyglet.window import Window
53    win = Window(width=640, height=480)
54
55Attach your own event handlers::
56
57    @win.event
58    def on_key_press(symbol, modifiers):
59        # ... handle this event ...
60
61Place drawing code for the window within the `Window.on_draw` event handler::
62
63    @win.event
64    def on_draw():
65        # ... drawing code ...
66
67Call `pyglet.app.run` to enter the main event loop (by default, this
68returns when all open windows are closed)::
69
70    from pyglet import app
71    app.run()
72
73Creating a game window
74----------------------
75
76Use :py:meth:`~pyglet.window.Window.set_exclusive_mouse` to hide the mouse
77cursor and receive relative mouse movement events.  Specify ``fullscreen=True``
78as a keyword argument to the :py:class:`~pyglet.window.Window` constructor to
79render to the entire screen rather than opening a window::
80
81    win = Window(fullscreen=True)
82    win.set_exclusive_mouse()
83
84Working with multiple screens
85-----------------------------
86
87By default, fullscreen windows are opened on the primary display (typically
88set by the user in their operating system settings).  You can retrieve a list
89of attached screens and select one manually if you prefer.  This is useful for
90opening a fullscreen window on each screen::
91
92    display = pyglet.canvas.get_display()
93    screens = display.get_screens()
94    windows = []
95    for screen in screens:
96        windows.append(window.Window(fullscreen=True, screen=screen))
97
98Specifying a screen has no effect if the window is not fullscreen.
99
100Specifying the OpenGL context properties
101----------------------------------------
102
103Each window has its own context which is created when the window is created.
104You can specify the properties of the context before it is created
105by creating a "template" configuration::
106
107    from pyglet import gl
108    # Create template config
109    config = gl.Config()
110    config.stencil_size = 8
111    config.aux_buffers = 4
112    # Create a window using this config
113    win = window.Window(config=config)
114
115To determine if a given configuration is supported, query the screen (see
116above, "Working with multiple screens")::
117
118    configs = screen.get_matching_configs(config)
119    if not configs:
120        # ... config is not supported
121    else:
122        win = window.Window(config=configs[0])
123
124"""
125
126import sys
127import math
128
129import pyglet
130import pyglet.window.key
131import pyglet.window.event
132
133from pyglet import gl
134from pyglet.event import EventDispatcher
135from pyglet.window import key
136from pyglet.util import with_metaclass
137
138
139_is_pyglet_doc_run = hasattr(sys, "is_pyglet_doc_run") and sys.is_pyglet_doc_run
140
141
142class WindowException(Exception):
143    """The root exception for all window-related errors."""
144    pass
145
146
147class NoSuchDisplayException(WindowException):
148    """An exception indicating the requested display is not available."""
149    pass
150
151
152class NoSuchConfigException(WindowException):
153    """An exception indicating the requested configuration is not
154    available."""
155    pass
156
157
158class NoSuchScreenModeException(WindowException):
159    """An exception indicating the requested screen resolution could not be
160    met."""
161    pass
162
163
164class MouseCursorException(WindowException):
165    """The root exception for all mouse cursor-related errors."""
166    pass
167
168
169class MouseCursor:
170    """An abstract mouse cursor."""
171
172    #: Indicates if the cursor is drawn using OpenGL.  This is True
173    #: for all mouse cursors except system cursors.
174    drawable = True
175
176    def draw(self, x, y):
177        """Abstract render method.
178
179        The cursor should be drawn with the "hot" spot at the given
180        coordinates.  The projection is set to the pyglet default (i.e.,
181        orthographic in window-space), however no other aspects of the
182        state can be assumed.
183
184        :Parameters:
185            `x` : int
186                X coordinate of the mouse pointer's hot spot.
187            `y` : int
188                Y coordinate of the mouse pointer's hot spot.
189
190        """
191        raise NotImplementedError('abstract')
192
193
194class DefaultMouseCursor(MouseCursor):
195    """The default mouse cursor #sed by the operating system."""
196    drawable = False
197
198
199class ImageMouseCursor(MouseCursor):
200    """A user-defined mouse cursor created from an image.
201
202    Use this class to create your own mouse cursors and assign them
203    to windows.  There are no constraints on the image size or format.
204    """
205    drawable = True
206
207    def __init__(self, image, hot_x=0, hot_y=0):
208        """Create a mouse cursor from an image.
209
210        :Parameters:
211            `image` : `pyglet.image.AbstractImage`
212                Image to use for the mouse cursor.  It must have a
213                valid ``texture`` attribute.
214            `hot_x` : int
215                X coordinate of the "hot" spot in the image relative to the
216                image's anchor.
217            `hot_y` : int
218                Y coordinate of the "hot" spot in the image, relative to the
219                image's anchor.
220        """
221        self.texture = image.get_texture()
222        self.hot_x = hot_x
223        self.hot_y = hot_y
224
225    def draw(self, x, y):
226        gl.glPushAttrib(gl.GL_ENABLE_BIT | gl.GL_CURRENT_BIT)
227        gl.glColor4f(1, 1, 1, 1)
228        gl.glEnable(gl.GL_BLEND)
229        gl.glBlendFunc(gl.GL_SRC_ALPHA, gl.GL_ONE_MINUS_SRC_ALPHA)
230        self.texture.blit(x - self.hot_x, y - self.hot_y, 0)
231        gl.glPopAttrib()
232
233
234class Projection:
235    """Abstract OpenGL projection."""
236
237    def set(self, window_width, window_height, viewport_width, viewport_height):
238        """Set the OpenGL projection
239
240        Using the passed in Window and viewport sizes,
241        set a desired orthographic or perspective projection.
242
243        :Parameters:
244            `window_width` : int
245                The Window width
246            `window_height` : int
247                The Window height
248            `viewport_width` : int
249                The Window internal viewport width.
250            `viewport_height` : int
251                The Window internal viewport height.
252        """
253        raise NotImplementedError('abstract')
254
255
256class Projection2D(Projection):
257    """A 2D orthographic projection"""
258
259    def set(self, window_width, window_height, viewport_width, viewport_height):
260        gl.glViewport(0, 0, max(1, viewport_width), max(1, viewport_height))
261        gl.glMatrixMode(gl.GL_PROJECTION)
262        gl.glLoadIdentity()
263        gl.glOrtho(0, max(1, window_width), 0, max(1, window_height), -1, 1)
264        gl.glMatrixMode(gl.GL_MODELVIEW)
265
266
267class Projection3D(Projection):
268    """A 3D perspective projection"""
269
270    def __init__(self, fov=60, znear=0.1, zfar=255):
271        """Create a 3D projection
272
273        :Parameters:
274            `fov` : float
275                The field of vision. Defaults to 60.
276            `znear` : float
277                The near clipping plane. Defaults to 0.1.
278            `zfar` : float
279                The far clipping plane. Defaults to 255.
280        """
281        self.fov = fov
282        self.znear = znear
283        self.zfar = zfar
284
285    def set(self, window_width, window_height, viewport_width, viewport_height):
286        gl.glViewport(0, 0, max(1, viewport_width), max(1, viewport_height))
287        gl.glMatrixMode(gl.GL_PROJECTION)
288        gl.glLoadIdentity()
289
290        # Pure GL implementation of gluPerspective:
291        aspect_ratio = float(window_width) / float(window_height)
292        f_width = math.tan(self.fov / 360.0 * math.pi ) * self.znear
293        f_height = f_width * aspect_ratio
294        gl.glFrustum(-f_height, f_height, -f_width, f_width, self.znear, self.zfar)
295
296        gl.glMatrixMode(gl.GL_MODELVIEW)
297
298
299def _PlatformEventHandler(data):
300    """Decorator for platform event handlers.
301
302    Apply giving the platform-specific data needed by the window to associate
303    the method with an event.  See platform-specific subclasses of this
304    decorator for examples.
305
306    The following attributes are set on the function, which is returned
307    otherwise unchanged:
308
309    _platform_event
310        True
311    _platform_event_data
312        List of data applied to the function (permitting multiple decorators
313        on the same method).
314    """
315
316    def _event_wrapper(f):
317        f._platform_event = True
318        if not hasattr(f, '_platform_event_data'):
319            f._platform_event_data = []
320        f._platform_event_data.append(data)
321        return f
322
323    return _event_wrapper
324
325
326def _ViewEventHandler(f):
327    f._view = True
328    return f
329
330
331class _WindowMetaclass(type):
332    """Sets the _platform_event_names class variable on the window
333    subclass.
334    """
335
336    def __init__(cls, name, bases, dict):
337        cls._platform_event_names = set()
338        for base in bases:
339            if hasattr(base, '_platform_event_names'):
340                cls._platform_event_names.update(base._platform_event_names)
341        for name, func in dict.items():
342            if hasattr(func, '_platform_event'):
343                cls._platform_event_names.add(name)
344        super(_WindowMetaclass, cls).__init__(name, bases, dict)
345
346
347class BaseWindow(with_metaclass(_WindowMetaclass, EventDispatcher)):
348    """Platform-independent application window.
349
350    A window is a "heavyweight" object occupying operating system resources.
351    The "client" or "content" area of a window is filled entirely with
352    an OpenGL viewport.  Applications have no access to operating system
353    widgets or controls; all rendering must be done via OpenGL.
354
355    Windows may appear as floating regions or can be set to fill an entire
356    screen (fullscreen).  When floating, windows may appear borderless or
357    decorated with a platform-specific frame (including, for example, the
358    title bar, minimize and close buttons, resize handles, and so on).
359
360    While it is possible to set the location of a window, it is recommended
361    that applications allow the platform to place it according to local
362    conventions.  This will ensure it is not obscured by other windows,
363    and appears on an appropriate screen for the user.
364
365    To render into a window, you must first call `switch_to`, to make
366    it the current OpenGL context.  If you use only one window in the
367    application, there is no need to do this.
368    """
369
370    # Filled in by metaclass with the names of all methods on this (sub)class
371    # that are platform event handlers.
372    _platform_event_names = set()
373
374    #: The default window style.
375    WINDOW_STYLE_DEFAULT = None
376    #: The window style for pop-up dialogs.
377    WINDOW_STYLE_DIALOG = 'dialog'
378    #: The window style for tool windows.
379    WINDOW_STYLE_TOOL = 'tool'
380    #: A window style without any decoration.
381    WINDOW_STYLE_BORDERLESS = 'borderless'
382
383    #: The default mouse cursor.
384    CURSOR_DEFAULT = None
385    #: A crosshair mouse cursor.
386    CURSOR_CROSSHAIR = 'crosshair'
387    #: A pointing hand mouse cursor.
388    CURSOR_HAND = 'hand'
389    #: A "help" mouse cursor; typically a question mark and an arrow.
390    CURSOR_HELP = 'help'
391    #: A mouse cursor indicating that the selected operation is not permitted.
392    CURSOR_NO = 'no'
393    #: A mouse cursor indicating the element can be resized.
394    CURSOR_SIZE = 'size'
395    #: A mouse cursor indicating the element can be resized from the top
396    #: border.
397    CURSOR_SIZE_UP = 'size_up'
398    #: A mouse cursor indicating the element can be resized from the
399    #: upper-right corner.
400    CURSOR_SIZE_UP_RIGHT = 'size_up_right'
401    #: A mouse cursor indicating the element can be resized from the right
402    #: border.
403    CURSOR_SIZE_RIGHT = 'size_right'
404    #: A mouse cursor indicating the element can be resized from the lower-right
405    #: corner.
406    CURSOR_SIZE_DOWN_RIGHT = 'size_down_right'
407    #: A mouse cursor indicating the element can be resized from the bottom
408    #: border.
409    CURSOR_SIZE_DOWN = 'size_down'
410    #: A mouse cursor indicating the element can be resized from the lower-left
411    #: corner.
412    CURSOR_SIZE_DOWN_LEFT = 'size_down_left'
413    #: A mouse cursor indicating the element can be resized from the left
414    #: border.
415    CURSOR_SIZE_LEFT = 'size_left'
416    #: A mouse cursor indicating the element can be resized from the upper-left
417    #: corner.
418    CURSOR_SIZE_UP_LEFT = 'size_up_left'
419    #: A mouse cursor indicating the element can be resized vertically.
420    CURSOR_SIZE_UP_DOWN = 'size_up_down'
421    #: A mouse cursor indicating the element can be resized horizontally.
422    CURSOR_SIZE_LEFT_RIGHT = 'size_left_right'
423    #: A text input mouse cursor (I-beam).
424    CURSOR_TEXT = 'text'
425    #: A "wait" mouse cursor; typically an hourglass or watch.
426    CURSOR_WAIT = 'wait'
427    #: The "wait" mouse cursor combined with an arrow.
428    CURSOR_WAIT_ARROW = 'wait_arrow'
429
430    #: True if the user has attempted to close the window.
431    #:
432    #: :deprecated: Windows are closed immediately by the default
433    #:      :py:meth:`~pyglet.window.Window.on_close` handler when `pyglet.app.event_loop` is being
434    #:      used.
435    has_exit = False
436
437    #: Window display contents validity.  The :py:mod:`pyglet.app` event loop
438    #: examines every window each iteration and only dispatches the :py:meth:`~pyglet.window.Window.on_draw`
439    #: event to windows that have `invalid` set.  By default, windows always
440    #: have `invalid` set to ``True``.
441    #:
442    #: You can prevent redundant redraws by setting this variable to ``False``
443    #: in the window's :py:meth:`~pyglet.window.Window.on_draw` handler, and setting it to True again in
444    #: response to any events that actually do require a window contents
445    #: update.
446    #:
447    #: :type: bool
448    #: .. versionadded:: 1.1
449    invalid = True
450
451    #: Legacy invalidation flag introduced in pyglet 1.2: set by all event
452    #: dispatches that go to non-empty handlers.  The default 1.2 event loop
453    #: will therefore redraw after any handled event or scheduled function.
454    _legacy_invalid = True
455
456    # Instance variables accessible only via properties
457
458    _width = None
459    _height = None
460    _caption = None
461    _resizable = False
462    _style = WINDOW_STYLE_DEFAULT
463    _fullscreen = False
464    _visible = False
465    _vsync = False
466    _screen = None
467    _config = None
468    _context = None
469    _projection = Projection2D()
470
471    # Used to restore window size and position after fullscreen
472    _windowed_size = None
473    _windowed_location = None
474
475    # Subclasses should update these after relevant events
476    _mouse_cursor = DefaultMouseCursor()
477    _mouse_x = 0
478    _mouse_y = 0
479    _mouse_visible = True
480    _mouse_exclusive = False
481    _mouse_in_window = False
482
483    _event_queue = None
484    _enable_event_queue = True     # overridden by EventLoop.
485    _allow_dispatch_event = False  # controlled by dispatch_events stack frame
486
487    # Class attributes
488
489    _default_width = 640
490    _default_height = 480
491
492    def __init__(self,
493                 width=None,
494                 height=None,
495                 caption=None,
496                 resizable=False,
497                 style=WINDOW_STYLE_DEFAULT,
498                 fullscreen=False,
499                 visible=True,
500                 vsync=True,
501                 display=None,
502                 screen=None,
503                 config=None,
504                 context=None,
505                 mode=None):
506        """Create a window.
507
508        All parameters are optional, and reasonable defaults are assumed
509        where they are not specified.
510
511        The `display`, `screen`, `config` and `context` parameters form
512        a hierarchy of control: there is no need to specify more than
513        one of these.  For example, if you specify `screen` the `display`
514        will be inferred, and a default `config` and `context` will be
515        created.
516
517        `config` is a special case; it can be a template created by the
518        user specifying the attributes desired, or it can be a complete
519        `config` as returned from `Screen.get_matching_configs` or similar.
520
521        The context will be active as soon as the window is created, as if
522        `switch_to` was just called.
523
524        :Parameters:
525            `width` : int
526                Width of the window, in pixels.  Defaults to 640, or the
527                screen width if `fullscreen` is True.
528            `height` : int
529                Height of the window, in pixels.  Defaults to 480, or the
530                screen height if `fullscreen` is True.
531            `caption` : str or unicode
532                Initial caption (title) of the window.  Defaults to
533                ``sys.argv[0]``.
534            `resizable` : bool
535                If True, the window will be resizable.  Defaults to False.
536            `style` : int
537                One of the ``WINDOW_STYLE_*`` constants specifying the
538                border style of the window.
539            `fullscreen` : bool
540                If True, the window will cover the entire screen rather
541                than floating.  Defaults to False.
542            `visible` : bool
543                Determines if the window is visible immediately after
544                creation.  Defaults to True.  Set this to False if you
545                would like to change attributes of the window before
546                having it appear to the user.
547            `vsync` : bool
548                If True, buffer flips are synchronised to the primary screen's
549                vertical retrace, eliminating flicker.
550            `display` : `Display`
551                The display device to use.  Useful only under X11.
552            `screen` : `Screen`
553                The screen to use, if in fullscreen.
554            `config` : `pyglet.gl.Config`
555                Either a template from which to create a complete config,
556                or a complete config.
557            `context` : `pyglet.gl.Context`
558                The context to attach to this window.  The context must
559                not already be attached to another window.
560            `mode` : `ScreenMode`
561                The screen will be switched to this mode if `fullscreen` is
562                True.  If None, an appropriate mode is selected to accomodate
563                `width` and `height.`
564
565        """
566        EventDispatcher.__init__(self)
567        self._event_queue = []
568
569        if not display:
570            display = pyglet.canvas.get_display()
571
572        if not screen:
573            screen = display.get_default_screen()
574
575        if not config:
576            for template_config in [gl.Config(double_buffer=True, depth_size=24),
577                                    gl.Config(double_buffer=True, depth_size=16),
578                                    None]:
579                try:
580                    config = screen.get_best_config(template_config)
581                    break
582                except NoSuchConfigException:
583                    pass
584            if not config:
585                raise NoSuchConfigException('No standard config is available.')
586
587        if not config.is_complete():
588            config = screen.get_best_config(config)
589
590        if not context:
591            context = config.create_context(gl.current_context)
592
593        # Set these in reverse order to above, to ensure we get user preference
594        self._context = context
595        self._config = self._context.config
596        # XXX deprecate config's being screen-specific
597        if hasattr(self._config, 'screen'):
598            self._screen = self._config.screen
599        else:
600            self._screen = screen
601        self._display = self._screen.display
602
603        if fullscreen:
604            if width is None and height is None:
605                self._windowed_size = self._default_width, self._default_height
606            width, height = self._set_fullscreen_mode(mode, width, height)
607            if not self._windowed_size:
608                self._windowed_size = width, height
609        else:
610            if width is None:
611                width = self._default_width
612            if height is None:
613                height = self._default_height
614
615        self._width = width
616        self._height = height
617        self._resizable = resizable
618        self._fullscreen = fullscreen
619        self._style = style
620        if pyglet.options['vsync'] is not None:
621            self._vsync = pyglet.options['vsync']
622        else:
623            self._vsync = vsync
624
625        if caption is None:
626            caption = sys.argv[0]
627
628        self._caption = caption
629
630        from pyglet import app
631        app.windows.add(self)
632        self._create()
633
634        self.switch_to()
635        if visible:
636            self.set_visible(True)
637            self.activate()
638
639    def __del__(self):
640        # Always try to clean up the window when it is dereferenced.
641        # Makes sure there are no dangling pointers or memory leaks.
642        # If the window is already closed, pass silently.
643        try:
644            self.close()
645        except:   # XXX  Avoid a NoneType error if already closed.
646            pass
647
648    def __repr__(self):
649        return '%s(width=%d, height=%d)' % (self.__class__.__name__, self.width, self.height)
650
651    def _create(self):
652        raise NotImplementedError('abstract')
653
654    def _recreate(self, changes):
655        """Recreate the window with current attributes.
656
657        :Parameters:
658            `changes` : list of str
659                List of attribute names that were changed since the last
660                `_create` or `_recreate`.  For example, ``['fullscreen']``
661                is given if the window is to be toggled to or from fullscreen.
662        """
663        raise NotImplementedError('abstract')
664
665    def flip(self):
666        """Swap the OpenGL front and back buffers.
667
668        Call this method on a double-buffered window to update the
669        visible display with the back buffer.  The contents of the back buffer
670        is undefined after this operation.
671
672        Windows are double-buffered by default.  This method is called
673        automatically by `EventLoop` after the :py:meth:`~pyglet.window.Window.on_draw` event.
674        """
675        raise NotImplementedError('abstract')
676
677    def switch_to(self):
678        """Make this window the current OpenGL rendering context.
679
680        Only one OpenGL context can be active at a time.  This method sets
681        the current window's context to be current.  You should use this
682        method in preference to `pyglet.gl.Context.set_current`, as it may
683        perform additional initialisation functions.
684        """
685        raise NotImplementedError('abstract')
686
687    def set_fullscreen(self, fullscreen=True, screen=None, mode=None,
688                       width=None, height=None):
689        """Toggle to or from fullscreen.
690
691        After toggling fullscreen, the GL context should have retained its
692        state and objects, however the buffers will need to be cleared and
693        redrawn.
694
695        If `width` and `height` are specified and `fullscreen` is True, the
696        screen may be switched to a different resolution that most closely
697        matches the given size.  If the resolution doesn't match exactly,
698        a higher resolution is selected and the window will be centered
699        within a black border covering the rest of the screen.
700
701        :Parameters:
702            `fullscreen` : bool
703                True if the window should be made fullscreen, False if it
704                should be windowed.
705            `screen` : Screen
706                If not None and fullscreen is True, the window is moved to the
707                given screen.  The screen must belong to the same display as
708                the window.
709            `mode` : `ScreenMode`
710                The screen will be switched to the given mode.  The mode must
711                have been obtained by enumerating `Screen.get_modes`.  If
712                None, an appropriate mode will be selected from the given
713                `width` and `height`.
714            `width` : int
715                Optional width of the window.  If unspecified, defaults to the
716                previous window size when windowed, or the screen size if
717                fullscreen.
718
719                .. versionadded:: 1.2
720            `height` : int
721                Optional height of the window.  If unspecified, defaults to
722                the previous window size when windowed, or the screen size if
723                fullscreen.
724
725                .. versionadded:: 1.2
726        """
727        if (fullscreen == self._fullscreen and
728            (screen is None or screen is self._screen) and
729            (width is None or width == self._width) and
730            (height is None or height == self._height)):
731            return
732
733        if not self._fullscreen:
734            # Save windowed size
735            self._windowed_size = self.get_size()
736            self._windowed_location = self.get_location()
737
738        if fullscreen and screen is not None:
739            assert screen.display is self.display
740            self._screen = screen
741
742        self._fullscreen = fullscreen
743        if self._fullscreen:
744            self._width, self._height = self._set_fullscreen_mode(mode, width, height)
745        else:
746            self.screen.restore_mode()
747
748            self._width, self._height = self._windowed_size
749            if width is not None:
750                self._width = width
751            if height is not None:
752                self._height = height
753
754        self._recreate(['fullscreen'])
755
756        if not self._fullscreen and self._windowed_location:
757            # Restore windowed location.
758            self.set_location(*self._windowed_location)
759
760    def _set_fullscreen_mode(self, mode, width, height):
761        if mode is not None:
762            self.screen.set_mode(mode)
763            if width is None:
764                width = self.screen.width
765            if height is None:
766                height = self.screen.height
767        elif width is not None or height is not None:
768            if width is None:
769                width = 0
770            if height is None:
771                height = 0
772            mode = self.screen.get_closest_mode(width, height)
773            if mode is not None:
774                self.screen.set_mode(mode)
775            elif self.screen.get_modes():
776                # Only raise exception if mode switching is at all possible.
777                raise NoSuchScreenModeException('No mode matching %dx%d' % (width, height))
778        else:
779            width = self.screen.width
780            height = self.screen.height
781        return width, height
782
783    def on_resize(self, width, height):
784        """A default resize event handler.
785
786        This default handler updates the GL viewport to cover the entire
787        window and sets the ``GL_PROJECTION`` matrix to be orthogonal in
788        window space.  The bottom-left corner is (0, 0) and the top-right
789        corner is the width and height of the window in pixels.
790
791        Override this event handler with your own to create another
792        projection, for example in perspective.
793        """
794        viewport_width, viewport_height = self.get_framebuffer_size()
795        self._projection.set(width, height, viewport_width, viewport_height)
796
797    def on_close(self):
798        """Default on_close handler."""
799        self.has_exit = True
800        from pyglet import app
801        if app.event_loop.is_running:
802            self.close()
803
804    def on_key_press(self, symbol, modifiers):
805        """Default on_key_press handler."""
806        if symbol == key.ESCAPE and not (modifiers & ~(key.MOD_NUMLOCK |
807                                                       key.MOD_CAPSLOCK |
808                                                       key.MOD_SCROLLLOCK)):
809            self.dispatch_event('on_close')
810
811    def close(self):
812        """Close the window.
813
814        After closing the window, the GL context will be invalid.  The
815        window instance cannot be reused once closed (see also `set_visible`).
816
817        The `pyglet.app.EventLoop.on_window_close` event is dispatched on
818        `pyglet.app.event_loop` when this method is called.
819        """
820        from pyglet import app
821        if not self._context:
822            return
823        app.windows.remove(self)
824        self._context.destroy()
825        self._config = None
826        self._context = None
827        if app.event_loop:
828            app.event_loop.dispatch_event('on_window_close', self)
829        self._event_queue = []
830
831    def draw_mouse_cursor(self):
832        """Draw the custom mouse cursor.
833
834        If the current mouse cursor has ``drawable`` set, this method
835        is called before the buffers are flipped to render it.
836
837        This method always leaves the ``GL_MODELVIEW`` matrix as current,
838        regardless of what it was set to previously.  No other GL state
839        is affected.
840
841        There is little need to override this method; instead, subclass
842        :py:class:`MouseCursor` and provide your own
843        :py:meth:`~MouseCursor.draw` method.
844        """
845        # Draw mouse cursor if set and visible.
846        # XXX leaves state in modelview regardless of starting state
847        if (self._mouse_cursor.drawable and
848            self._mouse_visible and
849            self._mouse_in_window):
850            gl.glMatrixMode(gl.GL_PROJECTION)
851            gl.glPushMatrix()
852            gl.glLoadIdentity()
853            gl.glOrtho(0, self.width, 0, self.height, -1, 1)
854
855            gl.glMatrixMode(gl.GL_MODELVIEW)
856            gl.glPushMatrix()
857            gl.glLoadIdentity()
858
859            self._mouse_cursor.draw(self._mouse_x, self._mouse_y)
860
861            gl.glMatrixMode(gl.GL_PROJECTION)
862            gl.glPopMatrix()
863
864            gl.glMatrixMode(gl.GL_MODELVIEW)
865            gl.glPopMatrix()
866
867    # These properties provide read-only access to instance variables.
868    @property
869    def caption(self):
870        """The window caption (title).  Read-only.
871
872        :type: str
873        """
874        return self._caption
875
876    @property
877    def resizeable(self):
878        """True if the window is resizable.  Read-only.
879
880        :type: bool
881        """
882        return self._resizable
883
884    @property
885    def style(self):
886        """The window style; one of the ``WINDOW_STYLE_*`` constants.
887        Read-only.
888
889        :type: int
890        """
891        return self._style
892
893    @property
894    def fullscreen(self):
895        """True if the window is currently fullscreen.  Read-only.
896
897        :type: bool
898        """
899        return self._fullscreen
900
901    @property
902    def visible(self):
903        """True if the window is currently visible.  Read-only.
904
905        :type: bool
906        """
907        return self._visible
908
909    @property
910    def vsync(self):
911        """True if buffer flips are synchronised to the screen's vertical
912        retrace.  Read-only.
913
914        :type: bool
915        """
916        return self._vsync
917
918    @property
919    def display(self):
920        """The display this window belongs to.  Read-only.
921
922        :type: :py:class:`Display`
923        """
924        return self._display
925
926    @property
927    def screen(self):
928        """The screen this window is fullscreen in.  Read-only.
929
930        :type: :py:class:`Screen`
931        """
932        return self._screen
933
934    @property
935    def config(self):
936        """A GL config describing the context of this window.  Read-only.
937
938        :type: :py:class:`pyglet.gl.Config`
939        """
940        return self._config
941
942    @property
943    def context(self):
944        """The OpenGL context attached to this window.  Read-only.
945
946        :type: :py:class:`pyglet.gl.Context`
947        """
948        return self._context
949
950    # These are the only properties that can be set
951    @property
952    def width(self):
953        """The width of the window, in pixels.  Read-write.
954
955        :type: int
956        """
957        return self.get_size()[0]
958
959    @width.setter
960    def width(self, new_width):
961        self.set_size(new_width, self.height)
962
963    @property
964    def height(self):
965        """The height of the window, in pixels.  Read-write.
966
967        :type: int
968        """
969        return self.get_size()[1]
970
971    @height.setter
972    def height(self, new_height):
973        self.set_size(self.width, new_height)
974
975    @property
976    def projection(self):
977        """The OpenGL window projection. Read-write.
978
979        The default window projection is orthographic (2D), but can
980        be changed to a 3D or custom projection. Custom projections
981        should subclass :py:class:`pyglet.window.Projection`. There
982        are two default projection classes are also provided, which
983        are :py:class:`pyglet.window.Projection3D` and
984        :py:class:`pyglet.window.Projection3D`.
985
986        :type: :py:class:`pyglet.window.Projection`
987        """
988        return self._projection
989
990    @projection.setter
991    def projection(self, projection):
992        assert isinstance(projection, Projection)
993        projection.set(self._width, self._height, *self.get_framebuffer_size())
994        self._projection = projection
995
996    def set_caption(self, caption):
997        """Set the window's caption.
998
999        The caption appears in the titlebar of the window, if it has one,
1000        and in the taskbar on Windows and many X11 window managers.
1001
1002        :Parameters:
1003            `caption` : str or unicode
1004                The caption to set.
1005
1006        """
1007        raise NotImplementedError('abstract')
1008
1009    def set_minimum_size(self, width, height):
1010        """Set the minimum size of the window.
1011
1012        Once set, the user will not be able to resize the window smaller
1013        than the given dimensions.  There is no way to remove the
1014        minimum size constraint on a window (but you could set it to 0,0).
1015
1016        The behaviour is undefined if the minimum size is set larger than
1017        the current size of the window.
1018
1019        The window size does not include the border or title bar.
1020
1021        :Parameters:
1022            `width` : int
1023                Minimum width of the window, in pixels.
1024            `height` : int
1025                Minimum height of the window, in pixels.
1026
1027        """
1028        raise NotImplementedError('abstract')
1029
1030    def set_maximum_size(self, width, height):
1031        """Set the maximum size of the window.
1032
1033        Once set, the user will not be able to resize the window larger
1034        than the given dimensions.  There is no way to remove the
1035        maximum size constraint on a window (but you could set it to a large
1036        value).
1037
1038        The behaviour is undefined if the maximum size is set smaller than
1039        the current size of the window.
1040
1041        The window size does not include the border or title bar.
1042
1043        :Parameters:
1044            `width` : int
1045                Maximum width of the window, in pixels.
1046            `height` : int
1047                Maximum height of the window, in pixels.
1048
1049        """
1050        raise NotImplementedError('abstract')
1051
1052    def set_size(self, width, height):
1053        """Resize the window.
1054
1055        The behaviour is undefined if the window is not resizable, or if
1056        it is currently fullscreen.
1057
1058        The window size does not include the border or title bar.
1059
1060        :Parameters:
1061            `width` : int
1062                New width of the window, in pixels.
1063            `height` : int
1064                New height of the window, in pixels.
1065
1066        """
1067        raise NotImplementedError('abstract')
1068
1069    def get_pixel_ratio(self):
1070        """Return the framebuffer/window size ratio.
1071
1072        Some platforms and/or window systems support subpixel scaling,
1073        making the framebuffer size larger than the window size.
1074        Retina screens on OS X and Gnome on Linux are some examples.
1075
1076        On a Retina systems the returned ratio would usually be 2.0 as a
1077        window of size 500 x 500 would have a frambuffer of 1000 x 1000.
1078        Fractional values between 1.0 and 2.0, as well as values above
1079        2.0 may also be encountered.
1080
1081        :rtype: float
1082        :return: The framebuffer/window size ratio
1083        """
1084        return self.get_framebuffer_size()[0] / self.width
1085
1086    def get_size(self):
1087        """Return the current size of the window.
1088
1089        The window size does not include the border or title bar.
1090
1091        :rtype: (int, int)
1092        :return: The width and height of the window, in pixels.
1093        """
1094        raise NotImplementedError('abstract')
1095
1096    def get_framebuffer_size(self):
1097        """Return the size in actual pixels of the Window framebuffer.
1098
1099        When using HiDPI screens, the size of the Window's framebuffer
1100        can be higher than that of the Window size requested. If you
1101        are performing operations that require knowing the actual number
1102        of pixels in the window, this method should be used instead of
1103        :py:func:`Window.get_size()`. For example, setting the Window
1104        projection or setting the glViewport size.
1105
1106        :rtype: (int, int)
1107        :return: The width and height of the Window viewport, in pixels.
1108        """
1109        return self.get_size()
1110
1111    # :deprecated: Use Window.get_framebuffer_size
1112    get_viewport_size = get_framebuffer_size
1113
1114    def set_location(self, x, y):
1115        """Set the position of the window.
1116
1117        :Parameters:
1118            `x` : int
1119                Distance of the left edge of the window from the left edge
1120                of the virtual desktop, in pixels.
1121            `y` : int
1122                Distance of the top edge of the window from the top edge of
1123                the virtual desktop, in pixels.
1124
1125        """
1126        raise NotImplementedError('abstract')
1127
1128    def get_location(self):
1129        """Return the current position of the window.
1130
1131        :rtype: (int, int)
1132        :return: The distances of the left and top edges from their respective
1133            edges on the virtual desktop, in pixels.
1134        """
1135        raise NotImplementedError('abstract')
1136
1137    def activate(self):
1138        """Attempt to restore keyboard focus to the window.
1139
1140        Depending on the window manager or operating system, this may not
1141        be successful.  For example, on Windows XP an application is not
1142        allowed to "steal" focus from another application.  Instead, the
1143        window's taskbar icon will flash, indicating it requires attention.
1144        """
1145        raise NotImplementedError('abstract')
1146
1147    def set_visible(self, visible=True):
1148        """Show or hide the window.
1149
1150        :Parameters:
1151            `visible` : bool
1152                If True, the window will be shown; otherwise it will be
1153                hidden.
1154
1155        """
1156        raise NotImplementedError('abstract')
1157
1158    def minimize(self):
1159        """Minimize the window.
1160        """
1161        raise NotImplementedError('abstract')
1162
1163    def maximize(self):
1164        """Maximize the window.
1165
1166        The behaviour of this method is somewhat dependent on the user's
1167        display setup.  On a multi-monitor system, the window may maximize
1168        to either a single screen or the entire virtual desktop.
1169        """
1170        raise NotImplementedError('abstract')
1171
1172    def set_vsync(self, vsync):
1173        """Enable or disable vertical sync control.
1174
1175        When enabled, this option ensures flips from the back to the front
1176        buffer are performed only during the vertical retrace period of the
1177        primary display.  This can prevent "tearing" or flickering when
1178        the buffer is updated in the middle of a video scan.
1179
1180        Note that LCD monitors have an analogous time in which they are not
1181        reading from the video buffer; while it does not correspond to
1182        a vertical retrace it has the same effect.
1183
1184        Also note that with multi-monitor systems the secondary monitor
1185        cannot be synchronised to, so tearing and flicker cannot be avoided
1186        when the window is positioned outside of the primary display.
1187
1188        :Parameters:
1189            `vsync` : bool
1190                If True, vsync is enabled, otherwise it is disabled.
1191
1192        """
1193        raise NotImplementedError('abstract')
1194
1195    def set_mouse_visible(self, visible=True):
1196        """Show or hide the mouse cursor.
1197
1198        The mouse cursor will only be hidden while it is positioned within
1199        this window.  Mouse events will still be processed as usual.
1200
1201        :Parameters:
1202            `visible` : bool
1203                If True, the mouse cursor will be visible, otherwise it
1204                will be hidden.
1205
1206        """
1207        self._mouse_visible = visible
1208        self.set_mouse_platform_visible()
1209
1210    def set_mouse_platform_visible(self, platform_visible=None):
1211        """Set the platform-drawn mouse cursor visibility.  This is called
1212        automatically after changing the mouse cursor or exclusive mode.
1213
1214        Applications should not normally need to call this method, see
1215        `set_mouse_visible` instead.
1216
1217        :Parameters:
1218            `platform_visible` : bool or None
1219                If None, sets platform visibility to the required visibility
1220                for the current exclusive mode and cursor type.  Otherwise,
1221                a bool value will override and force a visibility.
1222
1223        """
1224        raise NotImplementedError()
1225
1226    def set_mouse_cursor(self, cursor=None):
1227        """Change the appearance of the mouse cursor.
1228
1229        The appearance of the mouse cursor is only changed while it is
1230        within this window.
1231
1232        :Parameters:
1233            `cursor` : `MouseCursor`
1234                The cursor to set, or None to restore the default cursor.
1235
1236        """
1237        if cursor is None:
1238            cursor = DefaultMouseCursor()
1239        self._mouse_cursor = cursor
1240        self.set_mouse_platform_visible()
1241
1242    def set_exclusive_mouse(self, exclusive=True):
1243        """Hide the mouse cursor and direct all mouse events to this
1244        window.
1245
1246        When enabled, this feature prevents the mouse leaving the window.  It
1247        is useful for certain styles of games that require complete control of
1248        the mouse.  The position of the mouse as reported in subsequent events
1249        is meaningless when exclusive mouse is enabled; you should only use
1250        the relative motion parameters ``dx`` and ``dy``.
1251
1252        :Parameters:
1253            `exclusive` : bool
1254                If True, exclusive mouse is enabled, otherwise it is disabled.
1255
1256        """
1257        raise NotImplementedError('abstract')
1258
1259    def set_exclusive_keyboard(self, exclusive=True):
1260        """Prevent the user from switching away from this window using
1261        keyboard accelerators.
1262
1263        When enabled, this feature disables certain operating-system specific
1264        key combinations such as Alt+Tab (Command+Tab on OS X).  This can be
1265        useful in certain kiosk applications, it should be avoided in general
1266        applications or games.
1267
1268        :Parameters:
1269            `exclusive` : bool
1270                If True, exclusive keyboard is enabled, otherwise it is
1271                disabled.
1272
1273        """
1274        raise NotImplementedError('abstract')
1275
1276    def get_system_mouse_cursor(self, name):
1277        """Obtain a system mouse cursor.
1278
1279        Use `set_mouse_cursor` to make the cursor returned by this method
1280        active.  The names accepted by this method are the ``CURSOR_*``
1281        constants defined on this class.
1282
1283        :Parameters:
1284            `name` : str
1285                Name describing the mouse cursor to return.  For example,
1286                ``CURSOR_WAIT``, ``CURSOR_HELP``, etc.
1287
1288        :rtype: `MouseCursor`
1289        :return: A mouse cursor which can be used with `set_mouse_cursor`.
1290        """
1291        raise NotImplementedError()
1292
1293    def set_icon(self, *images):
1294        """Set the window icon.
1295
1296        If multiple images are provided, one with an appropriate size
1297        will be selected (if the correct size is not provided, the image
1298        will be scaled).
1299
1300        Useful sizes to provide are 16x16, 32x32, 64x64 (Mac only) and
1301        128x128 (Mac only).
1302
1303        :Parameters:
1304            `images` : sequence of `pyglet.image.AbstractImage`
1305                List of images to use for the window icon.
1306
1307        """
1308        pass
1309
1310    def clear(self):
1311        """Clear the window.
1312
1313        This is a convenience method for clearing the color and depth
1314        buffer.  The window must be the active context (see `switch_to`).
1315        """
1316        gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
1317
1318    def dispatch_event(self, *args):
1319        if not self._enable_event_queue or self._allow_dispatch_event:
1320            if EventDispatcher.dispatch_event(self, *args) != False:
1321                self._legacy_invalid = True
1322        else:
1323            self._event_queue.append(args)
1324
1325    def dispatch_events(self):
1326        """Poll the operating system event queue for new events and call
1327        attached event handlers.
1328
1329        This method is provided for legacy applications targeting pyglet 1.0,
1330        and advanced applications that must integrate their event loop
1331        into another framework.
1332
1333        Typical applications should use `pyglet.app.run`.
1334        """
1335        raise NotImplementedError('abstract')
1336
1337    # If documenting, show the event methods.  Otherwise, leave them out
1338    # as they are not really methods.
1339    if _is_pyglet_doc_run:
1340        def on_key_press(self, symbol, modifiers):
1341            """A key on the keyboard was pressed (and held down).
1342
1343            In pyglet 1.0 the default handler sets `has_exit` to ``True`` if
1344            the ``ESC`` key is pressed.
1345
1346            In pyglet 1.1 the default handler dispatches the :py:meth:`~pyglet.window.Window.on_close`
1347            event if the ``ESC`` key is pressed.
1348
1349            :Parameters:
1350                `symbol` : int
1351                    The key symbol pressed.
1352                `modifiers` : int
1353                    Bitwise combination of the key modifiers active.
1354
1355            :event:
1356            """
1357
1358        def on_key_release(self, symbol, modifiers):
1359            """A key on the keyboard was released.
1360
1361            :Parameters:
1362                `symbol` : int
1363                    The key symbol pressed.
1364                `modifiers` : int
1365                    Bitwise combination of the key modifiers active.
1366
1367            :event:
1368            """
1369
1370        def on_text(self, text):
1371            """The user input some text.
1372
1373            Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
1374            :py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
1375            is held down (key repeating); or called without key presses if
1376            another input method was used (e.g., a pen input).
1377
1378            You should always use this method for interpreting text, as the
1379            key symbols often have complex mappings to their unicode
1380            representation which this event takes care of.
1381
1382            :Parameters:
1383                `text` : unicode
1384                    The text entered by the user.
1385
1386            :event:
1387            """
1388
1389        def on_text_motion(self, motion):
1390            """The user moved the text input cursor.
1391
1392            Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
1393            :py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
1394            is help down (key repeating).
1395
1396            You should always use this method for moving the text input cursor
1397            (caret), as different platforms have different default keyboard
1398            mappings, and key repeats are handled correctly.
1399
1400            The values that `motion` can take are defined in
1401            :py:mod:`pyglet.window.key`:
1402
1403            * MOTION_UP
1404            * MOTION_RIGHT
1405            * MOTION_DOWN
1406            * MOTION_LEFT
1407            * MOTION_NEXT_WORD
1408            * MOTION_PREVIOUS_WORD
1409            * MOTION_BEGINNING_OF_LINE
1410            * MOTION_END_OF_LINE
1411            * MOTION_NEXT_PAGE
1412            * MOTION_PREVIOUS_PAGE
1413            * MOTION_BEGINNING_OF_FILE
1414            * MOTION_END_OF_FILE
1415            * MOTION_BACKSPACE
1416            * MOTION_DELETE
1417
1418            :Parameters:
1419                `motion` : int
1420                    The direction of motion; see remarks.
1421
1422            :event:
1423            """
1424
1425        def on_text_motion_select(self, motion):
1426            """The user moved the text input cursor while extending the
1427            selection.
1428
1429            Typically this is called after :py:meth:`~pyglet.window.Window.on_key_press` and before
1430            :py:meth:`~pyglet.window.Window.on_key_release`, but may also be called multiple times if the key
1431            is help down (key repeating).
1432
1433            You should always use this method for responding to text selection
1434            events rather than the raw :py:meth:`~pyglet.window.Window.on_key_press`, as different platforms
1435            have different default keyboard mappings, and key repeats are
1436            handled correctly.
1437
1438            The values that `motion` can take are defined in :py:mod:`pyglet.window.key`:
1439
1440            * MOTION_UP
1441            * MOTION_RIGHT
1442            * MOTION_DOWN
1443            * MOTION_LEFT
1444            * MOTION_NEXT_WORD
1445            * MOTION_PREVIOUS_WORD
1446            * MOTION_BEGINNING_OF_LINE
1447            * MOTION_END_OF_LINE
1448            * MOTION_NEXT_PAGE
1449            * MOTION_PREVIOUS_PAGE
1450            * MOTION_BEGINNING_OF_FILE
1451            * MOTION_END_OF_FILE
1452
1453            :Parameters:
1454                `motion` : int
1455                    The direction of selection motion; see remarks.
1456
1457            :event:
1458            """
1459
1460        def on_mouse_motion(self, x, y, dx, dy):
1461            """The mouse was moved with no buttons held down.
1462
1463            :Parameters:
1464                `x` : int
1465                    Distance in pixels from the left edge of the window.
1466                `y` : int
1467                    Distance in pixels from the bottom edge of the window.
1468                `dx` : int
1469                    Relative X position from the previous mouse position.
1470                `dy` : int
1471                    Relative Y position from the previous mouse position.
1472
1473            :event:
1474            """
1475
1476        def on_mouse_drag(self, x, y, dx, dy, buttons, modifiers):
1477            """The mouse was moved with one or more mouse buttons pressed.
1478
1479            This event will continue to be fired even if the mouse leaves
1480            the window, so long as the drag buttons are continuously held down.
1481
1482            :Parameters:
1483                `x` : int
1484                    Distance in pixels from the left edge of the window.
1485                `y` : int
1486                    Distance in pixels from the bottom edge of the window.
1487                `dx` : int
1488                    Relative X position from the previous mouse position.
1489                `dy` : int
1490                    Relative Y position from the previous mouse position.
1491                `buttons` : int
1492                    Bitwise combination of the mouse buttons currently pressed.
1493                `modifiers` : int
1494                    Bitwise combination of any keyboard modifiers currently
1495                    active.
1496
1497            :event:
1498            """
1499
1500        def on_mouse_press(self, x, y, button, modifiers):
1501            """A mouse button was pressed (and held down).
1502
1503            :Parameters:
1504                `x` : int
1505                    Distance in pixels from the left edge of the window.
1506                `y` : int
1507                    Distance in pixels from the bottom edge of the window.
1508                `button` : int
1509                    The mouse button that was pressed.
1510                `modifiers` : int
1511                    Bitwise combination of any keyboard modifiers currently
1512                    active.
1513
1514            :event:
1515            """
1516
1517        def on_mouse_release(self, x, y, button, modifiers):
1518            """A mouse button was released.
1519
1520            :Parameters:
1521                `x` : int
1522                    Distance in pixels from the left edge of the window.
1523                `y` : int
1524                    Distance in pixels from the bottom edge of the window.
1525                `button` : int
1526                    The mouse button that was released.
1527                `modifiers` : int
1528                    Bitwise combination of any keyboard modifiers currently
1529                    active.
1530
1531            :event:
1532            """
1533
1534        def on_mouse_scroll(self, x, y, scroll_x, scroll_y):
1535            """The mouse wheel was scrolled.
1536
1537            Note that most mice have only a vertical scroll wheel, so
1538            `scroll_x` is usually 0.  An exception to this is the Apple Mighty
1539            Mouse, which has a mouse ball in place of the wheel which allows
1540            both `scroll_x` and `scroll_y` movement.
1541
1542            :Parameters:
1543                `x` : int
1544                    Distance in pixels from the left edge of the window.
1545                `y` : int
1546                    Distance in pixels from the bottom edge of the window.
1547                `scroll_x` : int
1548                    Number of "clicks" towards the right (left if negative).
1549                `scroll_y` : int
1550                    Number of "clicks" upwards (downwards if negative).
1551
1552            :event:
1553            """
1554
1555        def on_close(self):
1556            """The user attempted to close the window.
1557
1558            This event can be triggered by clicking on the "X" control box in
1559            the window title bar, or by some other platform-dependent manner.
1560
1561            The default handler sets `has_exit` to ``True``.  In pyglet 1.1, if
1562            `pyglet.app.event_loop` is being used, `close` is also called,
1563            closing the window immediately.
1564
1565            :event:
1566            """
1567
1568        def on_mouse_enter(self, x, y):
1569            """The mouse was moved into the window.
1570
1571            This event will not be triggered if the mouse is currently being
1572            dragged.
1573
1574            :Parameters:
1575                `x` : int
1576                    Distance in pixels from the left edge of the window.
1577                `y` : int
1578                    Distance in pixels from the bottom edge of the window.
1579
1580            :event:
1581            """
1582
1583        def on_mouse_leave(self, x, y):
1584            """The mouse was moved outside of the window.
1585
1586            This event will not be triggered if the mouse is currently being
1587            dragged.  Note that the coordinates of the mouse pointer will be
1588            outside of the window rectangle.
1589
1590            :Parameters:
1591                `x` : int
1592                    Distance in pixels from the left edge of the window.
1593                `y` : int
1594                    Distance in pixels from the bottom edge of the window.
1595
1596            :event:
1597            """
1598
1599        def on_expose(self):
1600            """A portion of the window needs to be redrawn.
1601
1602            This event is triggered when the window first appears, and any time
1603            the contents of the window is invalidated due to another window
1604            obscuring it.
1605
1606            There is no way to determine which portion of the window needs
1607            redrawing.  Note that the use of this method is becoming
1608            increasingly uncommon, as newer window managers composite windows
1609            automatically and keep a backing store of the window contents.
1610
1611            :event:
1612            """
1613
1614        def on_resize(self, width, height):
1615            """The window was resized.
1616
1617            The window will have the GL context when this event is dispatched;
1618            there is no need to call `switch_to` in this handler.
1619
1620            :Parameters:
1621                `width` : int
1622                    The new width of the window, in pixels.
1623                `height` : int
1624                    The new height of the window, in pixels.
1625
1626            :event:
1627            """
1628
1629        def on_move(self, x, y):
1630            """The window was moved.
1631
1632            :Parameters:
1633                `x` : int
1634                    Distance from the left edge of the screen to the left edge
1635                    of the window.
1636                `y` : int
1637                    Distance from the top edge of the screen to the top edge of
1638                    the window.  Note that this is one of few methods in pyglet
1639                    which use a Y-down coordinate system.
1640
1641            :event:
1642            """
1643
1644        def on_activate(self):
1645            """The window was activated.
1646
1647            This event can be triggered by clicking on the title bar, bringing
1648            it to the foreground; or by some platform-specific method.
1649
1650            When a window is "active" it has the keyboard focus.
1651
1652            :event:
1653            """
1654
1655        def on_deactivate(self):
1656            """The window was deactivated.
1657
1658            This event can be triggered by clicking on another application
1659            window.  When a window is deactivated it no longer has the
1660            keyboard focus.
1661
1662            :event:
1663            """
1664
1665        def on_show(self):
1666            """The window was shown.
1667
1668            This event is triggered when a window is restored after being
1669            minimised, or after being displayed for the first time.
1670
1671            :event:
1672            """
1673
1674        def on_hide(self):
1675            """The window was hidden.
1676
1677            This event is triggered when a window is minimised or (on Mac OS X)
1678            hidden by the user.
1679
1680            :event:
1681            """
1682
1683        def on_context_lost(self):
1684            """The window's GL context was lost.
1685
1686            When the context is lost no more GL methods can be called until it
1687            is recreated.  This is a rare event, triggered perhaps by the user
1688            switching to an incompatible video mode.  When it occurs, an
1689            application will need to reload all objects (display lists, texture
1690            objects, shaders) as well as restore the GL state.
1691
1692            :event:
1693            """
1694
1695        def on_context_state_lost(self):
1696            """The state of the window's GL context was lost.
1697
1698            pyglet may sometimes need to recreate the window's GL context if
1699            the window is moved to another video device, or between fullscreen
1700            or windowed mode.  In this case it will try to share the objects
1701            (display lists, texture objects, shaders) between the old and new
1702            contexts.  If this is possible, only the current state of the GL
1703            context is lost, and the application should simply restore state.
1704
1705            :event:
1706            """
1707
1708        def on_draw(self):
1709            """The window contents must be redrawn.
1710
1711            The `EventLoop` will dispatch this event when the window
1712            should be redrawn.  This will happen during idle time after
1713            any window events and after any scheduled functions were called.
1714
1715            The window will already have the GL context, so there is no
1716            need to call `switch_to`.  The window's `flip` method will
1717            be called after this event, so your event handler should not.
1718
1719            You should make no assumptions about the window contents when
1720            this event is triggered; a resize or expose event may have
1721            invalidated the framebuffer since the last time it was drawn.
1722
1723            .. versionadded:: 1.1
1724
1725            :event:
1726            """
1727
1728
1729BaseWindow.register_event_type('on_key_press')
1730BaseWindow.register_event_type('on_key_release')
1731BaseWindow.register_event_type('on_text')
1732BaseWindow.register_event_type('on_text_motion')
1733BaseWindow.register_event_type('on_text_motion_select')
1734BaseWindow.register_event_type('on_mouse_motion')
1735BaseWindow.register_event_type('on_mouse_drag')
1736BaseWindow.register_event_type('on_mouse_press')
1737BaseWindow.register_event_type('on_mouse_release')
1738BaseWindow.register_event_type('on_mouse_scroll')
1739BaseWindow.register_event_type('on_mouse_enter')
1740BaseWindow.register_event_type('on_mouse_leave')
1741BaseWindow.register_event_type('on_close')
1742BaseWindow.register_event_type('on_expose')
1743BaseWindow.register_event_type('on_resize')
1744BaseWindow.register_event_type('on_move')
1745BaseWindow.register_event_type('on_activate')
1746BaseWindow.register_event_type('on_deactivate')
1747BaseWindow.register_event_type('on_show')
1748BaseWindow.register_event_type('on_hide')
1749BaseWindow.register_event_type('on_context_lost')
1750BaseWindow.register_event_type('on_context_state_lost')
1751BaseWindow.register_event_type('on_draw')
1752
1753
1754class FPSDisplay:
1755    """Display of a window's framerate.
1756
1757    This is a convenience class to aid in profiling and debugging.  Typical
1758    usage is to create an `FPSDisplay` for each window, and draw the display
1759    at the end of the windows' :py:meth:`~pyglet.window.Window.on_draw` event handler::
1760
1761        window = pyglet.window.Window()
1762        fps_display = FPSDisplay(window)
1763
1764        @window.event
1765        def on_draw():
1766            # ... perform ordinary window drawing operations ...
1767
1768            fps_display.draw()
1769
1770    The style and position of the display can be modified via the :py:func:`~pyglet.text.Label`
1771    attribute.  Different text can be substituted by overriding the
1772    `set_fps` method.  The display can be set to update more or less often
1773    by setting the `update_period` attribute. Note: setting the `update_period`
1774    to a value smaller than your Window refresh rate will cause inaccurate readings.
1775
1776    :Ivariables:
1777        `label` : Label
1778            The text label displaying the framerate.
1779
1780    """
1781
1782    #: Time in seconds between updates.
1783    #:
1784    #: :type: float
1785    update_period = 0.25
1786
1787    def __init__(self, window):
1788        from time import time
1789        from pyglet.text import Label
1790        self.label = Label('', x=10, y=10,
1791                           font_size=24, bold=True,
1792                           color=(127, 127, 127, 127))
1793
1794        self.window = window
1795        self._window_flip = window.flip
1796        window.flip = self._hook_flip
1797
1798        self.time = 0.0
1799        self.last_time = time()
1800        self.count = 0
1801
1802    def update(self):
1803        """Records a new data point at the current time.  This method
1804        is called automatically when the window buffer is flipped.
1805        """
1806        from time import time
1807        t = time()
1808        self.count += 1
1809        self.time += t - self.last_time
1810        self.last_time = t
1811
1812        if self.time >= self.update_period:
1813            self.set_fps(self.count / self.time)
1814            self.time %= self.update_period
1815            self.count = 0
1816
1817    def set_fps(self, fps):
1818        """Set the label text for the given FPS estimation.
1819
1820        Called by `update` every `update_period` seconds.
1821
1822        :Parameters:
1823            `fps` : float
1824                Estimated framerate of the window.
1825
1826        """
1827        self.label.text = '%.2f' % fps
1828
1829    def draw(self):
1830        """Draw the label.
1831
1832        The OpenGL state is assumed to be at default values, except
1833        that the MODELVIEW and PROJECTION matrices are ignored.  At
1834        the return of this method the matrix mode will be MODELVIEW.
1835        """
1836        gl.glMatrixMode(gl.GL_MODELVIEW)
1837        gl.glPushMatrix()
1838        gl.glLoadIdentity()
1839
1840        gl.glMatrixMode(gl.GL_PROJECTION)
1841        gl.glPushMatrix()
1842        gl.glLoadIdentity()
1843        gl.glOrtho(0, self.window.width, 0, self.window.height, -1, 1)
1844
1845        self.label.draw()
1846
1847        gl.glPopMatrix()
1848
1849        gl.glMatrixMode(gl.GL_MODELVIEW)
1850        gl.glPopMatrix()
1851
1852    def _hook_flip(self):
1853        self.update()
1854        self._window_flip()
1855
1856
1857if _is_pyglet_doc_run:
1858    # We are building documentation
1859    Window = BaseWindow
1860    Window.__name__ = 'Window'
1861    del BaseWindow
1862else:
1863    # Try to determine which platform to use.
1864    if pyglet.compat_platform == 'darwin':
1865        from pyglet.window.cocoa import CocoaWindow as Window
1866    elif pyglet.compat_platform in ('win32', 'cygwin'):
1867        from pyglet.window.win32 import Win32Window as Window
1868    else:
1869        # XXX HACK around circ problem, should be fixed after removal of
1870        # shadow nonsense
1871        #pyglet.window = sys.modules[__name__]
1872        #import key, mouse
1873
1874        from pyglet.window.xlib import XlibWindow as Window
1875
1876# XXX remove
1877# Create shadow window. (trickery is for circular import)
1878if not _is_pyglet_doc_run:
1879    pyglet.window = sys.modules[__name__]
1880    gl._create_shadow_window()
1881