drawnui/drawnui.net/drawnui-opentk

Claude Code and agent skill by drawnui/drawnui.net

Trust tier 3
  • community
  • ext:[{"type":"plugin","description":"Custom plugin integration"}]
Source check pending

Checking that the SKILL.md file is still in the repository.

DrawnUI on the OpenTK desktop head (Windows/Linux, plus what macOS would need) — DrawnUiWindow apps, CanvasHost overlays over raw OpenGL, update/rendering modes, desktop input wiring, output-dir assets, window icon and DWM chrome niceties, Linux/WSL2 fixes, trimmed single-file publish, and using an OpenTK head as an instant visual test harness for shared DrawnUI code. Trigger on "drawnui opentk", "DrawnUiWindow", "CanvasHost", "drawnui desktop", "GL overlay drawnui".

Install

npx vskill@latest install drawnui/drawnui.net/drawnui-opentk

Semi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard

SKILL.md

DrawnUI OpenTK (desktop head)

Package DrawnUi.OpenTk (+ addon DrawnUi.OpenTk.Game for games: DrawnGame from the shared SharedGame sources; KeyboardManager is in the core package). Framework rules in drawnui skill, C# composition in drawnui-fluent, GL-state-restore contract detail in the drawnui skill's "OpenTK / Mixed GL+DrawnUI" section.

App csproj shape

xml
<OutputType>WinExe</OutputType> <!-- no console window on Windows -->
<TargetFramework>net10.0</TargetFramework>
<RuntimeIdentifiers>win-x64;linux-x64;linux-arm64</RuntimeIdentifiers>
<ApplicationIcon>icon.ico</ApplicationIcon> <!-- Explorer/taskbar -->
<ItemGroup>
<EmbeddedResource Include="icon.ico" /> <!-- title-bar icon, loaded at runtime -->
<Content Include="fonts\**"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></Content>
<Content Include="Images\**"><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory></Content>
</ItemGroup>
  • Linux publish needs SkiaSharp.NativeAssets.Linux.
  • Self-contained single-file Release: SelfContained + PublishTrimmed (TrimMode=full) + PublishSingleFile + IncludeNativeLibrariesForSelfExtract. Full trim cuts SkiaSharp/OpenTK reflection — root them: <TrimmerRootAssembly Include="OpenTK.Graphics" /> + OpenTK.Windowing.Desktop, SkiaSharp, HarfBuzzSharp.
  • Cross-link assets from a sibling project: <Content Include="..\Other\fonts\X.ttf" Link="fonts\X.ttf" CopyToOutputDirectory="PreserveNewest" />.

Startup

Super.UseDrawnUi().ConfigureFonts(fonts => fonts.AddFont("fonts/X.ttf", "Alias", FontWeight.Regular)).Build(); ONCE before creating any window/canvas. Fonts/assets resolve relative to the OUTPUT dir (no MAUI asset pipeline); Super.Screen.Density = 1. Namespaces: DrawnUi.OpenTk, DrawnUi.Draw, DrawnUi.Views — alias using Color = DrawnUi.Color; to dodge OpenTK's Color.

Entry pattern A — DrawnUiWindow (fully-drawn app/game)

csharp
var canvas = new Canvas { RenderingMode = RenderingModeType.Accelerated,
UpdateMode = UpdateModeType.Constant, /* Fill, BackgroundColor, Content */ };
var ns = new NativeWindowSettings {
ClientSize = new Vector2i(800, 600), Title = "My App",
API = ContextAPI.OpenGL, Profile = ContextProfile.Core,
APIVersion = OperatingSystem.IsLinux() ? new Version(3,3) : new Version(4,6),
WindowState = WindowState.Normal, Icon = LoadWindowIcon(),
};
using var window = new DrawnUiWindow(new GameWindowSettings(), ns, canvas);
window.Run();

