From 713858bba34f289ea2055401439f6cd0c831282b Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Sat, 27 Jul 2024 23:48:23 +1000 Subject: [PATCH 1/7] Refactor Finances.cpp graphs --- src/openrct2-ui/windows/Finances.cpp | 262 ++++++++------------------- 1 file changed, 74 insertions(+), 188 deletions(-) diff --git a/src/openrct2-ui/windows/Finances.cpp b/src/openrct2-ui/windows/Finances.cpp index c879a6484b..380c6066e9 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -12,7 +12,6 @@ #include #include #include -#include #include #include #include @@ -333,14 +332,28 @@ static Widget _windowFinancesResearchWidgets[] = OnDrawSummary(dpi); break; case WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH: - OnDrawFinancialGraph(dpi); + { + auto& gameState = GetGameState(); + auto cashLessLoan = gameState.Cash - gameState.BankLoan; + auto fmt = cashLessLoan >= 0 ? STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_POSITIVE + : STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_NEGATIVE; + OnDrawGraph(dpi, cashLessLoan, gameState.CashHistory, fmt, true); break; + } case WINDOW_FINANCES_PAGE_VALUE_GRAPH: - OnDrawParkValueGraph(dpi); + { + auto& gameState = GetGameState(); + OnDrawGraph(dpi, gameState.Park.Value, gameState.Park.ValueHistory, STR_FINANCES_PARK_VALUE, false); break; + } case WINDOW_FINANCES_PAGE_PROFIT_GRAPH: - OnDrawProfitGraph(dpi); + { + auto& gameState = GetGameState(); + auto fmt = gameState.CurrentProfit >= 0 ? STR_FINANCES_WEEKLY_PROFIT_POSITIVE + : STR_FINANCES_WEEKLY_PROFIT_LOSS; + OnDrawGraph(dpi, gameState.CurrentProfit, gameState.WeeklyProfitHistory, fmt, true); break; + } case WINDOW_FINANCES_PAGE_MARKETING: OnDrawMarketing(dpi); break; @@ -607,190 +620,6 @@ static Widget _windowFinancesResearchWidgets[] = #pragma endregion -#pragma region Financial Graph Events - - void OnDrawFinancialGraph(DrawPixelInfo& dpi) - { - 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 }; - - const auto& gameState = GetGameState(); - - // Cash (less loan) - auto cashLessLoan = gameState.Cash - gameState.BankLoan; - auto ft = Formatter(); - ft.Add(cashLessLoan); - - DrawTextBasic( - dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 }, - cashLessLoan >= 0 ? STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_POSITIVE - : STR_FINANCES_FINANCIAL_GRAPH_CASH_LESS_LOAN_NEGATIVE, - ft); - - // Graph - GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30); - - // Calculate the Y axis scale (log2 of highest [+/-]balance) - int32_t yAxisScale = 0; - for (int32_t i = 0; i < 64; i++) - { - auto balance = gameState.CashHistory[i]; - if (balance == kMoney64Undefined) - continue; - - // Modifier balance then keep halving until less than 127 pixels - balance = std::abs(balance) >> yAxisScale; - while (balance > 127) - { - balance /= 2; - yAxisScale++; - } - } - - // Y axis labels - auto coords = graphTopLeft + ScreenCoordsXY{ 18, 14 }; - money64 axisBase; - for (axisBase = 12.00_GBP; axisBase >= -12.00_GBP; axisBase -= 6.00_GBP) - { - auto axisValue = axisBase << yAxisScale; - ft = Formatter(); - ft.Add(axisValue); - DrawTextBasic( - dpi, coords + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { coords + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, coords.y + 5 } }, colours[2], - INSET_RECT_FLAG_BORDER_INSET); - coords.y += 39; - } - - // X axis labels and values - coords = graphTopLeft + ScreenCoordsXY{ 98, 17 }; - Graph::Draw(dpi, gameState.CashHistory, 64, coords, yAxisScale, 128); - } - -#pragma endregion - -#pragma region Park Value Graph Events - - void OnDrawParkValueGraph(DrawPixelInfo& dpi) - { - 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 }; - - const auto& gameState = GetGameState(); - - // Park value - auto ft = Formatter(); - ft.Add(gameState.Park.Value); - DrawTextBasic(dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 }, STR_FINANCES_PARK_VALUE, ft); - - // Graph - GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30); - - // Calculate the Y axis scale (log2 of highest [+/-]balance) - int32_t yAxisScale = 0; - for (int32_t i = 0; i < 64; i++) - { - auto balance = gameState.Park.ValueHistory[i]; - if (balance == kMoney64Undefined) - continue; - - // Modifier balance then keep halving until less than 255 pixels - balance = std::abs(balance) >> yAxisScale; - while (balance > 255) - { - balance /= 2; - yAxisScale++; - } - } - - // Y axis labels - auto coords = graphTopLeft + ScreenCoordsXY{ 18, 14 }; - money64 axisBase; - for (axisBase = 24.00_GBP; axisBase >= 0.00_GBP; axisBase -= 6.00_GBP) - { - auto axisValue = axisBase << yAxisScale; - ft = Formatter(); - ft.Add(axisValue); - DrawTextBasic( - dpi, coords + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { coords + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, coords.y + 5 } }, colours[2], - INSET_RECT_FLAG_BORDER_INSET); - coords.y += 39; - } - - // X axis labels and values - coords = graphTopLeft + ScreenCoordsXY{ 98, 17 }; - Graph::Draw(dpi, gameState.Park.ValueHistory, 64, coords, yAxisScale, 0); - } - -#pragma endregion - -#pragma region Profit Graph Events - - void OnDrawProfitGraph(DrawPixelInfo& dpi) - { - auto& gameState = GetGameState(); - 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 }; - - // Weekly profit - auto ft = Formatter(); - ft.Add(gameState.CurrentProfit); - DrawTextBasic( - dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 }, - gameState.CurrentProfit >= 0 ? STR_FINANCES_WEEKLY_PROFIT_POSITIVE : STR_FINANCES_WEEKLY_PROFIT_LOSS, ft); - - // Graph - GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30); - - // Calculate the Y axis scale (log2 of highest [+/-]balance) - int32_t yAxisScale = 0; - for (int32_t i = 0; i < 64; i++) - { - auto balance = gameState.WeeklyProfitHistory[i]; - if (balance == kMoney64Undefined) - continue; - - // Modifier balance then keep halving until less than 127 pixels - balance = std::abs(balance) >> yAxisScale; - while (balance > 127) - { - balance /= 2; - yAxisScale++; - } - } - - // Y axis labels - auto screenPos = graphTopLeft + ScreenCoordsXY{ 18, 14 }; - money64 axisBase; - for (axisBase = 12.00_GBP; axisBase >= -12.00_GBP; axisBase -= 6.00_GBP) - { - money64 axisValue = axisBase << yAxisScale; - ft = Formatter(); - ft.Add(axisValue); - DrawTextBasic( - dpi, screenPos + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { screenPos + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, screenPos.y + 5 } }, colours[2], - INSET_RECT_FLAG_BORDER_INSET); - screenPos.y += 39; - } - - // X axis labels and values - screenPos = graphTopLeft + ScreenCoordsXY{ 98, 17 }; - Graph::Draw(dpi, gameState.WeeklyProfitHistory, 64, screenPos, yAxisScale, 128); - } - -#pragma endregion - #pragma region Marketing Events void OnMouseUpMarketing(WidgetIndex widgetIndex) @@ -951,6 +780,63 @@ static Widget _windowFinancesResearchWidgets[] = { ResizeFrameWithPage(); } + + void OnDrawGraph( + DrawPixelInfo& dpi, const money64 currentValue, const money64 (&series)[128], const StringId fmt, + const bool centred) const + { + 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 }; + + auto ft = Formatter(); + ft.Add(currentValue); + DrawTextBasic(dpi, graphTopLeft - ScreenCoordsXY{ 0, 11 }, fmt, ft); + + // Graph + GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30); + + // Calculate the Y axis scale (log2 of highest [+/-]balance) + int32_t yAxisScale = 0; + for (int32_t i = 0; i < 64; i++) + { + auto balance = series[i]; + if (balance == kMoney64Undefined) + continue; + + // Modifier balance then keep halving until less than limit pixels + balance = std::abs(balance) >> yAxisScale; + auto limit = centred ? 127 : 255; + while (balance > limit) + { + balance /= 2; + yAxisScale++; + } + } + + // Y axis labels + auto screenPos = graphTopLeft + ScreenCoordsXY{ 18, 14 }; + const money64 axisBaseStart = centred ? 12.00_GBP : 24.00_GBP; + const money64 axisBaseEnd = centred ? -12.00_GBP : 0.00_GBP; + for (money64 axisBase = axisBaseStart; axisBase >= axisBaseEnd; axisBase -= 6.00_GBP) + { + const money64 axisValue = axisBase << yAxisScale; + ft = Formatter(); + ft.Add(axisValue); + DrawTextBasic( + dpi, screenPos + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, + { FontStyle::Small, TextAlignment::RIGHT }); + GfxFillRectInset( + dpi, { screenPos + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, screenPos.y + 5 } }, colours[2], + INSET_RECT_FLAG_BORDER_INSET); + screenPos.y += 39; + } + + // X axis labels and values + screenPos = graphTopLeft + ScreenCoordsXY{ 98, 17 }; + const auto offset = centred ? 128 : 0; + Graph::Draw(dpi, series, 64, screenPos, yAxisScale, offset); + } }; static FinancesWindow* FinancesWindowOpen(uint8_t page) From 56dd6c9bdfc211915ea74feb7bfe56003d17352a Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Tue, 30 Jul 2024 04:41:58 +1000 Subject: [PATCH 2/7] Refactor finance graphs in Graph.cpp --- src/openrct2-ui/interface/Graph.cpp | 290 ++++++++++++++------------- src/openrct2-ui/interface/Graph.h | 6 +- src/openrct2-ui/windows/Finances.cpp | 48 +---- 3 files changed, 159 insertions(+), 185 deletions(-) diff --git a/src/openrct2-ui/interface/Graph.cpp b/src/openrct2-ui/interface/Graph.cpp index 8d146cd3a4..21a37cdf71 100644 --- a/src/openrct2-ui/interface/Graph.cpp +++ b/src/openrct2-ui/interface/Graph.cpp @@ -107,185 +107,193 @@ namespace OpenRCT2::Graph } } // namespace OpenRCT2::Graph -struct FinancialTooltipInfo -{ - const ScreenCoordsXY coords; - const money64 money{}; -}; - -static constexpr auto ChartMaxDataCount = 64; -static constexpr auto ChartMaxIndex = ChartMaxDataCount - 1; -static constexpr auto ChartDataWidth = 6; -static constexpr auto ChartMaxWidth = ChartMaxIndex * ChartDataWidth; -static constexpr auto ChartMaxHeight = 164; -static constexpr auto CursorXOffset = 3; -static constexpr auto DefaultDashedLength = 2; - -static int32_t IndexForCursorAndHistory(const int32_t historyCount, const int32_t cursorX, const int32_t chartX) -{ - const auto offsettedCursorX = cursorX + CursorXOffset; - return (historyCount - 1) - (offsettedCursorX - chartX) / ChartDataWidth; -} - -static const ScreenCoordsXY ScreenCoordsForHistoryIndex( - const int32_t index, const money64* history, const int32_t chartX, const int32_t chartY, const int32_t modifier, - const int32_t offset) -{ - auto coords = ScreenCoordsXY{ chartX + ChartDataWidth * (ChartMaxIndex - index), - chartY + ChartMaxHeight - - (((static_cast(history[index] >> modifier) + offset) * 170) / 256) }; - return coords; -} - -static const FinancialTooltipInfo FinanceTooltipInfoFromMoney( - const money64* history, const int32_t historyCount, const int32_t modifier, const int32_t offset, - const ScreenRect& chartFrame, const ScreenCoordsXY& cursorPosition) -{ - if (!chartFrame.Contains(cursorPosition)) - { - return { {}, kMoney64Undefined }; - } - - const auto historyIndex = IndexForCursorAndHistory(historyCount, cursorPosition.x, chartFrame.GetLeft()); - const auto coords = ScreenCoordsForHistoryIndex( - historyIndex, history, chartFrame.GetLeft(), chartFrame.GetTop(), modifier, offset); - - return { coords, history[historyIndex] }; -} - namespace OpenRCT2::Graph { - static void DrawMonths(DrawPixelInfo& dpi, const money64* history, int32_t count, const ScreenCoordsXY& origCoords) + 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) { auto& date = GetDate(); int32_t currentMonth = date.GetMonth(); int32_t currentDay = date.GetMonthTicks(); int32_t yearOver32 = (currentMonth * 4) + (currentDay >> 14) - 31; - auto screenCoords = origCoords; + auto screenCoords = bounds.Point1; for (int32_t i = count - 1; i >= 0; i--) { - if (history[i] != kMoney64Undefined && yearOver32 % 4 == 0) + if (series[i] != TkNoValue && yearOver32 % 4 == 0) { // Draw month text auto ft = Formatter(); ft.Add(DateGameShortMonthNames[DateGetMonth((yearOver32 / 4) + MONTH_COUNT)]); DrawTextBasic( - dpi, screenCoords - ScreenCoordsXY{ 0, 10 }, STR_GRAPH_LABEL, ft, + dpi, screenCoords - ScreenCoordsXY{ 0, 13 }, STR_GRAPH_LABEL, ft, { FontStyle::Small, TextAlignment::CENTRE }); // Draw month mark - GfxFillRect(dpi, { screenCoords, screenCoords + ScreenCoordsXY{ 0, 3 } }, PALETTE_INDEX_10); + GfxFillRect(dpi, { screenCoords - ScreenCoordsXY{ 0, 3 }, screenCoords }, PALETTE_INDEX_10); } yearOver32 = (yearOver32 + 1) % 32; - screenCoords.x += 6; - } - } - - static void DrawLineA( - DrawPixelInfo& dpi, const money64* history, int32_t count, const ScreenCoordsXY& origCoords, int32_t modifier, - int32_t offset) - { - ScreenCoordsXY lastCoords; - bool lastCoordsValid = false; - auto coords = origCoords; - for (int32_t i = count - 1; i >= 0; i--) - { - if (history[i] != kMoney64Undefined) - { - coords.y = origCoords.y + 170 - 6 - ((((history[i] >> modifier) + offset) * 170) / 256); - - if (lastCoordsValid) - { - auto leftTop1 = lastCoords + ScreenCoordsXY{ 1, 1 }; - auto rightBottom1 = coords + ScreenCoordsXY{ 1, 1 }; - auto leftTop2 = lastCoords + ScreenCoordsXY{ 0, 1 }; - auto rightBottom2 = coords + ScreenCoordsXY{ 0, 1 }; - GfxDrawLine(dpi, { leftTop1, rightBottom1 }, PALETTE_INDEX_10); - GfxDrawLine(dpi, { leftTop2, rightBottom2 }, PALETTE_INDEX_10); - } - if (i == 0) - GfxFillRect(dpi, { coords, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10); - - lastCoords = coords; - lastCoordsValid = true; - } - coords.x += 6; - } - } - - static void DrawLineB( - DrawPixelInfo& dpi, const money64* history, int32_t count, const ScreenCoordsXY& origCoords, int32_t modifier, - int32_t offset) - { - ScreenCoordsXY lastCoords; - bool lastCoordsValid = false; - auto coords = origCoords; - for (int32_t i = count - 1; i >= 0; i--) - { - if (history[i] != kMoney64Undefined) - { - coords.y = origCoords.y + 170 - 6 - ((((history[i] >> modifier) + offset) * 170) / 256); - - if (lastCoordsValid) - { - auto leftTop = lastCoords; - auto rightBottom = coords; - GfxDrawLine(dpi, { leftTop, rightBottom }, PALETTE_INDEX_21); - } - if (i == 0) - GfxFillRect(dpi, { coords - ScreenCoordsXY{ 1, 1 }, coords + ScreenCoordsXY{ 1, 1 } }, PALETTE_INDEX_21); - - lastCoords = coords; - lastCoordsValid = true; - } - coords.x += 6; + screenCoords.x += xStep; } } + template static void DrawHoveredValue( - DrawPixelInfo& dpi, const money64* history, const int32_t historyCount, const ScreenCoordsXY& screenCoords, - const int32_t modifier, const int32_t offset) + DrawPixelInfo& dpi, const T* series, const int32_t count, const ScreenRect& bounds, const int32_t xStep, + const T minValue, const T maxValue) { - const auto cursorPosition = ContextGetCursorPositionScaled(); - const ScreenRect chartFrame{ screenCoords, screenCoords + ScreenCoordsXY{ ChartMaxWidth, ChartMaxHeight } }; - - if (!chartFrame.Contains(cursorPosition)) - { + const ScreenCoordsXY cursorPos = ContextGetCursorPositionScaled(); + if (!bounds.Contains(cursorPos)) return; - } - const auto info = FinanceTooltipInfoFromMoney(history, ChartMaxDataCount, modifier, offset, chartFrame, cursorPosition); + int32_t i = (count - 1) - (cursorPos.x - bounds.GetLeft() + (xStep / 2)) / xStep; + if (i < 0) + i = 1; + if (i > count - 1) + i = count - 1; - if (info.money == kMoney64Undefined) - { + T value = series[i]; + if (value == TkNoValue) return; - } - GfxDrawDashedLine(dpi, { { info.coords.x, chartFrame.GetTop() }, info.coords }, DefaultDashedLength, 0); - GfxDrawDashedLine(dpi, { { chartFrame.GetLeft() - 10, info.coords.y }, info.coords }, DefaultDashedLength, 0); + 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 }; + ScreenCoordsXY lineCoords = { coords.x + 1, coords.y }; - if (cursorPosition.y > info.coords.y) + GfxDrawDashedLine(dpi, { { lineCoords.x, bounds.GetTop() }, lineCoords }, kDashLength, PALETTE_INDEX_10); + GfxDrawDashedLine(dpi, { { bounds.GetLeft(), lineCoords.y }, lineCoords }, kDashLength, PALETTE_INDEX_10); + + if (cursorPos.y > coords.y) { - GfxDrawDashedLine(dpi, { info.coords, { info.coords.x, cursorPosition.y } }, DefaultDashedLength, 0); + GfxDrawDashedLine(dpi, { lineCoords, { lineCoords.x, cursorPos.y } }, kDashLength, PALETTE_INDEX_10); } auto ft = Formatter(); - ft.Add(info.money); + ft.Add(value); DrawTextBasic( - dpi, info.coords - ScreenCoordsXY{ 0, 16 }, STR_FINANCES_SUMMARY_EXPENDITURE_VALUE, ft, { TextAlignment::CENTRE }); + dpi, coords - ScreenCoordsXY{ 0, 16 }, STR_FINANCES_SUMMARY_EXPENDITURE_VALUE, ft, { TextAlignment::CENTRE }); - GfxFillRect(dpi, { { info.coords - ScreenCoordsXY{ 2, 2 } }, info.coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10); - GfxFillRect( - dpi, { { info.coords - ScreenCoordsXY{ 1, 1 } }, { info.coords + ScreenCoordsXY{ 1, 1 } } }, PALETTE_INDEX_21); + GfxFillRect(dpi, { { coords - ScreenCoordsXY{ 2, 2 } }, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10); + GfxFillRect(dpi, { { coords - ScreenCoordsXY{ 1, 1 } }, { coords + ScreenCoordsXY{ 1, 1 } } }, PALETTE_INDEX_21); } - void Draw( - DrawPixelInfo& dpi, const money64* history, const int32_t count, const ScreenCoordsXY& screenCoords, - const int32_t modifier, const int32_t offset) + template + static void DrawLine( + DrawPixelInfo& dpi, const T* series, const int32_t count, const ScreenRect& bounds, const int32_t xStep, + const T minValue, const T maxValue) { - DrawMonths(dpi, history, count, screenCoords); - DrawLineA(dpi, history, count, screenCoords, modifier, offset); - DrawLineB(dpi, history, count, screenCoords, modifier, offset); - DrawHoveredValue(dpi, history, count, screenCoords, modifier, offset); + const int32_t screenRange = bounds.GetHeight(); + const int32_t valueRange = maxValue - minValue; + + ScreenCoordsXY lastCoords; + bool lastCoordsValid = false; + ScreenCoordsXY coords = bounds.Point1; + for (int32_t i = count - 1; i >= 0; i--) + { + auto value = series[i]; + if (value != TkNoValue) + { + coords.y = bounds.GetBottom() - ((value - minValue) * screenRange) / valueRange; + + if constexpr (TbackgroundLine) + { + if (lastCoordsValid) + { + auto leftTop1 = lastCoords + ScreenCoordsXY{ 1, 1 }; + auto rightBottom1 = coords + ScreenCoordsXY{ 1, 1 }; + auto leftTop2 = lastCoords + ScreenCoordsXY{ 0, 1 }; + auto rightBottom2 = coords + ScreenCoordsXY{ 0, 1 }; + GfxDrawLine(dpi, { leftTop1, rightBottom1 }, PALETTE_INDEX_10); + GfxDrawLine(dpi, { leftTop2, rightBottom2 }, PALETTE_INDEX_10); + } + if (i == 0) + { + GfxFillRect(dpi, { coords, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10); + } + } + else + { + if (lastCoordsValid) + { + auto leftTop = lastCoords; + auto rightBottom = coords; + GfxDrawLine(dpi, { leftTop, rightBottom }, PALETTE_INDEX_21); + } + if (i == 0) + { + GfxFillRect( + dpi, { coords - ScreenCoordsXY{ 1, 1 }, coords + ScreenCoordsXY{ 1, 1 } }, PALETTE_INDEX_21); + } + } + + lastCoords = coords; + lastCoordsValid = true; + } + coords.x += xStep; + } + } + + void DrawFinanceGraph( + DrawPixelInfo& dpi, const money64 (&series)[128], const ScreenRect& graphBounds, const bool centred, + const ColourWithFlags lineCol) + { + 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++) + { + Formatter ft; + ft.Add(curLabel); + DrawTextBasic( + dpi, { 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; + 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); + + // 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); } } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/interface/Graph.h b/src/openrct2-ui/interface/Graph.h index 2c3c8fe425..d7c40699b7 100644 --- a/src/openrct2-ui/interface/Graph.h +++ b/src/openrct2-ui/interface/Graph.h @@ -16,7 +16,7 @@ namespace OpenRCT2::Graph { void Draw(DrawPixelInfo& dpi, uint8_t* history, int32_t count, const ScreenCoordsXY& screenPos); - void Draw( - DrawPixelInfo& dpi, const money64* history, const int32_t count, const ScreenCoordsXY& coords, const int32_t modifier, - const int32_t offset); + + void DrawFinanceGraph( + DrawPixelInfo& dpi, const money64 (&series)[128], const ScreenRect& graphBounds, bool centred, ColourWithFlags lineCol); } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/windows/Finances.cpp b/src/openrct2-ui/windows/Finances.cpp index 380c6066e9..008c21f66d 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -782,60 +782,26 @@ static Widget _windowFinancesResearchWidgets[] = } void OnDrawGraph( - DrawPixelInfo& dpi, const money64 currentValue, const money64 (&series)[128], const StringId fmt, + DrawPixelInfo& dpi, const money64 currentValue, money64 (&series)[128], const StringId fmt, const bool centred) const { 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); // Graph - GfxFillRectInset(dpi, { graphTopLeft, graphBottomRight }, colours[1], INSET_RECT_F_30); + GfxFillRectInset(dpi, graphBounds, colours[1], INSET_RECT_F_30); - // Calculate the Y axis scale (log2 of highest [+/-]balance) - int32_t yAxisScale = 0; - for (int32_t i = 0; i < 64; i++) - { - auto balance = series[i]; - if (balance == kMoney64Undefined) - continue; + for (int i = 0; i < 128; i++) // TODO debug + series[i] = i % 2 * 96.00_GBP; + // series[i] = 0; - // Modifier balance then keep halving until less than limit pixels - balance = std::abs(balance) >> yAxisScale; - auto limit = centred ? 127 : 255; - while (balance > limit) - { - balance /= 2; - yAxisScale++; - } - } - - // Y axis labels - auto screenPos = graphTopLeft + ScreenCoordsXY{ 18, 14 }; - const money64 axisBaseStart = centred ? 12.00_GBP : 24.00_GBP; - const money64 axisBaseEnd = centred ? -12.00_GBP : 0.00_GBP; - for (money64 axisBase = axisBaseStart; axisBase >= axisBaseEnd; axisBase -= 6.00_GBP) - { - const money64 axisValue = axisBase << yAxisScale; - ft = Formatter(); - ft.Add(axisValue); - DrawTextBasic( - dpi, screenPos + ScreenCoordsXY{ 70, 0 }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { screenPos + ScreenCoordsXY{ 70, 5 }, { graphTopLeft.x + 482, screenPos.y + 5 } }, colours[2], - INSET_RECT_FLAG_BORDER_INSET); - screenPos.y += 39; - } - - // X axis labels and values - screenPos = graphTopLeft + ScreenCoordsXY{ 98, 17 }; - const auto offset = centred ? 128 : 0; - Graph::Draw(dpi, series, 64, screenPos, yAxisScale, offset); + Graph::DrawFinanceGraph(dpi, series, graphBounds, centred, colours[2]); } }; From 9e2eb73aa7fda008646bece43592c6ac8d2b1627 Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Tue, 30 Jul 2024 21:07:49 +1000 Subject: [PATCH 3/7] Extract window specific logic from Graph.cpp --- src/openrct2-ui/interface/Graph.cpp | 105 +++++++++------------------ src/openrct2-ui/interface/Graph.h | 52 ++++++++++++- src/openrct2-ui/windows/Finances.cpp | 88 +++++++++++++++++++--- 3 files changed, 160 insertions(+), 85 deletions(-) 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); } }; From f4a1d22662115ab1db9bec34c6c0a91e349831ba Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Wed, 31 Jul 2024 01:45:06 +1000 Subject: [PATCH 4/7] Draw park graphs with new method. Also clean up constants and includes. --- src/openrct2-ui/interface/Graph.cpp | 190 ++++++++++----------------- src/openrct2-ui/interface/Graph.h | 8 +- src/openrct2-ui/windows/Finances.cpp | 13 +- src/openrct2-ui/windows/Park.cpp | 123 ++++++++--------- src/openrct2/GameState.h | 6 +- src/openrct2/management/Finance.cpp | 2 +- src/openrct2/management/Finance.h | 2 +- src/openrct2/rct1/RCT1.h | 2 +- src/openrct2/rct2/RCT2.h | 2 +- src/openrct2/world/Park.cpp | 6 +- src/openrct2/world/Park.h | 3 +- 11 files changed, 142 insertions(+), 215 deletions(-) diff --git a/src/openrct2-ui/interface/Graph.cpp b/src/openrct2-ui/interface/Graph.cpp index 8b503379b6..6146ef68e5 100644 --- a/src/openrct2-ui/interface/Graph.cpp +++ b/src/openrct2-ui/interface/Graph.cpp @@ -13,103 +13,33 @@ #include #include #include - -using namespace OpenRCT2; - -namespace OpenRCT2::Graph -{ - static void DrawMonths(DrawPixelInfo& dpi, const uint8_t* history, int32_t count, const ScreenCoordsXY& origCoords) - { - auto& date = GetDate(); - int32_t currentMonth = date.GetMonth(); - int32_t currentDay = date.GetMonthTicks(); - int32_t yearOver32 = (currentMonth * 4) + (currentDay >> 14) - 31; - auto screenCoords = origCoords; - for (int32_t i = count - 1; i >= 0; i--) - { - if (history[i] != 255 && yearOver32 % 4 == 0) - { - // Draw month text - auto ft = Formatter(); - ft.Add(DateGameShortMonthNames[DateGetMonth((yearOver32 / 4) + MONTH_COUNT)]); - DrawTextBasic( - dpi, screenCoords - ScreenCoordsXY{ 0, 10 }, STR_GRAPH_LABEL, ft, - { FontStyle::Small, TextAlignment::CENTRE }); - - // Draw month mark - GfxFillRect(dpi, { screenCoords, screenCoords + ScreenCoordsXY{ 0, 3 } }, PALETTE_INDEX_10); - } - - yearOver32 = (yearOver32 + 1) % 32; - screenCoords.x += 6; - } - } - - static void DrawLineA(DrawPixelInfo& dpi, const uint8_t* history, int32_t count, const ScreenCoordsXY& origCoords) - { - auto lastCoords = ScreenCoordsXY{ -1, -1 }; - auto coords = origCoords; - for (int32_t i = count - 1; i >= 0; i--) - { - if (history[i] != 255) - { - coords.y = origCoords.y + ((255 - history[i]) * 100) / 256; - - if (lastCoords.x != -1) - { - auto leftTop1 = lastCoords + ScreenCoordsXY{ 1, 1 }; - auto rightBottom1 = coords + ScreenCoordsXY{ 1, 1 }; - auto leftTop2 = lastCoords + ScreenCoordsXY{ 0, 1 }; - auto rightBottom2 = coords + ScreenCoordsXY{ 0, 1 }; - GfxDrawLine(dpi, { leftTop1, rightBottom1 }, PALETTE_INDEX_10); - GfxDrawLine(dpi, { leftTop2, rightBottom2 }, PALETTE_INDEX_10); - } - if (i == 0) - GfxFillRect(dpi, { coords, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10); - - lastCoords = coords; - } - coords.x += 6; - } - } - - static void DrawLineB(DrawPixelInfo& dpi, const uint8_t* history, int32_t count, const ScreenCoordsXY& origCoords) - { - auto lastCoords = ScreenCoordsXY{ -1, -1 }; - auto coords = origCoords; - for (int32_t i = count - 1; i >= 0; i--) - { - if (history[i] != 255) - { - coords.y = origCoords.y + ((255 - history[i]) * 100) / 256; - - if (lastCoords.x != -1) - { - auto leftTop = lastCoords; - auto rightBottom = coords; - GfxDrawLine(dpi, { leftTop, rightBottom }, PALETTE_INDEX_21); - } - if (i == 0) - GfxFillRect(dpi, { coords - ScreenCoordsXY{ 1, 1 }, coords + ScreenCoordsXY{ 1, 1 } }, PALETTE_INDEX_21); - - lastCoords = coords; - } - coords.x += 6; - } - } - - void Draw(DrawPixelInfo& dpi, uint8_t* history, int32_t count, const ScreenCoordsXY& screenPos) - { - DrawMonths(dpi, history, count, screenPos); - DrawLineA(dpi, history, count, screenPos); - DrawLineB(dpi, history, count, screenPos); - } -} // namespace OpenRCT2::Graph +#include namespace OpenRCT2::Graph { constexpr int32_t kDashLength = 2; + template + static void DrawYLabels( + DrawPixelInfo& dpi, const ScreenRect& internalBounds, const T min, const T max, const int32_t numYLabels, + const int32_t yLabelStepPx, const StringId fmt, const ColourWithFlags lineCol) + { + T curLabel = max; + int32_t curScreenPos = internalBounds.GetTop() - 5; + const T yLabelStep = (max - min) / (numYLabels - 1); + for (int32_t i = 0; i < numYLabels; i++) + { + Formatter ft; + ft.Add(curLabel); + DrawTextBasic(dpi, { internalBounds.GetLeft(), curScreenPos }, fmt, ft, { FontStyle::Small, TextAlignment::RIGHT }); + GfxFillRectInset( + dpi, { { internalBounds.GetLeft(), curScreenPos + 5 }, { internalBounds.GetRight(), curScreenPos + 5 } }, + lineCol, INSET_RECT_FLAG_BORDER_INSET); + curScreenPos += yLabelStepPx; + curLabel -= yLabelStep; + } + } + template static void DrawMonths(DrawPixelInfo& dpi, const T* series, int32_t count, const ScreenRect& bounds, const int32_t xStep) { @@ -126,11 +56,12 @@ namespace OpenRCT2::Graph auto ft = Formatter(); ft.Add(DateGameShortMonthNames[DateGetMonth((yearOver32 / 4) + MONTH_COUNT)]); DrawTextBasic( - dpi, screenCoords - ScreenCoordsXY{ 0, 13 }, STR_GRAPH_LABEL, ft, + dpi, screenCoords - ScreenCoordsXY{ 0, 14 }, STR_GRAPH_LABEL, ft, { FontStyle::Small, TextAlignment::CENTRE }); // Draw month mark - GfxFillRect(dpi, { screenCoords - ScreenCoordsXY{ 0, 3 }, screenCoords }, PALETTE_INDEX_10); + GfxFillRect( + dpi, { screenCoords - ScreenCoordsXY{ 0, 4 }, screenCoords - ScreenCoordsXY{ 0, 1 } }, PALETTE_INDEX_10); } yearOver32 = (yearOver32 + 1) % 32; @@ -148,12 +79,15 @@ namespace OpenRCT2::Graph 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.x, bounds.GetBottom() } }, kDashLength, PALETTE_INDEX_10); - GfxDrawDashedLine(dpi, { { bounds.GetLeft(), lineCoords.y }, lineCoords }, kDashLength, PALETTE_INDEX_10); + dpi, + { + { coords.x, bounds.GetTop() }, + { coords.x, bounds.GetBottom() }, + }, + kDashLength, PALETTE_INDEX_10); + GfxDrawDashedLine(dpi, { { bounds.GetLeft(), coords.y }, coords }, kDashLength, PALETTE_INDEX_10); auto ft = Formatter(); ft.Add(value); @@ -222,39 +156,49 @@ namespace OpenRCT2::Graph void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p) { - 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, { p.internalBounds.GetLeft(), curScreenPos }, STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { { p.internalBounds.GetLeft(), curScreenPos + 5 }, { p.internalBounds.GetRight(), curScreenPos + 5 } }, - p.lineCol, INSET_RECT_FLAG_BORDER_INSET); - curScreenPos += p.yLabelStepPx; - curLabel -= yLabelStep; - } - + constexpr StringId fmt = STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE; + DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, fmt, p.lineCol); 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]; + const 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{ 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); + void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p) + { + constexpr uint8_t noValue = ParkRatingHistoryUndefined; + constexpr StringId fmt = STR_GRAPH_AXIS_LABEL; + // Since the park rating rating history is divided by 4, we have to fudge the max number here. + DrawYLabels(dpi, p.internalBounds, p.min, 1000, p.numYLabels, p.yLabelStepPx, fmt, p.lineCol); + 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) + { + const uint8_t value = p.series[p.hoverIdx]; + if (value != noValue) + DrawHoveredValue(dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max); + } + } + + void DrawGuestGraph(DrawPixelInfo& dpi, const GraphProperties& p) + { + constexpr uint32_t noValue = GuestsInParkHistoryUndefined; + constexpr StringId fmt = STR_GRAPH_AXIS_LABEL; + DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, fmt, p.lineCol); + 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) + { + const uint32_t value = p.series[p.hoverIdx]; + if (value != noValue) + DrawHoveredValue(dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max); + } } } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/interface/Graph.h b/src/openrct2-ui/interface/Graph.h index ef08d59b56..411a2a8b38 100644 --- a/src/openrct2-ui/interface/Graph.h +++ b/src/openrct2-ui/interface/Graph.h @@ -19,7 +19,7 @@ namespace OpenRCT2::Graph template struct GraphProperties { ScreenRect internalBounds; - T* series; + const T* series; T min; T max; int32_t hoverIdx; @@ -41,7 +41,7 @@ namespace OpenRCT2::Graph numYLabels = newNumYLabels; } - bool UpdateHoverIdx() + bool UpdateHoverIndex() { const ScreenCoordsXY cursorPos = ContextGetCursorPositionScaled(); @@ -64,7 +64,7 @@ namespace OpenRCT2::Graph } }; - void Draw(DrawPixelInfo& dpi, uint8_t* history, int32_t count, const ScreenCoordsXY& screenPos); - void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p); + void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p); + void DrawGuestGraph(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 bbd073c3fa..240963c007 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -256,7 +256,7 @@ static Widget _windowFinancesResearchWidgets[] = if (page == WINDOW_FINANCES_PAGE_VALUE_GRAPH || page == WINDOW_FINANCES_PAGE_PROFIT_GRAPH || page == WINDOW_FINANCES_PAGE_FINANCIAL_GRAPH) { - if (_graphProps.UpdateHoverIdx()) + if (_graphProps.UpdateHoverIndex()) { InvalidateWidget(WIDX_BACKGROUND); } @@ -839,19 +839,18 @@ static Widget _windowFinancesResearchWidgets[] = } void OnDrawGraph( - DrawPixelInfo& dpi, const money64 currentValue, money64 (&series)[kFinanceGraphSize], const StringId fmt, const bool centred) + DrawPixelInfo& dpi, const money64 currentValue, money64 (&series)[kFinanceHistorySize], const StringId fmt, + const bool centred) { - auto ft = Formatter(); + Formatter ft; ft.Add(currentValue); DrawTextBasic(dpi, _graphBounds.Point1 - ScreenCoordsXY{ 0, 11 }, fmt, ft); // Graph 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; - + // Calculate Y axis max and min. + // This is how the original code does it. Could be improved. money64 max = centred ? 12.00_GBP : 24.00_GBP; for (int32_t i = 0; i < kGraphNumPoints; i++) { diff --git a/src/openrct2-ui/windows/Park.cpp b/src/openrct2-ui/windows/Park.cpp index ccd002c478..6cf552796c 100644 --- a/src/openrct2-ui/windows/Park.cpp +++ b/src/openrct2-ui/windows/Park.cpp @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -27,13 +26,11 @@ #include #include #include -#include #include #include #include #include #include -#include #include namespace OpenRCT2::Ui::Windows @@ -41,6 +38,10 @@ namespace OpenRCT2::Ui::Windows static constexpr StringId WINDOW_TITLE = STR_STRINGID; static constexpr int32_t WH = 224; + static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 35, 15 }; + static constexpr ScreenCoordsXY kGraphBottomRightPadding{ 5, 5 }; + static constexpr uint8_t kGraphNumYLabels = 6; + // clang-format off enum WindowParkPage { WINDOW_PARK_PAGE_ENTRANCE, @@ -197,6 +198,12 @@ static constexpr WindowParkAward _parkAwards[] = { int32_t _numberOfRides = -1; uint8_t _peepAnimationFrame = 0; + Graph::GraphProperties _ratingProps{}; + Graph::GraphProperties _guestProps{}; + + ScreenRect _ratingGraphBounds; + ScreenRect _guestGraphBounds; + public: void OnOpen() override { @@ -206,6 +213,11 @@ static constexpr WindowParkAward _parkAwards[] = { _numberOfStaff = -1; _peepAnimationFrame = 0; SetPage(0); + + _ratingProps.lineCol = colours[2]; + _guestProps.lineCol = colours[2]; + _ratingProps.hoverIdx = -1; + _guestProps.hoverIdx = -1; } void OnClose() override @@ -692,6 +704,13 @@ static constexpr WindowParkAward _parkAwards[] = { WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7); AnchorBorderWidgets(); + + const Widget* background = &widgets[WIDX_PAGE_BACKGROUND]; + _ratingGraphBounds = { windowPos + ScreenCoordsXY{ background->left + 4, background->top + 15 }, + windowPos + ScreenCoordsXY{ background->right - 4, background->bottom - 4 } }; + _ratingProps.RecalculateLayout( + { _ratingGraphBounds.Point1 + kGraphTopLeftPadding, _ratingGraphBounds.Point2 - kGraphBottomRightPadding }, + kGraphNumYLabels, kParkRatingHistorySize); } void OnDrawRating(DrawPixelInfo& dpi) @@ -699,41 +718,21 @@ static constexpr WindowParkAward _parkAwards[] = { DrawWidgets(dpi); DrawTabImages(dpi); - auto screenPos = windowPos; Widget* widget = &widgets[WIDX_PAGE_BACKGROUND]; + const auto& gameState = OpenRCT2::GetGameState(); // Current value - auto ft = Formatter(); - ft.Add(GetGameState().Park.Rating); - DrawTextBasic(dpi, screenPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_PARK_RATING_LABEL, ft); + Formatter ft; + ft.Add(gameState.Park.Rating); + DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_PARK_RATING_LABEL, ft); // Graph border - GfxFillRectInset( - dpi, - { screenPos + ScreenCoordsXY{ widget->left + 4, widget->top + 15 }, - screenPos + ScreenCoordsXY{ widget->right - 4, widget->bottom - 4 } }, - colours[1], INSET_RECT_F_30); + GfxFillRectInset(dpi, _ratingGraphBounds, colours[1], INSET_RECT_F_30); - // Y axis labels - screenPos = screenPos + ScreenCoordsXY{ widget->left + 27, widget->top + 23 }; - for (int i = 5; i >= 0; i--) - { - uint32_t axisValue = i * 200; - ft = Formatter(); - ft.Add(axisValue); - DrawTextBasic( - dpi, screenPos + ScreenCoordsXY{ 10, 0 }, STR_GRAPH_AXIS_LABEL, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { screenPos + ScreenCoordsXY{ 15, 5 }, screenPos + ScreenCoordsXY{ width - 32, 5 } }, colours[2], - INSET_RECT_FLAG_BORDER_INSET); - screenPos.y += 20; - } - - // Graph - screenPos = windowPos + ScreenCoordsXY{ widget->left + 47, widget->top + 26 }; - - Graph::Draw(dpi, GetGameState().Park.RatingHistory, kParkRatingHistorySize, screenPos); + _ratingProps.min = 0; + _ratingProps.max = 250; + _ratingProps.series = gameState.Park.RatingHistory; + Graph::DrawRatingGraph(dpi, _ratingProps); } #pragma endregion @@ -765,6 +764,13 @@ static constexpr WindowParkAward _parkAwards[] = { WindowAlignTabs(this, WIDX_TAB_1, WIDX_TAB_7); AnchorBorderWidgets(); + + const Widget* background = &widgets[WIDX_PAGE_BACKGROUND]; + _guestGraphBounds = { windowPos + ScreenCoordsXY{ background->left + 4, background->top + 15 }, + windowPos + ScreenCoordsXY{ background->right - 4, background->bottom - 4 } }; + _guestProps.RecalculateLayout( + { _guestGraphBounds.Point1 + kGraphTopLeftPadding, _guestGraphBounds.Point2 - kGraphBottomRightPadding }, + kGraphNumYLabels, kGuestsInParkHistorySize); } void OnDrawGuests(DrawPixelInfo& dpi) @@ -772,56 +778,30 @@ static constexpr WindowParkAward _parkAwards[] = { DrawWidgets(dpi); DrawTabImages(dpi); - auto screenPos = windowPos; Widget* widget = &widgets[WIDX_PAGE_BACKGROUND]; - const auto& gameState = OpenRCT2::GetGameState(); // Current value - auto ft = Formatter(); + Formatter ft; ft.Add(gameState.NumGuestsInPark); - DrawTextBasic(dpi, screenPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_GUESTS_IN_PARK_LABEL, ft); + DrawTextBasic(dpi, windowPos + ScreenCoordsXY{ widget->left + 3, widget->top + 2 }, STR_GUESTS_IN_PARK_LABEL, ft); // Graph border - GfxFillRectInset( - dpi, - { screenPos + ScreenCoordsXY{ widget->left + 4, widget->top + 15 }, - screenPos + ScreenCoordsXY{ widget->right - 4, widget->bottom - 4 } }, - colours[1], INSET_RECT_F_30); + GfxFillRectInset(dpi, _guestGraphBounds, colours[1], INSET_RECT_F_30); - // Y axis labels - screenPos = screenPos + ScreenCoordsXY{ widget->left + 27, widget->top + 23 }; - for (int i = 5; i >= 0; i--) - { - uint32_t axisValue = i * 1000; - ft = Formatter(); - ft.Add(axisValue); - DrawTextBasic( - dpi, screenPos + ScreenCoordsXY{ 10, 0 }, STR_GRAPH_AXIS_LABEL, ft, - { FontStyle::Small, TextAlignment::RIGHT }); - GfxFillRectInset( - dpi, { screenPos + ScreenCoordsXY{ 15, 5 }, screenPos + ScreenCoordsXY{ width - 32, 5 } }, colours[2], - INSET_RECT_FLAG_BORDER_INSET); - screenPos.y += 20; - } - - // Graph - screenPos = windowPos + ScreenCoordsXY{ widget->left + 47, widget->top + 26 }; - - uint8_t cappedHistory[32]; - for (size_t i = 0; i < std::size(cappedHistory); i++) + // Calculate Y axis max and min + _guestProps.min = 0; + _guestProps.max = 5000; + for (size_t i = 0; i < std::size(gameState.GuestsInParkHistory); i++) { auto value = gameState.GuestsInParkHistory[i]; - if (value != std::numeric_limits::max()) - { - cappedHistory[i] = static_cast(std::min(value, 5000) / 20); - } - else - { - cappedHistory[i] = std::numeric_limits::max(); - } + if (value == GuestsInParkHistoryUndefined) + continue; + while (value > _guestProps.max) + _guestProps.max += 5000; } - Graph::Draw(dpi, cappedHistory, static_cast(std::size(cappedHistory)), screenPos); + _guestProps.series = gameState.GuestsInParkHistory; + Graph::DrawGuestGraph(dpi, _guestProps); } #pragma endregion @@ -1315,6 +1295,7 @@ static constexpr WindowParkAward _parkAwards[] = { windowPos + ScreenCoordsXY{ widgets[WIDX_TAB_7].left, widgets[WIDX_TAB_7].top }); } } +#pragma endregion }; static ParkWindow* ParkWindowOpen(uint8_t page) diff --git a/src/openrct2/GameState.h b/src/openrct2/GameState.h index bcf3db304e..6c03f6b610 100644 --- a/src/openrct2/GameState.h +++ b/src/openrct2/GameState.h @@ -47,13 +47,13 @@ namespace OpenRCT2 money64 ConstructionRightsPrice; money64 CurrentExpenditure; money64 CurrentProfit; - uint32_t GuestsInParkHistory[32]; + uint32_t GuestsInParkHistory[kGuestsInParkHistorySize]; ClimateType Climate; ClimateState ClimateCurrent; ClimateState ClimateNext; uint16_t ClimateUpdateTimer; money64 Cash; - money64 CashHistory[kFinanceGraphSize]; + money64 CashHistory[kFinanceHistorySize]; money64 InitialCash; money64 GuestInitialCash; uint8_t GuestInitialHappiness; @@ -69,7 +69,7 @@ namespace OpenRCT2 money64 TotalIncomeFromAdmissions; money64 TotalRideValueForMoney; uint16_t WeeklyProfitAverageDivisor; - money64 WeeklyProfitHistory[kFinanceGraphSize]; + money64 WeeklyProfitHistory[kFinanceHistorySize]; Objective ScenarioObjective; uint16_t ScenarioParkRatingWarningDays; money64 ScenarioCompletedCompanyValue; diff --git a/src/openrct2/management/Finance.cpp b/src/openrct2/management/Finance.cpp index 8dadd58ab1..887e23342a 100644 --- a/src/openrct2/management/Finance.cpp +++ b/src/openrct2/management/Finance.cpp @@ -185,7 +185,7 @@ void FinancePayRideUpkeep() void FinanceResetHistory() { auto& gameState = GetGameState(); - for (auto i = 0; i < kFinanceGraphSize; i++) + for (auto i = 0; i < kFinanceHistorySize; i++) { gameState.CashHistory[i] = kMoney64Undefined; gameState.WeeklyProfitHistory[i] = kMoney64Undefined; diff --git a/src/openrct2/management/Finance.h b/src/openrct2/management/Finance.h index afdd4cf544..956377057e 100644 --- a/src/openrct2/management/Finance.h +++ b/src/openrct2/management/Finance.h @@ -32,7 +32,7 @@ enum class ExpenditureType : int32_t }; constexpr uint8_t kExpenditureTableMonthCount = 16; -constexpr uint8_t kFinanceGraphSize = 128; +constexpr uint8_t kFinanceHistorySize = 128; constexpr uint8_t MaxBankLoanInterestRate = 255; diff --git a/src/openrct2/rct1/RCT1.h b/src/openrct2/rct1/RCT1.h index b1b6df3510..6ad1917aa6 100644 --- a/src/openrct2/rct1/RCT1.h +++ b/src/openrct2/rct1/RCT1.h @@ -769,7 +769,7 @@ namespace OpenRCT2::RCT1 uint8_t Unk1990AA[94]; uint16_t ParkRating; uint8_t ParkRatingHistory[kParkRatingHistorySize]; - uint8_t GuestsInParkHistory[32]; + uint8_t GuestsInParkHistory[kGuestsInParkHistorySize]; uint8_t ResearchPriority; uint8_t ResearchProgressStage; uint8_t LastResearchItem; diff --git a/src/openrct2/rct2/RCT2.h b/src/openrct2/rct2/RCT2.h index 220186ea87..9660bb0ef3 100644 --- a/src/openrct2/rct2/RCT2.h +++ b/src/openrct2/rct2/RCT2.h @@ -862,7 +862,7 @@ namespace OpenRCT2::RCT2 // Ignored in scenario uint8_t ParkRatingHistory[kParkRatingHistorySize]; - uint8_t GuestsInParkHistory[32]; + uint8_t GuestsInParkHistory[kGuestsInParkHistorySize]; // SC6[10] uint8_t ActiveResearchTypes; diff --git a/src/openrct2/world/Park.cpp b/src/openrct2/world/Park.cpp index 6483d3a961..0763d8e7a3 100644 --- a/src/openrct2/world/Park.cpp +++ b/src/openrct2/world/Park.cpp @@ -586,8 +586,10 @@ namespace OpenRCT2::Park gameState.NumGuestsInParkLastWeek = gameState.NumGuestsInPark; // Update park rating, guests in park and current cash history - HistoryPushRecord(gameState.Park.RatingHistory, gameState.Park.Rating / 4); - HistoryPushRecord(gameState.GuestsInParkHistory, gameState.NumGuestsInPark); + constexpr auto ratingHistorySize = std::extent_v; + HistoryPushRecord(gameState.Park.RatingHistory, gameState.Park.Rating / 4); + constexpr auto numGuestsHistorySize = std::extent_v; + HistoryPushRecord(gameState.GuestsInParkHistory, gameState.NumGuestsInPark); constexpr auto cashHistorySize = std::extent_v; HistoryPushRecord(gameState.CashHistory, FinanceGetCurrentCash() - gameState.BankLoan); diff --git a/src/openrct2/world/Park.h b/src/openrct2/world/Park.h index 737c87775a..7d305463a5 100644 --- a/src/openrct2/world/Park.h +++ b/src/openrct2/world/Park.h @@ -17,6 +17,7 @@ constexpr auto MAX_ENTRANCE_FEE = 999.00_GBP; constexpr uint8_t ParkRatingHistoryUndefined = std::numeric_limits::max(); constexpr uint32_t GuestsInParkHistoryUndefined = std::numeric_limits::max(); constexpr uint8_t kParkRatingHistorySize = 32; +constexpr uint8_t kGuestsInParkHistorySize = 32; constexpr uint8_t ParkNameMaxLength = 128; constexpr uint8_t ScenarioNameMaxLength = 128; constexpr uint16_t ScenarioDetailsNameMaxLength = 256; @@ -65,7 +66,7 @@ namespace OpenRCT2 std::vector Entrances; uint32_t Size; money64 Value; - money64 ValueHistory[kFinanceGraphSize]; + money64 ValueHistory[kFinanceHistorySize]; bool IsOpen() const; }; From 82f658963febb2e481ffd7ab12cd48b1b22fbb44 Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Fri, 2 Aug 2024 05:31:22 +1000 Subject: [PATCH 5/7] Improve readability of hover tooltip Also removed reliance on the localisation table. --- src/openrct2-ui/interface/Graph.cpp | 48 ++++++++++++---------------- src/openrct2-ui/windows/Finances.cpp | 10 ++++-- src/openrct2-ui/windows/Park.cpp | 4 +-- 3 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/openrct2-ui/interface/Graph.cpp b/src/openrct2-ui/interface/Graph.cpp index 6146ef68e5..b67f35cdaf 100644 --- a/src/openrct2-ui/interface/Graph.cpp +++ b/src/openrct2-ui/interface/Graph.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -22,16 +23,16 @@ namespace OpenRCT2::Graph template static void DrawYLabels( DrawPixelInfo& dpi, const ScreenRect& internalBounds, const T min, const T max, const int32_t numYLabels, - const int32_t yLabelStepPx, const StringId fmt, const ColourWithFlags lineCol) + const int32_t yLabelStepPx, const ColourWithFlags lineCol, const FmtString& fmt) { T curLabel = max; int32_t curScreenPos = internalBounds.GetTop() - 5; const T yLabelStep = (max - min) / (numYLabels - 1); for (int32_t i = 0; i < numYLabels; i++) { - Formatter ft; - ft.Add(curLabel); - DrawTextBasic(dpi, { internalBounds.GetLeft(), curScreenPos }, fmt, ft, { FontStyle::Small, TextAlignment::RIGHT }); + char buffer[64]{}; + FormatStringToBuffer(buffer, sizeof(buffer), fmt, curLabel); + DrawText(dpi, { internalBounds.GetLeft(), curScreenPos }, { FontStyle::Small, TextAlignment::RIGHT }, buffer); GfxFillRectInset( dpi, { { internalBounds.GetLeft(), curScreenPos + 5 }, { internalBounds.GetRight(), curScreenPos + 5 } }, lineCol, INSET_RECT_FLAG_BORDER_INSET); @@ -72,7 +73,7 @@ namespace OpenRCT2::Graph template static void DrawHoveredValue( DrawPixelInfo& dpi, const T value, const int32_t hoverIdx, const ScreenRect& bounds, const int32_t xStep, - const T minValue, const T maxValue) + const T minValue, const T maxValue, const_utf8string text, ColourWithFlags textCol) { const int32_t screenRange = bounds.GetHeight(); const int32_t valueRange = maxValue - minValue; @@ -89,10 +90,9 @@ namespace OpenRCT2::Graph kDashLength, PALETTE_INDEX_10); GfxDrawDashedLine(dpi, { { bounds.GetLeft(), coords.y }, coords }, kDashLength, PALETTE_INDEX_10); - auto ft = Formatter(); + Formatter ft; ft.Add(value); - DrawTextBasic( - dpi, coords - ScreenCoordsXY{ 0, 16 }, STR_FINANCES_SUMMARY_EXPENDITURE_VALUE, ft, { TextAlignment::CENTRE }); + DrawText(dpi, coords - ScreenCoordsXY{ 0, 16 }, { textCol, TextAlignment::CENTRE }, text); GfxFillRect(dpi, { { coords - ScreenCoordsXY{ 2, 2 } }, coords + ScreenCoordsXY{ 2, 2 } }, PALETTE_INDEX_10); GfxFillRect(dpi, { { coords - ScreenCoordsXY{ 1, 1 } }, { coords + ScreenCoordsXY{ 1, 1 } } }, PALETTE_INDEX_21); @@ -156,8 +156,8 @@ namespace OpenRCT2::Graph void DrawFinanceGraph(DrawPixelInfo& dpi, const GraphProperties& p) { - constexpr StringId fmt = STR_FINANCES_FINANCIAL_GRAPH_CASH_VALUE; - DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, fmt, p.lineCol); + const FmtString fmt("{BLACK}{CURRENCY2DP} -"); + DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt); 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); @@ -165,40 +165,34 @@ namespace OpenRCT2::Graph { const money64 value = p.series[p.hoverIdx]; if (value != kMoney64Undefined) - DrawHoveredValue(dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max); + { + char buffer[64]{}; + FormatStringToBuffer(buffer, sizeof(buffer), "{CURRENCY2DP}", value); + DrawHoveredValue( + dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max, buffer, + p.lineCol.withFlag(ColourFlag::withOutline, true)); + } } } void DrawRatingGraph(DrawPixelInfo& dpi, const GraphProperties& p) { constexpr uint8_t noValue = ParkRatingHistoryUndefined; - constexpr StringId fmt = STR_GRAPH_AXIS_LABEL; + const FmtString fmt("{BLACK}{COMMA32} -"); // Since the park rating rating history is divided by 4, we have to fudge the max number here. - DrawYLabels(dpi, p.internalBounds, p.min, 1000, p.numYLabels, p.yLabelStepPx, fmt, p.lineCol); + DrawYLabels(dpi, p.internalBounds, p.min, 1000, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt); 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) - { - const uint8_t value = p.series[p.hoverIdx]; - if (value != noValue) - DrawHoveredValue(dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max); - } } void DrawGuestGraph(DrawPixelInfo& dpi, const GraphProperties& p) { constexpr uint32_t noValue = GuestsInParkHistoryUndefined; - constexpr StringId fmt = STR_GRAPH_AXIS_LABEL; - DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, fmt, p.lineCol); + const FmtString fmt("{BLACK}{COMMA32} -"); + DrawYLabels(dpi, p.internalBounds, p.min, p.max, p.numYLabels, p.yLabelStepPx, p.lineCol, fmt); 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) - { - const uint32_t value = p.series[p.hoverIdx]; - if (value != noValue) - DrawHoveredValue(dpi, value, p.hoverIdx, p.internalBounds, p.xStepPx, p.min, p.max); - } } } // namespace OpenRCT2::Graph diff --git a/src/openrct2-ui/windows/Finances.cpp b/src/openrct2-ui/windows/Finances.cpp index 240963c007..6b69f7e04b 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -222,7 +222,7 @@ static Widget _windowFinancesResearchWidgets[] = 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. + static constexpr int32_t kGraphNumPoints = 64; #pragma endregion @@ -244,7 +244,6 @@ static Widget _windowFinancesResearchWidgets[] = SetPage(WINDOW_FINANCES_PAGE_SUMMARY); _lastPaintedMonth = std::numeric_limits::max(); ResearchUpdateUncompletedTypes(); - _graphProps.lineCol = colours[2]; _graphProps.hoverIdx = -1; } @@ -363,6 +362,7 @@ static Widget _windowFinancesResearchWidgets[] = _graphProps.RecalculateLayout( { _graphBounds.Point1 + kGraphTopLeftPadding, _graphBounds.Point2 - kGraphBottomRightPadding }, kGraphNumYLabels, kGraphNumPoints); + _graphProps.lineCol = colours[2]; } break; } @@ -848,6 +848,12 @@ static Widget _windowFinancesResearchWidgets[] = // Graph GfxFillRectInset(dpi, _graphBounds, colours[1], INSET_RECT_F_30); + // hide resize widget on graph area + constexpr ScreenCoordsXY offset{ 1, 1 }; + constexpr ScreenCoordsXY bigOffset{ 5, 5 }; + GfxFillRectInset( + dpi, { _graphBounds.Point2 - bigOffset, _graphBounds.Point2 - offset }, colours[1], + INSET_RECT_FLAG_FILL_DONT_LIGHTEN | INSET_RECT_FLAG_BORDER_NONE); // Calculate Y axis max and min. // This is how the original code does it. Could be improved. diff --git a/src/openrct2-ui/windows/Park.cpp b/src/openrct2-ui/windows/Park.cpp index 6cf552796c..cb7f735724 100644 --- a/src/openrct2-ui/windows/Park.cpp +++ b/src/openrct2-ui/windows/Park.cpp @@ -10,11 +10,11 @@ #include "../interface/Theme.h" #include -#include #include #include #include #include +#include #include #include #include @@ -38,7 +38,7 @@ namespace OpenRCT2::Ui::Windows static constexpr StringId WINDOW_TITLE = STR_STRINGID; static constexpr int32_t WH = 224; - static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 35, 15 }; + static constexpr ScreenCoordsXY kGraphTopLeftPadding{ 45, 15 }; static constexpr ScreenCoordsXY kGraphBottomRightPadding{ 5, 5 }; static constexpr uint8_t kGraphNumYLabels = 6; From 5742577c95f311cee6bc98d1de7969b10412ffb6 Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Sat, 3 Aug 2024 02:02:06 +1000 Subject: [PATCH 6/7] New Y axis label algorithm for finance graphs. --- src/openrct2-ui/windows/Finances.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/openrct2-ui/windows/Finances.cpp b/src/openrct2-ui/windows/Finances.cpp index 6b69f7e04b..8de32b5e06 100644 --- a/src/openrct2-ui/windows/Finances.cpp +++ b/src/openrct2-ui/windows/Finances.cpp @@ -856,19 +856,23 @@ static Widget _windowFinancesResearchWidgets[] = INSET_RECT_FLAG_FILL_DONT_LIGHTEN | INSET_RECT_FLAG_BORDER_NONE); // Calculate Y axis max and min. - // This is how the original code does it. Could be improved. - money64 max = centred ? 12.00_GBP : 24.00_GBP; + money64 maxVal = 0; for (int32_t i = 0; i < kGraphNumPoints; i++) { auto val = series[i]; if (val == kMoney64Undefined) continue; - while (std::abs(val) > max) - max *= 2; + while (std::abs(val) > maxVal) + maxVal = val; } - const money64 min = centred ? -max : 0.00_GBP; + // This algorithm increments the leading digit of the max and sets all other digits to zero. + // e.g. 681 => 700. + money64 oom = 10; + while (maxVal / oom >= 10) + oom *= 10; + const money64 max = ((maxVal + oom - 1) / oom) * oom; - _graphProps.min = min; + _graphProps.min = centred ? -max : 0.00_GBP; _graphProps.max = max; _graphProps.series = series; From 380cdb0c4596aede6c3250364dc7760f28934876 Mon Sep 17 00:00:00 2001 From: Michael Bernardi Date: Mon, 5 Aug 2024 15:55:37 +1000 Subject: [PATCH 7/7] Added changelog for graphing improvements --- distribution/changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/distribution/changelog.txt b/distribution/changelog.txt index f7cbe597a8..1a4496359b 100644 --- a/distribution/changelog.txt +++ b/distribution/changelog.txt @@ -1,8 +1,11 @@ 0.4.14 (in development) ------------------------------------------------------------------------ - Feature: [#15750] Allow using different types of park entrance in one park. +- Feature: [#22414] Finance graphs can be resized. - Change: [#21659] Increase the Hybrid Roller Coaster’s maximum lift speed to 17 km/h (11 mph). - Change: [#22466] The Clear Scenery tool now uses a bulldozer cursor instead of a generic crosshair. +- Fix: [#22307] Hover tooltips in financial charts are not invalidated properly. +- Fix: [#22395, #22396] Misaligned tick marks in financial and guest count graphs (original bug). 0.4.13 (2024-08-04) ------------------------------------------------------------------------