/*
	src/screen.cpp -- Top-level widget and interface between NanoGUI and GLFW

	A significant redesign of this code was contributed by Christian Schueller.

	NanoGUI was developed by Wenzel Jakob <wenzel.jakob@epfl.ch>.
	The widget drawing code is based on the NanoVG demo application
	by Mikko Mononen.

	All rights reserved. Use of this source code is governed by a
	BSD-style license that can be found in the LICENSE.txt file.
*/

#include <nanogui/opengl.h>
#include <nanogui/popup.h>
#include <nanogui/screen.h>
#include <nanogui/theme.h>
#include <nanogui/window.h>

#include <cstdlib>	// getenv()
#include <iostream>
#include <map>

#if defined(_WIN32)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#undef APIENTRY

#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <windows.h>

#define GLFW_EXPOSE_NATIVE_WGL
#define GLFW_EXPOSE_NATIVE_WIN32
#include <GLFW/glfw3native.h>
#endif

/* Allow enforcing the GL2 implementation of NanoVG */

// JLBC for Emscripten port:
#ifndef NANOVG_GLES3_IMPLEMENTATION
#define NANOVG_GL3_IMPLEMENTATION
#endif
#include <nanovg_gl.h>

// JLBC for MRPT: Fix for older GLFW version in Ubuntu Xenial:
#ifndef GLFW_MAXIMIZED
#define GLFW_MAXIMIZED 0x00020008
#endif

NAMESPACE_BEGIN(nanogui)

std::map<GLFWwindow*, Screen*> __nanogui_screens;

#if defined(NANOGUI_GLAD)
static bool gladInitialized = false;
#endif

/* Calculate pixel ratio for hi-dpi devices. */
static float get_pixel_ratio(GLFWwindow* window)
{
#if defined(_WIN32)
	HWND hWnd = glfwGetWin32Window(window);
	HMONITOR monitor = nullptr;
#if defined(MONITOR_DEFAULTTONEAREST)
	monitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTONEAREST);
#else
	static HMONITOR(WINAPI * MonitorFromWindow_)(HWND, DWORD) = nullptr;
	static bool MonitorFromWindow_tried = false;
	if (!MonitorFromWindow_tried)
	{
		auto user32 = LoadLibrary(TEXT("user32"));
		if (user32)
			MonitorFromWindow_ = (decltype(MonitorFromWindow_))GetProcAddress(
				user32, "MonitorFromWindow");
		MonitorFromWindow_tried = true;
	}
	if (MonitorFromWindow_) monitor = MonitorFromWindow_(hWnd, 2);
#endif	// defined(MONITOR_DEFAULTTONEAREST)
	/* The following function only exists on Windows 8.1+, but we don't want to
	 * make that a dependency */
	static HRESULT(WINAPI * GetDpiForMonitor_)(HMONITOR, UINT, UINT*, UINT*) =
		nullptr;
	static bool GetDpiForMonitor_tried = false;

	if (!GetDpiForMonitor_tried)
	{
		auto shcore = LoadLibrary(TEXT("shcore"));
		if (shcore)
			GetDpiForMonitor_ = (decltype(GetDpiForMonitor_))GetProcAddress(
				shcore, "GetDpiForMonitor");
		GetDpiForMonitor_tried = true;
	}

	if (GetDpiForMonitor_ && monitor)
	{
		uint32_t dpiX, dpiY;
		if (GetDpiForMonitor_(monitor, 0 /* effective DPI */, &dpiX, &dpiY) ==
			S_OK)
			return dpiX / 96.0;
	}
	return 1.f;
#elif defined(__linux__)
	(void)window;

	float ratio = 1.0f;
	FILE* fp;
	/* Try to read the pixel ratio from KDEs config */
	auto currentDesktop = std::getenv("XDG_CURRENT_DESKTOP");
	if (currentDesktop && currentDesktop == std::string("KDE"))
	{
		fp = popen("kreadconfig5 --group KScreen --key ScaleFactor", "r");
		if (!fp) return 1;

		if (fscanf(fp, "%f", &ratio) != 1) return 1;
	}
	else
	{
		/* Try to read the pixel ratio from GTK */
		fp = popen(
			"gsettings get org.gnome.desktop.interface scaling-factor", "r");
		if (!fp) return 1;

		int ratioInt = 1;
		if (fscanf(fp, "uint32 %i", &ratioInt) != 1) return 1;
		ratio = ratioInt;
	}
	if (pclose(fp) != 0) return 1;
	return ratio >= 1 ? ratio : 1;

