dlangui/src/dlangui/platforms/windows/winapp.d

718 lines
25 KiB
D

module dlangui.platforms.windows.winapp;
version (Windows) {
import core.runtime;
import win32.windows;
import std.string;
import std.utf;
import std.stdio;
import std.algorithm;
import std.file;
import dlangui.platforms.common.platform;
import dlangui.platforms.windows.win32fonts;
import dlangui.platforms.windows.win32drawbuf;
import dlangui.widgets.styles;
import dlangui.widgets.widget;
import dlangui.graphics.drawbuf;
import dlangui.graphics.images;
import dlangui.graphics.fonts;
import dlangui.core.logger;
version (USE_OPENGL) {
import dlangui.graphics.glsupport;
}
pragma(lib, "gdi32.lib");
pragma(lib, "user32.lib");
pragma(lib, "libpng15.lib");
/// this function should be defined in user application!
extern (C) int UIAppMain(string[] args);
immutable WIN_CLASS_NAME = "DLANGUI_APP";
__gshared HINSTANCE _hInstance;
__gshared int _cmdShow;
version (USE_OPENGL) {
bool setupPixelFormat(HDC hDC)
{
PIXELFORMATDESCRIPTOR pfd = {
PIXELFORMATDESCRIPTOR.sizeof, /* size */
1, /* version */
PFD_SUPPORT_OPENGL |
PFD_DRAW_TO_WINDOW |
PFD_DOUBLEBUFFER, /* support double-buffering */
PFD_TYPE_RGBA, /* color type */
16, /* prefered color depth */
0, 0, 0, 0, 0, 0, /* color bits (ignored) */
0, /* no alpha buffer */
0, /* alpha bits (ignored) */
0, /* no accumulation buffer */
0, 0, 0, 0, /* accum bits (ignored) */
16, /* depth buffer */
0, /* no stencil buffer */
0, /* no auxiliary buffers */
0, /* main layer PFD_MAIN_PLANE */
0, /* reserved */
0, 0, 0, /* no layer, visible, damage masks */
};
int pixelFormat;
pixelFormat = ChoosePixelFormat(hDC, &pfd);
if (pixelFormat == 0) {
Log.e("ChoosePixelFormat failed.");
return false;
}
if (SetPixelFormat(hDC, pixelFormat, &pfd) != TRUE) {
Log.e("SetPixelFormat failed.");
return false;
}
return true;
}
HPALETTE setupPalette(HDC hDC)
{
import core.stdc.stdlib;
HPALETTE hPalette = NULL;
int pixelFormat = GetPixelFormat(hDC);
PIXELFORMATDESCRIPTOR pfd;
LOGPALETTE* pPal;
int paletteSize;
DescribePixelFormat(hDC, pixelFormat, PIXELFORMATDESCRIPTOR.sizeof, &pfd);
if (pfd.dwFlags & PFD_NEED_PALETTE) {
paletteSize = 1 << pfd.cColorBits;
} else {
return null;
}
pPal = cast(LOGPALETTE*)
malloc(LOGPALETTE.sizeof + paletteSize * PALETTEENTRY.sizeof);
pPal.palVersion = 0x300;
pPal.palNumEntries = cast(ushort)paletteSize;
/* build a simple RGB color palette */
{
int redMask = (1 << pfd.cRedBits) - 1;
int greenMask = (1 << pfd.cGreenBits) - 1;
int blueMask = (1 << pfd.cBlueBits) - 1;
int i;
for (i=0; i<paletteSize; ++i) {
pPal.palPalEntry[i].peRed = cast(ubyte)(
(((i >> pfd.cRedShift) & redMask) * 255) / redMask);
pPal.palPalEntry[i].peGreen = cast(ubyte)(
(((i >> pfd.cGreenShift) & greenMask) * 255) / greenMask);
pPal.palPalEntry[i].peBlue = cast(ubyte)(
(((i >> pfd.cBlueShift) & blueMask) * 255) / blueMask);
pPal.palPalEntry[i].peFlags = 0;
}
}
hPalette = CreatePalette(pPal);
free(pPal);
if (hPalette) {
SelectPalette(hDC, hPalette, FALSE);
RealizePalette(hDC);
}
return hPalette;
}
private __gshared bool DERELICT_GL3_RELOADED = false;
}
class Win32Window : Window {
Win32Platform _platform;
HWND _hwnd;
version (USE_OPENGL) {
HGLRC _hGLRC; // opengl context
HPALETTE _hPalette;
}
string _caption;
Win32ColorDrawBuf _drawbuf;
bool useOpengl;
this(Win32Platform platform, string windowCaption, Window parent) {
_platform = platform;
_caption = windowCaption;
_hwnd = CreateWindow(toUTF16z(WIN_CLASS_NAME), // window class name
toUTF16z(windowCaption), // window caption
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
null, // parent window handle
null, // window menu handle
_hInstance, // program instance handle
cast(void*)this); // creation parameters
version (USE_OPENGL) {
import derelict.opengl3.wgl;
/* initialize OpenGL rendering */
HDC hDC = GetDC(_hwnd);
if (!DERELICT_GL3_RELOADED || openglEnabled) {
if (setupPixelFormat(hDC)) {
_hPalette = setupPalette(hDC);
_hGLRC = wglCreateContext(hDC);
if (_hGLRC) {
wglMakeCurrent(hDC, _hGLRC);
if (!DERELICT_GL3_RELOADED) {
// run this code only once
DERELICT_GL3_RELOADED = true;
try {
import derelict.opengl3.gl3;
DerelictGL3.reload();
// successful
if (initShaders()) {
setOpenglEnabled();
useOpengl = true;
} else {
Log.e("Failed to compile shaders");
}
} catch (Exception e) {
Log.e("Derelict exception", e);
}
} else {
if (initShaders()) {
setOpenglEnabled();
useOpengl = true;
} else {
Log.e("Failed to compile shaders");
}
}
}
} else {
Log.e("Pixelformat failed");
// disable GL
DERELICT_GL3_RELOADED = true;
}
}
}
}
version (USE_OPENGL) {
private void paintUsingOpenGL() {
// hack to stop infinite WM_PAINT loop
PAINTSTRUCT ps;
HDC hdc2 = BeginPaint(_hwnd, &ps);
EndPaint(_hwnd, &ps);
import derelict.opengl3.gl3;
import derelict.opengl3.wgl;
import dlangui.graphics.gldrawbuf;
//Log.d("onPaint() start drawing opengl viewport: ", _dx, "x", _dy);
//PAINTSTRUCT ps;
//HDC hdc = BeginPaint(_hwnd, &ps);
//scope(exit) EndPaint(_hwnd, &ps);
HDC hdc = GetDC(_hwnd);
wglMakeCurrent(hdc, _hGLRC);
glDisable(GL_DEPTH_TEST);
glViewport(0, 0, _dx, _dy);
float a = 1.0f;
float r = ((_backgroundColor >> 16) & 255) / 255.0f;
float g = ((_backgroundColor >> 8) & 255) / 255.0f;
float b = ((_backgroundColor >> 0) & 255) / 255.0f;
glClearColor(r, g, b, a);
glClear(GL_COLOR_BUFFER_BIT);
GLDrawBuf buf = new GLDrawBuf(_dx, _dy, false);
buf.beforeDrawing();
static if (false) {
// for testing for render
buf.fillRect(Rect(100, 100, 200, 200), 0x704020);
buf.fillRect(Rect(40, 70, 100, 120), 0x000000);
buf.fillRect(Rect(80, 80, 150, 150), 0x80008000); // green
drawableCache.get("exit").drawTo(buf, Rect(300, 100, 364, 164));
drawableCache.get("btn_default_pressed").drawTo(buf, Rect(300, 200, 564, 264));
drawableCache.get("btn_default_normal").drawTo(buf, Rect(300, 0, 400, 50));
drawableCache.get("btn_default_selected").drawTo(buf, Rect(0, 0, 100, 50));
FontRef fnt = currentTheme.font;
fnt.drawText(buf, 40, 40, "Some Text 1234567890 !@#$^*", 0x80FF0000);
} else {
onDraw(buf);
}
buf.afterDrawing();
SwapBuffers(hdc);
wglMakeCurrent(hdc, null);
}
}
~this() {
Log.d("Window destructor");
version (USE_OPENGL) {
import derelict.opengl3.wgl;
if (_hGLRC) {
uninitShaders();
wglMakeCurrent (null, null) ;
wglDeleteContext(_hGLRC);
_hGLRC = null;
}
}
if (_hwnd)
DestroyWindow(_hwnd);
_hwnd = null;
}
Win32ColorDrawBuf getDrawBuf() {
//RECT rect;
//GetClientRect(_hwnd, &rect);
//int dx = rect.right - rect.left;
//int dy = rect.bottom - rect.top;
if (_drawbuf is null)
_drawbuf = new Win32ColorDrawBuf(_dx, _dy);
else
_drawbuf.resize(_dx, _dy);
return _drawbuf;
}
override void show() {
ShowWindow(_hwnd, _cmdShow);
UpdateWindow(_hwnd);
}
override @property string windowCaption() {
return _caption;
}
override @property void windowCaption(string caption) {
_caption = caption;
SetWindowTextW(_hwnd, toUTF16z(_caption));
}
void onCreate() {
Log.d("Window onCreate");
_platform.onWindowCreated(_hwnd, this);
}
void onDestroy() {
Log.d("Window onDestroy");
_platform.onWindowDestroyed(_hwnd, this);
}
private void paintUsingGDI() {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(_hwnd, &ps);
scope(exit) EndPaint(_hwnd, &ps);
Win32ColorDrawBuf buf = getDrawBuf();
buf.fill(_backgroundColor);
onDraw(buf);
buf.drawTo(hdc, 0, 0);
}
void onPaint() {
Log.d("onPaint()");
long paintStart = currentTimeMillis;
version (USE_OPENGL) {
if (useOpengl && _hGLRC) {
paintUsingOpenGL();
} else {
paintUsingGDI();
}
} else {
paintUsingGDI();
}
long paintEnd = currentTimeMillis;
Log.d("WM_PAINT handling took ", paintEnd - paintStart, " ms");
}
protected ButtonDetails _lbutton;
protected ButtonDetails _mbutton;
protected ButtonDetails _rbutton;
private bool _mouseTracking;
private bool onMouse(uint message, uint flags, short x, short y) {
//Log.d("Win32 Mouse Message ", message, " flags=", flags, " x=", x, " y=", y);
MouseButton button = MouseButton.None;
MouseAction action = MouseAction.ButtonDown;
ButtonDetails * pbuttonDetails = null;
short wheelDelta = 0;
switch (message) {
case WM_MOUSEMOVE:
action = MouseAction.Move;
break;
case WM_LBUTTONDOWN:
action = MouseAction.ButtonDown;
button = MouseButton.Left;
pbuttonDetails = &_lbutton;
break;
case WM_RBUTTONDOWN:
action = MouseAction.ButtonDown;
button = MouseButton.Right;
pbuttonDetails = &_rbutton;
break;
case WM_MBUTTONDOWN:
action = MouseAction.ButtonDown;
button = MouseButton.Middle;
pbuttonDetails = &_mbutton;
break;
case WM_LBUTTONUP:
action = MouseAction.ButtonUp;
button = MouseButton.Left;
pbuttonDetails = &_lbutton;
break;
case WM_RBUTTONUP:
action = MouseAction.ButtonUp;
button = MouseButton.Right;
pbuttonDetails = &_rbutton;
break;
case WM_MBUTTONUP:
action = MouseAction.ButtonUp;
button = MouseButton.Middle;
pbuttonDetails = &_mbutton;
break;
case WM_MOUSELEAVE:
Log.d("WM_MOUSELEAVE");
action = MouseAction.Leave;
break;
case WM_MOUSEWHEEL:
{
action = MouseAction.Wheel;
wheelDelta = (cast(short)(flags >> 16)) / 120;
POINT pt;
pt.x = x;
pt.y = y;
ScreenToClient(_hwnd, &pt);
x = cast(short)pt.x;
y = cast(short)pt.y;
}
break;
default:
// unsupported event
return false;
}
if (action == MouseAction.ButtonDown) {
pbuttonDetails.down(x, y, cast(ushort)flags);
} else if (action == MouseAction.ButtonUp) {
pbuttonDetails.up(x, y, cast(ushort)flags);
}
if (((message == WM_MOUSELEAVE) || (x < 0 || y < 0 || x > _dx || y > _dy)) && _mouseTracking) {
action = MouseAction.Leave;
Log.d("WM_MOUSELEAVE - releasing capture");
_mouseTracking = false;
ReleaseCapture();
}
if (message != WM_MOUSELEAVE && !_mouseTracking) {
if (x >=0 && y >= 0 && x < _dx && y < _dy) {
Log.d("Setting capture");
_mouseTracking = true;
SetCapture(_hwnd);
}
}
MouseEvent event = new MouseEvent(action, button, cast(ushort)flags, x, y, wheelDelta);
event.lbutton = _lbutton;
event.rbutton = _rbutton;
event.mbutton = _mbutton;
bool res = dispatchMouseEvent(event);
if (res) {
Log.d("Calling update() after mouse event");
update();
}
return res;
}
/// request window redraw
override void invalidate() {
InvalidateRect(_hwnd, null, FALSE);
}
/// after drawing, call to schedule redraw if animation is active
override void scheduleAnimation() {
invalidate();
}
}
class Win32Platform : Platform {
this() {
}
bool registerWndClass() {
//MSG msg;
WNDCLASS wndclass;
wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
wndclass.lpfnWndProc = &WndProc;
wndclass.cbClsExtra = 0;
wndclass.cbWndExtra = 0;
wndclass.hInstance = _hInstance;
wndclass.hIcon = LoadIcon(null, IDI_APPLICATION);
wndclass.hCursor = LoadCursor(null, IDC_ARROW);
wndclass.hbrBackground = cast(HBRUSH)GetStockObject(WHITE_BRUSH);
wndclass.lpszMenuName = null;
wndclass.lpszClassName = toUTF16z(WIN_CLASS_NAME);
if(!RegisterClass(&wndclass))
{
return false;
}
return true;
}
override int enterMessageLoop() {
MSG msg;
while (GetMessage(&msg, null, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;
}
private Win32Window[ulong] _windowMap;
/// add window to window map
void onWindowCreated(HWND hwnd, Win32Window window) {
_windowMap[cast(ulong)hwnd] = window;
}
/// remove window from window map, returns true if there are some more windows left in map
bool onWindowDestroyed(HWND hwnd, Win32Window window) {
Win32Window wnd = getWindow(hwnd);
if (wnd) {
_windowMap.remove(cast(ulong)hwnd);
destroy(window);
}
return _windowMap.length > 0;
}
/// returns number of currently active windows
@property int windowCount() {
return cast(int)_windowMap.length;
}
/// returns window instance by HWND
Win32Window getWindow(HWND hwnd) {
if ((cast(ulong)hwnd) in _windowMap)
return _windowMap[cast(ulong)hwnd];
return null;
}
override Window createWindow(string windowCaption, Window parent) {
return new Win32Window(this, windowCaption, parent);
}
}
extern(Windows)
int DLANGUIWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow) {
int result;
try
{
Runtime.initialize();
result = myWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
//Runtime.terminate();
}
catch (Throwable e) // catch any uncaught exceptions
{
MessageBox(null, toUTF16z(e.toString()), "Error",
MB_OK | MB_ICONEXCLAMATION);
result = 0; // failed
}
return result;
}
string[] splitCmdLine(string line) {
string[] res;
int start = 0;
bool insideQuotes = false;
for (int i = 0; i <= line.length; i++) {
char ch = i < line.length ? line[i] : 0;
if (ch == '\"') {
if (insideQuotes) {
if (i > start)
res ~= line[start .. i];
start = i + 1;
insideQuotes = false;
} else {
insideQuotes = true;
start = i + 1;
}
} else if (!insideQuotes && (ch == ' ' || ch == '\t' || ch == 0)) {
if (i > start) {
res ~= line[start .. i];
}
start = i + 1;
}
}
return res;
}
private __gshared Win32Platform platform;
int myWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int iCmdShow)
{
setFileLogger(std.stdio.File("ui.log", "w"));
setLogLevel(LogLevel.Trace);
Log.d("myWinMain()");
string basePath = exePath();
Log.i("Current executable: ", exePath());
string cmdline = fromStringz(lpCmdLine);
Log.i("Command line: ", cmdline);
string[] args = splitCmdLine(cmdline);
Log.i("Command line params: ", args);
_cmdShow = iCmdShow;
_hInstance = hInstance;
platform = new Win32Platform();
if (!platform.registerWndClass()) {
MessageBoxA(null, "This program requires Windows NT!", "DLANGUI App".toStringz, MB_ICONERROR);
return 0;
}
Platform.setInstance(platform);
if (false) {
/// testing freetype font manager
import dlangui.graphics.ftfonts;
import win32.shlobj;
FreeTypeFontManager ftfontMan = new FreeTypeFontManager();
string fontsPath = "c:\\Windows\\Fonts\\";
static if (false) { // SHGetFolderPathW not found in shell32.lib
WCHAR szPath[MAX_PATH];
const CSIDL_FLAG_NO_ALIAS = 0x1000;
const CSIDL_FLAG_DONT_UNEXPAND = 0x2000;
if(SUCCEEDED(SHGetFolderPathW(NULL,
CSIDL_FONTS|CSIDL_FLAG_NO_ALIAS|CSIDL_FLAG_DONT_UNEXPAND,
NULL,
0,
szPath.ptr)))
{
fontsPath = toUTF8(fromWStringz(szPath));
}
}
ftfontMan.registerFont(fontsPath ~ "arial.ttf", FontFamily.SansSerif, "Arial", false, FontWeight.Normal);
ftfontMan.registerFont(fontsPath ~ "arialbd.ttf", FontFamily.SansSerif, "Arial", false, FontWeight.Bold);
ftfontMan.registerFont(fontsPath ~ "arialbi.ttf", FontFamily.SansSerif, "Arial", true, FontWeight.Bold);
ftfontMan.registerFont(fontsPath ~ "ariali.ttf", FontFamily.SansSerif, "Arial", true, FontWeight.Normal);
ftfontMan.registerFont(fontsPath ~ "cour.ttf", FontFamily.MonoSpace, "Courier New", false, FontWeight.Normal);
ftfontMan.registerFont(fontsPath ~ "courbd.ttf", FontFamily.MonoSpace, "Courier New", false, FontWeight.Bold);
ftfontMan.registerFont(fontsPath ~ "courbi.ttf", FontFamily.MonoSpace, "Courier New", true, FontWeight.Bold);
ftfontMan.registerFont(fontsPath ~ "couri.ttf", FontFamily.MonoSpace, "Courier New", true, FontWeight.Normal);
ftfontMan.registerFont(fontsPath ~ "times.ttf", FontFamily.Serif, "Times New Roman", false, FontWeight.Normal);
ftfontMan.registerFont(fontsPath ~ "timesbd.ttf", FontFamily.Serif, "Times New Roman", false, FontWeight.Bold);
ftfontMan.registerFont(fontsPath ~ "timesbi.ttf", FontFamily.Serif, "Times New Roman", true, FontWeight.Bold);
ftfontMan.registerFont(fontsPath ~ "timesi.ttf", FontFamily.Serif, "Times New Roman", true, FontWeight.Normal);
FontManager.instance = ftfontMan;
}
// use Win32 font manager
if (FontManager.instance is null) {
//Win32FontManager fontMan = new Win32FontManager();
FontManager.instance = new Win32FontManager();
}
currentTheme = createDefaultTheme();
version (USE_OPENGL) {
import derelict.opengl3.gl3;
DerelictGL3.load();
// just to check OpenGL context
Log.i("Trying to setup OpenGL context");
Win32Window tmpWindow = new Win32Window(platform, "", null);
destroy(tmpWindow);
if (openglEnabled)
Log.i("OpenGL support is enabled");
else
Log.w("OpenGL support is disabled");
// process messages
platform.enterMessageLoop();
}
// Load versions 1.2+ and all supported ARB and EXT extensions.
Log.i("Entering UIAppMain: ", args);
int result = UIAppMain(args);
Log.i("UIAppMain returned ", result);
return result;
}
extern(Windows)
LRESULT WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
RECT rect;
void * p = cast(void*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
Win32Window windowParam = p is null ? null : cast(Win32Window)(p);
Win32Window window = platform.getWindow(hwnd);
if (windowParam !is null && window !is null)
assert(window is windowParam);
if (window is null && windowParam !is null) {
Log.e("Cannot find window in map by HWND");
}
switch (message)
{
case WM_CREATE:
{
CREATESTRUCT * pcreateStruct = cast(CREATESTRUCT*)lParam;
window = cast(Win32Window)pcreateStruct.lpCreateParams;
void * ptr = cast(void*) window;
SetWindowLongPtr(hwnd, GWLP_USERDATA, cast(LONG_PTR)ptr);
window._hwnd = hwnd;
window.onCreate();
}
return 0;
case WM_DESTROY:
if (window !is null)
window.onDestroy();
if (platform.windowCount == 0)
PostQuitMessage(0);
return 0;
case WM_WINDOWPOSCHANGED:
{
if (window !is null) {
WINDOWPOS * pos = cast(WINDOWPOS*)lParam;
GetClientRect(hwnd, &rect);
int dx = rect.right - rect.left;
int dy = rect.bottom - rect.top;
//window.onResize(pos.cx, pos.cy);
window.onResize(dx, dy);
InvalidateRect(hwnd, null, FALSE);
}
}
return 0;
case WM_ERASEBKGND:
// processed
return 1;
case WM_PAINT:
{
if (window !is null)
window.onPaint();
}
return 0; // processed
case WM_MOUSELEAVE:
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_MBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MBUTTONUP:
case WM_RBUTTONUP:
case WM_MOUSEWHEEL:
if (window !is null) {
if (window.onMouse(message, cast(ushort)wParam, cast(short)(lParam & 0xFFFF), cast(short)((lParam >> 16) & 0xFFFF)))
return 0; // processed
}
// not processed - default handling
return DefWindowProc(hwnd, message, wParam, lParam);
case WM_GETMINMAXINFO:
case WM_NCCREATE:
case WM_NCCALCSIZE:
default:
//Log.d("Unhandled message ", message);
break;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
//===========================================
// end of version(Windows)
//===========================================
}