DrawnUiWindow owns the Skia GPU surface, VSync, mouse/keyboard routing, centering, chrome, fullscreen (F11 toggle / ESC exit built in), and the no-white-flash reveal (window hidden until first SwapBuffers). Super.Init() runs automatically in its OnLoad; Super.MaxFps = primary monitor refresh rate. Overridables: PositionWindow(), ConfigureWindowChrome(hwnd) (Windows-only, called only there), RenderScene() (raw GL behind the canvas — base restores GL state before and ResetContext() after; end yours with GL.Finish()).

Fixed-proportion scaling on resize: RescalingCanvas { LogicalWidth = W, LogicalHeight = H } (Pong, src/Shared/Samples/Pong.Shared/Views/RescalingCanvas.cs) or a RescalingLayout inside the canvas (HelloOpenTk Pages/RescalingLayout.cs). Both are sample classes, not package API.

Entry pattern B — CanvasHost (overlay over your own GL loop)

Your GameWindow subclass owns rendering; DrawnUI composites as a transparent overlay (HUD, tools, dialogs).

  • Lifecycle: host.Initialize(wakeLoop?) in OnLoad (also call Super.Init() yourself + VSync = VSyncMode.On), host.Resize(w,h) in OnResize (+ GL.Viewport), host.ResetGrContext(); host.Render(); in OnRenderFrame, host.Dispose() in OnUnload.
  • Per-frame order: restore GL state Skia left dirty → draw your 3D scene → GL.Finish() → ResetGrContext() → host.Render() → SwapBuffers().
  • Overlay Canvas MUST be RenderingMode = AcceleratedRetained + BackgroundColor = Colors.Transparent — otherwise Skia clears your scene. Also required for SkiaBackdrop/glass to have pixels to sample.
  • CanvasHost does NOT auto-wire input — forward OnMouseDown/Move/Up, OnTextInput, OnKeyDown to host.Gestures / host.Input.
  • White-flash fix is manual here: StartVisible=false + show after first SwapBuffers.
  • Event-driven wake: Initialize(GLFW.PostEmptyEvent) so Super.OnFrame can wake a WaitEvents loop.

Update modes (power profile)

  • Rule: Constant ONLY for dynamic games (something moves every frame anyway); every other app/tool gets Dynamic + UpdateFrequency = 0 — animations still run at full rate while active, an idle window stops rendering.
  • UpdateMode.Constant — VSync on, renders every frame. Dynamic games. Where the driver accepts VSync but does not wait for it (WSLg software GL), the window notices it on the first 60 frames (median under half the refresh period) and paces frames itself on a refresh grid.
  • UpdateMode.Dynamic — VSync off, renders only when dirty, frames on a refresh grid (next slot = last slot + period, animations step with the slot), sleeps via GLFW.WaitEventsTimeout. Pair with GameWindowSettings { UpdateFrequency = 0 } for low-power tools/editors/launchers.
  • Refresh period: GLFW reports whole hertz (59 for a 59.95 Hz panel, pacing 16.95 ms behind the display); on Windows DrawnUiWindow takes DWM's exact composition rate. Super.MaxFps = the rounded rate.

Input

  • DrawnUiWindow auto-routes mouse (all buttons + wheel + leaving the window, which ends hover; a CanvasHost app calls host.Gestures.OnMouseLeave() itself) + text input to the canvas (HandleDesktopPointerDown/Move/Up, HandleDesktopTextInput); editor keys (Backspace / Delete / Enter / Left / Right / Home / End, Shift selects, Ctrl+A) built in, Tab walks the Tab stops (see Keyboard navigation). A CanvasHost app's host.Input.OnKeyDown has the same editor keys but types Tab as 4 spaces. Adding game keys: override OnKeyDown, call base.OnKeyDown(e) FIRST, then OpenTkKeyMapper.Map(e.Key) → KeyboardManager.KeyboardPressed(...) (release in OnKeyUp).
  • Custom controls layered behind a SkiaEditor: return null from ProcessGestures on Up when you didn't capture on Down, or you steal the editor's focus.