#else
	Vector2i fbSize, size;
	glfwGetFramebufferSize(window, &fbSize[0], &fbSize[1]);
	glfwGetWindowSize(window, &size[0], &size[1]);
	return (float)fbSize[0] / (float)size[0];
#endif
}

Screen::Screen()
	: Widget(nullptr),
	  mGLFWWindow(nullptr),
	  mNVGContext(nullptr),
	  mBackground(0.3f, 0.3f, 0.32f, 1.f),
	  mShutdownGLFWOnDestruct(false),
	  mFullscreen(false)
{
	memset(mCursors, 0, sizeof(GLFWcursor*) * (int)Cursor::CursorCount);
}

Screen::Screen(
	const Vector2i& size, const std::string& caption, bool resizable,
	bool fullscreen, int colorBits, int alphaBits, int depthBits,
	int stencilBits, int nSamples, unsigned int glMajor, unsigned int glMinor,
	bool maximized, bool eglContext)
	: Widget(nullptr),
	  mGLFWWindow(nullptr),
	  mNVGContext(nullptr),
	  mBackground(0.3f, 0.3f, 0.32f, 1.f),
	  mCaption(caption),
	  mShutdownGLFWOnDestruct(false),
	  mFullscreen(fullscreen)
{
	memset(mCursors, 0, sizeof(GLFWcursor*) * (int)Cursor::CursorCount);

	/* Request a forward compatible OpenGL glMajor.glMinor core profile context.
	   Default value is an OpenGL 3.3 core profile context. */
	if (eglContext)
	{
		glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
		glfwWindowHint(GLFW_CONTEXT_CREATION_API, GLFW_NATIVE_CONTEXT_API);
	}
	glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
	glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

	glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, glMajor);
	glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, glMinor);

	glfwWindowHint(GLFW_SAMPLES, nSamples);
	glfwWindowHint(GLFW_RED_BITS, colorBits);
	glfwWindowHint(GLFW_GREEN_BITS, colorBits);
	glfwWindowHint(GLFW_BLUE_BITS, colorBits);
	glfwWindowHint(GLFW_ALPHA_BITS, alphaBits);
	glfwWindowHint(GLFW_STENCIL_BITS, stencilBits);
	glfwWindowHint(GLFW_DEPTH_BITS, depthBits);
	glfwWindowHint(GLFW_VISIBLE, GL_FALSE);
	glfwWindowHint(GLFW_RESIZABLE, resizable ? GL_TRUE : GL_FALSE);
	glfwWindowHint(GLFW_MAXIMIZED, maximized ? GL_TRUE : GL_FALSE);

	if (fullscreen)
	{
		GLFWmonitor* monitor = glfwGetPrimaryMonitor();
		const GLFWvidmode* mode = glfwGetVideoMode(monitor);
		mGLFWWindow = glfwCreateWindow(
			mode->width, mode->height, caption.c_str(), monitor, nullptr);
	}
	else
	{
		mGLFWWindow = glfwCreateWindow(
			size.x(), size.y(), caption.c_str(), nullptr, nullptr);
	}

	using namespace std::string_literals;

	if (!mGLFWWindow)
		throw std::runtime_error(
			"Could not create an "s + (eglContext ? "GLES"s : "OpenGL"s) +
			std::to_string(glMajor) + "."s + std::to_string(glMinor) +
			" context!"s);

	if (std::getenv("NANOGUI_VERBOSE") != nullptr)
	{
		printf("[nanogui::Screen] Created window with:\n");
		printf(
			" GLFW_CLIENT_API: 0x%08X\n",
			glfwGetWindowAttrib(mGLFWWindow, GLFW_CLIENT_API));
		printf(
			" GLFW_CONTEXT_CREATION_API: 0x%08X\n",
			glfwGetWindowAttrib(mGLFWWindow, GLFW_CONTEXT_CREATION_API));
		printf(
			" GLFW_OPENGL_PROFILE: 0x%08X\n",
			glfwGetWindowAttrib(mGLFWWindow, GLFW_OPENGL_PROFILE));
		printf(
			" GLFW_CONTEXT_VERSION_MAJOR: %i\n",
			glfwGetWindowAttrib(mGLFWWindow, GLFW_CONTEXT_VERSION_MAJOR));
		printf(
			" GLFW_CONTEXT_VERSION_MINOR: %i\n",
			glfwGetWindowAttrib(mGLFWWindow, GLFW_CONTEXT_VERSION_MINOR));
	}

	glfwMakeContextCurrent(mGLFWWindow);

#if defined(NANOGUI_GLAD)
	if (!gladInitialized)
	{
		gladInitialized = true;
		if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
			throw std::runtime_error("Could not initialize GLAD!");
		glGetError();  // pull and ignore unhandled errors like GL_INVALID_ENUM
	}
#endif

	glfwGetFramebufferSize(mGLFWWindow, &mFBSize[0], &mFBSize[1]);
	glViewport(0, 0, mFBSize[0], mFBSize[1]);
	glClearColor(
		mBackground[0], mBackground[1], mBackground[2], mBackground[3]);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
	glfwSwapInterval(0);
	glfwSwapBuffers(mGLFWWindow);

#if defined(__APPLE__)
	/* Poll for events once before starting a potentially
	   lengthy loading process. This is needed to be
	   classified as "interactive" by other software such
	   as iTerm2 */

	glfwPollEvents();
#endif

	/* Propagate GLFW events to the appropriate Screen instance */
	glfwSetCursorPosCallback(
		mGLFWWindow, [](GLFWwindow* w, double x, double y) {
			auto it = __nanogui_screens.find(w);
			if (it == __nanogui_screens.end()) return;
			Screen* s = it->second;
			if (!s->mProcessEvents) return;
			s->cursorPosCallbackEvent(x, y);
		});

	glfwSetMouseButtonCallback(
		mGLFWWindow, [](GLFWwindow* w, int button, int action, int modifiers) {
			auto it = __nanogui_screens.find(w);
			if (it == __nanogui_screens.end()) return;
			Screen* s = it->second;
			if (!s->mProcessEvents) return;
			s->mouseButtonCallbackEvent(button, action, modifiers);
		});

	glfwSetKeyCallback(
		mGLFWWindow,
		[](GLFWwindow* w, int key, int scancode, int action, int mods) {
			auto it = __nanogui_screens.find(w);
			if (it == __nanogui_screens.end()) return;
			Screen* s = it->second;
			if (!s->mProcessEvents) return;
			s->keyCallbackEvent(key, scancode, action, mods);
		});

	glfwSetCharCallback(mGLFWWindow, [](GLFWwindow* w, unsigned int codepoint) {
		auto it = __nanogui_screens.find(w);
		if (it == __nanogui_screens.end()) return;
		Screen* s = it->second;
		if (!s->mProcessEvents) return;
		s->charCallbackEvent(codepoint);
	});

	glfwSetDropCallback(
		mGLFWWindow, [](GLFWwindow* w, int count, const char** filenames) {
			auto it = __nanogui_screens.find(w);
			if (it == __nanogui_screens.end()) return;
			Screen* s = it->second;
			if (!s->mProcessEvents) return;
			s->dropCallbackEvent(count, filenames);
		});

	glfwSetScrollCallback(mGLFWWindow, [](GLFWwindow* w, double x, double y) {
		auto it = __nanogui_screens.find(w);
		if (it == __nanogui_screens.end()) return;
		Screen* s = it->second;
		if (!s->mProcessEvents) return;
		s->scrollCallbackEvent(x, y);
	});

	/* React to framebuffer size events -- includes window
	   size events and also catches things like dragging
	   a window from a Retina-capable screen to a normal
	   screen on Mac OS X */
	glfwSetFramebufferSizeCallback(
		mGLFWWindow, [](GLFWwindow* w, int width, int height) {
			auto it = __nanogui_screens.find(w);
			if (it == __nanogui_screens.end()) return;
			Screen* s = it->second;

			if (!s->mProcessEvents) return;

			s->resizeCallbackEvent(width, height);
		});

	// notify when the screen has lost focus (e.g. application switch)
	glfwSetWindowFocusCallback(mGLFWWindow, [](GLFWwindow* w, int focused) {
		auto it = __nanogui_screens.find(w);
		if (it == __nanogui_screens.end()) return;

		Screen* s = it->second;
		// focused: 0 when false, 1 when true
		s->focusEvent(focused != 0);
	});

	initialize(mGLFWWindow, true);
}

void Screen::initialize(GLFWwindow* window, bool shutdownGLFWOnDestruct)
{
	mGLFWWindow = window;
	mShutdownGLFWOnDestruct = shutdownGLFWOnDestruct;
	glfwGetWindowSize(mGLFWWindow, &mSize[0], &mSize[1]);
	glfwGetFramebufferSize(mGLFWWindow, &mFBSize[0], &mFBSize[1]);

	mPixelRatio = get_pixel_ratio(window);

#if defined(_WIN32) || defined(__linux__)
	if (mPixelRatio != 1 && !mFullscreen)
		glfwSetWindowSize(
			window, mSize.x() * mPixelRatio, mSize.y() * mPixelRatio);
#endif

#if defined(NANOGUI_GLAD)
	if (!gladInitialized)
	{
		gladInitialized = true;
		if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
			throw std::runtime_error("Could not initialize GLAD!");
		glGetError();  // pull and ignore unhandled errors like GL_INVALID_ENUM
	}
#endif

	/* Detect framebuffer properties and set up compatible NanoVG context */
	GLint nStencilBits = 0, nSamples = 0;
	glGetFramebufferAttachmentParameteriv(
		GL_DRAW_FRAMEBUFFER, GL_STENCIL, GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE,
		&nStencilBits);
	glGetIntegerv(GL_SAMPLES, &nSamples);

	int flags = 0;
	if (nStencilBits >= 8) flags |= NVG_STENCIL_STROKES;
	if (nSamples <= 1) flags |= NVG_ANTIALIAS;
#if !defined(NDEBUG)
	flags |= NVG_DEBUG;
#endif

#ifdef NANOVG_GLES3_IMPLEMENTATION
	mNVGContext = nvgCreateGLES3(flags);
#else
	mNVGContext = nvgCreateGL3(flags);
#endif

	if (mNVGContext == nullptr)
		throw std::runtime_error("Could not initialize NanoVG!");

	mVisible = glfwGetWindowAttrib(window, GLFW_VISIBLE) != 0;
	setTheme(new Theme(mNVGContext));
	mMousePos = Vector2i::Zero();
	mMouseState = mModifiers = 0;
	mDragActive = false;
	mLastInteraction = glfwGetTime();
	mProcessEvents = true;
	__nanogui_screens[mGLFWWindow] = this;

	for (int i = 0; i < (int)Cursor::CursorCount; ++i)
		mCursors[i] = glfwCreateStandardCursor(GLFW_ARROW_CURSOR + i);

	/// Fixes retina display-related font rendering issue (#185)
	nvgBeginFrame(mNVGContext, mSize[0], mSize[1], mPixelRatio);
	nvgEndFrame(mNVGContext);
}

Screen::~Screen()
{
	__nanogui_screens.erase(mGLFWWindow);
	for (int i = 0; i < (int)Cursor::CursorCount; ++i)
	{
		if (mCursors[i]) glfwDestroyCursor(mCursors[i]);
	}
	if (mNVGContext)
	{
#ifdef NANOVG_GLES3_IMPLEMENTATION
		nvgDeleteGLES3(mNVGContext);
#else
		nvgDeleteGL3(mNVGContext);
#endif
	}
	if (mGLFWWindow && mShutdownGLFWOnDestruct) glfwDestroyWindow(mGLFWWindow);
}

void Screen::setVisible(bool visible)
{
	if (mVisible != visible)
	{
		mVisible = visible;

		if (visible) glfwShowWindow(mGLFWWindow);
		else
			glfwHideWindow(mGLFWWindow);
	}
}

