drawnui/drawnui.net/drawnui-opentk
Claude Code and agent skill by drawnui/drawnui.net
- community
- ext:[{"type":"plugin","description":"Custom plugin integration"}]
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-opentkSemi-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
- 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)
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?)inOnLoad(also callSuper.Init()yourself +VSync = VSyncMode.On),host.Resize(w,h)inOnResize(+GL.Viewport),host.ResetGrContext(); host.Render();inOnRenderFrame,host.Dispose()inOnUnload. - 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 forSkiaBackdrop/glass to have pixels to sample. CanvasHostdoes NOT auto-wire input — forwardOnMouseDown/Move/Up,OnTextInput,OnKeyDowntohost.Gestures/host.Input.- White-flash fix is manual here:
StartVisible=false+ show after firstSwapBuffers. - Event-driven wake:
Initialize(GLFW.PostEmptyEvent)soSuper.OnFramecan wake aWaitEventsloop.
Update modes (power profile)
- Rule:
ConstantONLY for dynamic games (something moves every frame anyway); every other app/tool getsDynamic+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 viaGLFW.WaitEventsTimeout. Pair withGameWindowSettings { 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
DrawnUiWindowtakes DWM's exact composition rate.Super.MaxFps= the rounded rate.
Input
DrawnUiWindowauto-routes mouse (all buttons + wheel + leaving the window, which ends hover; aCanvasHostapp callshost.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). ACanvasHostapp'shost.Input.OnKeyDownhas the same editor keys but types Tab as 4 spaces. Adding game keys: overrideOnKeyDown, callbase.OnKeyDown(e)FIRST, thenOpenTkKeyMapper.Map(e.Key)→KeyboardManager.KeyboardPressed(...)(release inOnKeyUp).- Custom controls layered behind a
SkiaEditor: returnnullfromProcessGestureson Up when you didn't capture on Down, or you steal the editor's focus.
Window niceties
- Title-bar icon: embed
icon.icoasEmbeddedResource, at startup decode withSKBitmap.Decode, resize to 32×32, swap BGRA→RGBA ((p[i], p[i+2]) = (p[i+2], p[i])per pixel), wrap inOpenTK.Windowing.Common.Input.Image→WindowIcon→NativeWindowSettings.Icon. (ApplicationIconcsproj property covers only Explorer/taskbar.) - DWM chrome (override
ConfigureWindowChrome(hwnd), helperWindowChrome):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.Protocolpackage; it connects only once assistive technology turns the accessibility bus on).CanvasHostapps 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 itsOnKeyDownoverride 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, thenorca --replace --debug-file=<file>and read itsSPEECH OUTPUT:lines;pyatspireads the tree and can grab focus, do actions and set values. Afterwards stop a leftoverspeech-dispatcherand 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 withDISPLAY=:0on WSLg.- EGL "Arguments are inconsistent" → bundled
libglfw.so.3is an EGL build; symlink system GLXlibglfw3over 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) →
DrawnUiWindowpaces both update modes on a refresh grid (Constant: after it measures that vsync is ignored; Dynamic: always). A customGameWindow/CanvasHostapp must pace itself (UpdateFrequencyor 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\linuxon 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 systemlibglfw.so.3only if the EGL error appears), thenwsl -e bash -c "cd ~/myapp && DISPLAY=:0 ./MyApp". Traps:nohup ./MyApp &insidewsl -e bash -cis killed when the command returns, so run the wholewslcommand as a background job with the app in its foreground; the window appears as a Windows window titledMyApp (Ubuntu-...)(a[WARN:COPY MODE]prefix = WSLg copy presentation, still runs); Windows-simulated mouse/wheel input reaches it; default NAT networking hides Windowslocalhostservices;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
DrawnUiWindowrenders fromOnResizethere (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 goesOnMouseWheel→DesktopGestureHandler.OnMouseWheel(GLFW notch × 120) →Canvas.HandleDesktopWheel;CanvasHostapps forwardOnMouseWheelthemselves.
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.glfw3.4.0.44 shipsruntimes/osx-arm64andruntimes/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
WindowsUiaProviderall 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.
RuntimeIdentifiersin the samples lackosx-arm64;osx-x64— add them.
The context settings are the trap. The samples request GL 4.6 on anything that isn't Linux:
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
- SKILL.md in the repositoryCheckingdocs/skills/drawnui-opentk/SKILL.md
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
[](https://verified-skill.com/skills/drawnui/drawnui.net/drawnui-opentk)