From 4232a4ccfefc046bc69aef2fba0ad320d5b74475 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Sun, 19 Jan 2025 14:53:19 +0100 Subject: [PATCH 1/8] Move ILS visualization logic out of window class --- ILS_Window_Plugin/ILS_Window_Plugin.vcxproj | 2 + .../ILS_Window_Plugin.vcxproj.filters | 6 + ILS_Window_Plugin/IWTitleBar.cpp | 4 +- ILS_Window_Plugin/IWTitleBar.h | 2 +- ILS_Window_Plugin/IWVisualization.cpp | 457 ++++++++++++++++++ ILS_Window_Plugin/IWVisualization.h | 87 ++++ ILS_Window_Plugin/IWWindow.cpp | 412 ++-------------- ILS_Window_Plugin/IWWindow.h | 66 +-- 8 files changed, 604 insertions(+), 432 deletions(-) create mode 100644 ILS_Window_Plugin/IWVisualization.cpp create mode 100644 ILS_Window_Plugin/IWVisualization.h diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj index c7ba914..64ae172 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj @@ -123,6 +123,7 @@ + Create Create @@ -145,6 +146,7 @@ + diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters index 99de3c5..7e3f404 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters @@ -42,6 +42,9 @@ Source Files + + Source Files + @@ -100,6 +103,9 @@ Header Files + + Header Files + diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index 9192654..0b56c8c 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -17,7 +17,7 @@ IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, COLORREF tex this->outerFramePen.CreatePen(PS_SOLID, 1, outerFrameColor); this->text = title; - this->euroScopeFont.CreatePointFont(110, _T("EuroScope")); + this->font.CreatePointFont(110, _T("EuroScope")); this->eventListener = listener; } @@ -44,7 +44,7 @@ void IWTitleBar::OnPaint() CRect rect; GetClientRect(&rect); // Get the client area of the control - auto oldFont = dc.SelectObject(this->euroScopeFont); + auto oldFont = dc.SelectObject(this->font); // Fill the background with your custom color dc.FillSolidRect(rect, this->backgroundColor); // Dark background diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index 935ae04..8198ee0 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -38,7 +38,7 @@ class IWTitleBar : public CStatic { IWCloseBtn closeButton; IWMenuBtn menuButton; IWResizeBtn resizeButton; - CFont euroScopeFont; + CFont font; COLORREF backgroundColor; COLORREF textColor; diff --git a/ILS_Window_Plugin/IWVisualization.cpp b/ILS_Window_Plugin/IWVisualization.cpp new file mode 100644 index 0000000..1478df1 --- /dev/null +++ b/ILS_Window_Plugin/IWVisualization.cpp @@ -0,0 +1,457 @@ +#include "pch.h" +#include "IWVisualization.h" +#include + +BEGIN_MESSAGE_MAP(IWVisualization, CWnd) + ON_WM_PAINT() + ON_WM_SIZE() + ON_WM_MOVE() + ON_WM_LBUTTONDOWN() + ON_WM_ERASEBKGND() + ON_WM_MOUSEWHEEL() + ON_WM_TIMER() +END_MESSAGE_MAP() + +IWVisualization::IWVisualization(IWApproachDefinition selectedApproach, IWStyling styling, CFont* font) +{ + this->selectedApproach = selectedApproach; + this->approachLength = selectedApproach.defaultRange; + + this->leftToRight = selectedApproach.localizerCourse > 0 && selectedApproach.localizerCourse < 180; + this->showTagsByDefault = styling.showTagByDefault; + this->applyTemperatureCorrection = true; + this->tagMode = styling.defaultTagMode; + + this->rangeStatusTextColor = RGB(styling.rangeStatusTextColor.r, styling.rangeStatusTextColor.g, styling.rangeStatusTextColor.b); + this->windowBackground = RGB(styling.backgroundColor.r, styling.backgroundColor.g, styling.backgroundColor.b); + this->targetLabelColor = RGB(styling.targetLabelColor.r, styling.targetLabelColor.g, styling.targetLabelColor.b); + this->glideSlopePen.CreatePen(PS_SOLID, 1, RGB(styling.glideslopeColor.r, styling.glideslopeColor.g, styling.glideslopeColor.b)); + this->localizerBrush.CreateSolidBrush(RGB(styling.localizerColor.r, styling.localizerColor.g, styling.localizerColor.b)); + this->radarTargetPen.CreatePen(PS_SOLID, 1, RGB(styling.radarTargetColor.r, styling.radarTargetColor.g, styling.radarTargetColor.b)); + this->historyTrailPen.CreatePen(PS_SOLID, 1, RGB(styling.historyTrailColor.r, styling.historyTrailColor.g, styling.historyTrailColor.b)); + + this->font = font; + float fontPointsSize = styling.fontSize * 72 / 96; +} + + +void IWVisualization::OnPaint() +{ + CPaintDC dc(this); // Device context for painting + + // Create a memory DC for double buffering + CRect rect; + GetClientRect(&rect); + CDC memDC; + memDC.CreateCompatibleDC(&dc); + + CBitmap bufferBitmap; + bufferBitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height()); + CBitmap* pOldBitmap = memDC.SelectObject(&bufferBitmap); + + // Clear the background + memDC.FillSolidRect(rect, windowBackground); + + // Select font + CFont* oldFont = memDC.SelectObject(font); + + // Perform all drawing operations on memDC instead of dc + DrawGlideslopeAndLocalizer(memDC); + DrawRadarTargets(memDC); + if (showCurrentZoomValue) { + DrawCurrentZoomValue(memDC); + } + + // Restore the font + memDC.SelectObject(oldFont); + + // Copy the buffer to the screen + dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY); + + // Cleanup + memDC.SelectObject(pOldBitmap); +} + +void IWVisualization::DrawGlideslopeAndLocalizer(CDC& dc) +{ + // Draw glideslope line + CPen* pOldPen = dc.SelectObject(&glideSlopePen); + dc.MoveTo(glidePathTop); + dc.LineTo(glidePathBottom); + dc.SelectObject(pOldPen); + + // Draw localizer distance dots + CBrush* pOldBrush = dc.SelectObject(&localizerBrush); + for (int i = 0; i <= approachLength; i++) // Draw distance points. Every 5th point is large + { + int radius = i % 5 == 0 ? 4 : 2; + int x = glidePathBottom.x - i * pixelsPerNauticalMile; + int y = glidePathBottom.y; + + DrawDiamond(CPoint(x, y), radius, dc); + } + dc.SelectObject(pOldBrush); +} + +void IWVisualization::DrawRadarTargets(CDC& dc) +{ + if (m_latestLiveData == NULL) return; + + // Draw the radar targets + for (const IWRadarTarget& radarTarget : m_latestLiveData->radarTargets) + { + auto it = radarTarget.positionHistory.rbegin(); + auto end = radarTarget.positionHistory.rend(); + + // Draw the history trail in reverese so the newest dots appear on top + for (auto it = radarTarget.positionHistory.rbegin(); it != radarTarget.positionHistory.rend(); ++it) { + const IWTargetPosition& position = *it; + bool isNewestPosition = (it + 1 == end); // Check if the iterator is the last element + + CPoint ptTopView, ptSideView; + if (!CalculateTargetCoordinates(position, ptTopView, ptSideView)) { + // Target is outside the visible area + continue; + } + + int crossRadius = isNewestPosition ? TARGET_RADIUS : HISTORY_TRAIL_RADIUS; + + // Draw position seen from the side + dc.SelectObject(isNewestPosition ? radarTargetPen : historyTrailPen); + dc.MoveTo(CPoint(ptSideView.x, ptSideView.y - crossRadius)); + dc.LineTo(CPoint(ptSideView.x, ptSideView.y + crossRadius + 1)); + dc.MoveTo(CPoint(ptSideView.x - crossRadius, ptSideView.y)); + dc.LineTo(CPoint(ptSideView.x + crossRadius + 1, ptSideView.y)); + + if (isNewestPosition) { + dc.SelectStockObject(NULL_BRUSH); + dc.Ellipse(ptSideView.x - crossRadius, ptSideView.y - crossRadius, ptSideView.x + crossRadius + 1, ptSideView.y + crossRadius + 1); + } + + // Draw position seen from above + dc.SelectObject(isNewestPosition ? radarTargetPen : historyTrailPen); + dc.MoveTo(CPoint(ptTopView.x, ptTopView.y - crossRadius)); + dc.LineTo(CPoint(ptTopView.x, ptTopView.y + crossRadius + 1)); + dc.MoveTo(CPoint(ptTopView.x - crossRadius, ptTopView.y)); + dc.LineTo(CPoint(ptTopView.x + crossRadius + 1, ptTopView.y)); + + // Draw the main target + if (isNewestPosition) { + dc.SelectStockObject(NULL_BRUSH); + dc.Ellipse(ptTopView.x - crossRadius, ptTopView.y - crossRadius, ptTopView.x + crossRadius + 1, ptTopView.y + crossRadius + 1); + + // Check if callsign is in the vector of clicked targets + bool isClicked = this->clickedTargets.find(radarTarget.callsign) != this->clickedTargets.end(); + + if (this->showTagsByDefault) { + if (isClicked) continue; + } + else { + if (!isClicked) continue; + } + + dc.MoveTo(ptTopView); + dc.LineTo(ptTopView.x + LABEL_OFFSET, ptTopView.y + LABEL_OFFSET); + + // Callsign label + dc.SetTextColor(targetLabelColor); + dc.SetBkMode(TRANSPARENT); + + + CString targetLabel; + if (this->tagMode == IWTagMode::Squawk) { + targetLabel.Format(_T("%s"), radarTarget.squawk.c_str()); + } + else if (this->tagMode == IWTagMode::Callsign) { + targetLabel.Format(_T("%s"), radarTarget.callsign.c_str()); + } + + // Draw the label + CSize textSize = dc.GetTextExtent(targetLabel); + CPoint labelPosition(ptTopView.x + LABEL_OFFSET, ptTopView.y + LABEL_OFFSET); + CRect labelRect(labelPosition.x, labelPosition.y, labelPosition.x + textSize.cx, labelPosition.y + textSize.cy * 2); + dc.DrawText(targetLabel, labelRect, DT_LEFT); + } + } + } +} + +void IWVisualization::DrawCurrentZoomValue(CDC& dc) +{ + CRect rect; + GetClientRect(&rect); + + dc.SetTextColor(this->rangeStatusTextColor); + dc.SetBkMode(TRANSPARENT); + + CString zoomMessage; + zoomMessage.Format(_T("%d NM"), this->approachLength); + + CSize textSize = dc.GetTextExtent(zoomMessage); + CPoint position( + this->leftToRight ? rect.right - textSize.cx - 5 : rect.left + 5, + rect.top + 5 + ); + CRect textRect(position.x, position.y, position.x + textSize.cx, position.y + textSize.cy * 2); + + dc.DrawText(zoomMessage, textRect, this->leftToRight ? DT_RIGHT : DT_LEFT); +} + +void IWVisualization::SetActiveApproach(IWApproachDefinition selectedApproach) +{ + this->selectedApproach = selectedApproach; + this->approachLength = selectedApproach.defaultRange; + this->leftToRight = selectedApproach.localizerCourse > 0 && selectedApproach.localizerCourse < 180; + + UpdateDimentions(); + Invalidate(); +} + +void IWVisualization::SetLatestLiveData(IWLiveData* liveData) +{ + this->m_latestLiveData = liveData; + Invalidate(); +} + +void IWVisualization::DrawDiamond(CPoint pt, int radius, CDC& dc) +{ + CPoint pts[5]; + pts[0] = CPoint(pt.x, pt.y - radius); + pts[1] = CPoint(pt.x + radius, pt.y); + pts[2] = CPoint(pt.x, pt.y + radius); + pts[3] = CPoint(pt.x - radius, pt.y); + pts[4] = CPoint(pt.x, pt.y - radius); + + dc.SetPolyFillMode(ALTERNATE); + dc.Polygon(pts, 5); +} + +BOOL IWVisualization::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) +{ + if (zDelta > 0 && this->approachLength > 5) + { + this->approachLength -= 1; + } + else if (zDelta < 0 && this->approachLength < 50) + { + this->approachLength += 1; + } + + UpdateDimentions(); + Invalidate(); + + SetTimer(zoomMessageTimerId, 1000, nullptr); + + showCurrentZoomValue = true; + + return TRUE; +} + +void IWVisualization::OnSize(UINT nType, int cx, int cy) +{ + CWnd::OnSize(nType, cx, cy); + + if (nType != SIZE_MINIMIZED) + { + UpdateDimentions(); + Invalidate(); + } +} + +bool IWVisualization::CalculateTargetCoordinates(const IWTargetPosition& position, CPoint& ptTopView, CPoint& ptSideView) +{ + // Calculate position relative to the runway + double distanceToThreshold = CalculateDistance(position.latitude, position.longitude, selectedApproach.thresholdLatitude, selectedApproach.thresholdLongitude); + double directionToThreshold = CalculateBearing(position.latitude, position.longitude, selectedApproach.thresholdLatitude, selectedApproach.thresholdLongitude); + double angleDiff = (selectedApproach.localizerCourse - directionToThreshold) / 180.0 * PI; // anglediff in radians + double heightAboveThreshold = position.trueAltitude - selectedApproach.thresholdAltitude; + + auto airportTemperature = m_latestLiveData->airportTemperatures.find(selectedApproach.airport); + if (applyTemperatureCorrection && airportTemperature != m_latestLiveData->airportTemperatures.end()) { + // In newer flight simulators true altitude is affected by the temperature. + // In cold weather aircraft will be shown higher than they actually are, unless we correct for it. + // See: https://forums.flightsimulator.com/t/vatsim-ivao-pilotedge-users-be-aware-of-an-important-bug/426142/468 + int temperatureCorrection = CalculateTemperatureCorrection(position.trueAltitude, selectedApproach.thresholdAltitude, airportTemperature->second); + heightAboveThreshold -= temperatureCorrection; + } + + double projectedDistanceFromThreshold = distanceToThreshold * cos(angleDiff); + double projectedDistanceFromExtendedCenterline = distanceToThreshold * tan(angleDiff); + + if (projectedDistanceFromExtendedCenterline < 0 && abs(projectedDistanceFromExtendedCenterline) > selectedApproach.maxOffsetLeft) { + // Too far left + return false; + } + if (projectedDistanceFromExtendedCenterline > 0 && abs(projectedDistanceFromExtendedCenterline) > selectedApproach.maxOffsetRight) { + // Too far right + return false; + } + if (projectedDistanceFromThreshold < 0) { + // Wrong side of the threshold + return false; + } + if (heightAboveThreshold > approachHeightFt) { + // Too high + return false; + } + + int xPosition = glidePathBottom.x - projectedDistanceFromThreshold * pixelsPerNauticalMile; + int yPositionSlope = glidePathBottom.y - heightAboveThreshold * pixelsPerFt; + int yPositionCenterline = glidePathBottom.y + projectedDistanceFromExtendedCenterline * pixelsPerNauticalMile; + ptSideView = CPoint(xPosition, yPositionSlope); + ptTopView = CPoint(xPosition, yPositionCenterline); + + return true; +} + +void IWVisualization::UpdateDimentions() +{ + CRect rect; + GetClientRect(&rect); + + const int CALC_SIDE_MARGIN = rect.Width() * APP_LINE_MARGIN_SIDES; + const int CALC_TOP_MARGIN = rect.Height() * APP_LINE_MARGIN_TOP; + const int CALC_BOTTOM_MARGIN = rect.Height() * APP_LINE_MARGIN_BOTTOM; + + // Define the start and end coordintes for rendering the glidepath + this->glidePathTop = CPoint(leftToRight ? rect.left + CALC_SIDE_MARGIN : rect.right - CALC_SIDE_MARGIN, rect.top + CALC_TOP_MARGIN); + this->glidePathBottom = CPoint(leftToRight ? rect.right - CALC_SIDE_MARGIN : rect.left + CALC_SIDE_MARGIN, rect.bottom - CALC_BOTTOM_MARGIN); + + this->approachHeightFt = (approachLength * FT_PER_NM * sin(selectedApproach.glideslopeAngle / 180.0 * PI)); + + this->pixelsPerFt = (glidePathBottom.y - glidePathTop.y) / approachHeightFt; + this->pixelsPerNauticalMile = (glidePathBottom.x - glidePathTop.x) / float(approachLength); // PS: negative when direction is left->right +} + +void IWVisualization::OnTimer(UINT_PTR nIDEvent) +{ + if (nIDEvent == zoomMessageTimerId) + { + // Hide the zoom message + showCurrentZoomValue = false; + KillTimer(zoomMessageTimerId); + + // Invalidate to redraw without the message + CRect updateRect; + GetClientRect(&updateRect); + InvalidateRect(updateRect); + } + + CWnd::OnTimer(nIDEvent); +} + +void IWVisualization::OnLButtonDown(UINT nFlags, CPoint point) +{ + for (const IWRadarTarget& radarTarget : m_latestLiveData->radarTargets) { + auto newestPosition = radarTarget.positionHistory.size() > 0 ? &radarTarget.positionHistory.front() : nullptr; + + if (newestPosition) { + const IWTargetPosition& position = *newestPosition; + + CPoint ptTopView, ptSideView; + if (!CalculateTargetCoordinates(position, ptTopView, ptSideView)) { + // Target is outside the visible area + continue; + } + + CRect targetRect(ptTopView.x - TARGET_RADIUS, ptTopView.y - TARGET_RADIUS, ptTopView.x + TARGET_RADIUS, ptTopView.y + TARGET_RADIUS); + + if (targetRect.PtInRect(point)) + { + bool isAlreadyClicked = this->clickedTargets.find(radarTarget.callsign) != this->clickedTargets.end(); + + if (isAlreadyClicked) { + this->clickedTargets.erase(radarTarget.callsign); + } + else { + this->clickedTargets.insert(radarTarget.callsign); + } + CRect updateRect; + GetClientRect(&updateRect); + InvalidateRect(updateRect); + break; + } + } + } + + CWnd::OnLButtonDown(nFlags, point); +} + +double IWVisualization::CalculateDistance(double lat1, double lon1, double lat2, double lon2) { + // Convert latitude and longitude from degrees to radians + lat1 = lat1 * PI / 180.0; + lon1 = lon1 * PI / 180.0; + lat2 = lat2 * PI / 180.0; + lon2 = lon2 * PI / 180.0; + // Haversine formula + double dlat = lat2 - lat1; + double dlon = lon2 - lon1; + double a = pow(sin(dlat / 2), 2) + cos(lat1) * cos(lat2) * pow(sin(dlon / 2), 2); + double c = 2 * atan2(sqrt(a), sqrt(1 - a)); + double distance = EARTH_RADIUS_NM * c; + return distance; +} + +double IWVisualization::CalculateBearing(double lat1, double lon1, double lat2, double lon2) { + // Convert latitude and longitude from degrees to radians + lat1 = lat1 * PI / 180.0; + lon1 = lon1 * PI / 180.0; + lat2 = lat2 * PI / 180.0; + lon2 = lon2 * PI / 180.0; + + // Calculate the bearing + double y = sin(lon2 - lon1) * cos(lat2); + double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2 - lon1); + double bearing = atan2(y, x); + bearing = fmod(bearing + 2 * PI, 2 * PI); + + // Convert to degrees + return bearing * 180.0 / PI; +} + +double IWVisualization::CalculateTemperatureCorrection(int planePressAlt, int airportPressureAlt, double surfTemp) { + double isaDeviation = surfTemp - 15; + + // Formula is from here: https://www.pprune.org/tech-log/573002-accurate-temperature-correction-formula.html + return ((-isaDeviation / -0.0019812) * std::log(1 + (-0.0019812 * planePressAlt) / (288.15 + -0.0019812 * airportPressureAlt))); +} + +BOOL IWVisualization::OnEraseBkgnd(CDC* pDC) +{ + // Do nothing here to prevent background clearing + return TRUE; +} + +const bool IWVisualization::GetLeftToRight() +{ + return this->leftToRight; +} + +const bool IWVisualization::GetShowTagsByDefault() +{ + return this->showTagsByDefault; +} + +const bool IWVisualization::GetApplyTemperatureCorrection() +{ + return this->applyTemperatureCorrection; +} + +void IWVisualization::SetLeftToRight(bool leftToRight) +{ + this->leftToRight = leftToRight; + UpdateDimentions(); + Invalidate(); +} + +void IWVisualization::SetShowTagsByDefault(bool showTagsByDefault) +{ + this->showTagsByDefault = showTagsByDefault; + Invalidate(); +} + +void IWVisualization::SetApplyTemperatureCorrection(bool applyTemperatureCorrection) +{ + this->applyTemperatureCorrection = applyTemperatureCorrection; + Invalidate(); +} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWVisualization.h b/ILS_Window_Plugin/IWVisualization.h new file mode 100644 index 0000000..f28223b --- /dev/null +++ b/ILS_Window_Plugin/IWVisualization.h @@ -0,0 +1,87 @@ +#pragma once +#include +#include "IWDataTypes.h" +#include +#include + +#define APP_LINE_MARGIN_TOP 0.08 +#define APP_LINE_MARGIN_SIDES 0.08 +#define APP_LINE_MARGIN_BOTTOM 0.35 +#define LABEL_OFFSET 15 +#define TARGET_RADIUS 5 +#define HISTORY_TRAIL_RADIUS 5 +#define FT_PER_NM 6076.11549 +#define EARTH_RADIUS_NM 3440.065 +#define PI 3.14159265359 + +class IWVisualization : public CWnd +{ +public: + IWVisualization(IWApproachDefinition selectedApproach, IWStyling styling, CFont* font); + void DrawGlideslopeAndLocalizer(CDC& dc); + void DrawRadarTargets(CDC& dc); + void DrawCurrentZoomValue(CDC& dc); + void SetActiveApproach(IWApproachDefinition selectedApproach); + void SetLatestLiveData(IWLiveData* liveData); + + // Settings gettters + const bool GetLeftToRight(); + const bool GetShowTagsByDefault(); + const bool GetApplyTemperatureCorrection(); + + // Settings setters + void SetLeftToRight(bool leftToRight); + void SetShowTagsByDefault(bool showTagsByDefault); + void SetApplyTemperatureCorrection(bool applyTemperatureCorrection); + + DECLARE_MESSAGE_MAP() + +private: + afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); + afx_msg void OnTimer(UINT_PTR nIDEvent); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg void OnPaint(); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + + void DrawDiamond(CPoint pt, int size, CDC& dc); + bool CalculateTargetCoordinates(const IWTargetPosition& position, CPoint& ptTopView, CPoint& ptSideView); + void UpdateDimentions(); + + double CalculateDistance(double lat1, double lon1, double lat2, double lon2); + double CalculateBearing(double lat1, double lon1, double lat2, double lon2); + double CalculateTemperatureCorrection(int planePressAlt, int airportPressureAlt, double surfTemp); + + IWLiveData* m_latestLiveData = NULL; + IWApproachDefinition selectedApproach; + + COLORREF rangeStatusTextColor; + COLORREF windowBackground; + COLORREF targetLabelColor; + + CPen glideSlopePen; + CBrush localizerBrush; + CPen radarTargetPen; + CPen historyTrailPen; + IWTagMode tagMode; + CFont* font; + + std::set clickedTargets; + + int approachLength; + bool showCurrentZoomValue = false; + UINT_PTR zoomMessageTimerId = 1; + + // Dimentions + float approachHeightFt; + double pixelsPerFt; + double pixelsPerNauticalMile; + CPoint glidePathTop; + CPoint glidePathBottom; + + // Settings + bool showTagsByDefault; + bool applyTemperatureCorrection; + bool leftToRight; +}; + diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index 4a32216..ba8596c 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -16,16 +16,11 @@ BEGIN_MESSAGE_MAP(IWWindow, CWnd) ON_WM_SIZE() ON_WM_MOVE() ON_WM_SIZING() - ON_WM_LBUTTONDOWN() - ON_WM_LBUTTONUP() - ON_WM_CTLCOLOR() ON_MESSAGE(WM_UPDATE_DATA, &IWWindow::OnUpdateData) ON_WM_DESTROY() ON_WM_ERASEBKGND() ON_WM_NCACTIVATE() ON_WM_GETMINMAXINFO() - ON_WM_MOUSEWHEEL() - ON_WM_TIMER() ON_MESSAGE(WM_EXITSIZEMOVE, &IWWindow::OnExitSizeMove) ON_COMMAND_EX(MENU_ITEM_FLIP, &IWWindow::OnMenuOptionSelected) ON_COMMAND_EX(MENU_ITEM_SHOW_LABELS, &IWWindow::OnMenuOptionSelected) @@ -40,26 +35,15 @@ IWWindow::IWWindow(IWApproachDefinition selectedApproach, IWStyling styling) : t RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b), RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b), this -) +), ilsVisualization(selectedApproach, styling, &this->font) { this->selectedApproach = selectedApproach; - this->approachLength = selectedApproach.defaultRange; - this->leftToRight = selectedApproach.localizerCourse > 0 && selectedApproach.localizerCourse < 180; - - this->rangeStatusTextColor = RGB(styling.rangeStatusTextColor.r, styling.rangeStatusTextColor.g, styling.rangeStatusTextColor.b); - this->windowBackground = RGB(styling.backgroundColor.r, styling.backgroundColor.g, styling.backgroundColor.b); - this->targetLabelColor = RGB(styling.targetLabelColor.r, styling.targetLabelColor.g, styling.targetLabelColor.b); - this->windowBorderPen.CreatePen(PS_SOLID, 6, RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b)); - this->windowOuterBorderPen.CreatePen(PS_SOLID, 1, RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b)); - this->glideSlopePen.CreatePen(PS_SOLID, 1, RGB(styling.glideslopeColor.r, styling.glideslopeColor.g, styling.glideslopeColor.b)); - this->localizerBrush.CreateSolidBrush(RGB(styling.localizerColor.r, styling.localizerColor.g, styling.localizerColor.b)); - this->radarTargetPen.CreatePen(PS_SOLID, 1, RGB(styling.radarTargetColor.r, styling.radarTargetColor.g, styling.radarTargetColor.b)); - this->historyTrailPen.CreatePen(PS_SOLID, 1, RGB(styling.historyTrailColor.r, styling.historyTrailColor.g, styling.historyTrailColor.b)); + + this->windowBorderColor = RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b); + this->windowOuterBorderColor = RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b); float fontPointsSize = styling.fontSize * 72 / 96; - this->euroScopeFont.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); - this->tagMode = styling.defaultTagMode; - this->showTagsByDefault = styling.showTagByDefault; + this->font.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); } IWWindow::~IWWindow() @@ -76,13 +60,20 @@ int IWWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) if (!titleBar.CreateTopBar(this, barRect, IDC_TOPBAR)) { AfxMessageBox(_T("Failed to create top bar")); - return -1; // Handle error appropriately + return -1; + } + + // Create the window content + CRect contentRect = GetClientRectBelowTitleBar(); + if (!ilsVisualization.Create(NULL, NULL, WS_CHILD | WS_VISIBLE, contentRect, this, 0)) + { + AfxMessageBox(_T("Failed to create window content")); + return -1; } return 0; } - void IWWindow::OnPaint() { CPaintDC dc(this); // Device context for painting @@ -90,18 +81,18 @@ void IWWindow::OnPaint() // Create a memory DC for double buffering CRect rect; GetClientRect(&rect); - CBitmap bufferBitmap; CDC memDC; - memDC.CreateCompatibleDC(&dc); + + CBitmap bufferBitmap; bufferBitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height()); CBitmap* pOldBitmap = memDC.SelectObject(&bufferBitmap); - // Clear the background - memDC.FillSolidRect(rect, windowBackground); - - // Perform all drawing operations on memDC instead of dc - DrawContent(memDC); + // Clear the background and draw window borders + memDC.FillSolidRect(rect, windowOuterBorderColor); + CRect innerRect = rect; + innerRect.InflateRect(-WINDOW_OUTER_BORDER_WIDTH, -WINDOW_OUTER_BORDER_WIDTH); + memDC.FillSolidRect(innerRect, windowBorderColor); // Copy the buffer to the screen dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY); @@ -110,144 +101,6 @@ void IWWindow::OnPaint() memDC.SelectObject(pOldBitmap); } -void IWWindow::DrawContent(CDC& dc) -{ - CRect rect = GetClientRectBelowTitleBar(); - CFont* oldFont = dc.SelectObject(&euroScopeFont); - - dc.FillSolidRect(rect, windowBackground); - - // Draw glideslope line - CPen* pOldPen = dc.SelectObject(&glideSlopePen); - dc.MoveTo(glidePathTop); - dc.LineTo(glidePathBottom); - dc.SelectObject(pOldPen); - - // Draw localizer distance dots - CBrush* pOldBrush = dc.SelectObject(&localizerBrush); - for (int i = 0; i <= approachLength; i++) // Draw distance points. Every 5th point is large - { - int radius = i % 5 == 0 ? 4 : 2; - int x = glidePathBottom.x - i * pixelsPerNauticalMile; - int y = glidePathBottom.y; - - DrawDiamond(CPoint(x, y), radius, dc); - } - dc.SelectObject(pOldBrush); - - // Draw the radar targets - for (const IWRadarTarget& radarTarget : m_latestLiveData.radarTargets) - { - auto it = radarTarget.positionHistory.rbegin(); - auto end = radarTarget.positionHistory.rend(); - - // Draw the history trail in reverese so the newest dots appear on top - for (auto it = radarTarget.positionHistory.rbegin(); it != radarTarget.positionHistory.rend(); ++it) { - const IWTargetPosition& position = *it; - bool isNewestPosition = (it + 1 == end); // Check if the iterator is the last element - - CPoint ptTopView, ptSideView; - if (!CalculateTargetCoordinates(position, ptTopView, ptSideView)) { - // Target is outside the visible area - continue; - } - - int crossRadius = isNewestPosition ? TARGET_RADIUS : HISTORY_TRAIL_RADIUS; - - // Draw position seen from the side - dc.SelectObject(isNewestPosition ? radarTargetPen : historyTrailPen); - dc.MoveTo(CPoint(ptSideView.x, ptSideView.y - crossRadius)); - dc.LineTo(CPoint(ptSideView.x, ptSideView.y + crossRadius + 1)); - dc.MoveTo(CPoint(ptSideView.x - crossRadius, ptSideView.y)); - dc.LineTo(CPoint(ptSideView.x + crossRadius + 1, ptSideView.y)); - - if (isNewestPosition) { - dc.SelectStockObject(NULL_BRUSH); - dc.Ellipse(ptSideView.x - crossRadius, ptSideView.y - crossRadius, ptSideView.x + crossRadius + 1, ptSideView.y + crossRadius + 1); - } - - // Draw position seen from above - dc.SelectObject(isNewestPosition ? radarTargetPen : historyTrailPen); - dc.MoveTo(CPoint(ptTopView.x, ptTopView.y - crossRadius)); - dc.LineTo(CPoint(ptTopView.x, ptTopView.y + crossRadius + 1)); - dc.MoveTo(CPoint(ptTopView.x - crossRadius, ptTopView.y)); - dc.LineTo(CPoint(ptTopView.x + crossRadius + 1, ptTopView.y)); - - // Draw the main target - if (isNewestPosition) { - dc.SelectStockObject(NULL_BRUSH); - dc.Ellipse(ptTopView.x - crossRadius, ptTopView.y - crossRadius, ptTopView.x + crossRadius + 1, ptTopView.y + crossRadius + 1); - - // Check if callsign is in the vector of clicked targets - bool isClicked = this->clickedTargets.find(radarTarget.callsign) != this->clickedTargets.end(); - - if (this->showTagsByDefault) { - if (isClicked) continue; - } - else { - if (!isClicked) continue; - } - - dc.MoveTo(ptTopView); - dc.LineTo(ptTopView.x + LABEL_OFFSET, ptTopView.y + LABEL_OFFSET); - - // Callsign label - dc.SetTextColor(targetLabelColor); - dc.SetBkMode(TRANSPARENT); - - - CString targetLabel; - if (this->tagMode == IWTagMode::Squawk) { - targetLabel.Format(_T("%s"), radarTarget.squawk.c_str()); - } - else if (this->tagMode == IWTagMode::Callsign) { - targetLabel.Format(_T("%s"), radarTarget.callsign.c_str()); - } - - // Draw the label - CSize textSize = dc.GetTextExtent(targetLabel); - CPoint labelPosition(ptTopView.x + LABEL_OFFSET, ptTopView.y + LABEL_OFFSET); - CRect labelRect(labelPosition.x, labelPosition.y, labelPosition.x + textSize.cx, labelPosition.y + textSize.cy * 2); - dc.DrawText(targetLabel, labelRect, DT_LEFT); - } - } - } - - if (showZoomMessage) - { - CRect rect = GetClientRectBelowTitleBar(); - - dc.SetTextColor(this->rangeStatusTextColor); - dc.SetBkMode(TRANSPARENT); - - CString zoomMessage; - zoomMessage.Format(_T("%d NM"), this->approachLength); - - CSize textSize = dc.GetTextExtent(zoomMessage); - CPoint position( - this->leftToRight ? rect.right - textSize.cx - 5 : rect.left + 5, - rect.top + 5 - ); - CRect textRect(position.x, position.y, position.x + textSize.cx, position.y + textSize.cy*2); - - dc.DrawText(zoomMessage, textRect, this->leftToRight ? DT_RIGHT : DT_LEFT); - } - - // Draw custom window border - dc.SelectStockObject(NULL_BRUSH); - dc.SelectObject(windowBorderPen); - dc.Rectangle(rect); - - // Draw outer window border - CRect fullWindowArea; - GetClientRect(&fullWindowArea); - dc.SelectObject(windowOuterBorderPen); - dc.Rectangle(fullWindowArea); - - // Cleanup - dc.SelectObject(oldFont); -} - void IWWindow::OnSize(UINT nType, int cx, int cy) { CWnd::OnSize(nType, cx, cy); @@ -255,7 +108,6 @@ void IWWindow::OnSize(UINT nType, int cx, int cy) // Recalculate gradient or redraw on resize if (nType != SIZE_MINIMIZED) // Ignore if window is minimized { - UpdateDimentions(); Invalidate(); // Mark the entire client area for repaint } @@ -264,6 +116,12 @@ void IWWindow::OnSize(UINT nType, int cx, int cy) CRect barRect(0, 0, cx, TITLE_BAR_HEIGHT); titleBar.MoveWindow(barRect); } + + if (ilsVisualization.GetSafeHwnd()) + { + CRect contentRect = GetClientRectBelowTitleBar(); + ilsVisualization.MoveWindow(contentRect); + } } LRESULT IWWindow::OnExitSizeMove(WPARAM wParam, LPARAM lParam) @@ -320,16 +178,12 @@ LRESULT IWWindow::OnUpdateData(WPARAM wParam, LPARAM lParam) IWLiveData* pData = reinterpret_cast(wParam); if (pData) { m_latestLiveData = *pData; + ilsVisualization.SetLatestLiveData(&m_latestLiveData); } - // Trigger a repaint - CRect updateRect = GetClientRectBelowTitleBar(); // Adjust based on what needs redrawing - InvalidateRect(&updateRect, FALSE); // FALSE to avoid erasing the background unnecessarily - return 0; } - CRect IWWindow::GetClientRectBelowTitleBar() { CRect rect; @@ -341,23 +195,13 @@ CRect IWWindow::GetClientRectBelowTitleBar() ScreenToClient(&topBarRect); // Convert to client coordinates rect.top = topBarRect.bottom; // Move the top to below the top bar + rect.left += WINDOW_BORDER_WIDTH; + rect.right -= WINDOW_BORDER_WIDTH; + rect.bottom -= WINDOW_BORDER_WIDTH; return rect; } -void IWWindow::DrawDiamond(CPoint pt, int radius, CDC& dc) -{ - CPoint pts[5]; - pts[0] = CPoint(pt.x, pt.y - radius); - pts[1] = CPoint(pt.x + radius, pt.y); - pts[2] = CPoint(pt.x, pt.y + radius); - pts[3] = CPoint(pt.x - radius, pt.y); - pts[4] = CPoint(pt.x, pt.y - radius); - - dc.SetPolyFillMode(ALTERNATE); - dc.Polygon(pts, 5); -} - void IWWindow::OnResizeStart() { SendMessage(WM_NCLBUTTONDOWN, HTTOPRIGHT, NULL); // Resize using the top right corner @@ -400,183 +244,6 @@ void IWWindow::OnGetMinMaxInfo(MINMAXINFO* lpMMI) lpMMI->ptMinTrackSize.y = 100; // Minimum height in pixels } -BOOL IWWindow::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) -{ - if (zDelta > 0 && this->approachLength > 5) - { - this->approachLength -= 1; - } - else if (zDelta < 0 && this->approachLength < 50) - { - this->approachLength += 1; - } - - UpdateDimentions(); - - CRect updateRect = GetClientRectBelowTitleBar(); - InvalidateRect(updateRect); - - SetTimer(zoomMessageTimerId, 1000, nullptr); - - showZoomMessage = true; - - return TRUE; -} - -void IWWindow::OnTimer(UINT_PTR nIDEvent) -{ - if (nIDEvent == zoomMessageTimerId) - { - // Hide the zoom message - showZoomMessage = false; - KillTimer(zoomMessageTimerId); - - // Invalidate to redraw without the message - CRect updateRect = GetClientRectBelowTitleBar(); - InvalidateRect(updateRect); - } - - CWnd::OnTimer(nIDEvent); -} - -void IWWindow::OnLButtonDown(UINT nFlags, CPoint point) -{ - for (const IWRadarTarget& radarTarget : m_latestLiveData.radarTargets) { - auto newestPosition = radarTarget.positionHistory.size() > 0 ? &radarTarget.positionHistory.front() : nullptr; - - if (newestPosition) { - const IWTargetPosition& position = *newestPosition; - - CPoint ptTopView, ptSideView; - if (!CalculateTargetCoordinates(position, ptTopView, ptSideView)) { - // Target is outside the visible area - continue; - } - - CRect targetRect(ptTopView.x - TARGET_RADIUS, ptTopView.y - TARGET_RADIUS, ptTopView.x + TARGET_RADIUS, ptTopView.y + TARGET_RADIUS); - - if (targetRect.PtInRect(point)) - { - bool isAlreadyClicked = this->clickedTargets.find(radarTarget.callsign) != this->clickedTargets.end(); - - if (isAlreadyClicked) { - this->clickedTargets.erase(radarTarget.callsign); - } - else { - this->clickedTargets.insert(radarTarget.callsign); - } - CRect updateRect = GetClientRectBelowTitleBar(); - InvalidateRect(updateRect); - break; - } - } - } - - CWnd::OnLButtonDown(nFlags, point); -} - -bool IWWindow::CalculateTargetCoordinates(const IWTargetPosition& position, CPoint& ptTopView, CPoint& ptSideView) -{ - // Calculate position relative to the runway - double distanceToThreshold = CalculateDistance(position.latitude, position.longitude, selectedApproach.thresholdLatitude, selectedApproach.thresholdLongitude); - double directionToThreshold = CalculateBearing(position.latitude, position.longitude, selectedApproach.thresholdLatitude, selectedApproach.thresholdLongitude); - double angleDiff = (selectedApproach.localizerCourse - directionToThreshold) / 180.0 * PI; // anglediff in radians - double heightAboveThreshold = position.trueAltitude - selectedApproach.thresholdAltitude; - - auto airportTemperature = m_latestLiveData.airportTemperatures.find(selectedApproach.airport); - if (applyTemperatureCorrection && airportTemperature != m_latestLiveData.airportTemperatures.end()) { - // In newer flight simulators true altitude is affected by the temperature. - // In cold weather aircraft will be shown higher than they actually are, unless we correct for it. - // See: https://forums.flightsimulator.com/t/vatsim-ivao-pilotedge-users-be-aware-of-an-important-bug/426142/468 - int temperatureCorrection = CalculateTemperatureCorrection(position.trueAltitude, selectedApproach.thresholdAltitude, airportTemperature->second); - heightAboveThreshold -= temperatureCorrection; - } - - double projectedDistanceFromThreshold = distanceToThreshold * cos(angleDiff); - double projectedDistanceFromExtendedCenterline = distanceToThreshold * tan(angleDiff); - - if (projectedDistanceFromExtendedCenterline < 0 && abs(projectedDistanceFromExtendedCenterline) > this->selectedApproach.maxOffsetLeft) { - // Too far left - return false; - } - if (projectedDistanceFromExtendedCenterline > 0 && abs(projectedDistanceFromExtendedCenterline) > this->selectedApproach.maxOffsetRight) { - // Too far right - return false; - } - if (projectedDistanceFromThreshold < 0) { - // Wrong side of the threshold - return false; - } - if (heightAboveThreshold > approachHeightFt) { - // Too high - return false; - } - - int xPosition = glidePathBottom.x - projectedDistanceFromThreshold * pixelsPerNauticalMile; - int yPositionSlope = glidePathBottom.y - heightAboveThreshold * pixelsPerFt; - int yPositionCenterline = glidePathBottom.y + projectedDistanceFromExtendedCenterline * pixelsPerNauticalMile; - ptSideView = CPoint(xPosition, yPositionSlope); - ptTopView = CPoint(xPosition, yPositionCenterline); - return true; -} - -void IWWindow::UpdateDimentions() -{ - CRect rect = GetClientRectBelowTitleBar(); - - const int CALC_SIDE_MARGIN = rect.Width() * APP_LINE_MARGIN_SIDES; - const int CALC_TOP_MARGIN = rect.Height() * APP_LINE_MARGIN_TOP; - const int CALC_BOTTOM_MARGIN = rect.Height() * APP_LINE_MARGIN_BOTTOM; - - // Define the start and end coordintes for rendering the glidepath - this->glidePathTop = CPoint(leftToRight ? rect.left + CALC_SIDE_MARGIN : rect.right - CALC_SIDE_MARGIN, rect.top + CALC_TOP_MARGIN); - this->glidePathBottom = CPoint(leftToRight ? rect.right - CALC_SIDE_MARGIN : rect.left + CALC_SIDE_MARGIN, rect.bottom - CALC_BOTTOM_MARGIN); - - this->approachHeightFt = (approachLength * FT_PER_NM * sin(selectedApproach.glideslopeAngle / 180.0 * PI)); - - this->pixelsPerFt = (glidePathBottom.y - glidePathTop.y) / approachHeightFt; - this->pixelsPerNauticalMile = (glidePathBottom.x - glidePathTop.x) / float(approachLength); // PS: negative when direction is left->right -} - -double IWWindow::CalculateDistance(double lat1, double lon1, double lat2, double lon2) { - // Convert latitude and longitude from degrees to radians - lat1 = lat1 * PI / 180.0; - lon1 = lon1 * PI / 180.0; - lat2 = lat2 * PI / 180.0; - lon2 = lon2 * PI / 180.0; - // Haversine formula - double dlat = lat2 - lat1; - double dlon = lon2 - lon1; - double a = pow(sin(dlat / 2), 2) + cos(lat1) * cos(lat2) * pow(sin(dlon / 2), 2); - double c = 2 * atan2(sqrt(a), sqrt(1 - a)); - double distance = EARTH_RADIUS_NM * c; - return distance; -} - -double IWWindow::CalculateBearing(double lat1, double lon1, double lat2, double lon2) { - // Convert latitude and longitude from degrees to radians - lat1 = lat1 * PI / 180.0; - lon1 = lon1 * PI / 180.0; - lat2 = lat2 * PI / 180.0; - lon2 = lon2 * PI / 180.0; - - // Calculate the bearing - double y = sin(lon2 - lon1) * cos(lat2); - double x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2 - lon1); - double bearing = atan2(y, x); - bearing = fmod(bearing + 2 * PI, 2 * PI); - - // Convert to degrees - return bearing * 180.0 / PI; -} - -double IWWindow::CalculateTemperatureCorrection(int planePressAlt, int airportPressureAlt, double surfTemp) { - double isaDeviation = surfTemp - 15; - - // Formula is from here: https://www.pprune.org/tech-log/573002-accurate-temperature-correction-formula.html - return ((-isaDeviation / -0.0019812) * std::log(1 + (-0.0019812 * planePressAlt) / (288.15 + -0.0019812 * airportPressureAlt))); -} - std::string IWWindow::GetActiveApproachName() const { std::lock_guard lock(approachDataMutex); @@ -587,10 +254,9 @@ void IWWindow::SetActiveApproach(const IWApproachDefinition& selectedApproach) { std::unique_lock lock(approachDataMutex); this->selectedApproach = selectedApproach; - this->approachLength = selectedApproach.defaultRange; - this->leftToRight = selectedApproach.localizerCourse > 0 && selectedApproach.localizerCourse < 180; this->titleBar.SetTitle(selectedApproach.title); - UpdateDimentions(); + + ilsVisualization.SetActiveApproach(this->selectedApproach); Invalidate(); lock.unlock(); @@ -628,7 +294,7 @@ void IWWindow::CreatePopupMenu(CPoint point) // Add static menu items menu.AppendMenu( - MF_STRING | (this->showTagsByDefault ? MF_CHECKED : MF_UNCHECKED), + MF_STRING | (ilsVisualization.GetShowTagsByDefault() ? MF_CHECKED : MF_UNCHECKED), MENU_ITEM_SHOW_LABELS, _T("Show labels by default") ); @@ -641,7 +307,7 @@ void IWWindow::CreatePopupMenu(CPoint point) + ")"; menu.AppendMenu( - MF_STRING | (this->applyTemperatureCorrection ? MF_CHECKED : MF_UNCHECKED), + MF_STRING | (ilsVisualization.GetApplyTemperatureCorrection() ? MF_CHECKED : MF_UNCHECKED), MENU_ITEM_CORRECT_FOR_TEMPERATURE, _T(airportTemperatureMenuText.c_str()) ); @@ -653,25 +319,23 @@ void IWWindow::CreatePopupMenu(CPoint point) // Display the menu menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); - } BOOL IWWindow::OnMenuOptionSelected(UINT nID) { if (nID == MENU_ITEM_FLIP) { - this->leftToRight = !this->leftToRight; - UpdateDimentions(); + ilsVisualization.SetLeftToRight(!ilsVisualization.GetLeftToRight()); Invalidate(); } else if (nID == MENU_ITEM_SHOW_LABELS) { - this->showTagsByDefault = !this->showTagsByDefault; + ilsVisualization.SetShowTagsByDefault(!ilsVisualization.GetShowTagsByDefault()); Invalidate(); } else if (nID == MENU_ITEM_CORRECT_FOR_TEMPERATURE) { - this->applyTemperatureCorrection = !this->applyTemperatureCorrection; + ilsVisualization.SetApplyTemperatureCorrection(!ilsVisualization.GetApplyTemperatureCorrection()); Invalidate(); } return TRUE; diff --git a/ILS_Window_Plugin/IWWindow.h b/ILS_Window_Plugin/IWWindow.h index 8e96c93..5b73382 100644 --- a/ILS_Window_Plugin/IWWindow.h +++ b/ILS_Window_Plugin/IWWindow.h @@ -2,6 +2,7 @@ #include #include "IWDataTypes.h" #include "IWTitleBar.h" +#include "IWVisualization.h" #include #include @@ -9,23 +10,14 @@ #define IDC_TOPBAR 1002 #define WM_UPDATE_DATA (WM_USER + 1) -#define APP_LINE_MARGIN_TOP 0.08 -#define APP_LINE_MARGIN_SIDES 0.08 -#define APP_LINE_MARGIN_BOTTOM 0.35 -#define LABEL_OFFSET 15 -#define TARGET_RADIUS 5 -#define HISTORY_TRAIL_RADIUS 5 -#define PI 3.14159265359 -#define FT_PER_NM 6076.11549 - #define TITLE_BAR_HEIGHT 27 #define SIZE_SNAP_INCREMENTS 20.0 - -#define EARTH_RADIUS_NM 3440.065 -#define PI 3.14159265359 +#define WINDOW_BORDER_WIDTH 4 +#define WINDOW_OUTER_BORDER_WIDTH 1 class IWWindow; + class IIWWndEventListener { public: virtual void OnWindowClosed(IWWindow* window) = 0; @@ -46,18 +38,12 @@ class IWWindow : public CWnd, IWTitleBarEventListener { afx_msg BOOL OnEraseBkgnd(CDC* pDC); afx_msg BOOL OnNcActivate(BOOL bActive); afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); - afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt); - afx_msg void OnLButtonDown(UINT nFlags, CPoint point); BOOL OnMenuOptionSelected(UINT nID); void OnProcedureSelected(UINT nID); - void DrawContent(CDC& dc); CRect GetClientRectBelowTitleBar(); - - afx_msg void OnTimer(UINT_PTR nIDEvent); - - + DECLARE_MESSAGE_MAP() public: @@ -70,49 +56,18 @@ class IWWindow : public CWnd, IWTitleBarEventListener { private: IWTitleBar titleBar; - IWLiveData m_latestLiveData; - void DrawDiamond(CPoint pt, int size, CDC& dc); - bool CalculateTargetCoordinates(const IWTargetPosition& position, CPoint& ptTopView, CPoint& ptSideView); - void UpdateDimentions(); + IWVisualization ilsVisualization; + COLORREF windowBorderColor; + COLORREF windowOuterBorderColor; + void CreatePopupMenu(CPoint point); IWApproachDefinition selectedApproach; std::vector availableApproaches; - double CalculateDistance(double lat1, double lon1, double lat2, double lon2); - double CalculateBearing(double lat1, double lon1, double lat2, double lon2); - double CalculateTemperatureCorrection(int planePressAlt, int airportPressureAlt, double surfTemp); - - COLORREF rangeStatusTextColor; - COLORREF windowBackground; - COLORREF targetLabelColor; - CPen glideSlopePen; - CBrush localizerBrush; - CPen radarTargetPen; - CPen historyTrailPen; - CPen windowBorderPen; - CPen windowOuterBorderPen; IIWWndEventListener* m_listener = nullptr; - IWTagMode tagMode; - bool showTagsByDefault; - - std::set clickedTargets; - - int approachLength; - bool leftToRight; - bool applyTemperatureCorrection = true; - // Dimentions - float approachHeightFt; - double pixelsPerFt; - double pixelsPerNauticalMile; - CPoint glidePathTop; - CPoint glidePathBottom; - - bool showZoomMessage = false; - UINT_PTR zoomMessageTimerId = 1; - - CFont euroScopeFont; + CFont font; // For handling events from the title bar void OnResizeStart() override; @@ -120,5 +75,6 @@ class IWWindow : public CWnd, IWTitleBarEventListener { void OnMenuButtonClicked() override; // For thread safety between EuroScope and the window thread + IWLiveData m_latestLiveData; mutable std::mutex approachDataMutex; }; From 3cdaf610a95b2151c4c9b2d3d97ba96eeb5299f1 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Sun, 19 Jan 2025 16:58:53 +0100 Subject: [PATCH 2/8] Make global window border surround title bar for simplicity --- ILS_Window_Plugin/IWTitleBar.cpp | 27 +++++++++++---------------- ILS_Window_Plugin/IWTitleBar.h | 2 -- ILS_Window_Plugin/IWWindow.cpp | 3 +-- 3 files changed, 12 insertions(+), 20 deletions(-) diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index 0b56c8c..33f0c65 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -10,14 +10,13 @@ BEGIN_MESSAGE_MAP(IWTitleBar, CStatic) ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() -IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, COLORREF outerFrameColor, IWTitleBarEventListener* listener) +IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) { this->backgroundColor = backgroundColor; this->textColor = textColor; - this->outerFramePen.CreatePen(PS_SOLID, 1, outerFrameColor); this->text = title; - this->font.CreatePointFont(110, _T("EuroScope")); + this->font.CreatePointFont(100, _T("EuroScope")); this->eventListener = listener; } @@ -54,17 +53,11 @@ void IWTitleBar::OnPaint() dc.SetBkMode(TRANSPARENT); // Transparent background for text // Draw the text centered in the client area - CRect textPosition = rect; - textPosition.left += 10; - - dc.DrawText(_T(this->text.c_str()), -1, textPosition, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + CRect titleRect = rect; + titleRect.left += 3; + dc.DrawText(_T(this->text.c_str()), -1, titleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); dc.SelectObject(oldFont); - - dc.SelectStockObject(NULL_BRUSH); - dc.SelectObject(this->outerFramePen); - rect.bottom += 1; // Make the bottom border invisible - dc.Rectangle(rect); } void IWTitleBar::OnCloseButtonClicked() @@ -118,12 +111,14 @@ void IWTitleBar::OnSize(UINT nType, int cx, int cy) void IWTitleBar::PositionButtons(const CRect& rect) { const int margin = 6; - const int btnWidth = 16; // Button width - const int btnHeight = 14; // Button width - const int top = rect.top + 8; + const int btnHeight = rect.Height() - 9; + const int btnWidth = btnHeight * 1.2; + + // V center the buttons + const int top = (rect.Height() - btnHeight) / 2; const int bottom = top + btnHeight; - int right = rect.right - margin - 3; + int right = rect.right - 3; int left = right - btnWidth; // Position the resize button diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index 8198ee0..3f243e2 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -24,7 +24,6 @@ class IWTitleBar : public CStatic { std::string title, COLORREF backgroundColor, COLORREF textColor, - COLORREF outerFrameColor, IWTitleBarEventListener* listener ); virtual ~IWTitleBar() {} @@ -42,7 +41,6 @@ class IWTitleBar : public CStatic { COLORREF backgroundColor; COLORREF textColor; - CPen outerFramePen; std::string text; IWTitleBarEventListener* eventListener; diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index ba8596c..f39b590 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -33,7 +33,6 @@ IWWindow::IWWindow(IWApproachDefinition selectedApproach, IWStyling styling) : t selectedApproach.title, RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b), RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b), - RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b), this ), ilsVisualization(selectedApproach, styling, &this->font) { @@ -113,7 +112,7 @@ void IWWindow::OnSize(UINT nType, int cx, int cy) if (titleBar.GetSafeHwnd()) { - CRect barRect(0, 0, cx, TITLE_BAR_HEIGHT); + CRect barRect(WINDOW_BORDER_WIDTH, WINDOW_BORDER_WIDTH, cx - WINDOW_BORDER_WIDTH, TITLE_BAR_HEIGHT); titleBar.MoveWindow(barRect); } From 0155fae839de1a8734db4916d51543dddeaba680 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Sun, 19 Jan 2025 19:49:51 +0100 Subject: [PATCH 3/8] Support multiple window simulations (CDE and X11) --- ILS_Window_Plugin/ILS_Window_Plugin.vcxproj | 29 +++- .../ILS_Window_Plugin.vcxproj.filters | 95 ++++++++--- ILS_Window_Plugin/IWCdeCloseBtn.cpp | 24 +++ ILS_Window_Plugin/IWCdeCloseBtn.h | 16 ++ ILS_Window_Plugin/IWCdeMenuBtn.cpp | 21 +++ ILS_Window_Plugin/IWCdeMenuBtn.h | 16 ++ ILS_Window_Plugin/IWCdeTitleBar.cpp | 44 +++++ ILS_Window_Plugin/IWCdeTitleBar.h | 23 +++ ILS_Window_Plugin/IWCdeWindow.cpp | 147 ++++++++++++++++ ILS_Window_Plugin/IWCdeWindow.h | 17 ++ ILS_Window_Plugin/IWDataTypes.h | 1 + ILS_Window_Plugin/IWPlugin.cpp | 21 ++- ILS_Window_Plugin/IWTitleBar.cpp | 127 +++++--------- ILS_Window_Plugin/IWTitleBar.h | 58 +++---- ILS_Window_Plugin/IWWindow.cpp | 160 +++++++++++++++--- ILS_Window_Plugin/IWWindow.h | 69 ++++---- .../{IWCloseBtn.cpp => IWX11CloseBtn.cpp} | 4 +- .../{IWCloseBtn.h => IWX11CloseBtn.h} | 2 +- .../{IWMenuBtn.cpp => IWX11MenuBtn.cpp} | 10 +- .../{IWResizeBtn.h => IWX11MenuBtn.h} | 7 +- .../{IWResizeBtn.cpp => IWX11ResizeBtn.cpp} | 10 +- .../{IWMenuBtn.h => IWX11ResizeBtn.h} | 8 +- ILS_Window_Plugin/IWX11TitleBar.cpp | 60 +++++++ ILS_Window_Plugin/IWX11TitleBar.h | 16 ++ ILS_Window_Plugin/IWX11Window.cpp | 24 +++ ILS_Window_Plugin/IWX11Window.h | 13 ++ ILS_Window_Plugin/RenderUtils.h | 103 +++++++++++ ILS_Window_Plugin/ReunderUtils.cpp | 105 ++++++++++++ Sample config/ILS_Window_Plugin-config.json | 3 +- 29 files changed, 1013 insertions(+), 220 deletions(-) create mode 100644 ILS_Window_Plugin/IWCdeCloseBtn.cpp create mode 100644 ILS_Window_Plugin/IWCdeCloseBtn.h create mode 100644 ILS_Window_Plugin/IWCdeMenuBtn.cpp create mode 100644 ILS_Window_Plugin/IWCdeMenuBtn.h create mode 100644 ILS_Window_Plugin/IWCdeTitleBar.cpp create mode 100644 ILS_Window_Plugin/IWCdeTitleBar.h create mode 100644 ILS_Window_Plugin/IWCdeWindow.cpp create mode 100644 ILS_Window_Plugin/IWCdeWindow.h rename ILS_Window_Plugin/{IWCloseBtn.cpp => IWX11CloseBtn.cpp} (85%) rename ILS_Window_Plugin/{IWCloseBtn.h => IWX11CloseBtn.h} (69%) rename ILS_Window_Plugin/{IWMenuBtn.cpp => IWX11MenuBtn.cpp} (85%) rename ILS_Window_Plugin/{IWResizeBtn.h => IWX11MenuBtn.h} (84%) rename ILS_Window_Plugin/{IWResizeBtn.cpp => IWX11ResizeBtn.cpp} (81%) rename ILS_Window_Plugin/{IWMenuBtn.h => IWX11ResizeBtn.h} (83%) create mode 100644 ILS_Window_Plugin/IWX11TitleBar.cpp create mode 100644 ILS_Window_Plugin/IWX11TitleBar.h create mode 100644 ILS_Window_Plugin/IWX11Window.cpp create mode 100644 ILS_Window_Plugin/IWX11Window.h create mode 100644 ILS_Window_Plugin/RenderUtils.h create mode 100644 ILS_Window_Plugin/ReunderUtils.cpp diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj index 64ae172..69d765f 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj @@ -115,15 +115,21 @@ - - - + + + + + + + - + + + Create Create @@ -137,17 +143,24 @@ - - - + + + + + + + - + + + + diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters index 7e3f404..fa7c435 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters @@ -13,6 +13,24 @@ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + {06d11cba-76c2-4d9e-8107-6c5da2101bc1} + + + {8a7ca98b-50af-466b-a2f2-04eb78eba59e} + + + {687be561-bac8-411d-8e4a-810fa08569b5} + + + {c34849c8-8ad4-4f4f-acc9-2f8e9c582a1c} + + + {a1845d6f-0de7-4b25-9cd4-7b9629d2bf5e} + + + {f133707e-7c1a-4d8b-8a78-bdfd116acce5} + @@ -21,9 +39,6 @@ Source Files - - Source Files - Source Files @@ -33,17 +48,38 @@ Source Files - + Source Files - + Source Files - - Source Files + + Source Files\Theme\SolarisCDE - - Source Files + + Source Files\Theme\SolarisCDE + + + Source Files\Theme\SolarisCDE + + + Source Files\Theme\X11 + + + Source Files\Theme\X11 + + + Source Files\Theme\X11 + + + Source Files\Theme\X11 + + + Source Files\Theme\SolarisCDE + + + Source Files\Theme\X11 @@ -73,9 +109,6 @@ Header Files - - Header Files - Header Files @@ -91,21 +124,45 @@ Header Files - + Header Files - - Header Files + + Header Files\Theme\SolarisCDE - - Header Files + + Header Files\Theme\SolarisCDE - + + Header Files\Theme\SolarisCDE + + Header Files - + + Header Files\Theme\X11 + + + Header Files\Theme\X11 + + + Header Files\Theme\X11 + + Header Files + + Header Files\Theme\SolarisCDE + + + Header Files\Theme\X11 + + + Header Files\Theme\SolarisCDE + + + Header Files\Theme\X11 + diff --git a/ILS_Window_Plugin/IWCdeCloseBtn.cpp b/ILS_Window_Plugin/IWCdeCloseBtn.cpp new file mode 100644 index 0000000..842c0fe --- /dev/null +++ b/ILS_Window_Plugin/IWCdeCloseBtn.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "IWCdeCloseBtn.h" +#include "RenderUtils.h" + +IWCdeCloseBtn::IWCdeCloseBtn(COLORREF lightColor, COLORREF darkColor) +{ + this->lightColor = lightColor; + this->darkColor = darkColor; +} + +void IWCdeCloseBtn::DrawSymbol(CDC* pdc, CRect rect) +{ + CRect menuButton = rect; + menuButton.left = rect.right - rect.Height(); + + CRect closeIcon = menuButton; + closeIcon.left = menuButton.left + menuButton.Width() * 0.4; + closeIcon.right = closeIcon.right - menuButton.Width() * 0.4; + closeIcon.top = menuButton.top + menuButton.Width() * 0.4; + closeIcon.bottom = menuButton.bottom - menuButton.Width() * 0.4; + + Draw3dRect(pdc, menuButton, 1, lightColor, darkColor); + Draw3dRect(pdc, closeIcon, 1, lightColor, darkColor); +} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWCdeCloseBtn.h b/ILS_Window_Plugin/IWCdeCloseBtn.h new file mode 100644 index 0000000..6320b9e --- /dev/null +++ b/ILS_Window_Plugin/IWCdeCloseBtn.h @@ -0,0 +1,16 @@ +#pragma once + +#include "IWTitleBarBtn.h" + +class IWCdeCloseBtn : public IWTitleBarBtn +{ +public: + IWCdeCloseBtn(COLORREF lightColor, COLORREF darkColor); + +private: + void DrawSymbol(CDC* pDC, CRect rect) override; + + COLORREF lightColor; + COLORREF darkColor; +}; + diff --git a/ILS_Window_Plugin/IWCdeMenuBtn.cpp b/ILS_Window_Plugin/IWCdeMenuBtn.cpp new file mode 100644 index 0000000..b3c6f71 --- /dev/null +++ b/ILS_Window_Plugin/IWCdeMenuBtn.cpp @@ -0,0 +1,21 @@ +#include "pch.h" +#include "IWCdeMenuBtn.h" +#include "RenderUtils.h" + +IWCdeMenuBtn::IWCdeMenuBtn(COLORREF lightColor, COLORREF darkColor) +{ + this->lightColor = lightColor; + this->darkColor = darkColor; +} + +void IWCdeMenuBtn::DrawSymbol(CDC* pdc, CRect rect) +{ + CRect barIcon = rect; + barIcon.left = rect.left + rect.Width() * 0.25; + barIcon.right = rect.right - rect.Width() * 0.25; + barIcon.top = rect.top + rect.Height() / 2 - 2; + barIcon.bottom = barIcon.top + 4; + + Draw3dRect(pdc, rect, 1, lightColor, darkColor); + Draw3dRect(pdc, barIcon, 1, lightColor, darkColor); +} diff --git a/ILS_Window_Plugin/IWCdeMenuBtn.h b/ILS_Window_Plugin/IWCdeMenuBtn.h new file mode 100644 index 0000000..f09437f --- /dev/null +++ b/ILS_Window_Plugin/IWCdeMenuBtn.h @@ -0,0 +1,16 @@ +#pragma once + +#include "IWTitleBarBtn.h" + +class IWCdeMenuBtn : public IWTitleBarBtn +{ +public: + IWCdeMenuBtn(COLORREF lightColor, COLORREF darkColor); + +private: + void DrawSymbol(CDC* pDC, CRect rect) override; + + COLORREF lightColor; + COLORREF darkColor; +}; + diff --git a/ILS_Window_Plugin/IWCdeTitleBar.cpp b/ILS_Window_Plugin/IWCdeTitleBar.cpp new file mode 100644 index 0000000..22f6b6a --- /dev/null +++ b/ILS_Window_Plugin/IWCdeTitleBar.cpp @@ -0,0 +1,44 @@ +#include "pch.h" +#include "IWCdeTitleBar.h" +#include "RenderUtils.h" +#include "IWCdeCloseBtn.h" +#include "IWCdeMenuBtn.h" +#include "IWX11ResizeBtn.h" + +IWCdeTitleBar::IWCdeTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, COLORREF lightColor, COLORREF darkcolor, IWTitleBarEventListener* listener) : + IWTitleBar(title, backgroundColor, 13, listener) +{ + this->lightColor = lightColor; + this->darkColor = darkcolor; + this->textColor = textColor; + + this->closeButton = new IWCdeCloseBtn(lightColor, darkColor); + this->menuButton = new IWCdeMenuBtn(lightColor, darkColor); + this->resizeButton = new IWX11ResizeBtn(); +} + +void IWCdeTitleBar::PositionButtons(const CRect& rect) +{ + CRect menuButtonRect(rect.left, rect.top, rect.left + rect.Height(), rect.bottom); + if (menuButton->GetSafeHwnd()) + menuButton->MoveWindow(menuButtonRect); + + CRect closeButtonRect(rect.right - rect.Height(), rect.top, rect.right, rect.bottom); + if (closeButton->GetSafeHwnd()) + closeButton->MoveWindow(closeButtonRect); + + titleArea = rect; + titleArea.left = menuButtonRect.right; + titleArea.right = closeButtonRect.left; +} + +void IWCdeTitleBar::DrawTitle(CDC* pdc, CRect rect) +{ + Draw3dRect(pdc, titleArea, 1, lightColor, darkColor); + + auto oldFont = pdc->SelectObject(this->font); + pdc->SetTextColor(this->textColor); + pdc->SetBkMode(TRANSPARENT); + pdc->DrawText(_T(this->text.c_str()), -1, titleArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + pdc->SelectObject(oldFont); +} diff --git a/ILS_Window_Plugin/IWCdeTitleBar.h b/ILS_Window_Plugin/IWCdeTitleBar.h new file mode 100644 index 0000000..22285c3 --- /dev/null +++ b/ILS_Window_Plugin/IWCdeTitleBar.h @@ -0,0 +1,23 @@ +#pragma once +#include +#include +#include "IWTitleBar.h" + +class IWTitleBarEventListener; + +class IWCdeTitleBar : public IWTitleBar { + public: + IWCdeTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, COLORREF lightColor, COLORREF darkcolor, IWTitleBarEventListener* listener); + virtual ~IWCdeTitleBar() {} + + private: + void PositionButtons(const CRect& rect) override; + void DrawTitle(CDC* pdc, CRect rect) override; + + COLORREF backgroundColor; + COLORREF textColor; + COLORREF lightColor; + COLORREF darkColor; + + CRect titleArea; +}; \ No newline at end of file diff --git a/ILS_Window_Plugin/IWCdeWindow.cpp b/ILS_Window_Plugin/IWCdeWindow.cpp new file mode 100644 index 0000000..b325c12 --- /dev/null +++ b/ILS_Window_Plugin/IWCdeWindow.cpp @@ -0,0 +1,147 @@ +#include "pch.h" +#include "IWCdeWindow.h" +#include "RenderUtils.h" +#include "IWCdeTitleBar.h" + +IWCdeWindow::IWCdeWindow(IWApproachDefinition selectedApproach, IWStyling styling) + : IWWindow(selectedApproach, styling, 24, 5, 1) + , lightColor(AdjustColorBrightness(styling.windowFrameColor, 1.4)) + , darkColor(AdjustColorBrightness(styling.windowFrameColor, 0.4)) +{ + COLORREF textColor = RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b); + this->titleBar = new IWCdeTitleBar(selectedApproach.title, windowBorderColor, textColor, lightColor, darkColor, this); +} + +int IWCdeWindow::GetEdgeCursorPosition(CPoint point) +{ + CRect clientRect; + GetClientRect(&clientRect); + + bool topLeftCorner = + point.x < clientRect.left + TITLE_BAR_HEIGHT && point.y < clientRect.top + WINDOW_BORDER_THICKNESS || + point.x < clientRect.left + WINDOW_BORDER_THICKNESS && point.y < clientRect.top + TITLE_BAR_HEIGHT; + + bool topRightCorner = + point.x > clientRect.right - TITLE_BAR_HEIGHT && point.y < clientRect.top + WINDOW_BORDER_THICKNESS || + point.x > clientRect.right - WINDOW_BORDER_THICKNESS && point.y < clientRect.top + TITLE_BAR_HEIGHT; + + bool bottomLeftCorner = + point.x < clientRect.left + TITLE_BAR_HEIGHT && point.y > clientRect.bottom - WINDOW_BORDER_THICKNESS || + point.x < clientRect.left + WINDOW_BORDER_THICKNESS && point.y > clientRect.bottom - TITLE_BAR_HEIGHT; + + bool bottomRightCorner = + point.x > clientRect.right - TITLE_BAR_HEIGHT && point.y > clientRect.bottom - WINDOW_BORDER_THICKNESS || + point.x > clientRect.right - WINDOW_BORDER_THICKNESS && point.y > clientRect.bottom - TITLE_BAR_HEIGHT; + + bool topEdge = point.y < clientRect.top + WINDOW_BORDER_THICKNESS && point.y > clientRect.top; + bool bottomEdge = point.y > clientRect.bottom - WINDOW_BORDER_THICKNESS && point.y < clientRect.bottom; + bool leftEdge = point.x < clientRect.left + WINDOW_BORDER_THICKNESS && point.x > clientRect.left; + bool rightEdge = point.x > clientRect.right - WINDOW_BORDER_THICKNESS && point.x < clientRect.right; + + if (topLeftCorner) + return HTTOPLEFT; + + if (topRightCorner) + return HTTOPRIGHT; + + if (bottomLeftCorner) + return HTBOTTOMLEFT; + + if (bottomRightCorner) + return HTBOTTOMRIGHT; + + if (topEdge) + return HTTOP; + + if (leftEdge) + return HTLEFT; + + if (rightEdge) + return HTRIGHT; + + if (bottomEdge) + return HTBOTTOM; + + return HTNOWHERE; +} + +void IWCdeWindow::DrawBorder(CDC* pdc, CRect rect) +{ + int border3dSteps = 1; // Width of each 3D effect edge + + // Edges + + CRect leftBorderRect = rect; + leftBorderRect.top = leftBorderRect.top + TITLE_BAR_HEIGHT; + leftBorderRect.left = leftBorderRect.left + 1; + leftBorderRect.right = leftBorderRect.left + WINDOW_BORDER_THICKNESS - 1; + leftBorderRect.bottom = leftBorderRect.bottom - TITLE_BAR_HEIGHT; + Draw3dRect(pdc, leftBorderRect, border3dSteps, lightColor, darkColor); + + CRect bottomBorderRect = rect; + bottomBorderRect.top = bottomBorderRect.bottom - WINDOW_BORDER_THICKNESS; + bottomBorderRect.left = bottomBorderRect.left + TITLE_BAR_HEIGHT; + bottomBorderRect.right = bottomBorderRect.right - TITLE_BAR_HEIGHT; + bottomBorderRect.bottom = bottomBorderRect.bottom - 1; + Draw3dRect(pdc, bottomBorderRect, border3dSteps, lightColor, darkColor); + + CRect rightBorderRect = rect; + rightBorderRect.top = rightBorderRect.top + TITLE_BAR_HEIGHT; + rightBorderRect.left = rightBorderRect.right - WINDOW_BORDER_THICKNESS; + rightBorderRect.right = rightBorderRect.right - 1; + rightBorderRect.bottom = rightBorderRect.bottom - TITLE_BAR_HEIGHT; + Draw3dRect(pdc, rightBorderRect, border3dSteps, lightColor, darkColor); + + CRect topBorderRect = rect; + topBorderRect.top = topBorderRect.top + 1; + topBorderRect.left = topBorderRect.left + TITLE_BAR_HEIGHT; + topBorderRect.right = topBorderRect.right - TITLE_BAR_HEIGHT; + topBorderRect.bottom = topBorderRect.top + WINDOW_BORDER_THICKNESS - 1; + Draw3dRect(pdc, topBorderRect, border3dSteps, lightColor, darkColor); + + // Corners + + CRect topLeftCornerRect = rect; + topLeftCornerRect.top = topLeftCornerRect.top + 1; + topLeftCornerRect.left = topLeftCornerRect.left + 1; + topLeftCornerRect.right = topLeftCornerRect.left + TITLE_BAR_HEIGHT - 2; + topLeftCornerRect.bottom = topLeftCornerRect.top + TITLE_BAR_HEIGHT - 2; + Draw3dCorner(pdc, topLeftCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, true, true); + + CRect topRightCornerRect = rect; + topRightCornerRect.top = topRightCornerRect.top + 1; + topRightCornerRect.left = topRightCornerRect.right - TITLE_BAR_HEIGHT; + topRightCornerRect.right = topRightCornerRect.right - 2; + topRightCornerRect.bottom = topRightCornerRect.top + TITLE_BAR_HEIGHT - 2; + Draw3dCorner(pdc, topRightCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, true, false); + + CRect bottomRightCornerRect = rect; + bottomRightCornerRect.top = bottomRightCornerRect.bottom - TITLE_BAR_HEIGHT; + bottomRightCornerRect.left = bottomRightCornerRect.right - TITLE_BAR_HEIGHT; + bottomRightCornerRect.right = bottomRightCornerRect.right - 2; + bottomRightCornerRect.bottom = bottomRightCornerRect.bottom - 2; + Draw3dCorner(pdc, bottomRightCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, false, false); + + CRect bottomLeftCornerRect = rect; + bottomLeftCornerRect.top = bottomLeftCornerRect.bottom - TITLE_BAR_HEIGHT; + bottomLeftCornerRect.left = bottomLeftCornerRect.left + 1; + bottomLeftCornerRect.right = bottomLeftCornerRect.left + TITLE_BAR_HEIGHT - 2; + bottomLeftCornerRect.bottom = bottomLeftCornerRect.bottom - 2; + Draw3dCorner(pdc, bottomLeftCornerRect, WINDOW_BORDER_THICKNESS, border3dSteps, lightColor, darkColor, false, true); +} + +COLORREF IWCdeWindow::AdjustColorBrightness(RGB color, double factor) +{ + // Adjust each component + int red = static_cast(color.r * factor); + int green = static_cast(color.g * factor); + int blue = static_cast(color.b * factor); + + // Ensure the components are within the valid range + red = max(0, min(255, red)); + green = max(0, min(255, green)); + blue = max(0, min(255, blue)); + + // Combine them back into a COLORREF + return RGB(red, green, blue); +} diff --git a/ILS_Window_Plugin/IWCdeWindow.h b/ILS_Window_Plugin/IWCdeWindow.h new file mode 100644 index 0000000..fb8b443 --- /dev/null +++ b/ILS_Window_Plugin/IWCdeWindow.h @@ -0,0 +1,17 @@ +#pragma once +#include "IWWindow.h" +class IWCdeWindow : + public IWWindow +{ + +public: + IWCdeWindow(IWApproachDefinition selectedApproach, IWStyling styling); + +private: + void DrawBorder(CDC* pdc, CRect rect) override; + COLORREF AdjustColorBrightness(RGB color, double factor); + virtual int GetEdgeCursorPosition(CPoint point) override; + + const COLORREF lightColor; + const COLORREF darkColor; +}; diff --git a/ILS_Window_Plugin/IWDataTypes.h b/ILS_Window_Plugin/IWDataTypes.h index 3a322a6..f03b41f 100644 --- a/ILS_Window_Plugin/IWDataTypes.h +++ b/ILS_Window_Plugin/IWDataTypes.h @@ -67,4 +67,5 @@ struct IWStyling { struct IWBehaviourSettings { bool openWindowsBasedOnActiveRunways; + std::string windowSimulation; }; \ No newline at end of file diff --git a/ILS_Window_Plugin/IWPlugin.cpp b/ILS_Window_Plugin/IWPlugin.cpp index 93a16f1..189acc5 100644 --- a/ILS_Window_Plugin/IWPlugin.cpp +++ b/ILS_Window_Plugin/IWPlugin.cpp @@ -4,6 +4,8 @@ #include #include "IWUtils.h" #include +#include "IWCdeWindow.h" +#include "IWX11Window.h" using json = nlohmann::json; @@ -64,8 +66,15 @@ void IWPlugin::OpenNewWindow(IWApproachDefinition* approach) spawningPoint = CPoint(rect.left + 50, rect.top + 50); } } + + IWWindow* newWindow = nullptr; + if (this->behaviourSettings.windowSimulation == "X11") { + newWindow = new IWX11Window(*approach, windowStyling); + } + else { + newWindow = new IWCdeWindow(*approach, windowStyling); + } - IWWindow* newWindow = new IWWindow(*approach, windowStyling); auto hwndPopup = newWindow->CreateEx( WS_EX_NOACTIVATE | WS_EX_TOPMOST, WINDOW_CLASS_NAME, @@ -376,8 +385,16 @@ IWBehaviourSettings IWPlugin::ReadBehaviourSettings(const std::string& jsonFileP nlohmann::json jsonObject = jsonData["behaviour"]; + auto readStringWithDefault = [&jsonData, this](const std::string& key, const std::string& defaultValue) -> std::string { + if (jsonData.contains("behaviour") && jsonData["behaviour"].contains(key)) { + return jsonData["behaviour"][key].get(); + } + return defaultValue; + }; + return IWBehaviourSettings{ - jsonObject.at("openWindowsBasedOnActiveRunways").get() + jsonObject.at("openWindowsBasedOnActiveRunways").get(), + readStringWithDefault("windowSimulation", "X11") }; } diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index 33f0c65..b12f293 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -1,22 +1,24 @@ #include "pch.h" #include "IWTitleBar.h" +#include "IWTitleBarBtn.h" IMPLEMENT_DYNAMIC(IWTitleBar, CStatic) BEGIN_MESSAGE_MAP(IWTitleBar, CStatic) ON_WM_PAINT() ON_WM_SIZE() // Handle resizing - ON_BN_CLICKED(IDC_CLOSE_BUTTON, &IWTitleBar::OnCloseButtonClicked) ON_WM_LBUTTONDOWN() + ON_BN_CLICKED(IDC_CLOSE_BUTTON, &IWTitleBar::OnCloseButtonClicked) + ON_BN_CLICKED(IDC_MENU_BUTTON, &IWTitleBar::OnMenuButtonClicked) END_MESSAGE_MAP() -IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) +IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener) { this->backgroundColor = backgroundColor; - this->textColor = textColor; - this->text = title; - this->font.CreatePointFont(100, _T("EuroScope")); + + float fontPointsSize = fontSize * 72 / 96; + this->font.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); this->eventListener = listener; } @@ -26,9 +28,9 @@ BOOL IWTitleBar::CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID) return FALSE; // Create buttons with default settings - if (!resizeButton.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_RESIZE_BUTTON) || - !menuButton.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_MENU_BUTTON) || - !closeButton.Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_CLOSE_BUTTON)) + if (!resizeButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_RESIZE_BUTTON) || + !menuButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_MENU_BUTTON) || + !closeButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_CLOSE_BUTTON)) return FALSE; // Position buttons @@ -37,108 +39,63 @@ BOOL IWTitleBar::CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID) return TRUE; } -void IWTitleBar::OnPaint() +void IWTitleBar::OnSize(UINT nType, int cx, int cy) { - CPaintDC dc(this); // Device context for painting - CRect rect; - GetClientRect(&rect); // Get the client area of the control - - auto oldFont = dc.SelectObject(this->font); - - // Fill the background with your custom color - dc.FillSolidRect(rect, this->backgroundColor); // Dark background - - // Set the text color to white - dc.SetTextColor(this->textColor); - dc.SetBkMode(TRANSPARENT); // Transparent background for text - - // Draw the text centered in the client area - CRect titleRect = rect; - titleRect.left += 3; - dc.DrawText(_T(this->text.c_str()), -1, titleRect, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + CStatic::OnSize(nType, cx, cy); - dc.SelectObject(oldFont); + if (closeButton->GetSafeHwnd() && menuButton->GetSafeHwnd() && resizeButton->GetSafeHwnd()) + { + CRect rect; + GetClientRect(&rect); // Get the updated size of the title bar + PositionButtons(rect); + } } -void IWTitleBar::OnCloseButtonClicked() +void IWTitleBar::OnPaint() { - this->eventListener->OnCloseButtonClicked(); + CPaintDC dc(this); + CRect rect; + GetClientRect(&rect); + DrawTitle(&dc, rect); } - void IWTitleBar::OnLButtonDown(UINT nFlags, CPoint point) { - CRect resizeButtonRect; - resizeButton.GetClientRect(&resizeButtonRect); - resizeButton.ClientToScreen(&resizeButtonRect); + CWnd* pParent = GetParent(); + if (!pParent) + return; CRect menuButtonRect; - menuButton.GetClientRect(&menuButtonRect); - menuButton.ClientToScreen(&menuButtonRect); + menuButton->GetClientRect(&menuButtonRect); + menuButton->ClientToScreen(&menuButtonRect); - if (resizeButtonRect.PtInRect(point)) // If click is not on the close button - { + CRect resizeButtonRect; + resizeButton->GetClientRect(&resizeButtonRect); + resizeButton->ClientToScreen(&resizeButtonRect); + + if (resizeButtonRect.PtInRect(point)) { + // Start resizing the window this->eventListener->OnResizeStart(); } - else if (menuButtonRect.PtInRect(point)) - { + else if (menuButtonRect.PtInRect(point)) { + // Simulate clicking the menu button this->eventListener->OnMenuButtonClicked(); } else { // Simulate dragging the window - CWnd* pParent = GetParent(); - if (pParent) - { - pParent->SendMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y)); - } + pParent->SendMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y)); } CWnd::OnLButtonDown(nFlags, point); } -void IWTitleBar::OnSize(UINT nType, int cx, int cy) +void IWTitleBar::OnCloseButtonClicked() { - CStatic::OnSize(nType, cx, cy); - - if (closeButton.GetSafeHwnd() && menuButton.GetSafeHwnd() && resizeButton.GetSafeHwnd()) - { - CRect rect; - GetClientRect(&rect); // Get the updated size of the title bar - PositionButtons(rect); - } + this->eventListener->OnCloseButtonClicked(); } -void IWTitleBar::PositionButtons(const CRect& rect) +void IWTitleBar::OnMenuButtonClicked() { - const int margin = 6; - const int btnHeight = rect.Height() - 9; - const int btnWidth = btnHeight * 1.2; - - // V center the buttons - const int top = (rect.Height() - btnHeight) / 2; - const int bottom = top + btnHeight; - - int right = rect.right - 3; - int left = right - btnWidth; - - // Position the resize button - CRect resizeButtonRect(left, top, right, bottom); - if (resizeButton.GetSafeHwnd()) - resizeButton.MoveWindow(resizeButtonRect); - - right = left - margin; - left = right - btnWidth; - - // Position the menu button - CRect menuButtonRect(left, top, right, bottom); - if (menuButton.GetSafeHwnd()) - menuButton.MoveWindow(menuButtonRect); - - right = left - margin; - left = right - btnWidth; - - // Position the close button - CRect closeButtonRect(left, top, right, bottom); - if (closeButton.GetSafeHwnd()) - closeButton.MoveWindow(closeButtonRect); + this->eventListener->OnMenuButtonClicked(); } + diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index 3f243e2..7a62ea5 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -1,9 +1,6 @@ #pragma once #include #include -#include "IWCloseBtn.h" -#include "IWMenuBtn.h" -#include "IWResizeBtn.h" #define IDC_CLOSE_BUTTON 1001 #define IDC_MENU_BUTTON 1002 @@ -16,40 +13,39 @@ class IWTitleBarEventListener { virtual void OnMenuButtonClicked() = 0; }; -class IWTitleBar : public CStatic { +class IWTitleBarBtn; + +class IWTitleBar : public CStatic +{ DECLARE_DYNAMIC(IWTitleBar) + DECLARE_MESSAGE_MAP() - public: - IWTitleBar( - std::string title, - COLORREF backgroundColor, - COLORREF textColor, - IWTitleBarEventListener* listener - ); - virtual ~IWTitleBar() {} +public: + IWTitleBar(std::string title, COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener); + BOOL CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID); + void SetTitle(const std::string& title) { this->text = title; } - // Initialize the top bar - BOOL CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID); + virtual ~IWTitleBar() {} - void SetTitle(const std::string& title) { this->text = title; } +protected: + virtual void PositionButtons(const CRect& rect) {}; + virtual void DrawTitle(CDC* pdc, CRect rect) {}; - private: - IWCloseBtn closeButton; - IWMenuBtn menuButton; - IWResizeBtn resizeButton; - CFont font; + IWTitleBarBtn* menuButton; + IWTitleBarBtn* closeButton; + IWTitleBarBtn* resizeButton; - COLORREF backgroundColor; - COLORREF textColor; - std::string text; - IWTitleBarEventListener* eventListener; + std::string text; + CFont font; - afx_msg void OnPaint(); - afx_msg void OnCloseButtonClicked(); - afx_msg void OnLButtonDown(UINT nFlags, CPoint point); - afx_msg void OnSize(UINT nType, int cx, int cy); +private: + COLORREF backgroundColor; + IWTitleBarEventListener* eventListener; - void PositionButtons(const CRect& rect); + afx_msg void OnPaint(); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnCloseButtonClicked(); + afx_msg void OnMenuButtonClicked(); + afx_msg void OnSize(UINT nType, int cx, int cy); +}; - DECLARE_MESSAGE_MAP() -}; \ No newline at end of file diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index f39b590..157159a 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -1,6 +1,9 @@ #include "pch.h" #include "IWWindow.h" #include +#include "RenderUtils.h" +#include "IWX11TitleBar.h" +#include "IWCdeTitleBar.h" #define MAX_PROCEDURES 100 @@ -11,6 +14,9 @@ #define MENU_ITEM_PROCEDURES_NEW_START 30000 BEGIN_MESSAGE_MAP(IWWindow, CWnd) + ON_WM_LBUTTONDOWN() + ON_WM_MOUSEMOVE() + ON_WM_SETCURSOR() ON_WM_PAINT() ON_WM_CREATE() ON_WM_SIZE() @@ -29,25 +35,24 @@ BEGIN_MESSAGE_MAP(IWWindow, CWnd) ON_COMMAND_RANGE(MENU_ITEM_PROCEDURES_NEW_START, MENU_ITEM_PROCEDURES_NEW_START + MAX_PROCEDURES, &IWWindow::OnProcedureSelected) END_MESSAGE_MAP() -IWWindow::IWWindow(IWApproachDefinition selectedApproach, IWStyling styling) : titleBar( - selectedApproach.title, - RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b), - RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b), - this -), ilsVisualization(selectedApproach, styling, &this->font) +IWWindow::IWWindow(IWApproachDefinition selectedApproach, IWStyling styling, int titleBarHeight, int windowBorderThickness, int windowOuterBorderThickness) + : ilsVisualization(selectedApproach, styling, &this->font) + , TITLE_BAR_HEIGHT(titleBarHeight) + , WINDOW_BORDER_THICKNESS(windowBorderThickness) + , WINDOW_OUTER_BORDER_WIDTH(windowOuterBorderThickness) + , textColor(RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b)) + , windowBorderColor(RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b)) + , windowOuterBorderColor(RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b)) { - this->selectedApproach = selectedApproach; - - this->windowBorderColor = RGB(styling.windowFrameColor.r, styling.windowFrameColor.g, styling.windowFrameColor.b); - this->windowOuterBorderColor = RGB(styling.windowOuterFrameColor.r, styling.windowOuterFrameColor.g, styling.windowOuterFrameColor.b); - float fontPointsSize = styling.fontSize * 72 / 96; this->font.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); + + this->selectedApproach = selectedApproach; } IWWindow::~IWWindow() { - this->titleBar.DestroyWindow(); + this->titleBar->DestroyWindow(); } int IWWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) @@ -56,7 +61,7 @@ int IWWindow::OnCreate(LPCREATESTRUCT lpCreateStruct) return -1; CRect barRect(0, 0, lpCreateStruct->cx, TITLE_BAR_HEIGHT); - if (!titleBar.CreateTopBar(this, barRect, IDC_TOPBAR)) + if (!titleBar->CreateTopBar(this, barRect, IDC_TOPBAR)) { AfxMessageBox(_T("Failed to create top bar")); return -1; @@ -93,6 +98,8 @@ void IWWindow::OnPaint() innerRect.InflateRect(-WINDOW_OUTER_BORDER_WIDTH, -WINDOW_OUTER_BORDER_WIDTH); memDC.FillSolidRect(innerRect, windowBorderColor); + DrawBorder(&memDC, rect); + // Copy the buffer to the screen dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY); @@ -110,10 +117,10 @@ void IWWindow::OnSize(UINT nType, int cx, int cy) Invalidate(); // Mark the entire client area for repaint } - if (titleBar.GetSafeHwnd()) + if (titleBar->GetSafeHwnd()) { - CRect barRect(WINDOW_BORDER_WIDTH, WINDOW_BORDER_WIDTH, cx - WINDOW_BORDER_WIDTH, TITLE_BAR_HEIGHT); - titleBar.MoveWindow(barRect); + CRect barRect(WINDOW_BORDER_THICKNESS, WINDOW_BORDER_THICKNESS, cx - WINDOW_BORDER_THICKNESS, TITLE_BAR_HEIGHT); + titleBar->MoveWindow(barRect); } if (ilsVisualization.GetSafeHwnd()) @@ -142,12 +149,35 @@ void IWWindow::OnSizing(UINT nSide, LPRECT lpRect) int snappedWidth = static_cast(std::round(width / SIZE_SNAP_INCREMENTS) * SIZE_SNAP_INCREMENTS); int snappedHeight = static_cast(std::round(height / SIZE_SNAP_INCREMENTS) * SIZE_SNAP_INCREMENTS); - // Adjust the RECT based on the resizing side. - // It's always the top right corner in our case + // Adjust the RECT based on the resizing side. if (nSide == WMSZ_TOPRIGHT) { lpRect->top = lpRect->bottom - snappedHeight; lpRect->right = lpRect->left + snappedWidth; } + else if (nSide == WMSZ_BOTTOMLEFT) { + lpRect->bottom = lpRect->top + snappedHeight; + lpRect->left = lpRect->right - snappedWidth; + } + else if (nSide == WMSZ_BOTTOMRIGHT) { + lpRect->bottom = lpRect->top + snappedHeight; + lpRect->right = lpRect->left + snappedWidth; + } + else if (nSide == WMSZ_TOPLEFT) { + lpRect->top = lpRect->bottom - snappedHeight; + lpRect->left = lpRect->right - snappedWidth; + } + else if (nSide == WMSZ_TOP) { + lpRect->top = lpRect->bottom - snappedHeight; + } + else if (nSide == WMSZ_BOTTOM) { + lpRect->bottom = lpRect->top + snappedHeight; + } + else if (nSide == WMSZ_LEFT) { + lpRect->left = lpRect->right - snappedWidth; + } + else if (nSide == WMSZ_RIGHT) { + lpRect->right = lpRect->left + snappedWidth; + } CWnd::OnSizing(nSide, lpRect); // Call the base class handler } @@ -190,13 +220,13 @@ CRect IWWindow::GetClientRectBelowTitleBar() // Adjust rect to exclude the top bar area (assume the top bar is 30 pixels high) CRect topBarRect; - titleBar.GetWindowRect(&topBarRect); + titleBar->GetWindowRect(&topBarRect); ScreenToClient(&topBarRect); // Convert to client coordinates rect.top = topBarRect.bottom; // Move the top to below the top bar - rect.left += WINDOW_BORDER_WIDTH; - rect.right -= WINDOW_BORDER_WIDTH; - rect.bottom -= WINDOW_BORDER_WIDTH; + rect.left += WINDOW_BORDER_THICKNESS; + rect.right -= WINDOW_BORDER_THICKNESS; + rect.bottom -= WINDOW_BORDER_THICKNESS; return rect; } @@ -243,6 +273,90 @@ void IWWindow::OnGetMinMaxInfo(MINMAXINFO* lpMMI) lpMMI->ptMinTrackSize.y = 100; // Minimum height in pixels } +void IWWindow::OnLButtonDown(UINT nFlags, CPoint point) +{ + bool isTop, isLeft, isRight, isBottom; + int cursorPosition = GetEdgeCursorPosition(point); + + if (cursorPosition != HTNOWHERE) { + SendMessage(WM_NCLBUTTONDOWN, cursorPosition, MAKELPARAM(point.x, point.y)); + } + + CWnd::OnLButtonDown(nFlags, point); +} + +void IWWindow::OnMouseMove(UINT nFlags, CPoint point) +{ + /*CRect clientRect; + GetClientRect(&clientRect); + + bool isTop, isLeft, isRight, isBottom; + GetEdgeCursorPosition(point, isTop, isLeft, isRight, isBottom); + + if (isTop && isLeft) + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENWSE)); + else if (isTop && isRight) + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENESW)); + else if (isBottom && isLeft) + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENESW)); + else if (isBottom && isRight) + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENWSE)); + else if (isTop || isBottom) + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS)); + else if (isLeft || isRight) + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE)); + else + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); + + CWnd::OnMouseMove(nFlags, point);*/ +} + +BOOL IWWindow::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) +{ + if (nHitTest == HTCLIENT) + { + // Get the cursor position in screen coordinates + POINT cursorPos; + GetCursorPos(&cursorPos); + + // Convert the cursor position to client coordinates + ScreenToClient(&cursorPos); + + // Check the position of the cursor relative to the window + int cursorPosition = GetEdgeCursorPosition(cursorPos); + + if (cursorPosition == HTNOWHERE) + { + return FALSE; // Prevent the system from overriding the cursor + } + + switch (cursorPosition) + { + case HTTOPLEFT: + case HTBOTTOMRIGHT: + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENWSE)); + break; + case HTTOPRIGHT: + case HTBOTTOMLEFT: + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENESW)); + break; + case HTTOP: + case HTBOTTOM: + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS)); + break; + case HTLEFT: + case HTRIGHT: + SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE)); + break; + } + + return TRUE; // Prevent the system from overriding the cursor + } + + // Default behavior for other non-client areas + return CWnd::OnSetCursor(pWnd, nHitTest, message); +} + std::string IWWindow::GetActiveApproachName() const { std::lock_guard lock(approachDataMutex); @@ -253,7 +367,7 @@ void IWWindow::SetActiveApproach(const IWApproachDefinition& selectedApproach) { std::unique_lock lock(approachDataMutex); this->selectedApproach = selectedApproach; - this->titleBar.SetTitle(selectedApproach.title); + this->titleBar->SetTitle(selectedApproach.title); ilsVisualization.SetActiveApproach(this->selectedApproach); Invalidate(); diff --git a/ILS_Window_Plugin/IWWindow.h b/ILS_Window_Plugin/IWWindow.h index 5b73382..2a5a617 100644 --- a/ILS_Window_Plugin/IWWindow.h +++ b/ILS_Window_Plugin/IWWindow.h @@ -1,8 +1,8 @@ #pragma once #include #include "IWDataTypes.h" -#include "IWTitleBar.h" #include "IWVisualization.h" +#include "IWTitleBar.h" #include #include @@ -10,10 +10,7 @@ #define IDC_TOPBAR 1002 #define WM_UPDATE_DATA (WM_USER + 1) -#define TITLE_BAR_HEIGHT 27 #define SIZE_SNAP_INCREMENTS 20.0 -#define WINDOW_BORDER_WIDTH 4 -#define WINDOW_OUTER_BORDER_WIDTH 1 class IWWindow; @@ -25,40 +22,35 @@ class IIWWndEventListener { virtual void OnWindowRectangleChanged(IWWindow* window) = 0; }; -class IWWindow : public CWnd, IWTitleBarEventListener { - protected: - afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); - afx_msg void OnPaint(); - afx_msg void OnSize(UINT nType, int cx, int cy); - afx_msg LRESULT OnExitSizeMove(WPARAM wParam, LPARAM lParam); - afx_msg void OnSizing(UINT nSide, LPRECT lpRect); - afx_msg BOOL PreCreateWindow(CREATESTRUCT& cs); - afx_msg LRESULT OnUpdateData(WPARAM wParam, LPARAM lParam); - afx_msg void OnDestroy(); - afx_msg BOOL OnEraseBkgnd(CDC* pDC); - afx_msg BOOL OnNcActivate(BOOL bActive); - afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); - - BOOL OnMenuOptionSelected(UINT nID); - void OnProcedureSelected(UINT nID); - - CRect GetClientRectBelowTitleBar(); - - DECLARE_MESSAGE_MAP() +class IWWindow : public CWnd, public IWTitleBarEventListener { + DECLARE_MESSAGE_MAP() public: - IWWindow(IWApproachDefinition selectedApproach, IWStyling styling); + IWWindow(IWApproachDefinition selectedApproach, IWStyling styling, int titleBarHeight, int windowBorderThickness, int windowOuterBorderThickness); virtual ~IWWindow(); void SetListener(IIWWndEventListener* listener); std::string GetActiveApproachName() const; void SetActiveApproach(const IWApproachDefinition& selectedApproach); void SetAvailableApproaches(const std::vector& approaches); + protected: + virtual void DrawBorder(CDC* pdc, CRect windowRect) = 0; + virtual int GetEdgeCursorPosition(CPoint point) = 0; + + const int TITLE_BAR_HEIGHT; + const int WINDOW_BORDER_THICKNESS; + const int WINDOW_OUTER_BORDER_WIDTH; + const COLORREF textColor; + const COLORREF windowBorderColor; + const COLORREF windowOuterBorderColor; + + IWTitleBar* titleBar; + + private: - IWTitleBar titleBar; + IWVisualization ilsVisualization; - COLORREF windowBorderColor; - COLORREF windowOuterBorderColor; + CFont font; void CreatePopupMenu(CPoint point); @@ -67,8 +59,6 @@ class IWWindow : public CWnd, IWTitleBarEventListener { IIWWndEventListener* m_listener = nullptr; - CFont font; - // For handling events from the title bar void OnResizeStart() override; void OnCloseButtonClicked() override; @@ -77,4 +67,23 @@ class IWWindow : public CWnd, IWTitleBarEventListener { // For thread safety between EuroScope and the window thread IWLiveData m_latestLiveData; mutable std::mutex approachDataMutex; + + afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct); + afx_msg void OnPaint(); + afx_msg void OnSize(UINT nType, int cx, int cy); + afx_msg LRESULT OnExitSizeMove(WPARAM wParam, LPARAM lParam); + afx_msg void OnSizing(UINT nSide, LPRECT lpRect); + afx_msg BOOL PreCreateWindow(CREATESTRUCT& cs); + afx_msg LRESULT OnUpdateData(WPARAM wParam, LPARAM lParam); + afx_msg void OnDestroy(); + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg BOOL OnNcActivate(BOOL bActive); + afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + + BOOL OnMenuOptionSelected(UINT nID); + void OnProcedureSelected(UINT nID); + CRect GetClientRectBelowTitleBar(); }; diff --git a/ILS_Window_Plugin/IWCloseBtn.cpp b/ILS_Window_Plugin/IWX11CloseBtn.cpp similarity index 85% rename from ILS_Window_Plugin/IWCloseBtn.cpp rename to ILS_Window_Plugin/IWX11CloseBtn.cpp index f5b5f2f..ca70bee 100644 --- a/ILS_Window_Plugin/IWCloseBtn.cpp +++ b/ILS_Window_Plugin/IWX11CloseBtn.cpp @@ -1,7 +1,7 @@ #include "pch.h" -#include "IWCloseBtn.h" +#include "IWX11CloseBtn.h" -void IWCloseBtn::DrawSymbol(CDC* pDC, CRect rect) +void IWX11CloseBtn::DrawSymbol(CDC* pDC, CRect rect) { // Draw a black circle in the center CBrush brush(RGB(0, 0, 0)); // Solid black brush for the circle diff --git a/ILS_Window_Plugin/IWCloseBtn.h b/ILS_Window_Plugin/IWX11CloseBtn.h similarity index 69% rename from ILS_Window_Plugin/IWCloseBtn.h rename to ILS_Window_Plugin/IWX11CloseBtn.h index a5448fc..10e03cf 100644 --- a/ILS_Window_Plugin/IWCloseBtn.h +++ b/ILS_Window_Plugin/IWX11CloseBtn.h @@ -1,7 +1,7 @@ #pragma once #include "IWTitleBarBtn.h" -class IWCloseBtn : public IWTitleBarBtn +class IWX11CloseBtn : public IWTitleBarBtn { void DrawSymbol(CDC* pDC, CRect rect) override; diff --git a/ILS_Window_Plugin/IWMenuBtn.cpp b/ILS_Window_Plugin/IWX11MenuBtn.cpp similarity index 85% rename from ILS_Window_Plugin/IWMenuBtn.cpp rename to ILS_Window_Plugin/IWX11MenuBtn.cpp index 53cf847..b1faa40 100644 --- a/ILS_Window_Plugin/IWMenuBtn.cpp +++ b/ILS_Window_Plugin/IWX11MenuBtn.cpp @@ -1,12 +1,12 @@ #include "pch.h" -#include "IWMenuBtn.h" +#include "IWX11MenuBtn.h" -BEGIN_MESSAGE_MAP(IWMenuBtn, CButton) +BEGIN_MESSAGE_MAP(IWX11MenuBtn, CButton) ON_WM_LBUTTONDOWN() ON_WM_ERASEBKGND() END_MESSAGE_MAP() -void IWMenuBtn::DrawSymbol(CDC* pDC, CRect rect) +void IWX11MenuBtn::DrawSymbol(CDC* pDC, CRect rect) { // Calculate the center of the rectangle CPoint center = rect.CenterPoint(); @@ -32,7 +32,7 @@ void IWMenuBtn::DrawSymbol(CDC* pDC, CRect rect) pDC->SelectObject(oldBrush); } -void IWMenuBtn::OnLButtonDown(UINT nFlags, CPoint point) +void IWX11MenuBtn::OnLButtonDown(UINT nFlags, CPoint point) { CPoint screenPoint = point; ClientToScreen(&screenPoint); @@ -45,7 +45,7 @@ void IWMenuBtn::OnLButtonDown(UINT nFlags, CPoint point) } } -BOOL IWMenuBtn::OnEraseBkgnd(CDC* pDC) +BOOL IWX11MenuBtn::OnEraseBkgnd(CDC* pDC) { return TRUE; } diff --git a/ILS_Window_Plugin/IWResizeBtn.h b/ILS_Window_Plugin/IWX11MenuBtn.h similarity index 84% rename from ILS_Window_Plugin/IWResizeBtn.h rename to ILS_Window_Plugin/IWX11MenuBtn.h index 67f624b..1bdd5b4 100644 --- a/ILS_Window_Plugin/IWResizeBtn.h +++ b/ILS_Window_Plugin/IWX11MenuBtn.h @@ -1,14 +1,13 @@ #pragma once #include "IWTitleBarBtn.h" -class IWResizeBtn : public IWTitleBarBtn +class IWX11MenuBtn : public IWTitleBarBtn { void DrawSymbol(CDC* pDC, CRect rect) override; + DECLARE_MESSAGE_MAP() + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg BOOL OnEraseBkgnd(CDC* pDC); - - - DECLARE_MESSAGE_MAP() }; diff --git a/ILS_Window_Plugin/IWResizeBtn.cpp b/ILS_Window_Plugin/IWX11ResizeBtn.cpp similarity index 81% rename from ILS_Window_Plugin/IWResizeBtn.cpp rename to ILS_Window_Plugin/IWX11ResizeBtn.cpp index 352261f..39e816d 100644 --- a/ILS_Window_Plugin/IWResizeBtn.cpp +++ b/ILS_Window_Plugin/IWX11ResizeBtn.cpp @@ -1,18 +1,18 @@ #include "pch.h" -#include "IWResizeBtn.h" +#include "IWX11ResizeBtn.h" -BEGIN_MESSAGE_MAP(IWResizeBtn, CButton) +BEGIN_MESSAGE_MAP(IWX11ResizeBtn, CButton) ON_WM_LBUTTONDOWN() ON_WM_ERASEBKGND() END_MESSAGE_MAP() -BOOL IWResizeBtn::OnEraseBkgnd(CDC* pDC) +BOOL IWX11ResizeBtn::OnEraseBkgnd(CDC* pDC) { // Do nothing to keep the background transparent return TRUE; } -void IWResizeBtn::DrawSymbol(CDC* pDC, CRect rect) { +void IWX11ResizeBtn::DrawSymbol(CDC* pDC, CRect rect) { // Create a 1px black pen CPen pen(PS_SOLID, 1, RGB(0, 0, 0)); CPen* oldPen = pDC->SelectObject(&pen); @@ -40,7 +40,7 @@ void IWResizeBtn::DrawSymbol(CDC* pDC, CRect rect) { } -void IWResizeBtn::OnLButtonDown(UINT nFlags, CPoint point) +void IWX11ResizeBtn::OnLButtonDown(UINT nFlags, CPoint point) { CPoint screenPoint = point; ClientToScreen(&screenPoint); diff --git a/ILS_Window_Plugin/IWMenuBtn.h b/ILS_Window_Plugin/IWX11ResizeBtn.h similarity index 83% rename from ILS_Window_Plugin/IWMenuBtn.h rename to ILS_Window_Plugin/IWX11ResizeBtn.h index e3e0abc..ce2a4d8 100644 --- a/ILS_Window_Plugin/IWMenuBtn.h +++ b/ILS_Window_Plugin/IWX11ResizeBtn.h @@ -1,13 +1,13 @@ #pragma once #include "IWTitleBarBtn.h" -class IWMenuBtn : public IWTitleBarBtn +class IWX11ResizeBtn : public IWTitleBarBtn { void DrawSymbol(CDC* pDC, CRect rect) override; - DECLARE_MESSAGE_MAP() - afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg BOOL OnEraseBkgnd(CDC* pDC); -}; + + DECLARE_MESSAGE_MAP() +}; diff --git a/ILS_Window_Plugin/IWX11TitleBar.cpp b/ILS_Window_Plugin/IWX11TitleBar.cpp new file mode 100644 index 0000000..03ef4d6 --- /dev/null +++ b/ILS_Window_Plugin/IWX11TitleBar.cpp @@ -0,0 +1,60 @@ +#include "pch.h" +#include "IWX11TitleBar.h" + +#include "IWX11CloseBtn.h" +#include "IWX11MenuBtn.h" +#include "IWX11ResizeBtn.h" + +IWX11TitleBar::IWX11TitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) + : IWTitleBar(title, backgroundColor, 14, listener) +{ + this->closeButton = new IWX11CloseBtn(); + this->menuButton = new IWX11MenuBtn(); + this->resizeButton = new IWX11ResizeBtn(); +} + +void IWX11TitleBar::PositionButtons(const CRect& rect) +{ + const int margin = 6; + const int btnHeight = rect.Height() - 9; + const int btnWidth = btnHeight * 1.2; + + // V center the buttons + const int top = (rect.Height() - btnHeight) / 2; + const int bottom = top + btnHeight; + + int right = rect.right - 3; + int left = right - btnWidth; + + // Position the resize button + CRect resizeButtonRect(left, top, right, bottom); + if (resizeButton->GetSafeHwnd()) + resizeButton->MoveWindow(resizeButtonRect); + + right = left - margin; + left = right - btnWidth; + + // Position the menu button + CRect menuButtonRect(left, top, right, bottom); + if (menuButton->GetSafeHwnd()) + menuButton->MoveWindow(menuButtonRect); + + right = left - margin; + left = right - btnWidth; + + // Position the close button + CRect closeButtonRect(left, top, right, bottom); + if (closeButton->GetSafeHwnd()) + closeButton->MoveWindow(closeButtonRect); +} + +void IWX11TitleBar::DrawTitle(CDC* pdc, CRect rect) +{ + CRect textArea = rect; + textArea.left += 5; + auto oldFont = pdc->SelectObject(this->font); + pdc->SetTextColor(this->textColor); + pdc->SetBkMode(TRANSPARENT); + pdc->DrawText(_T(this->text.c_str()), -1, textArea, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + pdc->SelectObject(oldFont); +} diff --git a/ILS_Window_Plugin/IWX11TitleBar.h b/ILS_Window_Plugin/IWX11TitleBar.h new file mode 100644 index 0000000..10e5406 --- /dev/null +++ b/ILS_Window_Plugin/IWX11TitleBar.h @@ -0,0 +1,16 @@ +#pragma once +#include "IWTitleBar.h" + +class IWX11TitleBar : public IWTitleBar +{ +public: + IWX11TitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener); + virtual ~IWX11TitleBar() {} + +private: + void PositionButtons(const CRect& rect) override; + void DrawTitle(CDC* pdc, CRect rect) override; + + COLORREF textColor; +}; + diff --git a/ILS_Window_Plugin/IWX11Window.cpp b/ILS_Window_Plugin/IWX11Window.cpp new file mode 100644 index 0000000..fcc05e5 --- /dev/null +++ b/ILS_Window_Plugin/IWX11Window.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "IWX11Window.h" +#include "IWX11TitleBar.h" + +IWX11Window::IWX11Window(IWApproachDefinition selectedApproach, IWStyling styling) + : IWWindow(selectedApproach, styling, 26, 3, 1) +{ + this->titleBar = new IWX11TitleBar(selectedApproach.title, windowBorderColor, textColor, this); +} + +void IWX11Window::DrawBorder(CDC* pdc, CRect windowRect) +{ + // Draw the border + CPen pen(PS_SOLID, 1, RGB(0, 0, 0)); // Black pen for the border + CPen* oldPen = pdc->SelectObject(&pen); + pdc->SelectStockObject(NULL_BRUSH); // No fill for the rectangle + pdc->Rectangle(&windowRect); + pdc->SelectObject(oldPen); +} + +int IWX11Window::GetEdgeCursorPosition(CPoint point) +{ + return 0; +} diff --git a/ILS_Window_Plugin/IWX11Window.h b/ILS_Window_Plugin/IWX11Window.h new file mode 100644 index 0000000..874be80 --- /dev/null +++ b/ILS_Window_Plugin/IWX11Window.h @@ -0,0 +1,13 @@ +#pragma once +#include "IWWindow.h" +class IWX11Window : + public IWWindow +{ +public: + IWX11Window(IWApproachDefinition selectedApproach, IWStyling styling); + +private: + void DrawBorder(CDC* pdc, CRect windowRect) override; + int GetEdgeCursorPosition(CPoint point) override; +}; + diff --git a/ILS_Window_Plugin/RenderUtils.h b/ILS_Window_Plugin/RenderUtils.h new file mode 100644 index 0000000..a224c0b --- /dev/null +++ b/ILS_Window_Plugin/RenderUtils.h @@ -0,0 +1,103 @@ +#pragma once + +#include + +inline void Draw3dRect(CDC* pDC, CRect rect, int steps, COLORREF lightColor, COLORREF darkColor) +{ + for (int drawnSunkSteps = 0; drawnSunkSteps < steps; drawnSunkSteps++) { + // Draw the top-left border (light color) + pDC->FillSolidRect(rect.left, rect.top, rect.Width(), 1, lightColor); // Top + pDC->FillSolidRect(rect.left, rect.top, 1, rect.Height(), lightColor); // Left + + // Draw the bottom-right border (dark color) + pDC->FillSolidRect(rect.right - 1, rect.top, 1, rect.Height(), darkColor); // Right + pDC->FillSolidRect(rect.left, rect.bottom - 1, rect.Width(), 1, darkColor); // Bottom + + rect.DeflateRect(1, 1); + } +} + +inline void Draw3dCorner(CDC* pDC, CRect rect, int borderWidth, int steps, COLORREF lightColor, COLORREF darkColor, bool isTop, bool isLeft) +{ + CPen lightPen(PS_SOLID, 1, lightColor); + CPen darkPen(PS_SOLID, 1, darkColor); + + for (int drawnSunkSteps = 0; drawnSunkSteps < steps; drawnSunkSteps++) { + if (isTop && isLeft) + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.right, rect.top); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.right, rect.top); + pDC->LineTo(rect.right, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.bottom); + pDC->LineTo(rect.left, rect.bottom); + } + else if (isTop && !isLeft) + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.right, rect.top); + + pDC->MoveTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.bottom); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.right, rect.top); + pDC->LineTo(rect.right, rect.bottom); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.bottom); + + pDC->MoveTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + } + else if (!isTop && !isLeft) // Bottom right + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.right, rect.top); + + pDC->MoveTo(rect.left, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.top); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.right, rect.top); + pDC->LineTo(rect.right, rect.bottom); + pDC->LineTo(rect.left, rect.bottom); + } + else if (!isTop && isLeft) // Bottom left + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.top); + + pDC->MoveTo(rect.right, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.right, rect.bottom); + pDC->LineTo(rect.right, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + + pDC->MoveTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.top); + } + + rect.DeflateRect(1, 1); + } +} \ No newline at end of file diff --git a/ILS_Window_Plugin/ReunderUtils.cpp b/ILS_Window_Plugin/ReunderUtils.cpp new file mode 100644 index 0000000..d93b6e3 --- /dev/null +++ b/ILS_Window_Plugin/ReunderUtils.cpp @@ -0,0 +1,105 @@ +#include "RenderUtils.h" + +void Draw3dRect(CDC* pDC, CRect rect) +{ + // Colors for the sWunken effectrgb(150, 249, 252) + COLORREF lightColor = RGB(150, 249, 252); // Lighter shade for top/left + COLORREF darkColor = RGB(60, 111, 119); // Darker shade for bottom/right + + for (int drawnSunkSteps = 0; drawnSunkSteps < 2; drawnSunkSteps++) { + // Draw the top-left border (light color) + pDC->FillSolidRect(rect.left, rect.top, rect.Width(), 1, lightColor); // Top + pDC->FillSolidRect(rect.left, rect.top, 1, rect.Height(), lightColor); // Left + + // Draw the bottom-right border (dark color) + pDC->FillSolidRect(rect.right - 1, rect.top, 1, rect.Height(), darkColor); // Right + pDC->FillSolidRect(rect.left, rect.bottom - 1, rect.Width(), 1, darkColor); // Bottom + + rect.DeflateRect(1, 1); + } +} + +void Draw3dCorner(CDC* pDC, CRect rect, int borderWidth, COLORREF lightColor, COLORREF darkColor, bool isTop, bool isLeft) +{ + CPen lightPen(PS_SOLID, 1, lightColor); + CPen darkPen(PS_SOLID, 1, darkColor); + + for (int drawnSunkSteps = 0; drawnSunkSteps < 2; drawnSunkSteps++) { + if (isTop && isLeft) + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.right, rect.top); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.right, rect.top); + pDC->LineTo(rect.right, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.bottom); + pDC->LineTo(rect.left, rect.bottom); + } + else if (isTop && !isLeft) + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.right, rect.top); + + pDC->MoveTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.bottom); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.right, rect.top); + pDC->LineTo(rect.right, rect.bottom); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.bottom); + + pDC->MoveTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + pDC->LineTo(rect.left, rect.top + borderWidth - drawnSunkSteps * 2 - 2); + } + else if (!isTop && !isLeft) // Bottom right + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.right, rect.top); + + pDC->MoveTo(rect.left, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.right - borderWidth + drawnSunkSteps * 2 + 2, rect.top); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.right, rect.top); + pDC->LineTo(rect.right, rect.bottom); + pDC->LineTo(rect.left, rect.bottom); + } + else if (!isTop && isLeft) // Bottom left + { + // Light color + pDC->SelectObject(&lightPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.left, rect.top); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.top); + + pDC->MoveTo(rect.right, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + + // Dark color + pDC->SelectObject(&darkPen); + pDC->MoveTo(rect.left, rect.bottom); + pDC->LineTo(rect.right, rect.bottom); + pDC->LineTo(rect.right, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + + pDC->MoveTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.bottom - borderWidth + drawnSunkSteps * 2 + 2); + pDC->LineTo(rect.left + borderWidth - drawnSunkSteps * 2 - 2, rect.top); + } + + rect.DeflateRect(1, 1); + } +} \ No newline at end of file diff --git a/Sample config/ILS_Window_Plugin-config.json b/Sample config/ILS_Window_Plugin-config.json index 91be09e..62ae257 100644 --- a/Sample config/ILS_Window_Plugin-config.json +++ b/Sample config/ILS_Window_Plugin-config.json @@ -1,6 +1,7 @@ { "behaviour": { - "openWindowsBasedOnActiveRunways": true + "openWindowsBasedOnActiveRunways": true, + "windowSimulation": "CDE" }, "styling": { "fontSize": 12, From cec3730684442ff08d202e7e4050e137254e7f0c Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Mon, 27 Jan 2025 19:37:53 +0100 Subject: [PATCH 4/8] Make windows minimizable and only closable from menu --- ILS_Window_Plugin/ILS_Window_Plugin.vcxproj | 8 ++--- .../ILS_Window_Plugin.vcxproj.filters | 8 ++--- ILS_Window_Plugin/IWCdeCloseBtn.cpp | 24 --------------- ILS_Window_Plugin/IWCdeIconifyBtn.cpp | 24 +++++++++++++++ .../{IWCdeCloseBtn.h => IWCdeIconifyBtn.h} | 4 +-- ILS_Window_Plugin/IWCdeTitleBar.cpp | 12 ++++---- ILS_Window_Plugin/IWPlugin.cpp | 29 +++++++++++++++---- ILS_Window_Plugin/IWPlugin.h | 2 +- ILS_Window_Plugin/IWTitleBar.cpp | 10 +++---- ILS_Window_Plugin/IWTitleBar.h | 6 ++-- ILS_Window_Plugin/IWTitleBarBtn.h | 2 +- ILS_Window_Plugin/IWWindow.cpp | 16 ++++++++-- ILS_Window_Plugin/IWWindow.h | 2 +- ...{IWX11CloseBtn.cpp => IWX11IconifyBtn.cpp} | 4 +-- .../{IWX11CloseBtn.h => IWX11IconifyBtn.h} | 2 +- ILS_Window_Plugin/IWX11TitleBar.cpp | 12 ++++---- 16 files changed, 97 insertions(+), 68 deletions(-) delete mode 100644 ILS_Window_Plugin/IWCdeCloseBtn.cpp create mode 100644 ILS_Window_Plugin/IWCdeIconifyBtn.cpp rename ILS_Window_Plugin/{IWCdeCloseBtn.h => IWCdeIconifyBtn.h} (61%) rename ILS_Window_Plugin/{IWX11CloseBtn.cpp => IWX11IconifyBtn.cpp} (84%) rename ILS_Window_Plugin/{IWX11CloseBtn.h => IWX11IconifyBtn.h} (68%) diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj index 69d765f..180c96f 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj @@ -117,8 +117,8 @@ - - + + @@ -145,8 +145,8 @@ - - + + diff --git a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters index fa7c435..ac9e572 100644 --- a/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters +++ b/ILS_Window_Plugin/ILS_Window_Plugin.vcxproj.filters @@ -54,7 +54,7 @@ Source Files - + Source Files\Theme\SolarisCDE @@ -66,7 +66,7 @@ Source Files\Theme\X11 - + Source Files\Theme\X11 @@ -130,7 +130,7 @@ Header Files\Theme\SolarisCDE - + Header Files\Theme\SolarisCDE @@ -145,7 +145,7 @@ Header Files\Theme\X11 - + Header Files\Theme\X11 diff --git a/ILS_Window_Plugin/IWCdeCloseBtn.cpp b/ILS_Window_Plugin/IWCdeCloseBtn.cpp deleted file mode 100644 index 842c0fe..0000000 --- a/ILS_Window_Plugin/IWCdeCloseBtn.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "pch.h" -#include "IWCdeCloseBtn.h" -#include "RenderUtils.h" - -IWCdeCloseBtn::IWCdeCloseBtn(COLORREF lightColor, COLORREF darkColor) -{ - this->lightColor = lightColor; - this->darkColor = darkColor; -} - -void IWCdeCloseBtn::DrawSymbol(CDC* pdc, CRect rect) -{ - CRect menuButton = rect; - menuButton.left = rect.right - rect.Height(); - - CRect closeIcon = menuButton; - closeIcon.left = menuButton.left + menuButton.Width() * 0.4; - closeIcon.right = closeIcon.right - menuButton.Width() * 0.4; - closeIcon.top = menuButton.top + menuButton.Width() * 0.4; - closeIcon.bottom = menuButton.bottom - menuButton.Width() * 0.4; - - Draw3dRect(pdc, menuButton, 1, lightColor, darkColor); - Draw3dRect(pdc, closeIcon, 1, lightColor, darkColor); -} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWCdeIconifyBtn.cpp b/ILS_Window_Plugin/IWCdeIconifyBtn.cpp new file mode 100644 index 0000000..e607afb --- /dev/null +++ b/ILS_Window_Plugin/IWCdeIconifyBtn.cpp @@ -0,0 +1,24 @@ +#include "pch.h" +#include "IWCdeIconifyBtn.h" +#include "RenderUtils.h" + +IWCdeIconifyBtn::IWCdeIconifyBtn(COLORREF lightColor, COLORREF darkColor) +{ + this->lightColor = lightColor; + this->darkColor = darkColor; +} + +void IWCdeIconifyBtn::DrawSymbol(CDC* pdc, CRect rect) +{ + CRect buttonFrame = rect; + buttonFrame.left = rect.right - rect.Height(); + + CRect icon = buttonFrame; + icon.left = buttonFrame.left + buttonFrame.Width() * 0.4; + icon.right = icon.right - buttonFrame.Width() * 0.4; + icon.top = buttonFrame.top + buttonFrame.Width() * 0.4; + icon.bottom = buttonFrame.bottom - buttonFrame.Width() * 0.4; + + Draw3dRect(pdc, buttonFrame, 1, lightColor, darkColor); + Draw3dRect(pdc, icon, 1, lightColor, darkColor); +} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWCdeCloseBtn.h b/ILS_Window_Plugin/IWCdeIconifyBtn.h similarity index 61% rename from ILS_Window_Plugin/IWCdeCloseBtn.h rename to ILS_Window_Plugin/IWCdeIconifyBtn.h index 6320b9e..c001336 100644 --- a/ILS_Window_Plugin/IWCdeCloseBtn.h +++ b/ILS_Window_Plugin/IWCdeIconifyBtn.h @@ -2,10 +2,10 @@ #include "IWTitleBarBtn.h" -class IWCdeCloseBtn : public IWTitleBarBtn +class IWCdeIconifyBtn : public IWTitleBarBtn { public: - IWCdeCloseBtn(COLORREF lightColor, COLORREF darkColor); + IWCdeIconifyBtn(COLORREF lightColor, COLORREF darkColor); private: void DrawSymbol(CDC* pDC, CRect rect) override; diff --git a/ILS_Window_Plugin/IWCdeTitleBar.cpp b/ILS_Window_Plugin/IWCdeTitleBar.cpp index 22f6b6a..4488d70 100644 --- a/ILS_Window_Plugin/IWCdeTitleBar.cpp +++ b/ILS_Window_Plugin/IWCdeTitleBar.cpp @@ -1,7 +1,7 @@ #include "pch.h" #include "IWCdeTitleBar.h" #include "RenderUtils.h" -#include "IWCdeCloseBtn.h" +#include "IWCdeIconifyBtn.h" #include "IWCdeMenuBtn.h" #include "IWX11ResizeBtn.h" @@ -12,7 +12,7 @@ IWCdeTitleBar::IWCdeTitleBar(std::string title, COLORREF backgroundColor, COLORR this->darkColor = darkcolor; this->textColor = textColor; - this->closeButton = new IWCdeCloseBtn(lightColor, darkColor); + this->iconifyButton = new IWCdeIconifyBtn(lightColor, darkColor); this->menuButton = new IWCdeMenuBtn(lightColor, darkColor); this->resizeButton = new IWX11ResizeBtn(); } @@ -23,13 +23,13 @@ void IWCdeTitleBar::PositionButtons(const CRect& rect) if (menuButton->GetSafeHwnd()) menuButton->MoveWindow(menuButtonRect); - CRect closeButtonRect(rect.right - rect.Height(), rect.top, rect.right, rect.bottom); - if (closeButton->GetSafeHwnd()) - closeButton->MoveWindow(closeButtonRect); + CRect iconifyButtonRect(rect.right - rect.Height(), rect.top, rect.right, rect.bottom); + if (iconifyButton->GetSafeHwnd()) + iconifyButton->MoveWindow(iconifyButtonRect); titleArea = rect; titleArea.left = menuButtonRect.right; - titleArea.right = closeButtonRect.left; + titleArea.right = iconifyButtonRect.left; } void IWCdeTitleBar::DrawTitle(CDC* pdc, CRect rect) diff --git a/ILS_Window_Plugin/IWPlugin.cpp b/ILS_Window_Plugin/IWPlugin.cpp index 189acc5..e7aa881 100644 --- a/ILS_Window_Plugin/IWPlugin.cpp +++ b/ILS_Window_Plugin/IWPlugin.cpp @@ -43,10 +43,27 @@ IWPlugin::~IWPlugin() } } -void IWPlugin::OpenNewWindow(IWApproachDefinition* approach) +void IWPlugin::ShowWindow(IWApproachDefinition* approach) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); + // Check if a window with the same title is already open + IWWindow* windowWithSameTitle = nullptr; + for (const auto& window : windows) { + if (window->GetActiveApproachName() == approach->title) { + windowWithSameTitle = window; + break; + } + } + + if (windowWithSameTitle) { + // Restore the window and bring it to the front + windowWithSameTitle->ShowWindow(SW_RESTORE); + windowWithSameTitle->SetForegroundWindow(); + return; + } + + // Calculate the spawning point for the new window CPoint spawningPoint = CPoint(int(windows.size()) * 50, int(windows.size()) * 50 + 100); CSize windowSize = CSize(300, 200); @@ -76,7 +93,7 @@ void IWPlugin::OpenNewWindow(IWApproachDefinition* approach) } auto hwndPopup = newWindow->CreateEx( - WS_EX_NOACTIVATE | WS_EX_TOPMOST, + WS_EX_TOPMOST | WS_EX_APPWINDOW | WS_EX_NOACTIVATE, WINDOW_CLASS_NAME, _T(approach->title.c_str()), WS_POPUP, @@ -240,7 +257,7 @@ void IWPlugin::SyncWithActiveRunways() }); if (!alreadyOpen) { - this->OpenNewWindow(approach); + this->ShowWindow(approach); } } } @@ -421,10 +438,10 @@ void IWPlugin::OnWindowMenuOpenNew(std::string approachTitle) }); if (selectedApproach != availableApproaches.end()) { - OpenNewWindow(&(*selectedApproach)); + ShowWindow(&(*selectedApproach)); } else { - OpenNewWindow(&availableApproaches[0]); + ShowWindow(&availableApproaches[0]); } } @@ -488,7 +505,7 @@ bool IWPlugin::OnCompileCommand(const char* sCommandLine) if (it != this->availableApproaches.end()) { // Approach found: Open the approach window - this->OpenNewWindow(&(*it)); + this->ShowWindow(&(*it)); return true; // Command handled } else { diff --git a/ILS_Window_Plugin/IWPlugin.h b/ILS_Window_Plugin/IWPlugin.h index a8416ee..cc1eadd 100644 --- a/ILS_Window_Plugin/IWPlugin.h +++ b/ILS_Window_Plugin/IWPlugin.h @@ -24,7 +24,7 @@ class IWPlugin : public EuroScopePlugIn::CPlugIn, IIWWndEventListener { std::map savedWindowPositions; std::map airportTemperatures; - void OpenNewWindow(IWApproachDefinition* approach); + void ShowWindow(IWApproachDefinition* approach); void SyncWithActiveRunways(); void LoadSavedWindowPositions(); void ShowErrorMessage(std::string consequence, std::string details); diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index b12f293..82d614a 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -8,7 +8,7 @@ BEGIN_MESSAGE_MAP(IWTitleBar, CStatic) ON_WM_PAINT() ON_WM_SIZE() // Handle resizing ON_WM_LBUTTONDOWN() - ON_BN_CLICKED(IDC_CLOSE_BUTTON, &IWTitleBar::OnCloseButtonClicked) + ON_BN_CLICKED(IDC_CLOSE_BUTTON, &IWTitleBar::OnIconifyButtonClicked) ON_BN_CLICKED(IDC_MENU_BUTTON, &IWTitleBar::OnMenuButtonClicked) END_MESSAGE_MAP() @@ -30,7 +30,7 @@ BOOL IWTitleBar::CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID) // Create buttons with default settings if (!resizeButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_RESIZE_BUTTON) || !menuButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_MENU_BUTTON) || - !closeButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_CLOSE_BUTTON)) + !iconifyButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_CLOSE_BUTTON)) return FALSE; // Position buttons @@ -43,7 +43,7 @@ void IWTitleBar::OnSize(UINT nType, int cx, int cy) { CStatic::OnSize(nType, cx, cy); - if (closeButton->GetSafeHwnd() && menuButton->GetSafeHwnd() && resizeButton->GetSafeHwnd()) + if (iconifyButton->GetSafeHwnd() && menuButton->GetSafeHwnd() && resizeButton->GetSafeHwnd()) { CRect rect; GetClientRect(&rect); // Get the updated size of the title bar @@ -89,9 +89,9 @@ void IWTitleBar::OnLButtonDown(UINT nFlags, CPoint point) CWnd::OnLButtonDown(nFlags, point); } -void IWTitleBar::OnCloseButtonClicked() +void IWTitleBar::OnIconifyButtonClicked() { - this->eventListener->OnCloseButtonClicked(); + this->eventListener->OnIconifyButtonClicked(); } void IWTitleBar::OnMenuButtonClicked() diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index 7a62ea5..6fb120c 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -9,7 +9,7 @@ class IWTitleBarEventListener { public: virtual void OnResizeStart() = 0; - virtual void OnCloseButtonClicked() = 0; + virtual void OnIconifyButtonClicked() = 0; virtual void OnMenuButtonClicked() = 0; }; @@ -32,7 +32,7 @@ class IWTitleBar : public CStatic virtual void DrawTitle(CDC* pdc, CRect rect) {}; IWTitleBarBtn* menuButton; - IWTitleBarBtn* closeButton; + IWTitleBarBtn* iconifyButton; IWTitleBarBtn* resizeButton; std::string text; @@ -44,7 +44,7 @@ class IWTitleBar : public CStatic afx_msg void OnPaint(); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); - afx_msg void OnCloseButtonClicked(); + afx_msg void OnIconifyButtonClicked(); afx_msg void OnMenuButtonClicked(); afx_msg void OnSize(UINT nType, int cx, int cy); }; diff --git a/ILS_Window_Plugin/IWTitleBarBtn.h b/ILS_Window_Plugin/IWTitleBarBtn.h index 0827ab8..7417481 100644 --- a/ILS_Window_Plugin/IWTitleBarBtn.h +++ b/ILS_Window_Plugin/IWTitleBarBtn.h @@ -5,7 +5,7 @@ class IWTitleBarBtn : public CButton { public: virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); - virtual BOOL IWTitleBarBtn::OnEraseBkgnd(CDC* pDC); + virtual BOOL OnEraseBkgnd(CDC* pDC); protected: DECLARE_MESSAGE_MAP() diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index 157159a..a127805 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -10,6 +10,7 @@ #define MENU_ITEM_FLIP 10000 #define MENU_ITEM_SHOW_LABELS 10001 #define MENU_ITEM_CORRECT_FOR_TEMPERATURE 10002 +#define MENU_ITEM_CLOSE 10003 #define MENU_ITEM_PROCEDURES_SEL_START 20000 #define MENU_ITEM_PROCEDURES_NEW_START 30000 @@ -31,6 +32,7 @@ BEGIN_MESSAGE_MAP(IWWindow, CWnd) ON_COMMAND_EX(MENU_ITEM_FLIP, &IWWindow::OnMenuOptionSelected) ON_COMMAND_EX(MENU_ITEM_SHOW_LABELS, &IWWindow::OnMenuOptionSelected) ON_COMMAND_EX(MENU_ITEM_CORRECT_FOR_TEMPERATURE, &IWWindow::OnMenuOptionSelected) + ON_COMMAND_EX(MENU_ITEM_CLOSE, &IWWindow::OnMenuOptionSelected) ON_COMMAND_RANGE(MENU_ITEM_PROCEDURES_SEL_START, MENU_ITEM_PROCEDURES_SEL_START + MAX_PROCEDURES, &IWWindow::OnProcedureSelected) ON_COMMAND_RANGE(MENU_ITEM_PROCEDURES_NEW_START, MENU_ITEM_PROCEDURES_NEW_START + MAX_PROCEDURES, &IWWindow::OnProcedureSelected) END_MESSAGE_MAP() @@ -236,9 +238,9 @@ void IWWindow::OnResizeStart() SendMessage(WM_NCLBUTTONDOWN, HTTOPRIGHT, NULL); // Resize using the top right corner } -void IWWindow::OnCloseButtonClicked() +void IWWindow::OnIconifyButtonClicked() { - this->DestroyWindow(); + this->ShowWindow(SW_MINIMIZE); } void IWWindow::OnMenuButtonClicked() @@ -430,6 +432,12 @@ void IWWindow::CreatePopupMenu(CPoint point) _T("Change orientation") ); + menu.AppendMenu( + MF_STRING | MF_REMOVE, + MENU_ITEM_CLOSE, + _T("Close") + ); + // Display the menu menu.TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this); } @@ -451,6 +459,10 @@ BOOL IWWindow::OnMenuOptionSelected(UINT nID) ilsVisualization.SetApplyTemperatureCorrection(!ilsVisualization.GetApplyTemperatureCorrection()); Invalidate(); } + else if (nID == MENU_ITEM_CLOSE) + { + this->DestroyWindow(); + } return TRUE; } diff --git a/ILS_Window_Plugin/IWWindow.h b/ILS_Window_Plugin/IWWindow.h index 2a5a617..c901b9c 100644 --- a/ILS_Window_Plugin/IWWindow.h +++ b/ILS_Window_Plugin/IWWindow.h @@ -61,7 +61,7 @@ class IWWindow : public CWnd, public IWTitleBarEventListener { // For handling events from the title bar void OnResizeStart() override; - void OnCloseButtonClicked() override; + void OnIconifyButtonClicked() override; void OnMenuButtonClicked() override; // For thread safety between EuroScope and the window thread diff --git a/ILS_Window_Plugin/IWX11CloseBtn.cpp b/ILS_Window_Plugin/IWX11IconifyBtn.cpp similarity index 84% rename from ILS_Window_Plugin/IWX11CloseBtn.cpp rename to ILS_Window_Plugin/IWX11IconifyBtn.cpp index ca70bee..db8893a 100644 --- a/ILS_Window_Plugin/IWX11CloseBtn.cpp +++ b/ILS_Window_Plugin/IWX11IconifyBtn.cpp @@ -1,7 +1,7 @@ #include "pch.h" -#include "IWX11CloseBtn.h" +#include "IWX11IconifyBtn.h" -void IWX11CloseBtn::DrawSymbol(CDC* pDC, CRect rect) +void IWX11IconifyBtn::DrawSymbol(CDC* pDC, CRect rect) { // Draw a black circle in the center CBrush brush(RGB(0, 0, 0)); // Solid black brush for the circle diff --git a/ILS_Window_Plugin/IWX11CloseBtn.h b/ILS_Window_Plugin/IWX11IconifyBtn.h similarity index 68% rename from ILS_Window_Plugin/IWX11CloseBtn.h rename to ILS_Window_Plugin/IWX11IconifyBtn.h index 10e03cf..bc78c50 100644 --- a/ILS_Window_Plugin/IWX11CloseBtn.h +++ b/ILS_Window_Plugin/IWX11IconifyBtn.h @@ -1,7 +1,7 @@ #pragma once #include "IWTitleBarBtn.h" -class IWX11CloseBtn : public IWTitleBarBtn +class IWX11IconifyBtn : public IWTitleBarBtn { void DrawSymbol(CDC* pDC, CRect rect) override; diff --git a/ILS_Window_Plugin/IWX11TitleBar.cpp b/ILS_Window_Plugin/IWX11TitleBar.cpp index 03ef4d6..376e69d 100644 --- a/ILS_Window_Plugin/IWX11TitleBar.cpp +++ b/ILS_Window_Plugin/IWX11TitleBar.cpp @@ -1,14 +1,14 @@ #include "pch.h" #include "IWX11TitleBar.h" -#include "IWX11CloseBtn.h" +#include "IWX11IconifyBtn.h" #include "IWX11MenuBtn.h" #include "IWX11ResizeBtn.h" IWX11TitleBar::IWX11TitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) : IWTitleBar(title, backgroundColor, 14, listener) { - this->closeButton = new IWX11CloseBtn(); + this->iconifyButton = new IWX11IconifyBtn(); this->menuButton = new IWX11MenuBtn(); this->resizeButton = new IWX11ResizeBtn(); } @@ -42,10 +42,10 @@ void IWX11TitleBar::PositionButtons(const CRect& rect) right = left - margin; left = right - btnWidth; - // Position the close button - CRect closeButtonRect(left, top, right, bottom); - if (closeButton->GetSafeHwnd()) - closeButton->MoveWindow(closeButtonRect); + // Position the iconify button + CRect iconifyButtonRect(left, top, right, bottom); + if (iconifyButton->GetSafeHwnd()) + iconifyButton->MoveWindow(iconifyButtonRect); } void IWX11TitleBar::DrawTitle(CDC* pdc, CRect rect) From 3d26d3cced6d971f052ffb67346e98e8d89818c7 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Mon, 27 Jan 2025 21:48:12 +0100 Subject: [PATCH 5/8] Simplify title bar button logic --- ILS_Window_Plugin/IWCdeIconifyBtn.cpp | 2 +- ILS_Window_Plugin/IWCdeIconifyBtn.h | 2 +- ILS_Window_Plugin/IWCdeMenuBtn.cpp | 2 +- ILS_Window_Plugin/IWCdeMenuBtn.h | 2 +- ILS_Window_Plugin/IWCdeTitleBar.cpp | 6 +-- ILS_Window_Plugin/IWTitleBar.cpp | 17 +++---- ILS_Window_Plugin/IWTitleBar.h | 1 + ILS_Window_Plugin/IWTitleBarBtn.cpp | 65 ++++++++++++++++++++++++++- ILS_Window_Plugin/IWTitleBarBtn.h | 19 ++++++-- ILS_Window_Plugin/IWWindow.cpp | 27 ----------- ILS_Window_Plugin/IWWindow.h | 2 - ILS_Window_Plugin/IWX11IconifyBtn.h | 1 + ILS_Window_Plugin/IWX11MenuBtn.cpp | 22 --------- ILS_Window_Plugin/IWX11MenuBtn.h | 6 +-- ILS_Window_Plugin/IWX11ResizeBtn.cpp | 25 ----------- ILS_Window_Plugin/IWX11ResizeBtn.h | 7 +-- ILS_Window_Plugin/IWX11TitleBar.cpp | 6 +-- 17 files changed, 101 insertions(+), 111 deletions(-) diff --git a/ILS_Window_Plugin/IWCdeIconifyBtn.cpp b/ILS_Window_Plugin/IWCdeIconifyBtn.cpp index e607afb..acb91ef 100644 --- a/ILS_Window_Plugin/IWCdeIconifyBtn.cpp +++ b/ILS_Window_Plugin/IWCdeIconifyBtn.cpp @@ -2,7 +2,7 @@ #include "IWCdeIconifyBtn.h" #include "RenderUtils.h" -IWCdeIconifyBtn::IWCdeIconifyBtn(COLORREF lightColor, COLORREF darkColor) +IWCdeIconifyBtn::IWCdeIconifyBtn(COLORREF backgroundColor, COLORREF lightColor, COLORREF darkColor) : IWTitleBarBtn(backgroundColor) { this->lightColor = lightColor; this->darkColor = darkColor; diff --git a/ILS_Window_Plugin/IWCdeIconifyBtn.h b/ILS_Window_Plugin/IWCdeIconifyBtn.h index c001336..27ad3c0 100644 --- a/ILS_Window_Plugin/IWCdeIconifyBtn.h +++ b/ILS_Window_Plugin/IWCdeIconifyBtn.h @@ -5,7 +5,7 @@ class IWCdeIconifyBtn : public IWTitleBarBtn { public: - IWCdeIconifyBtn(COLORREF lightColor, COLORREF darkColor); + IWCdeIconifyBtn(COLORREF backgroundColor, COLORREF lightColor, COLORREF darkColor); private: void DrawSymbol(CDC* pDC, CRect rect) override; diff --git a/ILS_Window_Plugin/IWCdeMenuBtn.cpp b/ILS_Window_Plugin/IWCdeMenuBtn.cpp index b3c6f71..1eb860d 100644 --- a/ILS_Window_Plugin/IWCdeMenuBtn.cpp +++ b/ILS_Window_Plugin/IWCdeMenuBtn.cpp @@ -2,7 +2,7 @@ #include "IWCdeMenuBtn.h" #include "RenderUtils.h" -IWCdeMenuBtn::IWCdeMenuBtn(COLORREF lightColor, COLORREF darkColor) +IWCdeMenuBtn::IWCdeMenuBtn(COLORREF backgroundColor, COLORREF lightColor, COLORREF darkColor) : IWTitleBarBtn(backgroundColor) { this->lightColor = lightColor; this->darkColor = darkColor; diff --git a/ILS_Window_Plugin/IWCdeMenuBtn.h b/ILS_Window_Plugin/IWCdeMenuBtn.h index f09437f..7fd26fc 100644 --- a/ILS_Window_Plugin/IWCdeMenuBtn.h +++ b/ILS_Window_Plugin/IWCdeMenuBtn.h @@ -5,7 +5,7 @@ class IWCdeMenuBtn : public IWTitleBarBtn { public: - IWCdeMenuBtn(COLORREF lightColor, COLORREF darkColor); + IWCdeMenuBtn(COLORREF backgroundColor, COLORREF lightColor, COLORREF darkColor); private: void DrawSymbol(CDC* pDC, CRect rect) override; diff --git a/ILS_Window_Plugin/IWCdeTitleBar.cpp b/ILS_Window_Plugin/IWCdeTitleBar.cpp index 4488d70..da9544e 100644 --- a/ILS_Window_Plugin/IWCdeTitleBar.cpp +++ b/ILS_Window_Plugin/IWCdeTitleBar.cpp @@ -12,9 +12,9 @@ IWCdeTitleBar::IWCdeTitleBar(std::string title, COLORREF backgroundColor, COLORR this->darkColor = darkcolor; this->textColor = textColor; - this->iconifyButton = new IWCdeIconifyBtn(lightColor, darkColor); - this->menuButton = new IWCdeMenuBtn(lightColor, darkColor); - this->resizeButton = new IWX11ResizeBtn(); + this->iconifyButton = new IWCdeIconifyBtn(backgroundColor, lightColor, darkColor); + this->menuButton = new IWCdeMenuBtn(backgroundColor, lightColor, darkColor); + this->resizeButton = new IWX11ResizeBtn(backgroundColor); } void IWCdeTitleBar::PositionButtons(const CRect& rect) diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index 82d614a..6e0920b 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -10,6 +10,7 @@ BEGIN_MESSAGE_MAP(IWTitleBar, CStatic) ON_WM_LBUTTONDOWN() ON_BN_CLICKED(IDC_CLOSE_BUTTON, &IWTitleBar::OnIconifyButtonClicked) ON_BN_CLICKED(IDC_MENU_BUTTON, &IWTitleBar::OnMenuButtonClicked) + ON_COMMAND(IDC_RESIZE_BUTTON, &IWTitleBar::OnResizeButtonPressed) END_MESSAGE_MAP() IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener) @@ -33,6 +34,8 @@ BOOL IWTitleBar::CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID) !iconifyButton->Create(_T(""), WS_CHILD | WS_VISIBLE | BS_OWNERDRAW, CRect(), this, IDC_CLOSE_BUTTON)) return FALSE; + resizeButton->SetButtonID(IDC_RESIZE_BUTTON); + // Position buttons PositionButtons(rect); @@ -65,24 +68,14 @@ void IWTitleBar::OnLButtonDown(UINT nFlags, CPoint point) if (!pParent) return; - CRect menuButtonRect; - menuButton->GetClientRect(&menuButtonRect); - menuButton->ClientToScreen(&menuButtonRect); - CRect resizeButtonRect; resizeButton->GetClientRect(&resizeButtonRect); resizeButton->ClientToScreen(&resizeButtonRect); if (resizeButtonRect.PtInRect(point)) { - // Start resizing the window this->eventListener->OnResizeStart(); } - else if (menuButtonRect.PtInRect(point)) { - // Simulate clicking the menu button - this->eventListener->OnMenuButtonClicked(); - } else { - // Simulate dragging the window pParent->SendMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y)); } @@ -99,3 +92,7 @@ void IWTitleBar::OnMenuButtonClicked() this->eventListener->OnMenuButtonClicked(); } +void IWTitleBar::OnResizeButtonPressed() +{ + this->eventListener->OnResizeStart(); +} diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index 6fb120c..f6eb5d8 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -46,6 +46,7 @@ class IWTitleBar : public CStatic afx_msg void OnLButtonDown(UINT nFlags, CPoint point); afx_msg void OnIconifyButtonClicked(); afx_msg void OnMenuButtonClicked(); + afx_msg void OnResizeButtonPressed(); afx_msg void OnSize(UINT nType, int cx, int cy); }; diff --git a/ILS_Window_Plugin/IWTitleBarBtn.cpp b/ILS_Window_Plugin/IWTitleBarBtn.cpp index 1d1bbb0..efac844 100644 --- a/ILS_Window_Plugin/IWTitleBarBtn.cpp +++ b/ILS_Window_Plugin/IWTitleBarBtn.cpp @@ -1,11 +1,22 @@ #include "pch.h" #include "IWTitleBarBtn.h" +#include "RenderUtils.h" BEGIN_MESSAGE_MAP(IWTitleBarBtn, CButton) ON_WM_ERASEBKGND() + ON_WM_MOUSEMOVE() + ON_WM_MOUSELEAVE() + ON_WM_LBUTTONDOWN() END_MESSAGE_MAP() +IWTitleBarBtn::IWTitleBarBtn(COLORREF backgroundColor) +{ + this->backgroundColor = backgroundColor; + this->backgroundColorHover = backgroundColor; // TODO + buttonID = -1; // Set later by the parent if needed +} + BOOL IWTitleBarBtn::OnEraseBkgnd(CDC* pDC) { // Do nothing to keep the background transparent return TRUE; @@ -17,7 +28,15 @@ void IWTitleBarBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { // Get the button's rectangle CRect rect = lpDrawItemStruct->rcItem; - // Draw border only (hollow interior) + // If the button is hovered, change the background color + if (mouseOver) { + pDC->FillSolidRect(&rect, this->backgroundColorHover); // Light blue background on hover + } + else { + pDC->FillSolidRect(&rect, this->backgroundColor); // White background + } + + // Draw border (optional: use a different color on hover) CPen pen(PS_SOLID, 1, RGB(0, 0, 0)); // Black pen for the border CPen* oldPen = pDC->SelectObject(&pen); pDC->SelectStockObject(HOLLOW_BRUSH); // No fill for the rectangle @@ -27,3 +46,47 @@ void IWTitleBarBtn::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { // Call DrawSymbol to draw the custom symbol inside DrawSymbol(pDC, rect); } + +void IWTitleBarBtn::SetButtonID(int id) +{ + buttonID = id; +} + +void IWTitleBarBtn::OnMouseMove(UINT nFlags, CPoint point) { + CRect rect; + GetClientRect(&rect); + if (!mouseOver && rect.PtInRect(point)) { + mouseOver = TRUE; + Invalidate(); + + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = TME_LEAVE; + tme.hwndTrack = GetSafeHwnd(); + _TrackMouseEvent(&tme); + } + + CButton::OnMouseMove(nFlags, point); +} + +void IWTitleBarBtn::OnMouseLeave() { + if (mouseOver) { + mouseOver = FALSE; + Invalidate(); + } + CButton::OnMouseLeave(); +} + +void IWTitleBarBtn::OnLButtonDown(UINT nFlags, CPoint point) +{ + mouseOver = TRUE; + Invalidate(); + + CWnd* parent = GetParent(); + if (buttonID != -1 && parent) { + parent->SendMessage(WM_COMMAND, this->buttonID); + } + else { + CButton::OnLButtonDown(nFlags, point); + } +} diff --git a/ILS_Window_Plugin/IWTitleBarBtn.h b/ILS_Window_Plugin/IWTitleBarBtn.h index 7417481..818541c 100644 --- a/ILS_Window_Plugin/IWTitleBarBtn.h +++ b/ILS_Window_Plugin/IWTitleBarBtn.h @@ -3,14 +3,27 @@ class IWTitleBarBtn : public CButton { +DECLARE_MESSAGE_MAP() + + public: + IWTitleBarBtn(COLORREF backgroundColor); + void SetButtonID(int id); + virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); - virtual BOOL OnEraseBkgnd(CDC* pDC); protected: - DECLARE_MESSAGE_MAP() - virtual void DrawSymbol(CDC* pDC, CRect rect) = 0; +private: + BOOL mouseOver = FALSE; + int buttonID = -1; + COLORREF backgroundColor; + COLORREF backgroundColorHover; + + afx_msg BOOL OnEraseBkgnd(CDC* pDC); + afx_msg void OnMouseMove(UINT nFlags, CPoint point); + afx_msg void OnMouseLeave(); + afx_msg void OnLButtonDown(UINT nFlags, CPoint point); }; diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index a127805..a978018 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -16,7 +16,6 @@ BEGIN_MESSAGE_MAP(IWWindow, CWnd) ON_WM_LBUTTONDOWN() - ON_WM_MOUSEMOVE() ON_WM_SETCURSOR() ON_WM_PAINT() ON_WM_CREATE() @@ -287,32 +286,6 @@ void IWWindow::OnLButtonDown(UINT nFlags, CPoint point) CWnd::OnLButtonDown(nFlags, point); } -void IWWindow::OnMouseMove(UINT nFlags, CPoint point) -{ - /*CRect clientRect; - GetClientRect(&clientRect); - - bool isTop, isLeft, isRight, isBottom; - GetEdgeCursorPosition(point, isTop, isLeft, isRight, isBottom); - - if (isTop && isLeft) - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENWSE)); - else if (isTop && isRight) - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENESW)); - else if (isBottom && isLeft) - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENESW)); - else if (isBottom && isRight) - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENWSE)); - else if (isTop || isBottom) - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZENS)); - else if (isLeft || isRight) - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_SIZEWE)); - else - SetCursor(AfxGetApp()->LoadStandardCursor(IDC_ARROW)); - - CWnd::OnMouseMove(nFlags, point);*/ -} - BOOL IWWindow::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { if (nHitTest == HTCLIENT) diff --git a/ILS_Window_Plugin/IWWindow.h b/ILS_Window_Plugin/IWWindow.h index c901b9c..38c0b45 100644 --- a/ILS_Window_Plugin/IWWindow.h +++ b/ILS_Window_Plugin/IWWindow.h @@ -46,7 +46,6 @@ class IWWindow : public CWnd, public IWTitleBarEventListener { IWTitleBar* titleBar; - private: IWVisualization ilsVisualization; @@ -80,7 +79,6 @@ class IWWindow : public CWnd, public IWTitleBarEventListener { afx_msg BOOL OnNcActivate(BOOL bActive); afx_msg void OnGetMinMaxInfo(MINMAXINFO* lpMMI); afx_msg void OnLButtonDown(UINT nFlags, CPoint point); - afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); BOOL OnMenuOptionSelected(UINT nID); diff --git a/ILS_Window_Plugin/IWX11IconifyBtn.h b/ILS_Window_Plugin/IWX11IconifyBtn.h index bc78c50..c4331a3 100644 --- a/ILS_Window_Plugin/IWX11IconifyBtn.h +++ b/ILS_Window_Plugin/IWX11IconifyBtn.h @@ -3,6 +3,7 @@ class IWX11IconifyBtn : public IWTitleBarBtn { + using IWTitleBarBtn::IWTitleBarBtn; void DrawSymbol(CDC* pDC, CRect rect) override; }; diff --git a/ILS_Window_Plugin/IWX11MenuBtn.cpp b/ILS_Window_Plugin/IWX11MenuBtn.cpp index b1faa40..1a86cb9 100644 --- a/ILS_Window_Plugin/IWX11MenuBtn.cpp +++ b/ILS_Window_Plugin/IWX11MenuBtn.cpp @@ -1,10 +1,6 @@ #include "pch.h" #include "IWX11MenuBtn.h" -BEGIN_MESSAGE_MAP(IWX11MenuBtn, CButton) - ON_WM_LBUTTONDOWN() - ON_WM_ERASEBKGND() -END_MESSAGE_MAP() void IWX11MenuBtn::DrawSymbol(CDC* pDC, CRect rect) { @@ -31,21 +27,3 @@ void IWX11MenuBtn::DrawSymbol(CDC* pDC, CRect rect) // Restore the previous brush pDC->SelectObject(oldBrush); } - -void IWX11MenuBtn::OnLButtonDown(UINT nFlags, CPoint point) -{ - CPoint screenPoint = point; - ClientToScreen(&screenPoint); - - // Get the parent window - CWnd* parentWnd = GetParent(); - if (parentWnd) - { - parentWnd->SendMessage(WM_LBUTTONDOWN, NULL, MAKELPARAM(screenPoint.x, screenPoint.y)); - } -} - -BOOL IWX11MenuBtn::OnEraseBkgnd(CDC* pDC) -{ - return TRUE; -} diff --git a/ILS_Window_Plugin/IWX11MenuBtn.h b/ILS_Window_Plugin/IWX11MenuBtn.h index 1bdd5b4..588bdd1 100644 --- a/ILS_Window_Plugin/IWX11MenuBtn.h +++ b/ILS_Window_Plugin/IWX11MenuBtn.h @@ -3,11 +3,7 @@ class IWX11MenuBtn : public IWTitleBarBtn { + using IWTitleBarBtn::IWTitleBarBtn; void DrawSymbol(CDC* pDC, CRect rect) override; - - DECLARE_MESSAGE_MAP() - - afx_msg void OnLButtonDown(UINT nFlags, CPoint point); - afx_msg BOOL OnEraseBkgnd(CDC* pDC); }; diff --git a/ILS_Window_Plugin/IWX11ResizeBtn.cpp b/ILS_Window_Plugin/IWX11ResizeBtn.cpp index 39e816d..471ad02 100644 --- a/ILS_Window_Plugin/IWX11ResizeBtn.cpp +++ b/ILS_Window_Plugin/IWX11ResizeBtn.cpp @@ -1,17 +1,6 @@ #include "pch.h" #include "IWX11ResizeBtn.h" -BEGIN_MESSAGE_MAP(IWX11ResizeBtn, CButton) - ON_WM_LBUTTONDOWN() - ON_WM_ERASEBKGND() -END_MESSAGE_MAP() - -BOOL IWX11ResizeBtn::OnEraseBkgnd(CDC* pDC) -{ - // Do nothing to keep the background transparent - return TRUE; -} - void IWX11ResizeBtn::DrawSymbol(CDC* pDC, CRect rect) { // Create a 1px black pen CPen pen(PS_SOLID, 1, RGB(0, 0, 0)); @@ -38,17 +27,3 @@ void IWX11ResizeBtn::DrawSymbol(CDC* pDC, CRect rect) { // Restore old pen pDC->SelectObject(oldPen); } - - -void IWX11ResizeBtn::OnLButtonDown(UINT nFlags, CPoint point) -{ - CPoint screenPoint = point; - ClientToScreen(&screenPoint); - - // Get the parent window - CWnd* parentWnd = GetParent(); - if (parentWnd) - { - parentWnd->SendMessage(WM_LBUTTONDOWN, NULL, MAKELPARAM(screenPoint.x, screenPoint.y)); - } -} \ No newline at end of file diff --git a/ILS_Window_Plugin/IWX11ResizeBtn.h b/ILS_Window_Plugin/IWX11ResizeBtn.h index ce2a4d8..61f44a9 100644 --- a/ILS_Window_Plugin/IWX11ResizeBtn.h +++ b/ILS_Window_Plugin/IWX11ResizeBtn.h @@ -3,11 +3,6 @@ class IWX11ResizeBtn : public IWTitleBarBtn { + using IWTitleBarBtn::IWTitleBarBtn; void DrawSymbol(CDC* pDC, CRect rect) override; - - afx_msg void OnLButtonDown(UINT nFlags, CPoint point); - afx_msg BOOL OnEraseBkgnd(CDC* pDC); - - - DECLARE_MESSAGE_MAP() }; diff --git a/ILS_Window_Plugin/IWX11TitleBar.cpp b/ILS_Window_Plugin/IWX11TitleBar.cpp index 376e69d..22cfb27 100644 --- a/ILS_Window_Plugin/IWX11TitleBar.cpp +++ b/ILS_Window_Plugin/IWX11TitleBar.cpp @@ -8,9 +8,9 @@ IWX11TitleBar::IWX11TitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) : IWTitleBar(title, backgroundColor, 14, listener) { - this->iconifyButton = new IWX11IconifyBtn(); - this->menuButton = new IWX11MenuBtn(); - this->resizeButton = new IWX11ResizeBtn(); + this->iconifyButton = new IWX11IconifyBtn(backgroundColor); + this->menuButton = new IWX11MenuBtn(backgroundColor); + this->resizeButton = new IWX11ResizeBtn(backgroundColor); } void IWX11TitleBar::PositionButtons(const CRect& rect) From f7521fac46a420ee472f166060d7fc03ecfacab9 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Fri, 31 Jan 2025 17:55:49 +0100 Subject: [PATCH 6/8] Make titlebar use the CWnd title instead of passing the title around --- ILS_Window_Plugin/IWCdeTitleBar.cpp | 8 ++++---- ILS_Window_Plugin/IWCdeTitleBar.h | 4 ++-- ILS_Window_Plugin/IWCdeWindow.cpp | 2 +- ILS_Window_Plugin/IWTitleBar.cpp | 9 ++++++--- ILS_Window_Plugin/IWTitleBar.h | 6 ++---- ILS_Window_Plugin/IWWindow.cpp | 2 +- ILS_Window_Plugin/IWX11TitleBar.cpp | 8 ++++---- ILS_Window_Plugin/IWX11TitleBar.h | 4 ++-- ILS_Window_Plugin/IWX11Window.cpp | 2 +- 9 files changed, 23 insertions(+), 22 deletions(-) diff --git a/ILS_Window_Plugin/IWCdeTitleBar.cpp b/ILS_Window_Plugin/IWCdeTitleBar.cpp index da9544e..376cb63 100644 --- a/ILS_Window_Plugin/IWCdeTitleBar.cpp +++ b/ILS_Window_Plugin/IWCdeTitleBar.cpp @@ -5,8 +5,8 @@ #include "IWCdeMenuBtn.h" #include "IWX11ResizeBtn.h" -IWCdeTitleBar::IWCdeTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, COLORREF lightColor, COLORREF darkcolor, IWTitleBarEventListener* listener) : - IWTitleBar(title, backgroundColor, 13, listener) +IWCdeTitleBar::IWCdeTitleBar(COLORREF backgroundColor, COLORREF textColor, COLORREF lightColor, COLORREF darkcolor, IWTitleBarEventListener* listener) : + IWTitleBar(backgroundColor, 13, listener) { this->lightColor = lightColor; this->darkColor = darkcolor; @@ -32,13 +32,13 @@ void IWCdeTitleBar::PositionButtons(const CRect& rect) titleArea.right = iconifyButtonRect.left; } -void IWCdeTitleBar::DrawTitle(CDC* pdc, CRect rect) +void IWCdeTitleBar::DrawTitle(CDC* pdc, CRect rect, CString title) { Draw3dRect(pdc, titleArea, 1, lightColor, darkColor); auto oldFont = pdc->SelectObject(this->font); pdc->SetTextColor(this->textColor); pdc->SetBkMode(TRANSPARENT); - pdc->DrawText(_T(this->text.c_str()), -1, titleArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); + pdc->DrawText(_T(title), -1, titleArea, DT_CENTER | DT_VCENTER | DT_SINGLELINE); pdc->SelectObject(oldFont); } diff --git a/ILS_Window_Plugin/IWCdeTitleBar.h b/ILS_Window_Plugin/IWCdeTitleBar.h index 22285c3..e02ddd5 100644 --- a/ILS_Window_Plugin/IWCdeTitleBar.h +++ b/ILS_Window_Plugin/IWCdeTitleBar.h @@ -7,12 +7,12 @@ class IWTitleBarEventListener; class IWCdeTitleBar : public IWTitleBar { public: - IWCdeTitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, COLORREF lightColor, COLORREF darkcolor, IWTitleBarEventListener* listener); + IWCdeTitleBar(COLORREF backgroundColor, COLORREF textColor, COLORREF lightColor, COLORREF darkcolor, IWTitleBarEventListener* listener); virtual ~IWCdeTitleBar() {} private: void PositionButtons(const CRect& rect) override; - void DrawTitle(CDC* pdc, CRect rect) override; + void DrawTitle(CDC* pdc, CRect rect, CString title) override; COLORREF backgroundColor; COLORREF textColor; diff --git a/ILS_Window_Plugin/IWCdeWindow.cpp b/ILS_Window_Plugin/IWCdeWindow.cpp index b325c12..703ad58 100644 --- a/ILS_Window_Plugin/IWCdeWindow.cpp +++ b/ILS_Window_Plugin/IWCdeWindow.cpp @@ -9,7 +9,7 @@ IWCdeWindow::IWCdeWindow(IWApproachDefinition selectedApproach, IWStyling stylin , darkColor(AdjustColorBrightness(styling.windowFrameColor, 0.4)) { COLORREF textColor = RGB(styling.windowFrameTextColor.r, styling.windowFrameTextColor.g, styling.windowFrameTextColor.b); - this->titleBar = new IWCdeTitleBar(selectedApproach.title, windowBorderColor, textColor, lightColor, darkColor, this); + this->titleBar = new IWCdeTitleBar(windowBorderColor, textColor, lightColor, darkColor, this); } int IWCdeWindow::GetEdgeCursorPosition(CPoint point) diff --git a/ILS_Window_Plugin/IWTitleBar.cpp b/ILS_Window_Plugin/IWTitleBar.cpp index 6e0920b..1afd40d 100644 --- a/ILS_Window_Plugin/IWTitleBar.cpp +++ b/ILS_Window_Plugin/IWTitleBar.cpp @@ -13,10 +13,9 @@ BEGIN_MESSAGE_MAP(IWTitleBar, CStatic) ON_COMMAND(IDC_RESIZE_BUTTON, &IWTitleBar::OnResizeButtonPressed) END_MESSAGE_MAP() -IWTitleBar::IWTitleBar(std::string title, COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener) +IWTitleBar::IWTitleBar(COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener) { this->backgroundColor = backgroundColor; - this->text = title; float fontPointsSize = fontSize * 72 / 96; this->font.CreatePointFont(int(fontPointsSize * 10), _T("EuroScope")); @@ -59,7 +58,11 @@ void IWTitleBar::OnPaint() CPaintDC dc(this); CRect rect; GetClientRect(&rect); - DrawTitle(&dc, rect); + + // Get parent window + CString title; + GetParent()->GetWindowText(title); + DrawTitle(&dc, rect, title); } void IWTitleBar::OnLButtonDown(UINT nFlags, CPoint point) diff --git a/ILS_Window_Plugin/IWTitleBar.h b/ILS_Window_Plugin/IWTitleBar.h index f6eb5d8..f082a03 100644 --- a/ILS_Window_Plugin/IWTitleBar.h +++ b/ILS_Window_Plugin/IWTitleBar.h @@ -21,21 +21,19 @@ class IWTitleBar : public CStatic DECLARE_MESSAGE_MAP() public: - IWTitleBar(std::string title, COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener); + IWTitleBar(COLORREF backgroundColor, int fontSize, IWTitleBarEventListener* listener); BOOL CreateTopBar(CWnd* pParentWnd, const CRect& rect, UINT nID); - void SetTitle(const std::string& title) { this->text = title; } virtual ~IWTitleBar() {} protected: virtual void PositionButtons(const CRect& rect) {}; - virtual void DrawTitle(CDC* pdc, CRect rect) {}; + virtual void DrawTitle(CDC* pdc, CRect rect, CString title) {}; IWTitleBarBtn* menuButton; IWTitleBarBtn* iconifyButton; IWTitleBarBtn* resizeButton; - std::string text; CFont font; private: diff --git a/ILS_Window_Plugin/IWWindow.cpp b/ILS_Window_Plugin/IWWindow.cpp index a978018..3ec2ca4 100644 --- a/ILS_Window_Plugin/IWWindow.cpp +++ b/ILS_Window_Plugin/IWWindow.cpp @@ -342,7 +342,7 @@ void IWWindow::SetActiveApproach(const IWApproachDefinition& selectedApproach) { std::unique_lock lock(approachDataMutex); this->selectedApproach = selectedApproach; - this->titleBar->SetTitle(selectedApproach.title); + this->SetWindowTextA(selectedApproach.title.c_str()); ilsVisualization.SetActiveApproach(this->selectedApproach); Invalidate(); diff --git a/ILS_Window_Plugin/IWX11TitleBar.cpp b/ILS_Window_Plugin/IWX11TitleBar.cpp index 22cfb27..f983189 100644 --- a/ILS_Window_Plugin/IWX11TitleBar.cpp +++ b/ILS_Window_Plugin/IWX11TitleBar.cpp @@ -5,8 +5,8 @@ #include "IWX11MenuBtn.h" #include "IWX11ResizeBtn.h" -IWX11TitleBar::IWX11TitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) - : IWTitleBar(title, backgroundColor, 14, listener) +IWX11TitleBar::IWX11TitleBar(COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener) + : IWTitleBar(backgroundColor, 14, listener) { this->iconifyButton = new IWX11IconifyBtn(backgroundColor); this->menuButton = new IWX11MenuBtn(backgroundColor); @@ -48,13 +48,13 @@ void IWX11TitleBar::PositionButtons(const CRect& rect) iconifyButton->MoveWindow(iconifyButtonRect); } -void IWX11TitleBar::DrawTitle(CDC* pdc, CRect rect) +void IWX11TitleBar::DrawTitle(CDC* pdc, CRect rect, CString title) { CRect textArea = rect; textArea.left += 5; auto oldFont = pdc->SelectObject(this->font); pdc->SetTextColor(this->textColor); pdc->SetBkMode(TRANSPARENT); - pdc->DrawText(_T(this->text.c_str()), -1, textArea, DT_LEFT | DT_VCENTER | DT_SINGLELINE); + pdc->DrawText(_T(title), -1, textArea, DT_LEFT | DT_VCENTER | DT_SINGLELINE); pdc->SelectObject(oldFont); } diff --git a/ILS_Window_Plugin/IWX11TitleBar.h b/ILS_Window_Plugin/IWX11TitleBar.h index 10e5406..113f05b 100644 --- a/ILS_Window_Plugin/IWX11TitleBar.h +++ b/ILS_Window_Plugin/IWX11TitleBar.h @@ -4,12 +4,12 @@ class IWX11TitleBar : public IWTitleBar { public: - IWX11TitleBar(std::string title, COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener); + IWX11TitleBar(COLORREF backgroundColor, COLORREF textColor, IWTitleBarEventListener* listener); virtual ~IWX11TitleBar() {} private: void PositionButtons(const CRect& rect) override; - void DrawTitle(CDC* pdc, CRect rect) override; + void DrawTitle(CDC* pdc, CRect rect, CString title) override; COLORREF textColor; }; diff --git a/ILS_Window_Plugin/IWX11Window.cpp b/ILS_Window_Plugin/IWX11Window.cpp index fcc05e5..b8c5a72 100644 --- a/ILS_Window_Plugin/IWX11Window.cpp +++ b/ILS_Window_Plugin/IWX11Window.cpp @@ -5,7 +5,7 @@ IWX11Window::IWX11Window(IWApproachDefinition selectedApproach, IWStyling styling) : IWWindow(selectedApproach, styling, 26, 3, 1) { - this->titleBar = new IWX11TitleBar(selectedApproach.title, windowBorderColor, textColor, this); + this->titleBar = new IWX11TitleBar(windowBorderColor, textColor, this); } void IWX11Window::DrawBorder(CDC* pdc, CRect windowRect) From baf38b7cedfd114b827d5aa91f9beb82d521051b Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Fri, 31 Jan 2025 18:47:45 +0100 Subject: [PATCH 7/8] Rename from windowSimulation -> windowStyle which is more describing --- ILS_Window_Plugin/IWDataTypes.h | 2 +- ILS_Window_Plugin/IWPlugin.cpp | 4 ++-- ILS_Window_Plugin/IWVisualization.cpp | 1 - Sample config/ILS_Window_Plugin-config.json | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/ILS_Window_Plugin/IWDataTypes.h b/ILS_Window_Plugin/IWDataTypes.h index f03b41f..80c8cc0 100644 --- a/ILS_Window_Plugin/IWDataTypes.h +++ b/ILS_Window_Plugin/IWDataTypes.h @@ -67,5 +67,5 @@ struct IWStyling { struct IWBehaviourSettings { bool openWindowsBasedOnActiveRunways; - std::string windowSimulation; + std::string windowStyle; }; \ No newline at end of file diff --git a/ILS_Window_Plugin/IWPlugin.cpp b/ILS_Window_Plugin/IWPlugin.cpp index e7aa881..9de3e29 100644 --- a/ILS_Window_Plugin/IWPlugin.cpp +++ b/ILS_Window_Plugin/IWPlugin.cpp @@ -85,7 +85,7 @@ void IWPlugin::ShowWindow(IWApproachDefinition* approach) } IWWindow* newWindow = nullptr; - if (this->behaviourSettings.windowSimulation == "X11") { + if (this->behaviourSettings.windowStyle == "X11") { newWindow = new IWX11Window(*approach, windowStyling); } else { @@ -411,7 +411,7 @@ IWBehaviourSettings IWPlugin::ReadBehaviourSettings(const std::string& jsonFileP return IWBehaviourSettings{ jsonObject.at("openWindowsBasedOnActiveRunways").get(), - readStringWithDefault("windowSimulation", "X11") + readStringWithDefault("windowStyle", "X11") }; } diff --git a/ILS_Window_Plugin/IWVisualization.cpp b/ILS_Window_Plugin/IWVisualization.cpp index 1478df1..4c5ebbb 100644 --- a/ILS_Window_Plugin/IWVisualization.cpp +++ b/ILS_Window_Plugin/IWVisualization.cpp @@ -31,7 +31,6 @@ IWVisualization::IWVisualization(IWApproachDefinition selectedApproach, IWStylin this->historyTrailPen.CreatePen(PS_SOLID, 1, RGB(styling.historyTrailColor.r, styling.historyTrailColor.g, styling.historyTrailColor.b)); this->font = font; - float fontPointsSize = styling.fontSize * 72 / 96; } diff --git a/Sample config/ILS_Window_Plugin-config.json b/Sample config/ILS_Window_Plugin-config.json index 62ae257..7c55bd3 100644 --- a/Sample config/ILS_Window_Plugin-config.json +++ b/Sample config/ILS_Window_Plugin-config.json @@ -1,7 +1,7 @@ { "behaviour": { "openWindowsBasedOnActiveRunways": true, - "windowSimulation": "CDE" + "windowStyle": "CDE" }, "styling": { "fontSize": 12, From b8f4de223ad24f8a2443ed27693a06746f223a63 Mon Sep 17 00:00:00 2001 From: Even Rognlien Date: Fri, 31 Jan 2025 19:06:10 +0100 Subject: [PATCH 8/8] Improve readme --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2102c63..3a37385 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # ILS window plugin for EuroScope -Shows aircraft position relative to the ILS. +Shows aircraft positions relative to the ILS. ## Installation and use @@ -11,7 +11,7 @@ Shows aircraft position relative to the ILS. By default the plugin will automatically open ILS windows based on the active arrival runways. It's also possible to open windows manually using `.ils ` (where `<title>` refers to the `"title"` in the configuration file). > [!NOTE] -> **Limitation**: This plugin uses the altitude reported by the pilot clients. Depending on the pilot's weather settings and the flight simulator they are using, the height displayed in the ILS Window may not match what the pilot sees ([more info](https://forums.flightsimulator.com/t/vatsim-ivao-pilotedge-users-be-aware-of-an-important-bug/426142)). +> The plugin applies cold temperature correction to improve aircraft height accuracy. This is necessary for newer flight simulators with advanced atmospheric models ([more info](https://forums.flightsimulator.com/t/vatsim-ivao-pilotedge-users-be-aware-of-an-important-bug/426142)). For pilots using older simulators or custom weather settings, the displayed height may be slightly different from what is shown in the simulator. ## Configuration @@ -19,6 +19,8 @@ The plugin reads the JSON configuration file when the plugin is loaded. For deta - `localizerCourse` must be the **true** heading in degrees - `maxOffsetLeft` and `maxOffsetRight` is used to specify the maximum visible range (in nautical miles) left and right of the localizer. Most relevant for airports with paralell approaches. +- `windowStyle` must be `"CDE"` (Common Desktop Environment) or `"X11"` (X Window System). +- `defaultTagMode` must be set to `"squawk"` (shows only the SSR code) or `"callsign"`. ## Screenshot