// 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?