public static void run(String title, int width, int height, Consumer<ApplicationContext> applicationCode) {
    Objects.requireNonNull(title);
    if (width < 0 || height < 0) {
      throw new IllegalArgumentException("invalid size");
    }
    Objects.requireNonNull(applicationCode);
    
    final BufferedImage buffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    
    class Canvas extends JComponent {
      private static final long serialVersionUID = 1360301844144298605L;

      Canvas() {
        setOpaque(true);
        setFocusable(true);
      }
      @Override
      public Dimension getPreferredSize() {
        return new Dimension(width, height); 
      }
      
      @Override
      protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(buffer, 0, 0, null);
      }
    }
    
    ArrayBlockingQueue<KeyboardEvent> keyboardEventQueue =
        new ArrayBlockingQueue<>(20);
    
    Canvas canvas = new Canvas();
    canvas.addKeyListener(new KeyAdapter() {
      @Override
      public void keyPressed(KeyEvent e) {
        keyboardEventQueue.offer(new KeyboardEvent(KeyboardKey.key(e.getKeyCode()), e.getModifiersEx()));
      }
    });
    
    Thread thread = new ApplicationContext() {
      final Thread applicationThread;
      {
        final ApplicationContext context = this;
        applicationThread = new Thread(() -> {
          applicationCode.accept(context);
        });
      }
      
      private void checkThread() {
        if (Thread.currentThread() != applicationThread) {
          throw new IllegalStateException("try to do something with another thread than the application thread");
        }
      }
      
      @Override
      public KeyboardEvent pollKeyboard() {
        checkThread();
        return keyboardEventQueue.poll();
      }
      
      @Override
      public KeyboardEvent waitKeyboard() {
        checkThread();
        try {
          return keyboardEventQueue.take();
        } catch (InterruptedException e) {
          Thread.currentThread().interrupt();
          return null;
        }
      }
      
      @Override
      public void render(Consumer<Graphics2D> rendererCode) {
        checkThread();
        Graphics2D graphics = buffer.createGraphics();
        try {
          rendererCode.accept(graphics);
        } finally {
          graphics.dispose();
        }
        
        canvas.repaint();
      }
    }.applicationThread;
    thread.setName("Application-Thread");
    
    try {
      EventQueue.invokeAndWait(() -> {
        JPanel panel = new JPanel();
        panel.add(canvas);

        JFrame frame = new JFrame(title);
        frame.setIgnoreRepaint(true);
        frame.setResizable(false);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setContentPane(panel);
        frame.setLocationRelativeTo(null);
        frame.pack();
        frame.setVisible(true);

        EventQueue.invokeLater(thread::start);
      });
    } catch (InvocationTargetException e) {
      Throwable cause = e.getCause();
      if (cause instanceof RuntimeException) {
        throw (RuntimeException)cause;
      }
      if (cause instanceof Error) {
        throw (Error)cause;
      }
      throw new UndeclaredThrowableException((cause == null)? e: cause);
    } catch(InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }