#include "mungal4.h"
#pragma hdrstop

#include "l4mfdview.h"
#include "l4padrio.h"

namespace
{
	const char mfdViewClass[] = "RPMFDView";

	const int buttonGap = 4;

	//---------------------------------------------------------------
	// Sticky placement for the exploded view. See L4MFDVIEW.h; ported
	// from BT411's BT_GLASS_LAYOUT.
	//---------------------------------------------------------------
	enum { LayoutOff = 0, LayoutLoad, LayoutSave };

	const char layoutFileName[] = "mfd_layout.cfg";

	// every window taking part, so Save can walk them
	enum { maxLayoutWindows = 12 };
	struct LayoutWindow
	{
		void		*window;
		const char	*title;
		Logical		withSize;
		Logical		bare;		// frame stripped, per the file
	};
	LayoutWindow layoutWindows[maxLayoutWindows];
	int layoutWindowCount = 0;

	//---------------------------------------------------------------
	// Take a window's frame off: no title bar, no border, nothing to
	// drag it by. WS_SYSMENU stays - it draws nothing once the caption
	// is gone, but without one DefWindowProc will not honour Alt+F4,
	// and a bare window with no way to close it is a trap.
	//---------------------------------------------------------------
	void StripWindowFrame(HWND window)
	{
		LONG_PTR style = GetWindowLongPtrA(window, GWL_STYLE);
		style &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX |
			WS_MAXIMIZEBOX | WS_BORDER | WS_DLGFRAME);
		style |= WS_POPUP;
		SetWindowLongPtrA(window, GWL_STYLE, style);
	}

	int LayoutMode()
	{
		static int mode = -1;
		if (mode < 0)
		{
			const char *setting = getenv("RP412MFDLAYOUT");
			if (setting == NULL || setting[0] == '\0')
			{
				mode = LayoutOff;
			}
			else if (!stricmp(setting, "off") || !stricmp(setting, "0"))
			{
				mode = LayoutOff;
			}
			else if (!stricmp(setting, "load") || !stricmp(setting, "restore"))
			{
				mode = LayoutLoad;
			}
			else	// save / adjust / on / 1
			{
				mode = LayoutSave;
			}

			if (mode != LayoutOff)
			{
				DEBUG_STREAM << "MFDLayout: RP412MFDLAYOUT="
					<< ((mode == LayoutSave) ? "save" : "load")
					<< " (" << layoutFileName << ")\n" << std::flush;
			}
		}
		return mode;
	}

	//---------------------------------------------------------------
	// Button banks run UNDER the glass: a button reaches this far in
	// from the display edge, but only the indicator strip clears that
	// edge - the rest sits behind the picture, which is what the
	// player actually clicks. Applies to both the MFD strips (depth
	// measured vertically) and the map's side columns (horizontally).
	//---------------------------------------------------------------
	// 240 against the 480 glass: the two banks reach in far enough to
	// meet in the middle bar the strips' own width, so practically the
	// whole display is a press target and neither bank overlaps the
	// other. The clamps below keep that true on a scaled-down cockpit.
	const int buttonDepth = 240;
	const int indicatorStrip = 10;

	//---------------------------------------------------------------
	// The map paints its own legend beside each side button, and that
	// grid is not the naive height/6: measured off the 640-tall map,
	// the first cell starts 13 rows down and the six cells are 102
	// tall on a 105 pitch (13 + 6*102 + 5*3 = 640). Spacing the
	// buttons evenly from the top instead left every one of them
	// riding high of its label. Scaled to the live glass height.
	//---------------------------------------------------------------
	const int mapLegendSpan = 640;
	const int mapLegendTop = 13;
	const int mapLegendCell = 102;
	const int mapLegendPitch = 105;

	//---------------------------------------------------------------
	// Button fill colors by lamp brightness (0 off / 1-2 dim / 3
	// bright), red family for MFD strips, amber for the side columns.
	//---------------------------------------------------------------
	COLORREF ButtonFill(int amber, int level)
	{
		if (amber)
		{
			if (level >= 3)	return RGB(255, 210, 48);
			if (level >= 1)	return RGB(150, 124, 26);
			return RGB(64, 52, 10);
		}
		if (level >= 3)	return RGB(255, 72, 44);
		if (level >= 1)	return RGB(150, 44, 28);
		return RGB(64, 18, 12);
	}

	//---------------------------------------------------------------
	// Lamp byte -> current brightness level, animating flash modes.
	// Low 2 bits: solid/slow/med/fast; bits 2-3: state 1 brightness;
	// bits 4-5: state 2 brightness (the flash alternate).
	//---------------------------------------------------------------
	int LampLevel(int lamp_state)
	{
		int mode = lamp_state & 0x03;
		int level1 = (lamp_state >> 2) & 0x03;
		int level2 = (lamp_state >> 4) & 0x03;

		if (mode == 0)
		{
			return level1;
		}

		static const int half_period[4] = { 0, 500, 250, 125 };
		return ((GetTickCount() / half_period[mode]) & 1) ? level2 : level1;
	}

	LRESULT CALLBACK
		MFDViewWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
	{
		MFDSplitView *view =
			(MFDSplitView *) GetWindowLongPtrA(hwnd, GWLP_USERDATA);

		switch (message)
		{
		case WM_PAINT:
			if (view != NULL)
			{
				view->Paint();
				return 0;
			}
			// no instance (e.g. the plain viewscreen child): default paint
			break;

		case WM_LBUTTONDOWN:
			if (view != NULL)
			{
				view->MouseDown((int)(short) LOWORD(lParam), (int)(short) HIWORD(lParam));
				return 0;
			}
			break;

		case WM_LBUTTONUP:
		case WM_CAPTURECHANGED:
			if (view != NULL)
			{
				view->MouseUp();
				return 0;
			}
			break;

		case WM_EXITSIZEMOVE:
			//
			// A drag just finished. Write the whole arrangement now rather
			// than trusting teardown, so a hard kill still leaves the
			// layout that was on screen. No-op unless the mode is save.
			//
			RPWindowLayout_Save();
			return 0;

		case WM_CLOSE:
			// Part of the cockpit; just hide it.
			ShowWindow(hwnd, SW_HIDE);
			return 0;

		case WM_ERASEBKGND:
			if (view != NULL)
			{
				return 1;
			}
			break;		// viewscreen child: erase with the class black brush
		}
		return DefWindowProcA(hwnd, message, wParam, lParam);
	}
}

