#include "mungal4.h" #pragma hdrstop #include "l4mfdview.h" #include "l4padrio.h" namespace { const char mfdViewClass[] = "RPMFDView"; const int buttonGap = 4; //--------------------------------------------------------------- // 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; } 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_CLOSE: // Part of the cockpit; just hide it. ShowWindow(hwnd, SW_HIDE); return 0; case WM_ERASEBKGND: return 1; } 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 ) { Check_Pointer(this); sourceWidth = source_width; sourceHeight = source_height; buttonCount = 0; pressedIndex = -1; 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; int client_width = display_width; int client_height = display_height; LayoutButtons(button_style, anchorA, anchorB, display_width, display_height, &client_width, &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 = 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 = CreateWindowExA( 0, mfdViewClass, title, style, x, y, bounds.right - bounds.left, bounds.bottom - bounds.top, NULL, NULL, instance, NULL ); if (window != NULL) { SetWindowLongPtrA((HWND) window, GWLP_USERDATA, (LONG_PTR) this); ShowWindow((HWND) window, SW_SHOWNOACTIVATE); } else { DEBUG_STREAM << "MFDSplitView: window creation failed (" << title << ")\n" << std::flush; } } MFDSplitView::~MFDSplitView() { Check_Pointer(this); 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; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 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; RIO addresses descend // from the anchor, row-major (vRIO CockpitLayout::Mfd). //----------------------------------------------------------- int strip_h = display_height / 8; if (strip_h < 18) strip_h = 18; if (strip_h > 40) strip_h = 40; int button_w = (display_width - 5 * buttonGap) / 4; displayY = strip_h + 2 * buttonGap; *client_height = display_height + 2 * (strip_h + 2 * buttonGap); for (int i = 0; i < 8; ++i) { int row = i / 4; // 0 = top strip, 1 = bottom strip int col = i % 4; ScreenButton *button = &buttons[buttonCount++]; button->unit = anchorA - i; button->amber = 0; button->x = buttonGap + col * (button_w + buttonGap); button->y = row ? (displayY + displayH + buttonGap) : buttonGap; button->w = button_w; button->h = strip_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. //----------------------------------------------------------- int col_w = display_width / 8; if (col_w < 20) col_w = 20; if (col_w > 40) col_w = 40; int button_h = (display_height - 7 * buttonGap) / 6; displayX = col_w + 2 * buttonGap; *client_width = display_width + 2 * (col_w + 2 * buttonGap); for (int i = 0; i < 6; ++i) { ScreenButton *left = &buttons[buttonCount++]; left->unit = anchorA + i; left->amber = 1; left->x = buttonGap; left->y = buttonGap + i * (button_h + buttonGap); left->w = col_w; left->h = button_h; ScreenButton *right = &buttons[buttonCount++]; right->unit = anchorB + i; right->amber = 1; right->x = displayX + displayW + buttonGap; right->y = left->y; right->w = col_w; right->h = button_h; } } } void MFDSplitView::Paint() { PAINTSTRUCT ps; HDC hdc = BeginPaint((HWND) window, &ps); RECT client; GetClientRect((HWND) window, &client); FillRect(hdc, &client, (HBRUSH) GetStockObject(BLACK_BRUSH)); SetStretchBltMode(hdc, COLORONCOLOR); StretchDIBits( hdc, displayX, displayY, displayW, displayH, 0, 0, sourceWidth, sourceHeight, pixels, (const BITMAPINFO *) blitHeader, DIB_RGB_COLORS, SRCCOPY ); 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(hdc, &rect, fill); DeleteObject(fill); HBRUSH frame = CreateSolidBrush( (i == pressedIndex) ? RGB(255, 255, 255) : RGB(48, 48, 48)); FrameRect(hdc, &rect, frame); DeleteObject(frame); } 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); InvalidateRect((HWND) window, NULL, FALSE); return; } } } void MFDSplitView::MouseUp() { if (pressedIndex >= 0) { PadRIO::SetScreenButton(buttons[pressedIndex].unit, False); pressedIndex = -1; if (GetCapture() == (HWND) window) { ReleaseCapture(); } InvalidateRect((HWND) window, NULL, FALSE); } } void MFDSplitView::Repaint() { Check_Pointer(this); if (window != NULL) { InvalidateRect((HWND) window, NULL, FALSE); } }