2 * This file is part of OpenTTD.
3 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
4 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
5 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 /** @file statusbar_gui.cpp The GUI for the bottom status bar. */
11 #include "core/backup_type.hpp"
13 #include "news_func.h"
14 #include "company_func.h"
15 #include "string_func.h"
16 #include "strings_func.h"
17 #include "company_base.h"
18 #include "tilehighlight_func.h"
20 #include "company_gui.h"
21 #include "window_gui.h"
22 #include "saveload/saveload.h"
23 #include "window_func.h"
24 #include "statusbar_gui.h"
25 #include "toolbar_gui.h"
26 #include "core/geometry_func.hpp"
27 #include "zoom_func.h"
28 #include "timer/timer.h"
29 #include "timer/timer_game_calendar.h"
30 #include "timer/timer_window.h"
32 #include "widgets/statusbar_widget.h"
34 #include "table/strings.h"
35 #include "table/sprites.h"
37 #include "safeguards.h"
39 static bool DrawScrollingStatusText(const NewsItem
*ni
, int scroll_pos
, int left
, int right
, int top
, int bottom
)
41 CopyInDParam(ni
->params
);
43 /* Replace newlines and the likes with spaces. */
44 std::string message
= StrMakeValid(GetString(ni
->string_id
), SVS_REPLACE_TAB_CR_NL_WITH_SPACE
);
46 DrawPixelInfo tmp_dpi
;
47 if (!FillDrawPixelInfo(&tmp_dpi
, left
, top
, right
- left
, bottom
)) return true;
49 int width
= GetStringBoundingBox(message
).width
;
50 int pos
= (_current_text_dir
== TD_RTL
) ? (scroll_pos
- width
) : (right
- scroll_pos
- left
);
52 AutoRestoreBackup
dpi_backup(_cur_dpi
, &tmp_dpi
);
53 DrawString(pos
, INT16_MAX
, 0, message
, TC_LIGHT_BLUE
, SA_LEFT
| SA_FORCE
);
55 return (_current_text_dir
== TD_RTL
) ? (pos
< right
- left
) : (pos
+ width
> 0);
58 struct StatusBarWindow
: Window
{
62 static const int TICKER_STOP
= 1640; ///< scrolling is finished when counter reaches this value
63 static const int COUNTER_STEP
= 2; ///< this is subtracted from active counters every tick
64 static constexpr auto REMINDER_START
= std::chrono::milliseconds(1350); ///< time in ms for reminder notification (red dot on the right) to stay
66 StatusBarWindow(WindowDesc
*desc
) : Window(desc
)
68 this->ticker_scroll
= TICKER_STOP
;
71 CLRBITS(this->flags
, WF_WHITE_BORDER
);
72 PositionStatusbar(this);
75 Point
OnInitialPosition([[maybe_unused
]] int16_t sm_width
, [[maybe_unused
]] int16_t sm_height
, [[maybe_unused
]] int window_number
) override
77 Point pt
= { 0, _screen
.height
- sm_height
};
81 void FindWindowPlacementAndResize([[maybe_unused
]] int def_width
, [[maybe_unused
]] int def_height
) override
83 Window::FindWindowPlacementAndResize(_toolbar_width
, def_height
);
86 void UpdateWidgetSize(WidgetID widget
, Dimension
*size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
*fill
, [[maybe_unused
]] Dimension
*resize
) override
91 SetDParamMaxValue(0, TimerGameCalendar::DateAtStartOfYear(CalendarTime::MAX_YEAR
));
92 d
= GetStringBoundingBox(STR_JUST_DATE_LONG
);
96 int64_t max_money
= UINT32_MAX
;
97 for (const Company
*c
: Company::Iterate()) max_money
= std::max
<int64_t>(c
->money
, max_money
);
98 SetDParam(0, 100LL * max_money
);
99 d
= GetStringBoundingBox(STR_JUST_CURRENCY_LONG
);
107 d
.width
+= padding
.width
;
108 d
.height
+= padding
.height
;
109 *size
= maxdim(d
, *size
);
112 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
114 Rect tr
= r
.Shrink(WidgetDimensions::scaled
.framerect
, RectPadding::zero
);
115 tr
.top
= CenterBounds(r
.top
, r
.bottom
, GetCharacterHeight(FS_NORMAL
));
119 SetDParam(0, TimerGameCalendar::date
);
120 DrawString(tr
, STR_JUST_DATE_LONG
, TC_WHITE
, SA_HOR_CENTER
);
124 if (_local_company
== COMPANY_SPECTATOR
) {
125 DrawString(tr
, STR_STATUSBAR_SPECTATOR
, TC_FROMSTRING
, SA_HOR_CENTER
);
126 } else if (_settings_game
.difficulty
.infinite_money
) {
127 DrawString(tr
, STR_STATUSBAR_INFINITE_MONEY
, TC_FROMSTRING
, SA_HOR_CENTER
);
129 /* Draw company money, if any */
130 const Company
*c
= Company::GetIfValid(_local_company
);
132 SetDParam(0, c
->money
);
133 DrawString(tr
, STR_JUST_CURRENCY_LONG
, TC_WHITE
, SA_HOR_CENTER
);
140 /* Draw status bar */
141 if (this->saving
) { // true when saving is active
142 DrawString(tr
, STR_STATUSBAR_SAVING_GAME
, TC_FROMSTRING
, SA_HOR_CENTER
| SA_VERT_CENTER
);
143 } else if (_do_autosave
) {
144 DrawString(tr
, STR_STATUSBAR_AUTOSAVE
, TC_FROMSTRING
, SA_HOR_CENTER
);
145 } else if (_pause_mode
!= PM_UNPAUSED
) {
146 StringID msg
= (_pause_mode
& PM_PAUSED_LINK_GRAPH
) ? STR_STATUSBAR_PAUSED_LINK_GRAPH
: STR_STATUSBAR_PAUSED
;
147 DrawString(tr
, msg
, TC_FROMSTRING
, SA_HOR_CENTER
);
148 } else if (this->ticker_scroll
< TICKER_STOP
&& _statusbar_news_item
!= nullptr && _statusbar_news_item
->string_id
!= 0) {
149 /* Draw the scrolling news text */
150 if (!DrawScrollingStatusText(_statusbar_news_item
, ScaleGUITrad(this->ticker_scroll
), tr
.left
, tr
.right
, tr
.top
, tr
.bottom
)) {
151 InvalidateWindowData(WC_STATUS_BAR
, 0, SBI_NEWS_DELETED
);
152 if (Company::IsValidID(_local_company
)) {
153 /* This is the default text */
154 SetDParam(0, _local_company
);
155 DrawString(tr
, STR_STATUSBAR_COMPANY_NAME
, TC_FROMSTRING
, SA_HOR_CENTER
);
159 if (Company::IsValidID(_local_company
)) {
160 /* This is the default text */
161 SetDParam(0, _local_company
);
162 DrawString(tr
, STR_STATUSBAR_COMPANY_NAME
, TC_FROMSTRING
, SA_HOR_CENTER
);
166 if (!this->reminder_timeout
.HasFired()) {
167 Dimension icon_size
= GetSpriteSize(SPR_UNREAD_NEWS
);
168 DrawSprite(SPR_UNREAD_NEWS
, PAL_NONE
, tr
.right
- icon_size
.width
, CenterBounds(r
.top
, r
.bottom
, icon_size
.height
));
175 * Some data on this window has become invalid.
176 * @param data Information about the changed data.
177 * @param gui_scope Whether the call is done from GUI scope. You may not do everything when not in GUI scope. See #InvalidateWindowData() for details.
179 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
181 if (!gui_scope
) return;
183 default: NOT_REACHED();
184 case SBI_SAVELOAD_START
: this->saving
= true; break;
185 case SBI_SAVELOAD_FINISH
: this->saving
= false; break;
186 case SBI_SHOW_TICKER
: this->ticker_scroll
= 0; break;
187 case SBI_SHOW_REMINDER
: this->reminder_timeout
.Reset(); break;
188 case SBI_NEWS_DELETED
:
189 this->ticker_scroll
= TICKER_STOP
; // reset ticker ...
190 this->reminder_timeout
.Abort(); // ... and reminder
195 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
198 case WID_S_MIDDLE
: ShowLastNewsMessage(); break;
199 case WID_S_RIGHT
: if (_local_company
!= COMPANY_SPECTATOR
) ShowCompanyFinances(_local_company
); break;
200 default: ResetObjectToPlace();
204 /** Move information on the ticker slowly from one side to the other. */
205 IntervalTimer
<TimerWindow
> ticker_scroll_interval
= {std::chrono::milliseconds(15), [this](uint count
) {
206 if (_pause_mode
!= PM_UNPAUSED
) return;
208 if (this->ticker_scroll
< TICKER_STOP
) {
209 this->ticker_scroll
+= count
;
210 this->SetWidgetDirty(WID_S_MIDDLE
);
214 TimeoutTimer
<TimerWindow
> reminder_timeout
= {REMINDER_START
, [this]() {
215 this->SetWidgetDirty(WID_S_MIDDLE
);
218 IntervalTimer
<TimerGameCalendar
> daily_interval
= {{TimerGameCalendar::DAY
, TimerGameCalendar::Priority::NONE
}, [this](auto) {
219 this->SetWidgetDirty(WID_S_LEFT
);
223 static constexpr NWidgetPart _nested_main_status_widgets
[] = {
224 NWidget(NWID_HORIZONTAL
),
225 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_S_LEFT
), SetMinimalSize(140, 12), EndContainer(),
226 NWidget(WWT_PUSHBTN
, COLOUR_GREY
, WID_S_MIDDLE
), SetMinimalSize(40, 12), SetDataTip(0x0, STR_STATUSBAR_TOOLTIP_SHOW_LAST_NEWS
), SetResize(1, 0),
227 NWidget(WWT_PUSHBTN
, COLOUR_GREY
, WID_S_RIGHT
), SetMinimalSize(140, 12),
231 static WindowDesc
_main_status_desc(__FILE__
, __LINE__
,
232 WDP_MANUAL
, nullptr, 0, 0,
233 WC_STATUS_BAR
, WC_NONE
,
234 WDF_NO_FOCUS
| WDF_NO_CLOSE
,
235 std::begin(_nested_main_status_widgets
), std::end(_nested_main_status_widgets
)
239 * Checks whether the news ticker is currently being used.
241 bool IsNewsTickerShown()
243 const StatusBarWindow
*w
= dynamic_cast<StatusBarWindow
*>(FindWindowById(WC_STATUS_BAR
, 0));
244 return w
!= nullptr && w
->ticker_scroll
< StatusBarWindow::TICKER_STOP
;
248 * Show our status bar.
252 new StatusBarWindow(&_main_status_desc
);