//########################################################################
//############################ MFDSplitView ##############################
//########################################################################

MFDSplitView::MFDSplitView(
	const char *title,
	int source_width,
	int source_height,
	int display_width,
	int display_height,
	int x,
	int y,
	ButtonStyle button_style,
	int anchorA,
	int anchorB,
	void *parent
)
{
	Check_Pointer(this);

	sourceWidth = source_width;
	sourceHeight = source_height;
	buttonCount = 0;
	pressedIndex = -1;
	paneTitle = title;
	ownWindow = (parent == NULL) ? True : False;

	pixels = new unsigned long[source_width * source_height];
	memset(pixels, 0, source_width * source_height * sizeof(unsigned long));

	BITMAPINFOHEADER *header = new BITMAPINFOHEADER;
	memset(header, 0, sizeof(BITMAPINFOHEADER));
	header->biSize = sizeof(BITMAPINFOHEADER);
	header->biWidth = source_width;
	header->biHeight = -source_height;		// top-down
	header->biPlanes = 1;
	header->biBitCount = 32;
	header->biCompression = BI_RGB;
	blitHeader = header;

	buttonStyle = button_style;
	buttonAnchorA = anchorA;
	buttonAnchorB = anchorB;

	int client_width = display_width;
	int client_height = display_height;
	LayoutButtons(button_style, anchorA, anchorB,
		display_width, display_height, &client_width, &client_height);
	clientWidth = client_width;
	clientHeight = client_height;

	HINSTANCE instance = GetModuleHandleA(NULL);

	static Logical class_registered = False;
	if (!class_registered)
	{
		class_registered = True;

		WNDCLASSA window_class;
		memset(&window_class, 0, sizeof(window_class));
		window_class.style = CS_HREDRAW | CS_VREDRAW;
		window_class.lpfnWndProc = MFDViewWndProc;
		window_class.hInstance = instance;
		window_class.hCursor = LoadCursor(NULL, IDC_ARROW);
		window_class.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
		window_class.lpszClassName = mfdViewClass;
		RegisterClassA(&window_class);
	}

	DWORD style;
	int window_w, window_h;

	if (parent != NULL)
	{
		// chrome-less pane inside the cockpit window; clips siblings so
		// overlapping panes and the viewscreen never paint over it
		style = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS;
		window_w = client_width;
		window_h = client_height;
	}
	else
	{
		style = WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX;
		RECT bounds;
		bounds.left = 0;
		bounds.top = 0;
		bounds.right = client_width;
		bounds.bottom = client_height;
		AdjustWindowRect(&bounds, style, FALSE);
		window_w = bounds.right - bounds.left;
		window_h = bounds.bottom - bounds.top;
	}

	window = CreateWindowExA(
		0,
		mfdViewClass,
		title,
		style,
		x, y,
		window_w,
		window_h,
		(HWND) parent, NULL, instance, NULL
	);

	if (window != NULL)
	{
		SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this);
		ShowWindow((HWND) window, SW_SHOWNOACTIVATE);

		// only the exploded view's own windows can be dragged, so only
		// those have a placement worth remembering. Position only - see
		// RPWindowLayout_Register.
		if (ownWindow)
		{
			RPWindowLayout_Register(window, paneTitle, False);
		}
	}
	else
	{
		DEBUG_STREAM << "MFDSplitView: window creation failed (" << title << ")\n" << std::flush;
	}
}