void Screen::setCaption(const std::string& caption)
{
	if (caption != mCaption)
	{
		glfwSetWindowTitle(mGLFWWindow, caption.c_str());
		mCaption = caption;
	}
}

void Screen::setSize(const Vector2i& size)
{
	Widget::setSize(size);

#if defined(_WIN32) || defined(__linux__)
	glfwSetWindowSize(
		mGLFWWindow, size.x() * mPixelRatio, size.y() * mPixelRatio);
#else
	glfwSetWindowSize(mGLFWWindow, size.x(), size.y());
#endif
}

void Screen::drawAll()
{
	glClearColor(
		mBackground[0], mBackground[1], mBackground[2], mBackground[3]);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

	drawContents();
	drawWidgets();

	glfwSwapBuffers(mGLFWWindow);
}

void Screen::drawWidgets()
{
	if (!mVisible) return;

	glfwMakeContextCurrent(mGLFWWindow);

	glfwGetFramebufferSize(mGLFWWindow, &mFBSize[0], &mFBSize[1]);
	glfwGetWindowSize(mGLFWWindow, &mSize[0], &mSize[1]);

#if defined(_WIN32) || defined(__linux__)
	mSize = (mSize.cast<float>() / mPixelRatio).cast<int>();
	mFBSize = (mSize.cast<float>() * mPixelRatio).cast<int>();
#else
	/* Recompute pixel ratio on OSX */
	if (mSize[0]) mPixelRatio = (float)mFBSize[0] / (float)mSize[0];
#endif

	glViewport(0, 0, mFBSize[0], mFBSize[1]);
	glBindSampler(0, 0);
	nvgBeginFrame(mNVGContext, mSize[0], mSize[1], mPixelRatio);

	draw(mNVGContext);

	double elapsed = glfwGetTime() - mLastInteraction;

	if (elapsed > 0.5f)
	{
		/* Draw tooltips */
		const Widget* widget = findWidget(mMousePos);
		if (widget && !widget->tooltip().empty())
		{
			int tooltipWidth = 150;

			float bounds[4];
			nvgFontFace(mNVGContext, "sans");
			nvgFontSize(mNVGContext, 15.0f);
			nvgTextAlign(mNVGContext, NVG_ALIGN_LEFT | NVG_ALIGN_TOP);
			nvgTextLineHeight(mNVGContext, 1.1f);
			Vector2i pos = widget->absolutePosition() +
				Vector2i(widget->width() / 2, widget->height() + 10);

			nvgTextBounds(
				mNVGContext, pos.x(), pos.y(), widget->tooltip().c_str(),
				nullptr, bounds);
			int h = (bounds[2] - bounds[0]) / 2;
			if (h > tooltipWidth / 2)
			{
				nvgTextAlign(mNVGContext, NVG_ALIGN_CENTER | NVG_ALIGN_TOP);
				nvgTextBoxBounds(
					mNVGContext, pos.x(), pos.y(), tooltipWidth,
					widget->tooltip().c_str(), nullptr, bounds);

				h = (bounds[2] - bounds[0]) / 2;
			}
			nvgGlobalAlpha(
				mNVGContext, std::min(1.0, 2 * (elapsed - 0.5f)) * 0.8);

			nvgBeginPath(mNVGContext);
			nvgFillColor(mNVGContext, Color(0, 255));
			nvgRoundedRect(
				mNVGContext, bounds[0] - 4 - h, bounds[1] - 4,
				(int)(bounds[2] - bounds[0]) + 8,
				(int)(bounds[3] - bounds[1]) + 8, 3);

			int px = (int)((bounds[2] + bounds[0]) / 2) - h;
			nvgMoveTo(mNVGContext, px, bounds[1] - 10);
			nvgLineTo(mNVGContext, px + 7, bounds[1] + 1);
			nvgLineTo(mNVGContext, px - 7, bounds[1] + 1);
			nvgFill(mNVGContext);

			nvgFillColor(mNVGContext, Color(255, 255));
			nvgFontBlur(mNVGContext, 0.0f);
			nvgTextBox(
				mNVGContext, pos.x() - h, pos.y(), tooltipWidth,
				widget->tooltip().c_str(), nullptr);
		}
	}

	nvgEndFrame(mNVGContext);
}

