diff --git a/src/openrct2-ui/interface/Graph.cpp b/src/openrct2-ui/interface/Graph.cpp index 21a37cdf71..8b503379b6 100644 --- a/src/openrct2-ui/interface/Graph.cpp +++ b/src/openrct2-ui/interface/Graph.cpp @@ -10,7 +10,6 @@ #include "../UiStringIds.h" #include -#include #include #include #include @@ -110,13 +109,9 @@ namespace OpenRCT2::Graph namespace OpenRCT2::Graph { constexpr int32_t kDashLength = 2; - constexpr ScreenCoordsXY kFinanceTopLeftPadding{ 88, 20 }; - constexpr ScreenCoordsXY kFinanceBottomRightPadding{ 15, 18 }; - constexpr uint8_t kNumFinanceGraphYLabels = 5; template - static void DrawMonths( - DrawPixelInfo& dpi, const T* series, int32_t count, const ScreenRect& bounds, const int32_t xStep) + static void DrawMonths(DrawPixelInfo& dpi, const T* series, int32_t count, const ScreenRect& bounds, const int32_t xStep) { auto& date = GetDate(); int32_t currentMonth = date.GetMonth(); @@ -143,38 +138,23 @@ namespace OpenRCT2::Graph } } - template + template static void DrawHoveredValue( - DrawPixelInfo& dpi, const T* series, const int32_t count, const ScreenRect& bounds, const int32_t xStep, + DrawPixelInfo& dpi, const T value, const int32_t hoverIdx, const ScreenRect& bounds, const int32_t xStep, const T minValue, const T maxValue) { - const ScreenCoordsXY cursorPos = ContextGetCursorPositionScaled(); - if (!bounds.Contains(cursorPos)) - return; - - int32_t i = (count - 1) - (cursorPos.x - bounds.GetLeft() + (xStep / 2)) / xStep; - if (i < 0) - i = 1; - if (i > count - 1) - i = count - 1; - - T value = series[i]; - if (value == TkNoValue) - return; const int32_t screenRange = bounds.GetHeight(); const int32_t valueRange = maxValue - minValue; - int32_t test = bounds.GetBottom() - ((value - minValue) * screenRange) / valueRange; - ScreenCoordsXY coords = { bounds.GetRight() - i * xStep, test }; + + const int32_t yPosition = bounds.GetBottom() - ((value - minValue) * screenRange) / valueRange; + ScreenCoordsXY coords = { bounds.GetRight() - hoverIdx * xStep, yPosition }; + // Line needs to be shifted over 1 pixel to match with the month ticks. ScreenCoordsXY lineCoords = { coords.x + 1, coords.y }; - GfxDrawDashedLine(dpi, { { lineCoords.x, bounds.GetTop() }, lineCoords }, kDashLength, PALETTE_INDEX_10); + GfxDrawDashedLine( + dpi, { { lineCoords.x, bounds.GetTop() }, { lineCoords.x, bounds.GetBottom() } }, kDashLength, PALETTE_INDEX_10); GfxDrawDashedLine(dpi, { { bounds.GetLeft(), lineCoords.y }, lineCoords }, kDashLength, PALETTE_INDEX_10); - if (cursorPos.y > coords.y) - { - GfxDrawDashedLine(dpi, { lineCoords, { lineCoords.x, cursorPos.y } }, kDashLength, PALETTE_INDEX_10); - } - auto ft = Formatter(); ft.Add(value); DrawTextBasic( @@ -240,60 +220,41 @@ namespace OpenRCT2::Graph } } - void DrawFinanceGraph( - DrawPixelInfo& dpi, const money64 (&series)[128], const ScreenRect& graphBounds, const bool centred, - const ColourWithFlags lineCol) + void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p) { - constexpr int32_t count = 64; // todo for whatever reason this is 64. - - ScreenRect internalBounds{ graphBounds.Point1 + kFinanceTopLeftPadding, - graphBounds.Point2 - kFinanceBottomRightPadding }; - const int32_t yLabelStepPx = (internalBounds.GetBottom() - internalBounds.GetTop()) / (kNumFinanceGraphYLabels - 1); - const int32_t xStepPx = (internalBounds.GetRight() - internalBounds.GetLeft()) / (count - 1); - - // adjust bounds to be exact multiples of the steps. - internalBounds.Point2 = internalBounds.Point1 - + ScreenCoordsXY{ xStepPx * (count - 1), yLabelStepPx * (kNumFinanceGraphYLabels - 1) }; - - money64 graphMaximum = centred ? 12.00_GBP : 24.00_GBP; - for (int32_t i = 0; i < count; i++) - { - auto currentValue = series[i]; - if (currentValue == kMoney64Undefined) - continue; - while (std::abs(currentValue) > graphMaximum) - graphMaximum *= 2; - } - const money64 graphMinimum = centred ? -graphMaximum : 0.00_GBP; - - const money64 yLabelStep = (graphMaximum - graphMinimum) / (kNumFinanceGraphYLabels - 1); - money64 curLabel = graphMaximum; - int32_t curScreenPos = internalBounds.GetTop() - 5; - for (uint8_t i = 0; i < kNumFinanceGraphYLabels; i++) + money64 curLabel = p.max; + int32_t curScreenPos = p.internalBounds.GetTop() - 5; + const money64 yLabelStep = (p.max - p.min) / (p.numYLabels - 1); + for (int32_t i = 0; i < p.numYLabels; i++) { Formatter ft; ft.Add(curLabel); DrawTextBasic( - dpi, { internalBounds.GetLeft(), curScreenPos }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, + dpi, { p.internalBounds.GetLeft(), curScreenPos }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, { FontStyle::Small, TextAlignment::RIGHT }); GfxFillRectInset( - dpi, { { internalBounds.GetLeft(), curScreenPos + 5 }, { internalBounds.GetRight(), curScreenPos + 5 } }, - lineCol, INSET_RECT_FLAG_BORDER_INSET); - curScreenPos += yLabelStepPx; + dpi, { { p.internalBounds.GetLeft(), curScreenPos + 5 }, { p.internalBounds.GetRight(), curScreenPos + 5 } }, + p.lineCol, INSET_RECT_FLAG_BORDER_INSET); + curScreenPos += p.yLabelStepPx; curLabel -= yLabelStep; } - DrawMonths(dpi, series, count, internalBounds, xStepPx); - DrawLine(dpi, series, count, internalBounds, xStepPx, graphMinimum, graphMaximum); - DrawLine(dpi, series, count, internalBounds, xStepPx, graphMinimum, graphMaximum); - DrawHoveredValue(dpi, series, count, internalBounds, xStepPx, graphMinimum, graphMaximum); + DrawMonths(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx); + DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); + DrawLine(dpi, p.series, p.numPoints, p.internalBounds, p.xStepPx, p.min, p.max); + if (p.hoverIdx >= 0 && p.hoverIdx < p.numPoints) + { + money64 value = p.series[p.hoverIdx]; + if (value != kMoney64Undefined) + DrawHoveredValue(dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max); + } // todo debug code. - ScreenCoordsXY bottomLeft{ internalBounds.Point1.x, internalBounds.Point2.y }; - ScreenCoordsXY topRight{ internalBounds.Point2.x, internalBounds.Point1.y }; - GfxDrawLine(dpi, { internalBounds.Point1, topRight }, 33); - GfxDrawLine(dpi, { internalBounds.Point1, bottomLeft }, 33); - GfxDrawLine(dpi, { bottomLeft, internalBounds.Point2 }, 33); - GfxDrawLine(dpi, { topRight, internalBounds.Point2 }, 33); + ScreenCoordsXY bottomLeft{ p.internalBounds.Point1.x, p.internalBounds.Point2.y }; + ScreenCoordsXY topRight{ p.internalBounds.Point2.x, p.internalBounds.Point1.y }; + GfxDrawLine(dpi, { p.internalBounds.Point1, topRight }, 33); + GfxDrawLine(dpi, { p.internalBounds.Point1, bottomLeft }, 33); + GfxDrawLine(dpi, { bottomLeft, p.internalBounds.Point2 }, 33); + GfxDrawLine(dpi, { topRight, p.internalBounds.Point2 }, 33); } } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/interface/Graph.h b/src/openrct2-ui/interface/Graph.h index d7c40699b7..ef08d59b56 100644 --- a/src/openrct2-ui/interface/Graph.h +++ b/src/openrct2-ui/interface/Graph.h @@ -9,14 +9,62 @@ #pragma once +#include #include #include #include namespace OpenRCT2::Graph { + template struct GraphProperties + { + ScreenRect internalBounds; + T* series; + T min; + T max; + int32_t hoverIdx; + int32_t numPoints; + int32_t numYLabels; + int32_t xStepPx; + int32_t yLabelStepPx; + ColourWithFlags lineCol; + + void RecalculateLayout(const ScreenRect newBounds, const int32_t newNumYLabels, const int32_t newNumPoints) + { + yLabelStepPx = (newBounds.GetBottom() - newBounds.GetTop()) / (newNumYLabels - 1); + xStepPx = (newBounds.GetRight() - newBounds.GetLeft()) / (newNumPoints - 1); + // adjust bounds to be exact multiples of the steps. + internalBounds = { newBounds.Point1, + newBounds.Point1 + + ScreenCoordsXY{ xStepPx * (newNumPoints - 1), yLabelStepPx * (newNumYLabels - 1) } }; + numPoints = newNumPoints; + numYLabels = newNumYLabels; + } + + bool UpdateHoverIdx() + { + const ScreenCoordsXY cursorPos = ContextGetCursorPositionScaled(); + + int32_t i = -1; + if (internalBounds.Contains(cursorPos)) + { + i = (numPoints - 1) - (cursorPos.x - internalBounds.GetLeft() + (xStepPx / 2)) / xStepPx; + if (i < 0) + i = 1; + if (i > numPoints - 1) + i = numPoints - 1; + } + + if (hoverIdx != i) + { + hoverIdx = i; + return true; + } + return false; + } + }; + void Draw(DrawPixelInfo& dpi, uint8_t* history, int32_t count, const ScreenCoordsXY& screenPos); - void DrawFinanceGraph( - DrawPixelInfo& dpi, const money64 (&series)[128], const ScreenRect& graphBounds, bool centred, ColourWithFlags lineCol); + void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p); } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/windows/Finances.cpp b/src/openrct2-ui/windows/Finances.cpp index 008c21f66d..bbd073c3fa 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -177,6 +177,8 @@ static Widget _windowFinancesResearchWidgets[] = #pragma endregion +#pragma region Constants + static constexpr StringId _windowFinancesSummaryRowLabels[EnumValue(ExpenditureType::Count)] = { STR_FINANCES_SUMMARY_RIDE_CONSTRUCTION, STR_FINANCES_SUMMARY_RIDE_RUNNING_COSTS, @@ -217,12 +219,19 @@ static Widget _windowFinancesResearchWidgets[] = }; static_assert(std::size(_windowFinancesPageHoldDownWidgets) == WINDOW_FINANCES_PAGE_COUNT); + static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 88, 20 }; + static constexpr ScreenCoordsXY kGraphBottomRightPadding{ 15, 18 }; + static constexpr uint8_t kGraphNumYLabels = 5; + static constexpr int32_t kGraphNumPoints = 64; // todo. was always 64 in original code. + #pragma endregion class FinancesWindow final : public Window { private: - uint32_t _lastPaintedMonth; + uint32_t _lastPaintedMonth = std::numeric_limits::max(); + ScreenRect _graphBounds; + Graph::GraphProperties _graphProps{}; void SetDisabledTabs() { @@ -235,12 +244,23 @@ static Widget _windowFinancesResearchWidgets[] = SetPage(WINDOW_FINANCES_PAGE_SUMMARY); _lastPaintedMonth = std::numeric_limits::max(); ResearchUpdateUncompletedTypes(); + _graphProps.lineCol = colours[2]; + _graphProps.hoverIdx = -1; } void OnUpdate() override { frame_no++; InvalidateWidget(WIDX_TAB_1 + page); + + if (page == WINDOW_FINANCES_PAGE_VALUE_GRAPH || page == WINDOW_FINANCES_PAGE_PROFIT_GRAPH + || page == WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH) + { + if (_graphProps.UpdateHoverIdx()) + { + InvalidateWidget(WIDX_BACKGROUND); + } + } } void OnMouseDown(WidgetIndex widgetIndex) override @@ -318,6 +338,33 @@ static Widget _windowFinancesResearchWidgets[] = case WINDOW_FINANCES_PAGE_RESEARCH: WindowResearchFundingPrepareDraw(this, WIDX_RESEARCH_FUNDING); break; + case WINDOW_FINANCES_PAGE_VALUE_GRAPH: + case WINDOW_FINANCES_PAGE_PROFIT_GRAPH: + case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH: + { + // recalculate size for graph pages only. + Widget* pageWidget; + switch (page) + { + case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH: + pageWidget = &_windowFinancesCashWidgets[WIDX_PAGE_BACKGROUND]; + break; + case WINDOW_FINANCES_PAGE_VALUE_GRAPH: + pageWidget = &_windowFinancesParkValueWidgets[WIDX_PAGE_BACKGROUND]; + break; + case WINDOW_FINANCES_PAGE_PROFIT_GRAPH: + pageWidget = &_windowFinancesProfitWidgets[WIDX_PAGE_BACKGROUND]; + break; + default: + return; + } + _graphBounds = { windowPos + ScreenCoordsXY{ pageWidget->left + 4, pageWidget->top + 15 }, + windowPos + ScreenCoordsXY{ pageWidget->right - 4, pageWidget->bottom - 4 } }; + _graphProps.RecalculateLayout( + { _graphBounds.Point1 + kGraphTopLeftPadding, _graphBounds.Point2 - kGraphBottomRightPadding }, + kGraphNumYLabels, kGraphNumPoints); + } + break; } } @@ -469,16 +516,26 @@ static Widget _windowFinancesResearchWidgets[] = { width = WW_RESEARCH; height = WH_RESEARCH; + flags &= ~WF_RESIZABLE; } else if (p == WINDOW_FINANCES_PAGE_SUMMARY) { width = WW_OTHER_TABS; height = WH_SUMMARY; + flags &= ~WF_RESIZABLE; + } + else if ( + p == WINDOW_FINANCES_PAGE_VALUE_GRAPH || p == WINDOW_FINANCES_PAGE_PROFIT_GRAPH + || p == WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH) + { + flags |= WF_RESIZABLE; + WindowSetResize(*this, WW_OTHER_TABS, WH_OTHER_TABS, 2000, 2000); } else { width = WW_OTHER_TABS; height = WH_OTHER_TABS; + flags &= ~WF_RESIZABLE; } OnResize(); OnPrepareDraw(); @@ -782,26 +839,35 @@ static Widget _windowFinancesResearchWidgets[] = } void OnDrawGraph( - DrawPixelInfo& dpi, const money64 currentValue, money64 (&series)[128], const StringId fmt, - const bool centred) const + DrawPixelInfo& dpi, const money64 currentValue, money64 (&series)[kFinanceGraphSize], const StringId fmt, const bool centred) { - const Widget* pageWidget = &_windowFinancesCashWidgets[WIDX_PAGE_BACKGROUND]; - auto graphTopLeft = windowPos + ScreenCoordsXY{ pageWidget->left + 4, pageWidget->top + 15 }; - auto graphBottomRight = windowPos + ScreenCoordsXY{ pageWidget->right - 4, pageWidget->bottom - 4 }; - ScreenRect graphBounds(graphTopLeft, graphBottomRight); - auto ft = Formatter(); ft.Add(currentValue); - DrawTextBasic(dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 }, fmt, ft); + DrawTextBasic(dpi, _graphBounds.Point1 - ScreenCoordsXY{ 0, 11 }, fmt, ft); // Graph - GfxFillRectInset(dpi, graphBounds, colours[1], INSET_RECT_F_30); + GfxFillRectInset(dpi, _graphBounds, colours[1], INSET_RECT_F_30); for (int i = 0; i < 128; i++) // TODO debug series[i] = i % 2 * 96.00_GBP; // series[i] = 0; - Graph::DrawFinanceGraph(dpi, series, graphBounds, centred, colours[2]); + money64 max = centred ? 12.00_GBP : 24.00_GBP; + for (int32_t i = 0; i < kGraphNumPoints; i++) + { + auto val = series[i]; + if (val == kMoney64Undefined) + continue; + while (std::abs(val) > max) + max *= 2; + } + const money64 min = centred ? -max : 0.00_GBP; + + _graphProps.min = min; + _graphProps.max = max; + _graphProps.series = series; + + Graph::DrawFinanceGraph(dpi, _graphProps); } };