Ported from BT411's BT_GLASS_LAYOUT (29c502d).
The exploded view's panes are draggable desktop windows, but the
arrangement is recomputed on every launch, so dragging one somewhere
useful never survived the menu-race-menu loop.
RP412MFDLAYOUT persists it to mfd_layout.cfg beside bindings.txt:
off / 0 / unset computed arrangement only, no file (default)
load / restore restore saved positions at startup, never write
save / adjust restore, then rewrite on each finished drag
(WM_EXITSIZEMOVE) and on teardown
One "<title>=x,y,w,h" line per pane. Position is restored and the size
read and discarded: a pane's size follows its content and its button
banks, so letting an old size back in would misshape it after any
geometry change - and this port has changed that geometry twice already.
Load runs after the computed arrangement rather than instead of it, so a
pane the file does not mention simply keeps its computed spot. Only the
exploded view registers: the composited cockpit's panes are chrome-less
children with nothing to drag, so they have no position worth keeping.
RP412 needs no equivalent of BT411's "restored" flag. Its re-snap is
LayoutCockpit on WM_SIZE, which only runs in cockpit mode, so nothing
comes back later to overwrite a hand-placed window.
Verified by round trip: dragged Map to 777,333 in save mode, the file
took all six panes, and a fresh launch in load mode put it physically
back at 777,333. The harness also resized the window while moving it,
which incidentally proved the saved size really is ignored - the pane
came back correctly sized from a cfg that recorded 136x39.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
764 lines
21 KiB
C++
764 lines
21 KiB
C++
#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";
|
|
|
|
// the panes that own a desktop window, so Save can walk them
|
|
enum { maxLayoutPanes = 8 };
|
|
MFDSplitView *layoutPanes[maxLayoutPanes];
|
|
int layoutPaneCount = 0;
|
|
|
|
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.
|
|
//
|
|
MFDSplitView_SaveLayout();
|
|
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 position worth remembering
|
|
if (ownWindow && layoutPaneCount < maxLayoutPanes)
|
|
{
|
|
layoutPanes[layoutPaneCount++] = this;
|
|
}
|
|
}
|
|
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
|
|
for (int i = 0; i < layoutPaneCount; ++i)
|
|
{
|
|
if (layoutPanes[i] == this)
|
|
{
|
|
layoutPanes[i] = layoutPanes[--layoutPaneCount];
|
|
layoutPanes[layoutPaneCount] = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Sticky placement: restore saved positions over the computed ones.
|
|
//
|
|
// Called after the exploded view has built and placed every pane, so a
|
|
// pane the file does not mention simply keeps where the layout put it.
|
|
// Position only - the saved size is read and discarded, because a pane's
|
|
// size comes from its content and its button banks, and letting an old
|
|
// w,h back in would misshape it after any geometry change.
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
void
|
|
MFDSplitView_LoadLayout()
|
|
{
|
|
if (LayoutMode() == LayoutOff || layoutPaneCount == 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;
|
|
}
|
|
|
|
for (int i = 0; i < layoutPaneCount; ++i)
|
|
{
|
|
MFDSplitView *pane = layoutPanes[i];
|
|
if (pane != NULL && pane->Title() != NULL &&
|
|
!strcmp(pane->Title(), cursor))
|
|
{
|
|
pane->SetPosition(x, y);
|
|
++restored;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
fclose(file);
|
|
|
|
DEBUG_STREAM << "MFDLayout: restored " << restored << " pane position(s) from "
|
|
<< layoutFileName << "\n" << std::flush;
|
|
}
|
|
|
|
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
// Write where every open pane 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
|
|
MFDSplitView_SaveLayout()
|
|
{
|
|
if (LayoutMode() != LayoutSave || layoutPaneCount == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
FILE *file = fopen(layoutFileName, "wt");
|
|
if (file == NULL)
|
|
{
|
|
DEBUG_STREAM << "MFDLayout: could not write " << layoutFileName
|
|
<< "\n" << std::flush;
|
|
return;
|
|
}
|
|
|
|
fputs("# RP412 exploded-view pane placement. RP412MFDLAYOUT=save writes\n"
|
|
"# this on each finished drag and on exit; =load restores it.\n"
|
|
"# <title>=<x>,<y>,<w>,<h> - the size is written for reference and\n"
|
|
"# ignored on load.\n", file);
|
|
|
|
int wrote = 0;
|
|
for (int i = 0; i < layoutPaneCount; ++i)
|
|
{
|
|
MFDSplitView *pane = layoutPanes[i];
|
|
if (pane == NULL || pane->Title() == NULL)
|
|
{
|
|
continue;
|
|
}
|
|
HWND pane_window = (HWND) pane->Window();
|
|
if (pane_window == NULL || !IsWindow(pane_window))
|
|
{
|
|
continue;
|
|
}
|
|
RECT bounds;
|
|
if (!GetWindowRect(pane_window, &bounds))
|
|
{
|
|
continue;
|
|
}
|
|
fprintf(file, "%s=%ld,%ld,%ld,%ld\n", pane->Title(),
|
|
(long) bounds.left, (long) bounds.top,
|
|
(long) (bounds.right - bounds.left),
|
|
(long) (bounds.bottom - bounds.top));
|
|
++wrote;
|
|
}
|
|
fclose(file);
|
|
|
|
DEBUG_STREAM << "MFDLayout: saved " << wrote << " pane position(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);
|
|
}
|
|
}
|