bool Screen::keyboardEvent(int key, int scancode, int action, int modifiers)
{
	if (mFocusPath.size() > 0)
	{
		for (auto it = mFocusPath.rbegin() + 1; it != mFocusPath.rend(); ++it)
			if ((*it)->focused() &&
				(*it)->keyboardEvent(key, scancode, action, modifiers))
				return true;
	}

	return false;
}

bool Screen::keyboardCharacterEvent(unsigned int codepoint)
{
	if (mFocusPath.size() > 0)
	{
		for (auto it = mFocusPath.rbegin() + 1; it != mFocusPath.rend(); ++it)
			if ((*it)->focused() && (*it)->keyboardCharacterEvent(codepoint))
				return true;
	}
	return false;
}

bool Screen::resizeEvent(const Vector2i& size)
{
	if (mResizeCallback)
	{
		mResizeCallback(size);
		return true;
	}
	return false;
}

bool Screen::cursorPosCallbackEvent(double x, double y)
{
	Vector2i p((int)x, (int)y);

#if defined(_WIN32) || defined(__linux__)
	p = (p.cast<float>() / mPixelRatio).cast<int>();
#endif

	bool ret = false;
	mLastInteraction = glfwGetTime();
	try
	{
		p -= Vector2i(1, 2);

		if (!mDragActive)
		{
			Widget* widget = findWidget(p);
			if (widget != nullptr && widget->cursor() != mCursor)
			{
				mCursor = widget->cursor();
				glfwSetCursor(mGLFWWindow, mCursors[(int)mCursor]);
			}
		}
		else
		{
			ret = mDragWidget->mouseDragEvent(
				p - mDragWidget->parent()->absolutePosition(), p - mMousePos,
				mMouseState, mModifiers);
		}

		if (!ret)
			ret = mouseMotionEvent(p, p - mMousePos, mMouseState, mModifiers);

		mMousePos = p;

		return ret;
	}
	catch (const std::exception& e)
	{
		std::cerr << "Caught exception in event handler: " << e.what()
				  << std::endl;
		return false;
	}
}

bool Screen::mouseButtonCallbackEvent(int button, int action, int modifiers)
{
	mModifiers = modifiers;
	mLastInteraction = glfwGetTime();
	try
	{
		if (mFocusPath.size() > 1)
		{
			const Window* window =
				dynamic_cast<Window*>(mFocusPath[mFocusPath.size() - 2]);
			if (window && window->modal())
			{
				if (!window->contains(mMousePos)) return false;
			}
		}

		if (action == GLFW_PRESS) mMouseState |= 1 << button;
		else
			mMouseState &= ~(1 << button);

		auto dropWidget = findWidget(mMousePos);
		if (mDragActive && action == GLFW_RELEASE && dropWidget != mDragWidget)
			mDragWidget->mouseButtonEvent(
				mMousePos - mDragWidget->parent()->absolutePosition(), button,
				false, mModifiers);

		if (dropWidget != nullptr && dropWidget->cursor() != mCursor)
		{
			mCursor = dropWidget->cursor();
			glfwSetCursor(mGLFWWindow, mCursors[(int)mCursor]);
		}

		if (action == GLFW_PRESS &&
			(button == GLFW_MOUSE_BUTTON_1 || button == GLFW_MOUSE_BUTTON_2))
		{
			mDragWidget = findWidget(mMousePos);
			if (mDragWidget == this) mDragWidget = nullptr;
			mDragActive = mDragWidget != nullptr;
			if (!mDragActive) updateFocus(nullptr);
		}
		else
		{
			mDragActive = false;
			mDragWidget = nullptr;
		}

		return mouseButtonEvent(
			mMousePos, button, action == GLFW_PRESS, mModifiers);
	}
	catch (const std::exception& e)
	{
		std::cerr << "Caught exception in event handler: " << e.what()
				  << std::endl;
		return false;
	}
}

bool Screen::keyCallbackEvent(int key, int scancode, int action, int mods)
{
	mLastInteraction = glfwGetTime();
	try
	{
		return keyboardEvent(key, scancode, action, mods);
	}
	catch (const std::exception& e)
	{
		std::cerr << "Caught exception in event handler: " << e.what()
				  << std::endl;
		return false;
	}
}

