Tryst::SDL::Viewport
An SDL3 rendering surface living inside a Tk frame.
app = Tryst::App.new(title: "game")
app.show
viewport = Tryst::SDL::Viewport.new(app, width: 640, height: 480)
Drawing goes through SDL, not Tk, so the frame is a hole in the widget tree that SDL paints into rather than something Tk renders. Keyboard input, though, stays TK'S: the embedded SDL window is not in SDL's own event loop and receives nothing, so key state comes from Tk bindings on the frame - see #key_down?.
ON MACOS THE SURFACE COVERS THE WHOLE WINDOW. Tk on Aqua gives a
native window to a toplevel and none to the frames inside it, so
SDL is handed the toplevel and paints over every other widget in
it, wherever the frame happens to sit. #covers_toplevel? says so
at runtime. A viewport meant to share a window with Tk widgets
needs its own toplevel there, or its overlays drawn in SDL.
Constructors
Builds the Tk frame, adopts its native window and puts a renderer in it.
parent is a widget path to build inside; nil means the root.
vsync ties presentation to the display refresh, which is what
anything animating wants and what an emulator pacing its own
frames does not.
Class methods
SDL's video subsystem MUST come up after Tk, never before. Bringing it up first aborts on macOS, where both want to own the NSApplication - and it aborts before any Crystal of yours runs, so it looks like the program never started. Viewport takes care of the order by initialising video here, once a live App has necessarily already initialised Tk.
Instance methods
Whether SDL is painting over the whole toplevel rather than just this frame. True on macOS - see the note on the class.
Tears down the renderer, the SDL window and the Tk frame, in that order. Idempotent.
Note this does NOT shut SDL down - that is process-wide and belongs to whatever owns the application. See Tryst::SDL.quit for the ordering constraint that comes with it.
Whether this window has keyboard input focus right now - false once the user switches to another application, another Space, or another window of this one.
A POLL rather than a Tk <Deactivate>/<Activate> binding, which is the obvious-looking alternative and does not work here: Tk only generates those from its own NSWindow notifications, and for a toplevel SDL has adopted <Activate> arrives while <Deactivate> never does. SDL_GetWindowFlags reads window-manager state SDL already tracks, so this needs no event loop of its own - which an adopted window has never been part of anyway.
X11 gives SDL a child window of the frame rather than the toplevel, and a child never receives X input focus, so this is false there regardless of what the user is looking at.
Whether a key is held right now, by lowercased Tk keysym - "left", "space", "a". For a game loop, which wants to ask rather than be told.
String#downcase allocates even for already-lowercase strings; in a game loop this runs once per key per frame, so avoid it.
The drawable size in real pixels, which is not the frame's size in Tk units on a scaled display.
Draws a frame: yields the renderer, then presents.
The presenting is the point. Nothing drawn appears until it happens, and forgetting it looks exactly like the renderer not working at all - a blank surface and no error anywhere.
Does NOT clear first. An incremental frame that redraws only what
changed is a perfectly good thing to want, so wiping the surface
is left to the caller and spelled r.clear.
Which renderer backend SDL chose - "metal", "opengl", "direct3d11" and so on. Worth logging when something draws wrong.