/*
* This source file is part of RmlUi, the HTML/CSS Interface Middleware
*
* For the latest information, see http://github.com/mikke89/RmlUi
*
* Copyright (c) 2008-2010 CodePoint Ltd, Shift Technology Ltd
* Copyright (c) 2019 The RmlUi Team, and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include
#include
#include
#include "ShellFileInterface.h"
#include
#include
#include
static LRESULT CALLBACK WindowProcedure(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param);
static bool activated = true;
static bool running = false;
static const char* instance_name = nullptr;
static HWND window_handle = nullptr;
static HINSTANCE instance_handle = nullptr;
static double time_frequency;
static LARGE_INTEGER time_startup;
static std::unique_ptr file_interface;
static HCURSOR cursor_default = nullptr;
static HCURSOR cursor_move = nullptr;
static HCURSOR cursor_cross = nullptr;
static HCURSOR cursor_unavailable = nullptr;
bool Shell::Initialise()
{
instance_handle = GetModuleHandle(nullptr);
InputWin32::Initialise();
LARGE_INTEGER time_ticks_per_second;
QueryPerformanceFrequency(&time_ticks_per_second);
QueryPerformanceCounter(&time_startup);
time_frequency = 1.0 / (double) time_ticks_per_second.QuadPart;
// Load cursors
cursor_default = LoadCursorA(nullptr, IDC_ARROW);
cursor_move = LoadCursorA(nullptr, IDC_SIZEALL);
cursor_cross = LoadCursorA(nullptr, IDC_CROSS);
cursor_unavailable = LoadCursorA(nullptr, IDC_NO);
Rml::Core::String root = FindSamplesRoot();
file_interface = std::make_unique(root);
Rml::Core::SetFileInterface(file_interface.get());
return true;
}
void Shell::Shutdown()
{
InputWin32::Shutdown();
file_interface.reset();
}
Rml::Core::String Shell::FindSamplesRoot()
{
Rml::Core::String path = "../../Samples/";
// Fetch the path of the executable, append the path onto that.
char executable_file_name[MAX_PATH];
if (GetModuleFileNameA(instance_handle, executable_file_name, MAX_PATH) >= MAX_PATH &&
GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
executable_file_name[0] = 0;
}
Rml::Core::String executable_path = Rml::Core::String(executable_file_name);
executable_path = executable_path.substr(0, executable_path.rfind("\\") + 1);
return executable_path + path;
}
static ShellRenderInterfaceExtensions *shell_renderer = nullptr;
bool Shell::OpenWindow(const char* name, ShellRenderInterfaceExtensions *_shell_renderer, unsigned int width, unsigned int height, bool allow_resize)
{
WNDCLASS window_class;
// Fill out the window class struct.
window_class.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC;
window_class.lpfnWndProc = WindowProcedure;
window_class.cbClsExtra = 0;
window_class.cbWndExtra = 0;
window_class.hInstance = instance_handle;
window_class.hIcon = LoadIcon(nullptr, IDI_WINLOGO);
window_class.hCursor = cursor_default;
window_class.hbrBackground = nullptr;
window_class.lpszMenuName = nullptr;
window_class.lpszClassName = name;
if (!RegisterClass(&window_class))
{
DisplayError("Could not register window class.");
CloseWindow();
return false;
}
window_handle = CreateWindowEx(WS_EX_APPWINDOW | WS_EX_WINDOWEDGE,
name, // Window class name.
name,
WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW,
0, 0, // Window position.
width, height,// Window size.
nullptr,
nullptr,
instance_handle,
nullptr);
if (!window_handle)
{
DisplayError("Could not create window.");
CloseWindow();
return false;
}
instance_name = name;
DWORD style = (allow_resize ? WS_OVERLAPPEDWINDOW : (WS_OVERLAPPEDWINDOW & ~WS_SIZEBOX & ~WS_MAXIMIZEBOX));
DWORD extended_style = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE;
// Adjust the window size to take into account the edges
RECT window_rect;
window_rect.top = 0;
window_rect.left = 0;
window_rect.right = width;
window_rect.bottom = height;
AdjustWindowRectEx(&window_rect, style, FALSE, extended_style);
SetWindowLong(window_handle, GWL_EXSTYLE, extended_style);
SetWindowLong(window_handle, GWL_STYLE, style);
if (_shell_renderer != nullptr)
{
shell_renderer = _shell_renderer;
if(!shell_renderer->AttachToNative(window_handle))
{
CloseWindow();
return false;
}
}
// Resize the window.
SetWindowPos(window_handle, HWND_TOP, 0, 0, window_rect.right - window_rect.left, window_rect.bottom - window_rect.top, SWP_NOACTIVATE);
// Display the new window
ShowWindow(window_handle, SW_SHOW);
SetForegroundWindow(window_handle);
SetFocus(window_handle);
return true;
}
void Shell::CloseWindow()
{
if(shell_renderer) {
shell_renderer->DetachFromNative();
}
DestroyWindow(window_handle);
UnregisterClass(instance_name, instance_handle);
}
// Returns a platform-dependent handle to the window.
void* Shell::GetWindowHandle()
{
return window_handle;
}
void Shell::EventLoop(ShellIdleFunction idle_function)
{
MSG message;
running = true;
// Loop on PeekMessage() / GetMessage() until exit has been requested.
while (running)
{
if (PeekMessage(&message, nullptr, 0, 0, PM_NOREMOVE))
{
GetMessage(&message, nullptr, 0, 0);
TranslateMessage(&message);
DispatchMessage(&message);
}
idle_function();
}
}
void Shell::RequestExit()
{
running = false;
}
void Shell::DisplayError(const char* fmt, ...)
{
const int buffer_size = 1024;
char buffer[buffer_size];
va_list argument_list;
// Print the message to the buffer.
va_start(argument_list, fmt);
int len = vsnprintf(buffer, buffer_size - 2, fmt, argument_list);
if ( len < 0 || len > buffer_size - 2 )
{
len = buffer_size - 2;
}
buffer[len] = '\n';
buffer[len + 1] = '\0';
va_end(argument_list);
MessageBox(window_handle, buffer, "Shell Error", MB_OK);
}
void Shell::Log(const char* fmt, ...)
{
const int buffer_size = 1024;
char buffer[buffer_size];
va_list argument_list;
// Print the message to the buffer.
va_start(argument_list, fmt);
int len = vsnprintf(buffer, buffer_size - 2, fmt, argument_list);
if ( len < 0 || len > buffer_size - 2 )
{
len = buffer_size - 2;
}
buffer[len] = '\n';
buffer[len + 1] = '\0';
va_end(argument_list);
OutputDebugString(buffer);
}
double Shell::GetElapsedTime()
{
LARGE_INTEGER counter;
QueryPerformanceCounter(&counter);
return double(counter.QuadPart - time_startup.QuadPart) * time_frequency;
}
void Shell::SetMouseCursor(const Rml::Core::String& cursor_name)
{
if (window_handle)
{
HCURSOR cursor_handle = nullptr;
if (cursor_name.empty())
cursor_handle = cursor_default;
else if(cursor_name == "move")
cursor_handle = cursor_move;
else if (cursor_name == "cross")
cursor_handle = cursor_cross;
else if (cursor_name == "unavailable")
cursor_handle = cursor_unavailable;
if (cursor_handle)
{
SetCursor(cursor_handle);
SetClassLongPtrA(window_handle, GCLP_HCURSOR, (LONG_PTR)cursor_handle);
}
}
}
void Shell::SetClipboardText(const Rml::Core::String& text_utf8)
{
if (window_handle)
{
if (!OpenClipboard(window_handle))
return;
EmptyClipboard();
const Rml::Core::WString text = Rml::Core::StringUtilities::ToUTF16(text_utf8);
size_t size = sizeof(wchar_t) * (text.size() + 1);
HGLOBAL clipboard_data = GlobalAlloc(GMEM_FIXED, size);
memcpy(clipboard_data, text.data(), size);
if (SetClipboardData(CF_UNICODETEXT, clipboard_data) == nullptr)
{
CloseClipboard();
GlobalFree(clipboard_data);
}
else
CloseClipboard();
}
}
void Shell::GetClipboardText(Rml::Core::String& text)
{
if (window_handle)
{
if (!OpenClipboard(window_handle))
return;
HANDLE clipboard_data = GetClipboardData(CF_UNICODETEXT);
if (clipboard_data == nullptr)
{
CloseClipboard();
return;
}
const wchar_t* clipboard_text = (const wchar_t*)GlobalLock(clipboard_data);
if (clipboard_text)
text = Rml::Core::StringUtilities::ToUTF8(clipboard_text);
GlobalUnlock(clipboard_data);
CloseClipboard();
}
}
static LRESULT CALLBACK WindowProcedure(HWND window_handle, UINT message, WPARAM w_param, LPARAM l_param)
{
// See what kind of message we've got.
switch (message)
{
case WM_ACTIVATE:
{
if (LOWORD(w_param) != WA_INACTIVE)
{
activated = true;
}
else
{
activated = false;
}
}
break;
// When the window closes, request exit
case WM_CLOSE:
{
running = false;
return 0;
}
break;
case WM_SIZE:
{
int width = LOWORD(l_param);;
int height = HIWORD(l_param);;
shell_renderer->SetViewport(width, height);
}
break;
default:
{
InputWin32::ProcessWindowsEvent(message, w_param, l_param);
}
break;
}
// All unhandled messages go to DefWindowProc.
return DefWindowProc(window_handle, message, w_param, l_param);
}