How to draw in Win32 tab controls with OpenGL?


// Win32-Splitter.cpp : Defines the entry point for the application.
//
#include "framework.h"
#include "Win32-CommandRichEdit-Splitter.h"
#include <windowsx.h>
#include <cstdio>
#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <richedit.h>
#include <commctrl.h>
#include <GL/glew.h>
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glu32.lib")
#define MAX_LOADSTRING 100
// Additional control IDs for the child windows
#define ID_TOP       1001
#define ID_SPLITTER  1002
#define ID_BOTTOM    1003
#define ID_TAB1 1004
#define ID_TAB2 1005
// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name
// Global handles for our panes and splitter
HWND hWndTop = NULL;
HWND hWndSplitter = NULL;
HWND hWndBottom = NULL;
HWND hWndTab1 = NULL;    // Content window for Tab 1
HWND hWndTab2 = NULL;    // Content window for Tab 2
HBRUSH hbrTab1 = NULL;   // Background brush for Tab 1
HBRUSH hbrTab2 = NULL;   // Background brush for Tab 2
HDC hdcTab1 = NULL;    // Device context for Tab 1
HGLRC hglrcTab1 = NULL; // OpenGL context for Tab 1
HDC hdcTab2 = NULL;    // Device context for Tab 2
HGLRC hglrcTab2 = NULL; // OpenGL context for Tab 2
bool glewInitialized = false; // Track GLEW initialization
// Global state for the splitter
int g_iSplitterPos = -1;    // y-coordinate of the splitter (top edge)
const int splitterHeight = 3; // Height of the splitter (in pixels)
bool g_bDragging = false;   // Are we currently dragging the splitter?
// Forward declarations of functions included in this code module:
ATOM                MyRegisterClass(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
// Forward declaration for the splitter window procedure.
LRESULT CALLBACK    SplitterWndProc(HWND, UINT, WPARAM, LPARAM);
// Helper function to check for, log, and clear OpenGL errors.
void CheckGLErrors(const char* context)
{
    GLenum error1 = glGetError();
    GLenum error;
    // Retrieve and print any errors until no errors are reported.
    while ((error = glGetError()) != GL_NO_ERROR)
    {
        // gluErrorString gives a human-readable string for an error code.
        const char* errorString = reinterpret_cast<const char*>(gluErrorString(error));
        if (!errorString)
        {
            errorString = "Unknown error";
        }
        char msg[256];
        // Format your error message. Customize this if needed.
        sprintf_s(msg, sizeof(msg), "OpenGL error after %s: 0x%X: %s\n", context, error, errorString);
        // Output the error string. You might also log it to a file or console.
        OutputDebugStringA(msg);
    }
}
//
//  FUNCTION: wWinMain
//
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    // Initialize global strings.
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WIN32COMMANDRICHEDITSPLITTER, szWindowClass, MAX_LOADSTRING);
    INITCOMMONCONTROLSEX icex;
    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC = ICC_TAB_CLASSES; // Initialize TabControl classes
    InitCommonControlsEx(&icex);
    // Register the main window class.
    MyRegisterClass(hInstance);
    // Register our custom splitter window class.
    WNDCLASS wc = { 0 };
    wc.lpfnWndProc = SplitterWndProc;
    wc.hInstance = hInstance;
    wc.lpszClassName = L"SplitterClass";
    // Set the default cursor (will be used to change to IDC_SIZENS in the WM_SETCURSOR message)
    wc.hCursor = LoadCursor(NULL, IDC_SIZENS);
    RegisterClass(&wc);
    // Perform application initialization.
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32COMMANDRICHEDITSPLITTER));
    MSG msg;
    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}
//
//  FUNCTION: MyRegisterClass()
//
//  PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDC_WIN32COMMANDRICHEDITSPLITTER));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32COMMANDRICHEDITSPLITTER);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcex);
}
//
//   FUNCTION: InitInstance(HINSTANCE, int)
//
//   PURPOSE: Saves instance handle and creates main window
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable
    HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}
