text: from typing import Tuple
  import tkinter
  
  from moderngl_window.context.base import BaseWindow
  from moderngl_window.context.tk.keys import Keys
  from pyopengltk import OpenGLFrame
  
  
  class Window(BaseWindow):
      #: Name of the window
      name = "tk"
      #: tkinter specific key constants
      keys = Keys
  
      _mouse_button_map = {
          1: 1,
          3: 2,
          2: 3,
      }
  
      def __init__(self, **kwargs):
          super().__init__(**kwargs)
  
          self._tk = tkinter.Tk()
          self._gl_widget = ModernglTkWindow(
              self._tk, width=self.width, height=self.height
          )
          self._gl_widget.pack(fill=tkinter.BOTH, expand=tkinter.YES)
          self._tk.resizable(self._resizable, self._resizable)
  
          if self._fullscreen:
              self._tk.attributes("-fullscreen", True)
  
          self.cursor = self._cursor
  
          # Set up events
          self._gl_widget.bind("<Configure>", self.tk_resize)
          self._tk.bind("<KeyPress>", self.tk_key_press)
          self._tk.bind("<KeyRelease>", self.tk_key_release)
          self._tk.bind("<Motion>", self.tk_mouse_motion)
          self._tk.bind("<Button>", self.tk_mouse_button_press)
          self._tk.bind("<ButtonRelease>", self.tk_mouse_button_release)
          self._tk.bind("<MouseWheel>", self.tk_mouse_wheel)
          self._tk.bind("<Map>", self.tk_map)
          self._tk.bind("<Unmap>", self.tk_unmap)
  
          self._tk.protocol("WM_DELETE_WINDOW", self.tk_close_window)
  
          self.title = self._title
  
          # Ensure the window is opened/visible
          self._tk.update()
          self._gl_widget.tkMakeCurrent()
          self.init_mgl_context()
  
          self.set_default_viewport()
  
      def _set_fullscreen(self, value: bool) -> None:
          self._tk.attributes("-fullscreen", value)
  
      @property
      def size(self) -> Tuple[int, int]:
          """Tuple[int, int]: current window size.
  
          This property also support assignment::
  
              # Resize the window to 1000 x 1000
              window.size = 1000, 1000
          """
          return self._width, self._height
  
      @size.setter
      def size(self, value: Tuple[int, int]):
          self._tk.geometry("{}x{}".format(value[0], value[1]))
  
      @property
      def position(self) -> Tuple[int, int]:
          """Tuple[int, int]: The current window position.
  
          This property can also be set to move the window::
  
              # Move window to 100, 100
              window.position = 100, 100
          """
          _, x, y = self._tk.geometry().split("+")
          return int(x), int(y)
  
      @position.setter
      def position(self, value: Tuple[int, int]):
          self._tk.geometry("+{}+{}".format(value[0], value[1]))
  
      @property
      def cursor(self) -> bool:
          """bool: Should the mouse cursor be visible inside the window?
  
          This property can also be assigned to::
  
              # Disable cursor
              window.cursor = False
          """
          return self._cursor
  
      @cursor.setter
      def cursor(self, value: bool):
          if value is True:
              self._tk.config(cursor="arrow")
          else:
              self._tk.config(cursor="none")
  
          self._cursor = value
  
      @property
      def title(self) -> str:
          """str: Window title.
  
          This property can also be set::
  
              window.title = "New Title"
          """
          return self._title
  
      @title.setter
      def title(self, value: str):
          self._tk.title(value)
          self._title = value
  
      def swap_buffers(self) -> None:
          """Swap buffers, set viewport, trigger events and increment frame counter"""
          err = self._ctx.error
          if err != "GL_NO_ERROR":
              print(err)
  
          # Ensure we process events or tkinter will eventually stall.
          self._tk.update_idletasks()
          self._tk.update()
  
          self._gl_widget.tkSwapBuffers()
          self._frames += 1
  
      def _set_icon(self, icon_path: str) -> None:
          self._tk.iconphoto(False, tkinter.PhotoImage(file=icon_path))
  
      def tk_key_press(self, event: tkinter.Event) -> None:
          """Handle all queued key press events in tkinter dispatching events to standard methods"""
          self._key_event_func(event.keysym, self.keys.ACTION_PRESS, self._modifiers)
  
          self._handle_modifiers(event, True)
          if event.char:
              self._unicode_char_entered_func(event.char)
  
          if self._exit_key is not None and event.keysym == self._exit_key:
              self.close()
  
      def tk_key_release(self, event: tkinter.Event) -> None:
          """Handle all queued key release events in tkinter dispatching events to standard methods
  
          Args:
              event (tkinter.Event): The key release event
          """
          self._handle_modifiers(event, False)
          self._key_event_func(event.keysym, self.keys.ACTION_RELEASE, self._modifiers)
  
      def tk_mouse_motion(self, event: tkinter.Event) -> None:
          """Handle and translate tkinter mouse position events
  
          Args:
              event (tkinter.Event): The mouse motion event
          """
          x, y = event.x, event.y
          dx, dy = self._calc_mouse_delta(x, y)
  
          if self._mouse_buttons.any:
              self._mouse_drag_event_func(x, y, dx, dy)
          else:
              self._mouse_position_event_func(x, y, dx, dy)
  
      def tk_mouse_button_press(self, event: tkinter.Event) -> None:
          """Handle tkinter mouse press events.
  
          Args:
              event (tkinter.Event): The mouse button press event
          """
          self._handle_modifiers(event, True)
          button = self._mouse_button_map.get(event.num)
          if not button:
              return
  
          self._handle_mouse_button_state_change(button, True)
          self._mouse_press_event_func(event.x, event.y, button)
  
      def tk_mouse_button_release(self, event: tkinter.Event) -> None:
          """Handle tkinter mouse press events.
  
          Args:
              event (tkinter.Event): The mouse button release event
          """
          self._handle_modifiers(event, True)
          button = self._mouse_button_map.get(event.num)
          if not button:
              return
  
          self._handle_mouse_button_state_change(button, False)
          self._mouse_release_event_func(event.x, event.y, button)
  
      def tk_mouse_wheel(self, event: tkinter.Event) -> None:
          """Handle mouse wheel event.
  
          Args:
              event (tkinter.Event): The mouse wheel event
          """
          self._handle_modifiers(event, True)
          self._mouse_scroll_event_func(0, event.delta / 120.0)
  
      def _handle_modifiers(self, event: tkinter.Event, press: bool) -> None:
          """Update internal key modifiers
  
          Args:
              event (tkinter.Event): The key event
              press (bool): Press or release event
          """
          if event.keysym in ["Shift_L", "Shift_R"]:
              self._modifiers.shift = press
          elif event.keysym in ["Control_L", "Control_R"]:
              self._modifiers.ctrl = press
          elif event.keysym in ["Alt_L", "Alt_R"]:
              self._modifiers.alt = press
  
      def tk_resize(self, event) -> None:
          """tkinter specific window resize event.
          Forwards resize events to the configured resize function.
  
          Args:
              event (tkinter.Event): The resize event
          """
          self._width, self._height = event.width, event.height
          # TODO: How do we know the actual buffer size?
          self._buffer_width, self._buffer_height = event.width, event.height
  
          # Race condition when going fullscreen mode.
          # The moderngl context might not be created yet.
          if not self._ctx:
              return
  
          self.set_default_viewport()
          self._resize_func(event.width, event.height)
  
      def tk_close_window(self) -> None:
          """tkinter close window callback"""
          self._close_func()
          self._close = True
  
      def tk_map(self, event):
          self._iconify_func(False)
  
      def tk_unmap(self, event):
          self._iconify_func(True)
  
      def destroy(self) -> None:
          """Destroy logic for tkinter window."""
          self._tk.destroy()
  
  
  class ModernglTkWindow(OpenGLFrame):
      def __init__(self, *args, **kwargs):
          super().__init__(*args, **kwargs)
  
      def redraw(self):
          """pyopengltk's own render method."""
          pass
  
      def initgl(self):
          """pyopengltk's user code for initialization."""
          pass
  
      def tkResize(self, event):
          """Should never be called. Event overridden."""
          raise ValueError("tkResize should never be called. The event is overridden.")
  
      def tkMap(self, event):
          """Called when frame goes onto the screen"""
          # Only create context once
          # In a window like this we are not likely to lose the context
          # even when window is minimized.
          if not getattr(self, "_wid", None):
              super().tkMap(event)
  