bool Screen::charCallbackEvent(unsigned int codepoint)
{
	mLastInteraction = glfwGetTime();
	try
	{
		return keyboardCharacterEvent(codepoint);
	}
	catch (const std::exception& e)
	{
		std::cerr << "Caught exception in event handler: " << e.what()
				  << std::endl;
		return false;
	}
}

bool Screen::dropCallbackEvent(int count, const char** filenames)
{
	std::vector<std::string> arg(count);
	for (int i = 0; i < count; ++i)
		arg[i] = filenames[i];
	return dropEvent(arg);
}

bool Screen::scrollCallbackEvent(double x, double y)
{
	mLastInteraction = glfwGetTime();
	try
	{
		if (mFocusPath.size() > 1)
		{
			const Window* window =
				dynamic_cast<Window*>(mFocusPath[mFocusPath.size() - 2]);
			if (window && window->modal())
			{
				if (!window->contains(mMousePos)) return false;
			}
		}
		return scrollEvent(mMousePos, Vector2f(x, y));
	}
	catch (const std::exception& e)
	{
		std::cerr << "Caught exception in event handler: " << e.what()
				  << std::endl;
		return false;
	}
}

bool Screen::resizeCallbackEvent(int, int)
{
	Vector2i fbSize, size;
	glfwGetFramebufferSize(mGLFWWindow, &fbSize[0], &fbSize[1]);
	glfwGetWindowSize(mGLFWWindow, &size[0], &size[1]);

#if defined(_WIN32) || defined(__linux__)
	size = (size.cast<float>() / mPixelRatio).cast<int>();
#endif

	if (fbSize == Vector2i(0, 0) || size == Vector2i(0, 0)) return false;

	mFBSize = fbSize;
	mSize = size;
	mLastInteraction = glfwGetTime();

	try
	{
		return resizeEvent(mSize);
	}
	catch (const std::exception& e)
	{
		std::cerr << "Caught exception in event handler: " << e.what()
				  << std::endl;
		return false;
	}
}

void Screen::updateFocus(Widget* widget)
{
	for (auto w : mFocusPath)
	{
		if (!w->focused()) continue;
		w->focusEvent(false);
	}
	mFocusPath.clear();
	Widget* window = nullptr;
	while (widget)
	{
		mFocusPath.push_back(widget);
		if (dynamic_cast<Window*>(widget)) window = widget;
		widget = widget->parent();
	}
	for (auto it = mFocusPath.rbegin(); it != mFocusPath.rend(); ++it)
		(*it)->focusEvent(true);

	if (window) moveWindowToFront((Window*)window);
}

void Screen::disposeWindow(Window* window)
{
	if (std::find(mFocusPath.begin(), mFocusPath.end(), window) !=
		mFocusPath.end())
		mFocusPath.clear();
	if (mDragWidget == window) mDragWidget = nullptr;
	removeChild(window);
}

void Screen::centerWindow(Window* window)
{
	if (window->size() == Vector2i::Zero())
	{
		window->setSize(window->preferredSize(mNVGContext));
		window->performLayout(mNVGContext);
	}
	window->setPosition((mSize - window->size()) / 2);
}

void Screen::moveWindowToFront(Window* window)
{
	mChildren.erase(
		std::remove(mChildren.begin(), mChildren.end(), window),
		mChildren.end());
	mChildren.push_back(window);
	/* Brute force topological sort (no problem for a few windows..) */
	bool changed = false;
	do
	{
		size_t baseIndex = 0;
		for (size_t index = 0; index < mChildren.size(); ++index)
			if (mChildren[index] == window) baseIndex = index;
		changed = false;
		for (size_t index = 0; index < mChildren.size(); ++index)
		{
			Popup* pw = dynamic_cast<Popup*>(mChildren[index]);
			if (pw && pw->parentWindow() == window && index < baseIndex)
			{
				moveWindowToFront(pw);
				changed = true;
				break;
			}
		}
	} while (changed);
}

NAMESPACE_END(nanogui)