//
//  FUNCTION: SplitterWndProc
//
//  PURPOSE: Window procedure for the splitter child window. This window paints
//           itself black and handles mouse events so that the user can drag it.
//
LRESULT CALLBACK SplitterWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_SETCURSOR:
        // Change the cursor to the vertical sizing icon when over the splitter.
        SetCursor(LoadCursor(NULL, IDC_SIZENS));
        return TRUE;
    case WM_LBUTTONDOWN:
        SetCapture(hWnd);
        g_bDragging = true;
        return 0;
    case WM_MOUSEMOVE:
        if (g_bDragging)
        {
            // Get the current mouse position relative to the parent.
            POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
            ClientToScreen(hWnd, &pt);
            ScreenToClient(GetParent(hWnd), &pt);
            // Send a custom message (WM_USER+1) to the parent window with the new Y position.
            SendMessage(GetParent(hWnd), WM_USER + 1, 0, pt.y);
        }
        return 0;
    case WM_LBUTTONUP:
        if (g_bDragging)
        {
            g_bDragging = false;
            ReleaseCapture();
        }
        return 0;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        RECT rc;
        GetClientRect(hWnd, &rc);
        FillRect(hdc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
        EndPaint(hWnd, &ps);
    }
    return 0;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
}
void RenderTab(HWND hWndTab, HDC hdc, HGLRC hglrc, COLORREF bgColor)
{
    // Activate the OpenGL context
    if (!wglMakeCurrent(hdc, hglrc)) {
        MessageBox(NULL, L"wglMakeCurrent failed", L"Error", MB_OK | MB_ICONERROR);
        return;
    }
    
    // Get the window's dimensions
    RECT rc;
    GetClientRect(hWndTab, &rc);
    int width = rc.right - rc.left;
    int height = rc.bottom - rc.top;
    // Set the viewport to match the window size
    glViewport(0, 0, width, height);
    // Set up a simple 2D orthographic projection
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); // Maps coordinates from -1 to 1
    // Set up the modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    // Disable depth testing (since this is 2D drawing)
    glDisable(GL_DEPTH_TEST);
    // Clear the screen with the specified background color
    glClearColor(GetRValue(bgColor) / 255.0f, GetGValue(bgColor) / 255.0f, GetBValue(bgColor) / 255.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    CheckGLErrors("after wglMakeCurrent");
    // Draw a simple triangle
    glBegin(GL_TRIANGLES);
    glColor3f(1.0f, 0.0f, 0.0f); glVertex2f(0.0f, 0.5f);  // Red top vertex
    glColor3f(0.0f, 1.0f, 0.0f); glVertex2f(-0.5f, -0.5f); // Green bottom-left
    glColor3f(0.0f, 0.0f, 1.0f); glVertex2f(0.5f, -0.5f);  // Blue bottom-right
    glEnd();
    // Swap buffers to display the result
    SwapBuffers(hdc);
    // Deactivate the context
    wglMakeCurrent(NULL, NULL);
}
//
//  FUNCTION: WndProc
//
//  PURPOSE: Processes messages for the main window. In addition to the default
//           commands, we create and manage the two panes and the splitter.
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
    {
        // Create the top pane as a TabControl
        hWndTop = CreateWindowEx(0, WC_TABCONTROL, NULL,
            WS_CHILD | WS_VISIBLE,
            0, 0, 100, 100,
            hWnd, (HMENU)ID_TOP, hInst, NULL);
        // Add tabs to the TabControl
        TCITEM tci = { 0 };
        tci.mask = TCIF_TEXT;
        WCHAR tab1Text[] = L"Tab 1";
        tci.pszText = tab1Text;
        TabCtrl_InsertItem(hWndTop, 0, &tci);
        WCHAR tab2Text[] = L"Tab 2";
        tci.pszText = tab2Text;
        TabCtrl_InsertItem(hWndTop, 1, &tci);
        // Create OpenGL windows for each tab (use a generic window class)
        hWndTab1 = CreateWindowEx(0, L"STATIC", NULL,
            WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
            0, 0, 100, 100,
            hWnd, (HMENU)ID_TAB1, hInst, NULL);
        hWndTab2 = CreateWindowEx(0, L"STATIC", NULL,
            WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // Initially hidden
            0, 0, 100, 100,
            hWnd, (HMENU)ID_TAB2, hInst, NULL);
        // Set up OpenGL for Tab 1
        hdcTab1 = GetDC(hWndTab1);
        PIXELFORMATDESCRIPTOR pfd = { sizeof(pfd), 1, PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
                                      PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 24, 8, 0, PFD_MAIN_PLANE, 0, 0, 0, 0 };
        int pixelFormat = ChoosePixelFormat(hdcTab1, &pfd);
        SetPixelFormat(hdcTab1, pixelFormat, &pfd);
        hglrcTab1 = wglCreateContext(hdcTab1);
        wglMakeCurrent(hdcTab1, hglrcTab1);
        // Initialize GLEW (only once)
        if (!glewInitialized)
        {
            if (glewInit() != GLEW_OK)
            {
                MessageBox(hWnd, L"Failed to initialize GLEW", L"Error", MB_OK | MB_ICONERROR);
                DestroyWindow(hWnd);
                return -1;
            }
            glewInitialized = true;
        }
        // Set up OpenGL for Tab 2
        hdcTab2 = GetDC(hWndTab2);
        pixelFormat = ChoosePixelFormat(hdcTab2, &pfd);
        SetPixelFormat(hdcTab2, pixelFormat, &pfd);
        hglrcTab2 = wglCreateContext(hdcTab2);
        // Share resources between contexts (optional, for shared textures/buffers)
        wglShareLists(hglrcTab1, hglrcTab2);
        // Deactivate context for now
        wglMakeCurrent(NULL, NULL);
        // Create splitter and bottom pane (unchanged)
        hWndSplitter = CreateWindowEx(0, L"SplitterClass", NULL,
            WS_CHILD | WS_VISIBLE,
            0, 0, 100, splitterHeight,
            hWnd, (HMENU)ID_SPLITTER, hInst, NULL);
        LoadLibrary(L"riched20.dll");
        hWndBottom = CreateWindowEx(WS_EX_CLIENTEDGE, L"RichEdit20W", NULL,
            WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_AUTOVSCROLL,
            0, 0, 100, 100,
            hWnd, (HMENU)ID_BOTTOM, hInst, NULL);
        SendMessage(hWndBottom, EM_SETBKGNDCOLOR, 0, (LPARAM)RGB(144, 238, 144));
        SendMessage(hWndBottom, WM_SETTEXT, 0, (LPARAM)L"This is the bottom pane rich edit control.");
    }
    break;
    case WM_SIZE:
    {
        RECT rcClient;
        GetClientRect(hWnd, &rcClient);
        int clientWidth = rcClient.right;
        int clientHeight = rcClient.bottom;
        if (g_iSplitterPos == -1)
        {
            int bottomPaneHeight = (int)(clientHeight * 0.18);
            g_iSplitterPos = clientHeight - bottomPaneHeight - splitterHeight;
            if (g_iSplitterPos < 0)
                g_iSplitterPos = 0;
        }
        // Resize the top pane (TabControl)
        MoveWindow(hWndTop, 0, 0, clientWidth, g_iSplitterPos, TRUE);
        // Get the TabControl's display area
        RECT rcDisplay;
        GetClientRect(hWndTop, &rcDisplay);
        TabCtrl_AdjustRect(hWndTop, FALSE, &rcDisplay);
        MapWindowPoints(hWndTop, hWnd, (LPPOINT)&rcDisplay, 2);
        // Resize and reposition tab content windows
        MoveWindow(hWndTab1, rcDisplay.left, rcDisplay.top,
            rcDisplay.right - rcDisplay.left, rcDisplay.bottom - rcDisplay.top, TRUE);
        MoveWindow(hWndTab2, rcDisplay.left, rcDisplay.top,
            rcDisplay.right - rcDisplay.left, rcDisplay.bottom - rcDisplay.top, TRUE);
        // Resize the splitter and bottom pane
        MoveWindow(hWndSplitter, 0, g_iSplitterPos, clientWidth, splitterHeight, TRUE);
        MoveWindow(hWndBottom, 0, g_iSplitterPos + splitterHeight, clientWidth,
            clientHeight - (g_iSplitterPos + splitterHeight), TRUE);
    }
    break;
    case WM_NOTIFY:
    {
        LPNMHDR pnmhdr = (LPNMHDR)lParam;
        if (pnmhdr->hwndFrom == hWndTop && pnmhdr->code == TCN_SELCHANGE)
        {
            int iSel = TabCtrl_GetCurSel(hWndTop);
            if (iSel == 0)
            {
                ShowWindow(hWndTab1, SW_SHOW);
                ShowWindow(hWndTab2, SW_HIDE);
                InvalidateRect(hWndTab1, NULL, TRUE); // Trigger repaint
            }
            else if (iSel == 1)
            {
                ShowWindow(hWndTab1, SW_HIDE);
                ShowWindow(hWndTab2, SW_SHOW);
                InvalidateRect(hWndTab2, NULL, TRUE); // Trigger repaint
            }
        }
    }
    break;
    // Custom message from the splitter (WM_USER+1) with a new Y coordinate.
    case WM_USER + 1:
    {
        int newY = (int)lParam;
        RECT rc;
        GetClientRect(hWnd, &rc);
        int clientHeight = rc.bottom;
        // Enforce minimum sizes for the panes.
        const int minTop = 50;
        const int minBottom = 50;
        if (newY < minTop)
            newY = minTop;
        if (newY > clientHeight - splitterHeight - minBottom)
            newY = clientHeight - splitterHeight - minBottom;
        g_iSplitterPos = newY;
        // Reposition the child windows.
        MoveWindow(hWndTop, 0, 0, rc.right, g_iSplitterPos, TRUE);
        MoveWindow(hWndSplitter, 0, g_iSplitterPos, rc.right, splitterHeight, TRUE);
        MoveWindow(hWndBottom, 0, g_iSplitterPos + splitterHeight, rc.right,
            clientHeight - (g_iSplitterPos + splitterHeight), TRUE);
    }
    break;
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        // Parse the menu selections:
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        // Paint the active tab's OpenGL content
        int iSel = TabCtrl_GetCurSel(hWndTop);
        if (iSel == 0 && hWndTab1 && hdcTab1 && hglrcTab1)
        {
            BeginPaint(hWndTab1, &ps);
            RenderTab(hWndTab1, hdcTab1, hglrcTab1, RGB(255, 182, 193)); // Light red
            EndPaint(hWndTab1, &ps);
        }
        else if (iSel == 1 && hWndTab2 && hdcTab2 && hglrcTab2)
        {
            BeginPaint(hWndTab2, &ps);
            RenderTab(hWndTab2, hdcTab2, hglrcTab2, RGB(255, 255, 224)); // Light yellow
            EndPaint(hWndTab2, &ps);
        }
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        if (hglrcTab1) wglDeleteContext(hglrcTab1);
        if (hdcTab1) ReleaseDC(hWndTab1, hdcTab1);
        if (hglrcTab2) wglDeleteContext(hglrcTab2);
        if (hdcTab2) ReleaseDC(hWndTab2, hdcTab2);
        if (hbrTab1) DeleteObject(hbrTab1);
        if (hbrTab2) DeleteObject(hbrTab2);
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
//
//  FUNCTION: About
//
//  PURPOSE: Message handler for the about dialog box.
//
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

I called glGetError() and currently I have found the error code is 0 or GL_NOERROR. So far I have found that nothing is yet drawing in the tabs so far. As far as I know there should be a triangle drawing in each tab. How to get this working?