Window niceties

  • Title-bar icon: embed icon.ico as EmbeddedResource, at startup decode with SKBitmap.Decode, resize to 32×32, swap BGRA→RGBA ((p[i], p[i+2]) = (p[i+2], p[i]) per pixel), wrap in OpenTK.Windowing.Common.Input.Image → WindowIcon → NativeWindowSettings.Icon. (ApplicationIcon csproj property covers only Explorer/taskbar.)
  • DWM chrome (override ConfigureWindowChrome(hwnd), helper WindowChrome): SetCaptionColor(hwnd,r,g,b) / SetBorderColor (Win11+, caption text auto black/white by luminance), SetDarkMode (Win10 20H1+), SetRoundedCorners (Win11+).
  • System menu gets a "Fullscreen" item (Windows), and screen readers work with no app code: UI Automation on Windows (Narrator, NVDA), AT-SPI2 on Linux (Orca, through the Tmds.DBus.Protocol package; it connects only once assistive technology turns the accessibility bus on). CanvasHost apps get neither.
  • Keyboard navigation (DrawnUiWindow): Tab / Shift+Tab walk the Tab stops, also out of a drawn editor (no tab characters); Enter / Space / Escape / arrows / Home / End / PageUp / PageDown go to the focused node only while it has keyboard focus and no editor has the caret, so a game's own keys reach its OnKeyDown override untouched; the canvas draws the focus ring. F11 / Escape-out-of-fullscreen first.
  • Checking Orca in WSL (WSLg): inside dbus-run-session, start /usr/libexec/at-spi-bus-launcher --launch-immediately, the app, then orca --replace --debug-file=<file> and read its SPEECH OUTPUT: lines; pyatspi reads the tree and can grab focus, do actions and set values. Afterwards stop a leftover speech-dispatcher and remove $XDG_RUNTIME_DIR/speech-dispatcher.

Assets

Source strings ("Images/x.gif", "Lottie/x.json") resolve relative to the output dir. #1 gotcha: a bare <Content Include="x.png" /> in a WinExe SDK project is NOT copied — build succeeds, runtime silently renders nothing. Always CopyToOutputDirectory="PreserveNewest" and verify with ls bin/Debug/net10.0/Images. Keep shared code head-agnostic: same logical paths, per-head asset roots (OpenTK = output dir, MAUI = Resources/Raw, Web = wwwroot).

Linux / WSL2

  • apt install libglfw3 libopenal1 libgl1 libgles2-mesa; run with DISPLAY=:0 on WSLg.
  • EGL "Arguments are inconsistent" → bundled libglfw.so.3 is an EGL build; symlink system GLX libglfw3 over it.
  • GLXBadFBConfig → request OpenGL 3.3 on Linux (Mesa D3D12 lacks 4.6).
  • D3D12 "Removing Device" + segfault on WSLg → keep WindowState = WindowState.Normal.
  • Uncapped FPS (Mesa / WSLg ignores the swap interval) → DrawnUiWindow paces both update modes on a refresh grid (Constant: after it measures that vsync is ignored; Dynamic: always). A custom GameWindow / CanvasHost app must pace itself (UpdateFrequency or its own timer).
  • Run the Linux build from Windows (no .NET SDK in WSL needed), verified 2026-09-25: reference SkiaSharp.NativeAssets.Linux, dotnet publish -c Release -r linux-x64 --self-contained -o out\linux on Windows, copy into the WSL file system + chmod +x (the bundled GLFW 3.4 ran fine on WSLg/Ubuntu 22.04, trimmed single-file included; symlink the system libglfw.so.3 only if the EGL error appears), then wsl -e bash -c "cd ~/myapp && DISPLAY=:0 ./MyApp". Traps: nohup ./MyApp & inside wsl -e bash -c is killed when the command returns, so run the whole wsl command as a background job with the app in its foreground; the window appears as a Windows window titled MyApp (Ubuntu-...) (a [WARN:COPY MODE] prefix = WSLg copy presentation, still runs); Windows-simulated mouse/wheel input reaches it; default NAT networking hides Windows localhost services; LocalApplicationData = ~/.local/share. The WSLg window rect includes a ~35 px shadow, so scripted edge drags must target the visible border. Docs: docs/articles/opentk/faq.md.
  • Resize / wheel semantics per OS (DrawnUiWindow, 2026-09-25): Windows/macOS block the render loop in the OS modal size loop, so DrawnUiWindow renders from OnResize there (live redraw while dragging an edge); Linux keeps looping during resize, so it only repaints via the normal loop. OnRefresh (expose/uncover) renders on every OS. The mouse wheel goes OnMouseWheel → DesktopGestureHandler.OnMouseWheel (GLFW notch × 120) → Canvas.HandleDesktopWheel; CanvasHost apps forward OnMouseWheel themselves.

