I have a very specific use case: I need a window (layered + topmost), where I can draw and all input (mouse and keyboard) is ignored, so the user can still interact with other windows. I already have a complete application working perfectly, with all the features I need.
Since it is already done, I can improve a few things that are bothering me, the main problem is called GDI. I'm using it to draw on the screen and not only is slow (solved the flashing problem with a backbuffer), it's really troublesome to get some transparency and anti-aliasing. For this reason I thought about using OpenGL to do the drawing (still relying on GDI to draw text). Let's make clear that I never used OpenGL before.
Here is the code to create the window the way I want, letting all input pass through and drawing a simple rect on the center of the screen.
import winim
const
ws_ex_layered = 0x80000
ws_ex_topmost = 0x8
proc draw_scene(hdc: HDC, w: int, h: int) =
let squareSize = 100
let left = int32((w - squareSize) div 2)
let top = int32((h - squareSize) div 2)
let right = int32(left + squareSize)
let bottom = int32(top + squareSize)
let blueBrush = CreateSolidBrush(RGB(0, 0, 255))
let oldBrush = SelectObject(hdc, blueBrush)
Rectangle(hdc, left, top, right, bottom)
SelectObject(hdc, oldBrush)
DeleteObject(blueBrush)
proc wnd_proc(
hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM
): LRESULT {.stdcall.} =
case uMsg
of WM_PAINT:
var ps: PAINTSTRUCT
let hdc = BeginPaint(hWnd, addr ps)
let w = GetSystemMetrics(SM_CXSCREEN)
let h = GetSystemMetrics(SM_CYSCREEN)
draw_scene(hdc, w, h)
EndPaint(hWnd, addr ps)
return 0
of WM_DESTROY:
PostQuitMessage(0)
return 0
else:
return DefWindowProc(hWnd, uMsg, wParam, lParam)
proc win_main() =
var wc: WNDCLASS
wc.style = CS_HREDRAW or CS_VREDRAW or CS_OWNDC
wc.lpfnWndProc = wnd_proc
wc.hInstance = GetModuleHandle(nil)
wc.hCursor = LoadCursor(0, IDC_ARROW)
wc.hbrBackground = CreateSolidBrush(RGB(0, 0, 0))
wc.lpszClassName = "Overlay"
if RegisterClass(addr wc) == 0:
MessageBox(0, "faile to register class", "Error", MB_OK)
return
let screen_w = GetSystemMetrics(SM_CXSCREEN)
let screen_h = GetSystemMetrics(SM_CYSCREEN)
let hwnd = CreateWindowEx(
ws_ex_topmost or ws_ex_layered,
wc.lpszClassName,
"",
WS_POPUP,
0,
0,
screen_w,
screen_h,
0,
0,
wc.hInstance,
nil,
)
discard SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 255, LWA_COLORKEY or LWA_ALPHA)
ShowWindow(hwnd, SW_SHOW)
UpdateWindow(hwnd)
var msg: MSG
while GetMessage(addr msg, 0, 0, 0) > 0:
TranslateMessage(addr msg)
DispatchMessage(addr msg)
when isMainModule:
win_main()
Does anyone know how to draw this rectangle with OpenGL, then copy that texture into a bitmap so I can draw using GDI on the screen? This approach may sound strange, because it is. But as I said before I never used OpenGL and this seems to be the only way I can draw on a window layered + topmost. If anyone knows an easier way please tell me.
If you are curious about what I'm doing, here is a screenshot. It's an angle meter overlay, I can add and remove lines and get the angle between them. It’s crucial that I can still interact with the windows underneath.
Here is my old OpenGL code. It creates new frame buffer object with newFrameBufferObj proc, bind it and draw scene. Then copy rendered image on the frame buffer object to memory (glReadPixels) and save it to PNG file. https://colab.research.google.com/drive/1J0B0qVvovrJZJI1OU75jIMUjWnymi_6G
Rendering everything with OpenGL probably faster than copying OpenGL rendered image to main memory and mixing GDI's drawing APIs. If you like directly calling windows API, old example minimal code to use OpenGL on windows (no error checks): https://github.com/demotomohiro/nim-4k-intro-sample/blob/master/src/minimumGLTriangle.nim
You can learn OpenGL here: https://www.khronos.org/opengl/wiki/Getting_Started
Thank you guys. I do need to take some time to study opengl, but while taking a look at boxy I found pixie, and even though still using the cpu, it gives me enough freedom to draw what I need the way I want.
The main reason I wanted to go with opengl is because I would be able to draw with anti aliasing and transparency. The app is not really on real time, it only refreshes the screen when given an input, I will try pixie first and if performance does become a problem I will have no choice but dive deep into opengl.