#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" "#