macOS (unbuilt as of 2026-08-27 — investigated, not yet run)

Nothing in the stack blocks it; nobody has shipped it. What was verified:

  • GLFW natives exist — opentk.redist.glfw 3.4.0.44 ships runtimes/osx-arm64 and runtimes/osx-x64

with libglfw.3.dylib. OpenTK 4 windowing is GLFW-based, hence cross-platform.

  • Skia natives exist — use SkiaSharp.NativeAssets.macOS, the counterpart of the

SkiaSharp.NativeAssets.Linux reference the samples already carry.

  • Windows-only code is guarded — DWM chrome, the WndProc hook and WindowsUiaProvider all sit

inside if (OperatingSystem.IsWindows()) in DrawnUiWindow.cs. P/Invokes don't bind until called, so macOS won't trip on them. Expect a plainer window and no accessibility there.

  • RuntimeIdentifiers in the samples lack osx-arm64;osx-x64 — add them.

The context settings are the trap. The samples request GL 4.6 on anything that isn't Linux:

csharp
APIVersion = OperatingSystem.IsLinux() ? new Version(3,3) : new Version(4,6), // FAILS on macOS

macOS caps at OpenGL 4.1, so 4.6 fails context creation and the window never appears — the usual reason people conclude "OpenTK doesn't run on Mac". It does. Use:

This is the start of the file. Read the full SKILL.md on GitHub.

Security

  • Security scanPassed, 90 of 100Pattern checks on the skill's files.
  • Model reviewPassed

Quality evaluation

  • Task evaluationNot runNo evaluation yet. Run one in Skill Studio.

Source

Popularity

7-day trend score
0

Not refreshed yet.

Extension points

  • pluginCustom plugin integration

Works with

Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.

  • Claude Code
  • Cursor
  • GitHub Copilot
  • Windsurf
  • Codex
  • Gemini CLI
  • Cline
  • Amp
  • Roo Code
  • Goose
  • Continue
  • Kilo Code
  • Junie
  • Trae
  • OpenHands
  • Qwen Code
  • AI Agent
  • Tabnine Chat
  • Sourcegraph Cody
  • Amazon Q
  • Firebase Genkit
  • Sweep
  • Devin
  • Phind
  • Mentat
  • Claude Desktop
  • Aider
  • GPT Pilot
  • Plandex
  • bolt.new
  • v0
  • Replit Agent
  • Anse
  • TypingMind
  • BetterChatGPT
  • LibreChat
  • Warp AI
  • Pieces
  • Fig AI
Embed badge
vskill VERIFIED badge
[![vskill VERIFIED](https://verified-skill.com/api/v1/skills/drawnui/drawnui.net/drawnui-opentk/badge)](https://verified-skill.com/skills/drawnui/drawnui.net/drawnui-opentk)

Something wrong with this skill or its page? Tell us.