MFDSplitView::~MFDSplitView()
{
	Check_Pointer(this);

	// out of the layout registry before the window goes
	RPWindowLayout_Forget(window);

	if (window != NULL)
	{
		SetWindowLongPtrA((HWND) window, GWLP_USERDATA, 0);
		DestroyWindow((HWND) window);
		window = NULL;
	}
	delete (BITMAPINFOHEADER *) blitHeader;
	blitHeader = NULL;
	delete [] pixels;
	pixels = NULL;
}

Logical
	MFDSplitView::TestInstance() const
{
	return pixels != NULL;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Join the saved layout. Registering twice just refreshes the entry, so
// a window re-registering after being rebuilt is harmless.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	RPWindowLayout_Register(void *window, const char *title, Logical with_size)
{
	if (window == NULL || title == NULL)
	{
		return;
	}
	for (int i = 0; i < layoutWindowCount; ++i)
	{
		if (layoutWindows[i].window == window)
		{
			layoutWindows[i].title = title;
			layoutWindows[i].withSize = with_size;
			return;
		}
	}
	if (layoutWindowCount >= maxLayoutWindows)
	{
		return;
	}
	layoutWindows[layoutWindowCount].window = window;
	layoutWindows[layoutWindowCount].title = title;
	layoutWindows[layoutWindowCount].withSize = with_size;
	layoutWindows[layoutWindowCount].bare = False;
	++layoutWindowCount;
}

void
	RPWindowLayout_Forget(void *window)
{
	for (int i = 0; i < layoutWindowCount; ++i)
	{
		if (layoutWindows[i].window == window)
		{
			layoutWindows[i] = layoutWindows[--layoutWindowCount];
			layoutWindows[layoutWindowCount].window = NULL;
			layoutWindows[layoutWindowCount].title = NULL;
			return;
		}
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Sticky placement: restore the saved placement over the computed one.
//
// Called once everything has been built and placed, so a window the file
// does not mention simply keeps where it was put. Size comes back only
// for windows registered with it - see the header.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	RPWindowLayout_Load()
{
	if (LayoutMode() == LayoutOff || layoutWindowCount == 0)
	{
		return;
	}

	FILE *file = fopen(layoutFileName, "rt");
	if (file == NULL)
	{
		DEBUG_STREAM << "MFDLayout: no " << layoutFileName
			<< " yet (using the computed arrangement)\n" << std::flush;
		return;
	}

	int restored = 0;
	char line[256];
	while (fgets(line, sizeof(line), file) != NULL)
	{
		char *cursor = line;
		while (*cursor == ' ' || *cursor == '\t')
		{
			++cursor;
		}
		if (*cursor == '#' || *cursor == '\r' || *cursor == '\n' || *cursor == '\0')
		{
			continue;
		}

		// titles carry no '=', so the first one splits the line
		char *equals = strchr(cursor, '=');
		if (equals == NULL)
		{
			continue;
		}
		*equals = '\0';
		char *end = equals;
		while (end > cursor && (end[-1] == ' ' || end[-1] == '\t'))
		{
			--end;
		}
		*end = '\0';

		int x = 0, y = 0, w = 0, h = 0;
		if (sscanf(equals + 1, "%d,%d,%d,%d", &x, &y, &w, &h) < 2)
		{
			continue;
		}

		//
		// Optional trailing keyword, "x,y,w,h,noframe". Searched for
		// rather than parsed in place so a hand-edited line survives a
		// stray space or a missing comma.
		//
		Logical bare = (strstr(equals + 1, "noframe") != NULL) ? True : False;

		for (int i = 0; i < layoutWindowCount; ++i)
		{
			LayoutWindow &entry = layoutWindows[i];
			if (entry.title == NULL || strcmp(entry.title, cursor) != 0)
			{
				continue;
			}
			HWND target = (HWND) entry.window;
			if (target == NULL || !IsWindow(target))
			{
				break;
			}

			// a saved size of nothing is a corrupt line, not an instruction
			Logical size_it = (entry.withSize && w > 0 && h > 0) ? True : False;

			//
			// Saved coordinates belong to whatever monitors were plugged
			// in that day. If the placement lands on none of today's, drop
			// it and keep the computed one - restoring the game window
			// somewhere invisible would leave nothing to drag back.
			//
			RECT landing;
			landing.left = x;
			landing.top = y;
			if (size_it)
			{
				landing.right = x + w;
				landing.bottom = y + h;
			}
			else
			{
				RECT current;
				if (!GetWindowRect(target, &current))
				{
					break;
				}
				landing.right = x + (current.right - current.left);
				landing.bottom = y + (current.bottom - current.top);
			}
			if (MonitorFromRect(&landing, MONITOR_DEFAULTTONULL) == NULL)
			{
				DEBUG_STREAM << "MFDLayout: " << cursor << " was saved at "
					<< x << "," << y << " - off every monitor now, ignoring\n"
					<< std::flush;
				break;
			}

			//
			// Frame off, if the line asked. A bare window's rect IS its
			// client rect, so whatever client area it has now is the size
			// to hand back - which also makes the round trip stable: once
			// bare, what Save records is already the client.
			//
			entry.bare = bare;
			if (bare)
			{
				if (!size_it)
				{
					RECT client;
					if (GetClientRect(target, &client))
					{
						w = client.right;
						h = client.bottom;
						size_it = True;
					}
				}
				StripWindowFrame(target);
			}

			SetWindowPos(target, NULL, x, y, w, h,
				SWP_NOZORDER | SWP_NOACTIVATE |
				(size_it ? 0 : SWP_NOSIZE) | (bare ? SWP_FRAMECHANGED : 0));
			++restored;
			break;
		}
	}
	fclose(file);

	DEBUG_STREAM << "MFDLayout: restored " << restored << " window placement(s) from "
		<< layoutFileName << "\n" << std::flush;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Write where every registered window currently is. The whole file each
// time - it is a handful of lines, and a rewrite leaves nothing
// half-written for a hard kill to catch.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	RPWindowLayout_Save()
{
	if (LayoutMode() != LayoutSave || layoutWindowCount == 0)
	{
		return;
	}

	FILE *file = fopen(layoutFileName, "wt");
	if (file == NULL)
	{
		DEBUG_STREAM << "MFDLayout: could not write " << layoutFileName
			<< "\n" << std::flush;
		return;
	}

	fputs("# RP412 window placement. RP412MFDLAYOUT=save writes this on each\n"
		  "# finished drag and on exit; =load restores it.\n"
		  "# <title>=<x>,<y>,<w>,<h>. The game window gets its size back too;\n"
		  "# for the display panes the size is reference only, since theirs\n"
		  "# follows their content.\n"
		  "# Append ,noframe to take that window's title bar and border off.\n"
		  "# Put it where you want it first - a bare window has nothing to\n"
		  "# drag by, so its line stops changing once the frame is gone.\n", file);

	int wrote = 0;
	for (int i = 0; i < layoutWindowCount; ++i)
	{
		LayoutWindow &entry = layoutWindows[i];
		if (entry.title == NULL)
		{
			continue;
		}
		HWND entry_window = (HWND) entry.window;
		if (entry_window == NULL || !IsWindow(entry_window))
		{
			continue;
		}
		//
		// rcNormalPosition rather than GetWindowRect: a minimised window
		// reports a nonsense rect and a maximised one reports the screen,
		// and neither is what to come back to. This is the restored
		// placement whatever state the window is in, so quitting from
		// maximised still records where the window will reappear.
		//
		WINDOWPLACEMENT placement;
		memset(&placement, 0, sizeof(placement));
		placement.length = sizeof(placement);
		if (!GetWindowPlacement(entry_window, &placement))
		{
			continue;
		}
		RECT bounds = placement.rcNormalPosition;
		// the flag is the player's instruction, not something measured off
		// the window, so a rewrite has to carry it back out
		fprintf(file, "%s=%ld,%ld,%ld,%ld%s\n", entry.title,
			(long) bounds.left, (long) bounds.top,
			(long) (bounds.right - bounds.left),
			(long) (bounds.bottom - bounds.top),
			entry.bare ? ",noframe" : "");
		++wrote;
	}
	fclose(file);

	DEBUG_STREAM << "MFDLayout: saved " << wrote << " window placement(s) to "
		<< layoutFileName << "\n" << std::flush;
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Compute the display rectangle and the button rectangles, growing the
// client area to make room for the strips.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	MFDSplitView::LayoutButtons(
		ButtonStyle button_style,
		int anchorA,
		int anchorB,
		int display_width,
		int display_height,
		int *client_width,
		int *client_height
	)
{
	displayX = 0;
	displayY = 0;
	displayW = display_width;
	displayH = display_height;

	if (button_style == MFDStrips)
	{
		//-----------------------------------------------------------
		// 4 buttons above the glass, 4 below, flush to the pane
		// edges (no border against the viewscreen behind); RIO
		// addresses descend from the anchor, row-major (vRIO
		// CockpitLayout::Mfd).
		//
		// The banks reach BEHIND the display: each button is
		// mfdButtonHeight tall but only mfdIndicatorStrip of it
		// clears the glass edge, so the lamp reads as a thin strip
		// and the picture above/below it is the click target. Paint
		// draws the buttons before the MFD image for that reason.
		//-----------------------------------------------------------
		int strip_h = indicatorStrip;
		int button_h = buttonDepth;

		// on a scaled-down cockpit, keep the two banks from meeting
		int limit = (display_height + 2 * strip_h) / 2;
		if (button_h > limit)		button_h = limit;
		if (button_h < strip_h)		button_h = strip_h;

		int button_w = (display_width - 3 * buttonGap) / 4;

		displayY = strip_h;
		*client_height = display_height + 2 * strip_h;

		for (int i = 0; i < 8; ++i)
		{
			int row = i / 4;			// 0 = top bank, 1 = bottom bank
			int col = i % 4;

			ScreenButton *button = &buttons[buttonCount++];
			button->unit = anchorA - i;
			button->amber = 0;
			button->x = col * (button_w + buttonGap);
			button->y = row
				? (displayY + displayH + strip_h - button_h)
				: 0;
			button->w = (col == 3)
				? (display_width - 3 * (button_w + buttonGap))
				: button_w;
			button->h = button_h;
		}
	}
	else if (button_style == SideColumns)
	{
		//-----------------------------------------------------------
		// 6 buttons down each side of the glass (the pod wires only 6
		// of each column's 8 addresses to buttons; the rest are Tesla
		// relays). Left = anchorA ascending, right = anchorB.
		//
		// Same trick as the MFD banks, turned on its side: each
		// button reaches buttonDepth in behind the map and leaves an
		// indicatorStrip clearing the edge, so the lamp reads as a
		// slim column and the map itself is the click target.
		//-----------------------------------------------------------
		int strip_w = indicatorStrip;
		int button_w = buttonDepth;

		// keep the two columns from meeting behind a narrow map
		int limit = (display_width + 2 * strip_w) / 2;
		if (button_w > limit)		button_w = limit;
		if (button_w < strip_w)		button_w = strip_w;

		displayX = strip_w;
		*client_width = display_width + 2 * strip_w;

		for (int i = 0; i < 6; ++i)
		{
			//-------------------------------------------------------
			// Take top and bottom off the legend grid separately and
			// subtract: scaling a height directly would let rounding
			// drift the buttons out of step with the labels.
			//-------------------------------------------------------
			int cell_top = mapLegendTop + i * mapLegendPitch;
			int top = (cell_top * display_height) / mapLegendSpan;
			int bottom =
				((cell_top + mapLegendCell) * display_height) / mapLegendSpan;
			int h = bottom - top;
			if (h < 1)	h = 1;

			ScreenButton *left = &buttons[buttonCount++];
			left->unit = anchorA + i;
			left->amber = 1;
			left->x = 0;
			left->y = top;
			left->w = button_w;
			left->h = h;

			ScreenButton *right = &buttons[buttonCount++];
			right->unit = anchorB + i;
			right->amber = 1;
			right->x = displayX + displayW + strip_w - button_w;
			right->y = left->y;
			right->w = button_w;
			right->h = h;
		}
	}
}

void
	MFDSplitView::Paint()
{
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint((HWND) window, &ps);

	RECT client;
	GetClientRect((HWND) window, &client);

	//---------------------------------------------------------------
	// Double-buffered: compose the whole pane off screen and blit it
	// in one operation, otherwise the clear-then-draw sequence
	// flickers at the repaint cadence.
	//---------------------------------------------------------------
	HDC mem = CreateCompatibleDC(hdc);
	HBITMAP surface = CreateCompatibleBitmap(hdc, client.right, client.bottom);
	HBITMAP old_surface = (HBITMAP) SelectObject(mem, surface);

	FillRect(mem, &client, (HBRUSH) GetStockObject(BLACK_BRUSH));

	//---------------------------------------------------------------
	// Buttons first, glass second: the MFD banks extend under the
	// display so the picture is the click target, and the image
	// painted over them leaves only their indicator strips showing.
	//---------------------------------------------------------------
	for (int i = 0; i < buttonCount; ++i)
	{
		const ScreenButton *button = &buttons[i];

		int level = PadRIO::IsActive()
			? LampLevel(PadRIO::GetLampState(button->unit))
			: 0;

		RECT rect;
		rect.left = button->x;
		rect.top = button->y;
		rect.right = button->x + button->w;
		rect.bottom = button->y + button->h;

		HBRUSH fill = CreateSolidBrush(ButtonFill(button->amber, level));
		FillRect(mem, &rect, fill);
		DeleteObject(fill);

		HBRUSH frame = CreateSolidBrush(
			(i == pressedIndex) ? RGB(255, 255, 255) : RGB(48, 48, 48));
		FrameRect(mem, &rect, frame);
		DeleteObject(frame);
	}

	// HALFTONE area-averages the downscale (the compact glass shows the
	// full 640x480 canvas at ~half size); COLORONCOLOR dropped every other
	// row/column, which shredded the 1-bit vector strokes and small text.
	// HALFTONE requires the brush origin be set (MSDN).
	SetStretchBltMode(mem, HALFTONE);
	SetBrushOrgEx(mem, 0, 0, NULL);
	StretchDIBits(
		mem,
		displayX, displayY, displayW, displayH,
		0, 0, sourceWidth, sourceHeight,
		pixels, (const BITMAPINFO *) blitHeader,
		DIB_RGB_COLORS, SRCCOPY
	);

	BitBlt(hdc, 0, 0, client.right, client.bottom, mem, 0, 0, SRCCOPY);

	SelectObject(mem, old_surface);
	DeleteObject(surface);
	DeleteDC(mem);

	EndPaint((HWND) window, &ps);
}

void
	MFDSplitView::MouseDown(int x, int y)
{
	for (int i = 0; i < buttonCount; ++i)
	{
		const ScreenButton *button = &buttons[i];
		if (x >= button->x && x < button->x + button->w &&
			y >= button->y && y < button->y + button->h)
		{
			pressedIndex = i;
			SetCapture((HWND) window);
			PadRIO::SetScreenButton(button->unit, True);
			Repaint();
			return;
		}
	}
}

void
	MFDSplitView::MouseUp()
{
	if (pressedIndex >= 0)
	{
		PadRIO::SetScreenButton(buttons[pressedIndex].unit, False);
		pressedIndex = -1;
		if (GetCapture() == (HWND) window)
		{
			ReleaseCapture();
		}
		Repaint();
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Paint NOW rather than queueing an invalidation: the game loop pumps
// one message per frame, and queued WM_PAINTs (lowest priority) starve
// behind it - panes would freeze on their first frame.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Off screen, and stay off: the pane keeps its pixels and geometry, it
// simply stops being shown. Repaint() on a hidden window is harmless.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	MFDSplitView::Hide()
{
	Check_Pointer(this);
	if (window != NULL)
	{
		ShowWindow((HWND) window, SW_HIDE);
	}
}

void
	MFDSplitView::SetPosition(int x, int y)
{
	Check_Pointer(this);
	if (window != NULL)
	{
		SetWindowPos((HWND) window, NULL, x, y, 0, 0,
			SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
	}
}

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Re-scale to a new glass size: the pixel buffer keeps its native
// source resolution, so only the destination rectangle, the button
// geometry and the window size change.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
	MFDSplitView::Resize(int display_width, int display_height)
{
	Check_Pointer(this);

	buttonCount = 0;
	pressedIndex = -1;

	int client_width = display_width;
	int client_height = display_height;
	LayoutButtons(buttonStyle, buttonAnchorA, buttonAnchorB,
		display_width, display_height, &client_width, &client_height);
	clientWidth = client_width;
	clientHeight = client_height;

	if (window != NULL)
	{
		SetWindowPos((HWND) window, NULL, 0, 0,
			client_width, client_height,
			SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
	}
}

void
	MFDSplitView::Repaint()
{
	Check_Pointer(this);
	if (window != NULL)
	{
		RedrawWindow((HWND) window, NULL, NULL,
			RDW_INVALIDATE | RDW_UPDATENOW);
	}
}
