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 music_gui.cpp GUI for the music playback. */
12 #include "base_media_base.h"
13 #include "music/music_driver.hpp"
14 #include "window_gui.h"
15 #include "strings_func.h"
16 #include "window_func.h"
17 #include "sound_func.h"
19 #include "zoom_func.h"
20 #include "core/random_func.hpp"
21 #include "core/mem_func.hpp"
23 #include "core/geometry_func.hpp"
24 #include "string_func.h"
25 #include "settings_type.h"
26 #include "settings_gui.h"
27 #include "dropdown_func.h"
28 #include "dropdown_type.h"
29 #include "slider_func.h"
32 #include "widgets/music_widget.h"
34 #include "table/strings.h"
35 #include "table/sprites.h"
37 #include "safeguards.h"
41 struct PlaylistEntry
: MusicSongInfo
{
42 const MusicSet
*set
; ///< music set the song comes from
43 uint set_index
; ///< index of song in set
45 PlaylistEntry(const MusicSet
*set
, uint set_index
) : MusicSongInfo(set
->songinfo
[set_index
]), set(set
), set_index(set_index
) { }
46 bool IsValid() const { return !this->songname
.empty(); }
48 typedef std::vector
<PlaylistEntry
> Playlist
;
50 enum PlaylistChoices
{
61 Playlist active_playlist
; ///< current play order of songs, including any shuffle
62 Playlist displayed_playlist
; ///< current playlist as displayed in GUI, never in shuffled order
63 Playlist music_set
; ///< all songs in current music set, in set order
65 PlaylistChoices selected_playlist
;
67 void BuildPlaylists();
69 void ChangePlaylist(PlaylistChoices pl
);
70 void ChangeMusicSet(const std::string
&set_name
);
80 bool IsPlaying() const;
81 bool IsShuffle() const;
82 PlaylistEntry
GetCurrentSong() const;
84 bool IsCustomPlaylist() const;
85 void PlaylistAdd(size_t song_index
);
86 void PlaylistRemove(size_t song_index
);
91 void SetPositionBySetIndex(uint set_index
);
92 void ChangePlaylistPosition(int ofs
);
93 int playlist_position
;
95 void SaveCustomPlaylist(PlaylistChoices pl
);
97 Playlist standard_playlists
[PLCH_MAX
];
103 /** Rebuild all playlists for the current music set */
104 void MusicSystem::BuildPlaylists()
106 const MusicSet
*set
= BaseMusic::GetUsedSet();
108 /* Clear current playlists */
109 for (auto &playlist
: this->standard_playlists
) playlist
.clear();
110 this->music_set
.clear();
112 /* Build standard playlists, and a list of available music */
113 for (uint i
= 0; i
< NUM_SONGS_AVAILABLE
; i
++) {
114 PlaylistEntry
entry(set
, i
);
115 if (!entry
.IsValid()) continue;
117 this->music_set
.push_back(entry
);
119 /* Add theme song to theme-only playlist */
120 if (i
== 0) this->standard_playlists
[PLCH_THEMEONLY
].push_back(entry
);
122 /* Don't add the theme song to standard playlists */
124 this->standard_playlists
[PLCH_ALLMUSIC
].push_back(entry
);
125 uint theme
= (i
- 1) / NUM_SONGS_CLASS
;
126 this->standard_playlists
[PLCH_OLDSTYLE
+ theme
].push_back(entry
);
130 /* Load custom playlists
131 * Song index offsets are 1-based, zero indicates invalid/end-of-list value */
132 for (uint i
= 0; i
< NUM_SONGS_PLAYLIST
; i
++) {
133 if (_settings_client
.music
.custom_1
[i
] > 0 && _settings_client
.music
.custom_1
[i
] <= NUM_SONGS_AVAILABLE
) {
134 PlaylistEntry
entry(set
, _settings_client
.music
.custom_1
[i
] - 1);
135 if (entry
.IsValid()) this->standard_playlists
[PLCH_CUSTOM1
].push_back(entry
);
137 if (_settings_client
.music
.custom_2
[i
] > 0 && _settings_client
.music
.custom_2
[i
] <= NUM_SONGS_AVAILABLE
) {
138 PlaylistEntry
entry(set
, _settings_client
.music
.custom_2
[i
] - 1);
139 if (entry
.IsValid()) this->standard_playlists
[PLCH_CUSTOM2
].push_back(entry
);
145 * Switch to another playlist, or reload the current one.
146 * @param pl Playlist to select
148 void MusicSystem::ChangePlaylist(PlaylistChoices pl
)
150 assert(pl
< PLCH_MAX
&& pl
>= PLCH_ALLMUSIC
);
152 if (pl
!= PLCH_THEMEONLY
) _settings_client
.music
.playlist
= pl
;
154 if (_game_mode
!= GM_MENU
|| pl
== PLCH_THEMEONLY
) {
155 this->displayed_playlist
= this->standard_playlists
[pl
];
156 this->active_playlist
= this->displayed_playlist
;
157 this->selected_playlist
= pl
;
158 this->playlist_position
= 0;
160 if (_settings_client
.music
.shuffle
) this->Shuffle();
161 if (_settings_client
.music
.playing
) this->Play();
164 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
165 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
169 * Change to named music set, and reset playback.
170 * @param set_name Name of music set to select
172 void MusicSystem::ChangeMusicSet(const std::string
&set_name
)
174 BaseMusic::SetSetByName(set_name
);
175 BaseMusic::ini_set
= set_name
;
177 this->BuildPlaylists();
178 this->ChangePlaylist(this->selected_playlist
);
180 InvalidateWindowData(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
, 0, true);
181 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0, 1, true);
182 InvalidateWindowData(WC_MUSIC_WINDOW
, 0, 1, true);
186 * Set playlist position by set index.
187 * @param set_index Set index to select.
189 void MusicSystem::SetPositionBySetIndex(uint set_index
)
191 auto it
= std::find_if(std::begin(this->active_playlist
), std::end(this->active_playlist
), [&set_index
](const PlaylistEntry
&ple
) { return ple
.set_index
== set_index
; });
192 if (it
!= std::end(this->active_playlist
)) this->playlist_position
= std::distance(std::begin(this->active_playlist
), it
);
196 * Get set index from current playlist position.
197 * @return current set index, or UINT_MAX if nothing is selected.
199 uint
MusicSystem::GetSetIndex()
201 return static_cast<size_t>(this->playlist_position
) < this->active_playlist
.size()
202 ? this->active_playlist
[this->playlist_position
].set_index
207 * Enable shuffle mode.
209 void MusicSystem::Shuffle()
211 _settings_client
.music
.shuffle
= true;
213 uint set_index
= this->GetSetIndex();
214 this->active_playlist
= this->displayed_playlist
;
215 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
216 size_t shuffle_index
= InteractiveRandom() % (this->active_playlist
.size() - i
);
217 std::swap(this->active_playlist
[i
], this->active_playlist
[i
+ shuffle_index
]);
219 this->SetPositionBySetIndex(set_index
);
221 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
222 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
226 * Disable shuffle mode.
228 void MusicSystem::Unshuffle()
230 _settings_client
.music
.shuffle
= false;
232 uint set_index
= this->GetSetIndex();
233 this->active_playlist
= this->displayed_playlist
;
234 this->SetPositionBySetIndex(set_index
);
236 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
237 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
240 /** Start/restart playback at current song */
241 void MusicSystem::Play()
243 /* Always set the playing flag, even if there is no music */
244 _settings_client
.music
.playing
= true;
245 MusicDriver::GetInstance()->StopSong();
246 /* Make sure playlist_position is a valid index, if playlist has changed etc. */
247 this->ChangePlaylistPosition(0);
249 /* If there is no music, don't try to play it */
250 if (this->active_playlist
.empty()) return;
252 MusicSongInfo song
= this->active_playlist
[this->playlist_position
];
253 if (_game_mode
== GM_MENU
&& this->selected_playlist
== PLCH_THEMEONLY
) song
.loop
= true;
254 MusicDriver::GetInstance()->PlaySong(song
);
256 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
259 /** Stop playback and set flag that we don't intend to play music */
260 void MusicSystem::Stop()
262 MusicDriver::GetInstance()->StopSong();
263 _settings_client
.music
.playing
= false;
265 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
268 /** Skip to next track */
269 void MusicSystem::Next()
271 this->ChangePlaylistPosition(+1);
272 if (_settings_client
.music
.playing
) this->Play();
274 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
277 /** Skip to previous track */
278 void MusicSystem::Prev()
280 this->ChangePlaylistPosition(-1);
281 if (_settings_client
.music
.playing
) this->Play();
283 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
286 /** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */
287 void MusicSystem::CheckStatus()
289 if ((_game_mode
== GM_MENU
) != (this->selected_playlist
== PLCH_THEMEONLY
)) {
290 /* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */
291 this->ChangePlaylist((_game_mode
== GM_MENU
) ? PLCH_THEMEONLY
: (PlaylistChoices
)_settings_client
.music
.playlist
);
293 if (this->active_playlist
.empty()) return;
294 /* If we were supposed to be playing, but music has stopped, move to next song */
295 if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next();
298 /** Is the player getting music right now? */
299 bool MusicSystem::IsPlaying() const
301 return _settings_client
.music
.playing
&& !this->active_playlist
.empty();
304 /** Is shuffle mode enabled? */
305 bool MusicSystem::IsShuffle() const
307 return _settings_client
.music
.shuffle
;
310 /** Return the current song, or a dummy if none */
311 MusicSystem::PlaylistEntry
MusicSystem::GetCurrentSong() const
313 if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0);
314 return this->active_playlist
[this->playlist_position
];
317 /** Is one of the custom playlists selected? */
318 bool MusicSystem::IsCustomPlaylist() const
320 return (this->selected_playlist
== PLCH_CUSTOM1
) || (this->selected_playlist
== PLCH_CUSTOM2
);
324 * Append a song to a custom playlist.
325 * Always adds to the currently active playlist.
326 * @param song_index Index of song in the current music set to add
328 void MusicSystem::PlaylistAdd(size_t song_index
)
330 if (!this->IsCustomPlaylist()) return;
332 /* Pick out song from the music set */
333 if (song_index
>= this->music_set
.size()) return;
334 PlaylistEntry entry
= this->music_set
[song_index
];
336 /* Check for maximum length */
337 if (this->standard_playlists
[this->selected_playlist
].size() >= NUM_SONGS_PLAYLIST
) return;
339 /* Add it to the appropriate playlist, and the display */
340 this->standard_playlists
[this->selected_playlist
].push_back(entry
);
341 this->displayed_playlist
.push_back(entry
);
343 /* Add it to the active playlist, if playback is shuffled select a random position to add at */
344 if (this->active_playlist
.empty()) {
345 this->active_playlist
.push_back(entry
);
346 if (this->IsPlaying()) this->Play();
347 } else if (this->IsShuffle()) {
348 /* Generate a random position between 0 and n (inclusive, new length) to insert at */
349 size_t maxpos
= this->displayed_playlist
.size();
350 size_t newpos
= InteractiveRandom() % maxpos
;
351 this->active_playlist
.insert(this->active_playlist
.begin() + newpos
, entry
);
352 /* Make sure to shift up the current playback position if the song was inserted before it */
353 if ((int)newpos
<= this->playlist_position
) this->playlist_position
++;
355 this->active_playlist
.push_back(entry
);
358 this->SaveCustomPlaylist(this->selected_playlist
);
360 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
364 * Remove a song from a custom playlist.
365 * @param song_index Index in the custom playlist to remove.
367 void MusicSystem::PlaylistRemove(size_t song_index
)
369 if (!this->IsCustomPlaylist()) return;
371 Playlist
&pl
= this->standard_playlists
[this->selected_playlist
];
372 if (song_index
>= pl
.size()) return;
374 /* Remove from "simple" playlists */
375 PlaylistEntry song
= pl
[song_index
];
376 pl
.erase(pl
.begin() + song_index
);
377 this->displayed_playlist
.erase(this->displayed_playlist
.begin() + song_index
);
379 /* Find in actual active playlist (may be shuffled) and remove,
380 * if it's the current song restart playback */
381 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
382 Playlist::iterator s2
= this->active_playlist
.begin() + i
;
383 if (s2
->filename
== song
.filename
&& s2
->cat_index
== song
.cat_index
) {
384 this->active_playlist
.erase(s2
);
385 if ((int)i
== this->playlist_position
&& this->IsPlaying()) this->Play();
390 this->SaveCustomPlaylist(this->selected_playlist
);
392 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
396 * Remove all songs from the current custom playlist.
397 * Effectively stops playback too.
399 void MusicSystem::PlaylistClear()
401 if (!this->IsCustomPlaylist()) return;
403 this->standard_playlists
[this->selected_playlist
].clear();
404 this->ChangePlaylist(this->selected_playlist
);
406 this->SaveCustomPlaylist(this->selected_playlist
);
410 * Change playlist position pointer by the given offset, making sure to keep it within valid range.
411 * If the playlist is empty, position is always set to 0.
412 * @param ofs Amount to move playlist position by.
414 void MusicSystem::ChangePlaylistPosition(int ofs
)
416 if (this->active_playlist
.empty()) {
417 this->playlist_position
= 0;
419 this->playlist_position
+= ofs
;
420 while (this->playlist_position
>= (int)this->active_playlist
.size()) this->playlist_position
-= (int)this->active_playlist
.size();
421 while (this->playlist_position
< 0) this->playlist_position
+= (int)this->active_playlist
.size();
426 * Save a custom playlist to settings after modification.
427 * @param pl Playlist to store back
429 void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl
)
431 uint8_t *settings_pl
;
432 if (pl
== PLCH_CUSTOM1
) {
433 settings_pl
= _settings_client
.music
.custom_1
;
434 } else if (pl
== PLCH_CUSTOM2
) {
435 settings_pl
= _settings_client
.music
.custom_2
;
441 MemSetT(settings_pl
, 0, NUM_SONGS_PLAYLIST
);
443 for (const auto &song
: this->standard_playlists
[pl
]) {
444 /* Music set indices in the settings playlist are 1-based, 0 means unused slot */
445 settings_pl
[num
++] = (uint8_t)song
.set_index
+ 1;
451 * Check music playback status and start/stop/song-finished.
452 * Called from main loop.
456 _music
.CheckStatus();
460 * Change the configured music set and reset playback
461 * @param index Index of music set to switch to
463 void ChangeMusicSet(int index
)
465 if (BaseMusic::GetIndexOfUsedSet() == index
) return;
466 _music
.ChangeMusicSet(BaseMusic::GetSet(index
)->name
);
470 * Prepare the music system for use.
471 * Called from \c InitializeGame
473 void InitializeMusic()
475 _music
.BuildPlaylists();
479 struct MusicTrackSelectionWindow
: public Window
{
480 MusicTrackSelectionWindow(WindowDesc
&desc
, WindowNumber number
) : Window(desc
)
482 this->InitNested(number
);
483 this->LowerWidget(WID_MTS_LIST_LEFT
);
484 this->LowerWidget(WID_MTS_LIST_RIGHT
);
485 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
486 this->LowerWidget(WID_MTS_ALL
+ _settings_client
.music
.playlist
);
489 void SetStringParameters(WidgetID widget
) const override
492 case WID_MTS_PLAYLIST
:
493 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ _settings_client
.music
.playlist
);
495 case WID_MTS_CAPTION
:
496 SetDParamStr(0, BaseMusic::GetUsedSet()->name
);
502 * Some data on this window has become invalid.
503 * @param data Information about the changed data.
504 * @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.
506 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
508 if (!gui_scope
) return;
509 for (int i
= 0; i
< 6; i
++) {
510 this->SetWidgetLoweredState(WID_MTS_ALL
+ i
, i
== _settings_client
.music
.playlist
);
512 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
521 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
524 case WID_MTS_PLAYLIST
: {
525 Dimension d
= {0, 0};
527 for (int i
= 0; i
< 6; i
++) {
528 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ i
);
529 d
= maxdim(d
, GetStringBoundingBox(STR_PLAYLIST_PROGRAM
));
531 d
.width
+= padding
.width
;
532 d
.height
+= padding
.height
;
533 size
= maxdim(size
, d
);
537 case WID_MTS_LIST_LEFT
: case WID_MTS_LIST_RIGHT
: {
538 Dimension d
= {0, 0};
540 for (const auto &song
: _music
.music_set
) {
541 SetDParam(0, song
.tracknr
);
543 SetDParamStr(2, song
.songname
);
544 Dimension d2
= GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME
);
545 d
.width
= std::max(d
.width
, d2
.width
);
546 d
.height
+= d2
.height
;
548 d
.width
+= padding
.width
;
549 d
.height
+= padding
.height
;
550 size
= maxdim(size
, d
);
556 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
559 case WID_MTS_LIST_LEFT
: {
560 GfxFillRect(r
.Shrink(WidgetDimensions::scaled
.bevel
), PC_BLACK
);
562 Rect tr
= r
.Shrink(WidgetDimensions::scaled
.framerect
);
563 for (const auto &song
: _music
.music_set
) {
564 SetDParam(0, song
.tracknr
);
566 SetDParamStr(2, song
.songname
);
567 DrawString(tr
, STR_PLAYLIST_TRACK_NAME
);
568 tr
.top
+= GetCharacterHeight(FS_SMALL
);
573 case WID_MTS_LIST_RIGHT
: {
574 GfxFillRect(r
.Shrink(WidgetDimensions::scaled
.bevel
), PC_BLACK
);
576 Rect tr
= r
.Shrink(WidgetDimensions::scaled
.framerect
);
577 for (const auto &song
: _music
.active_playlist
) {
578 SetDParam(0, song
.tracknr
);
580 SetDParamStr(2, song
.songname
);
581 DrawString(tr
, STR_PLAYLIST_TRACK_NAME
);
582 tr
.top
+= GetCharacterHeight(FS_SMALL
);
589 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
592 case WID_MTS_LIST_LEFT
: { // add to playlist
593 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, GetCharacterHeight(FS_SMALL
));
594 _music
.PlaylistAdd(y
);
598 case WID_MTS_LIST_RIGHT
: { // remove from playlist
599 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, GetCharacterHeight(FS_SMALL
));
600 _music
.PlaylistRemove(y
);
604 case WID_MTS_MUSICSET
: {
606 ShowDropDownList(this, BuildSetDropDownList
<BaseMusic
>(&selected
), selected
, widget
);
610 case WID_MTS_CLEAR
: // clear
611 _music
.PlaylistClear();
614 case WID_MTS_ALL
: case WID_MTS_OLD
: case WID_MTS_NEW
:
615 case WID_MTS_EZY
: case WID_MTS_CUSTOM1
: case WID_MTS_CUSTOM2
: // set playlist
616 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_MTS_ALL
));
621 void OnDropdownSelect(WidgetID widget
, int index
) override
624 case WID_MTS_MUSICSET
:
625 ChangeMusicSet(index
);
633 static constexpr NWidgetPart _nested_music_track_selection_widgets
[] = {
634 NWidget(NWID_HORIZONTAL
),
635 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
636 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_MTS_CAPTION
), SetDataTip(STR_PLAYLIST_MUSIC_SELECTION_SETNAME
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
637 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_MTS_MUSICSET
), SetDataTip(STR_PLAYLIST_CHANGE_SET
, STR_PLAYLIST_TOOLTIP_CHANGE_SET
),
639 NWidget(WWT_PANEL
, COLOUR_GREY
),
640 NWidget(NWID_HORIZONTAL
), SetPIP(2, 4, 2),
642 NWidget(NWID_VERTICAL
),
643 NWidget(WWT_LABEL
, COLOUR_GREY
), SetFill(1, 0), SetDataTip(STR_PLAYLIST_TRACK_INDEX
, STR_NULL
),
644 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_LEFT
), SetFill(1, 1), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK
), EndContainer(),
645 NWidget(NWID_SPACER
), SetFill(1, 0), SetMinimalSize(0, 2),
647 /* Middle buttons. */
648 NWidget(NWID_VERTICAL
),
649 NWidget(NWID_SPACER
), SetMinimalSize(60, 30), // Space above the first button from the title bar.
650 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
651 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
652 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
653 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
654 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_CUSTOM1
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED
),
655 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_CUSTOM2
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED
),
656 NWidget(NWID_SPACER
), SetMinimalSize(0, 16), // Space above 'clear' button
657 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_MTS_CLEAR
), SetFill(1, 0), SetDataTip(STR_PLAYLIST_CLEAR
, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1
),
658 NWidget(NWID_SPACER
), SetFill(0, 1),
661 NWidget(NWID_VERTICAL
),
662 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_MTS_PLAYLIST
), SetFill(1, 0), SetDataTip(STR_PLAYLIST_PROGRAM
, STR_NULL
),
663 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_RIGHT
), SetFill(1, 1), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK
), EndContainer(),
664 NWidget(NWID_SPACER
), SetFill(1, 0), SetMinimalSize(0, 2),
670 static WindowDesc
_music_track_selection_desc(
671 WDP_AUTO
, nullptr, 0, 0,
672 WC_MUSIC_TRACK_SELECTION
, WC_NONE
,
674 _nested_music_track_selection_widgets
677 static void ShowMusicTrackSelection()
679 AllocateWindowDescFront
<MusicTrackSelectionWindow
>(_music_track_selection_desc
, 0);
682 struct MusicWindow
: public Window
{
683 MusicWindow(WindowDesc
&desc
, WindowNumber number
) : Window(desc
)
685 this->InitNested(number
);
686 this->LowerWidget(_settings_client
.music
.playlist
+ WID_M_ALL
);
687 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _settings_client
.music
.shuffle
);
689 UpdateDisabledButtons();
692 void UpdateDisabledButtons()
694 /* Disable music control widgets if there is no music
695 * -- except Programme button! So you can still select a music set. */
696 this->SetWidgetsDisabledState(
697 BaseMusic::GetUsedSet()->num_available
== 0,
698 WID_M_PREV
, WID_M_NEXT
, WID_M_STOP
, WID_M_PLAY
, WID_M_SHUFFLE
,
699 WID_M_ALL
, WID_M_OLD
, WID_M_NEW
, WID_M_EZY
, WID_M_CUSTOM1
, WID_M_CUSTOM2
703 void UpdateWidgetSize(WidgetID widget
, Dimension
&size
, [[maybe_unused
]] const Dimension
&padding
, [[maybe_unused
]] Dimension
&fill
, [[maybe_unused
]] Dimension
&resize
) override
706 /* Make sure that WID_M_SHUFFLE and WID_M_PROGRAMME have the same size.
707 * This can't be done by using NC_EQUALSIZE as the WID_M_INFO is
708 * between those widgets and of different size. */
709 case WID_M_SHUFFLE
: case WID_M_PROGRAMME
: {
710 Dimension d
= maxdim(GetStringBoundingBox(STR_MUSIC_PROGRAM
), GetStringBoundingBox(STR_MUSIC_SHUFFLE
));
711 d
.width
+= padding
.width
;
712 d
.height
+= padding
.height
;
713 size
= maxdim(size
, d
);
717 case WID_M_TRACK_NR
: {
718 Dimension d
= GetStringBoundingBox(STR_MUSIC_TRACK_NONE
);
719 d
.width
+= padding
.width
;
720 d
.height
+= padding
.height
;
721 size
= maxdim(size
, d
);
725 case WID_M_TRACK_NAME
: {
726 Dimension d
= GetStringBoundingBox(STR_MUSIC_TITLE_NONE
);
727 for (const auto &song
: _music
.music_set
) {
728 SetDParamStr(0, song
.songname
);
729 d
= maxdim(d
, GetStringBoundingBox(STR_MUSIC_TITLE_NAME
));
731 d
.width
+= padding
.width
;
732 d
.height
+= padding
.height
;
733 size
= maxdim(size
, d
);
737 /* Hack-ish: set the proper widget data; only needs to be done once
738 * per (Re)Init as that's the only time the language changes. */
739 case WID_M_PREV
: this->GetWidget
<NWidgetCore
>(WID_M_PREV
)->widget_data
= _current_text_dir
== TD_RTL
? SPR_IMG_SKIP_TO_NEXT
: SPR_IMG_SKIP_TO_PREV
; break;
740 case WID_M_NEXT
: this->GetWidget
<NWidgetCore
>(WID_M_NEXT
)->widget_data
= _current_text_dir
== TD_RTL
? SPR_IMG_SKIP_TO_PREV
: SPR_IMG_SKIP_TO_NEXT
; break;
741 case WID_M_PLAY
: this->GetWidget
<NWidgetCore
>(WID_M_PLAY
)->widget_data
= _current_text_dir
== TD_RTL
? SPR_IMG_PLAY_MUSIC_RTL
: SPR_IMG_PLAY_MUSIC
; break;
745 void DrawWidget(const Rect
&r
, WidgetID widget
) const override
748 case WID_M_TRACK_NR
: {
749 GfxFillRect(r
.Shrink(WidgetDimensions::scaled
.bevel
.left
, WidgetDimensions::scaled
.bevel
.top
, 0, WidgetDimensions::scaled
.bevel
.bottom
), PC_BLACK
);
750 if (BaseMusic::GetUsedSet()->num_available
== 0) {
753 StringID str
= STR_MUSIC_TRACK_NONE
;
754 if (_music
.IsPlaying()) {
755 SetDParam(0, _music
.GetCurrentSong().tracknr
);
757 str
= STR_MUSIC_TRACK_DIGIT
;
759 DrawString(r
.Shrink(WidgetDimensions::scaled
.framerect
), str
);
763 case WID_M_TRACK_NAME
: {
764 GfxFillRect(r
.Shrink(0, WidgetDimensions::scaled
.bevel
.top
, WidgetDimensions::scaled
.bevel
.right
, WidgetDimensions::scaled
.bevel
.bottom
), PC_BLACK
);
765 StringID str
= STR_MUSIC_TITLE_NONE
;
766 MusicSystem::PlaylistEntry
entry(_music
.GetCurrentSong());
767 if (BaseMusic::GetUsedSet()->num_available
== 0) {
768 str
= STR_MUSIC_TITLE_NOMUSIC
;
769 } else if (_music
.IsPlaying()) {
770 str
= STR_MUSIC_TITLE_NAME
;
771 SetDParamStr(0, entry
.songname
);
773 DrawString(r
.Shrink(WidgetDimensions::scaled
.framerect
), str
, TC_FROMSTRING
, SA_HOR_CENTER
);
777 case WID_M_MUSIC_VOL
:
778 DrawSliderWidget(r
, 0, INT8_MAX
, 0, _settings_client
.music
.music_vol
, nullptr);
781 case WID_M_EFFECT_VOL
:
782 DrawSliderWidget(r
, 0, INT8_MAX
, 0, _settings_client
.music
.effect_vol
, nullptr);
788 * Some data on this window has become invalid.
789 * @param data Information about the changed data.
790 * @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.
792 void OnInvalidateData([[maybe_unused
]] int data
= 0, [[maybe_unused
]] bool gui_scope
= true) override
794 if (!gui_scope
) return;
795 for (int i
= 0; i
< 6; i
++) {
796 this->SetWidgetLoweredState(WID_M_ALL
+ i
, i
== _settings_client
.music
.playlist
);
799 UpdateDisabledButtons();
808 void OnClick([[maybe_unused
]] Point pt
, WidgetID widget
, [[maybe_unused
]] int click_count
) override
811 case WID_M_PREV
: // skip to prev
815 case WID_M_NEXT
: // skip to next
819 case WID_M_STOP
: // stop playing
823 case WID_M_PLAY
: // start playing
827 case WID_M_MUSIC_VOL
: case WID_M_EFFECT_VOL
: { // volume sliders
828 uint8_t &vol
= (widget
== WID_M_MUSIC_VOL
) ? _settings_client
.music
.music_vol
: _settings_client
.music
.effect_vol
;
829 if (ClickSliderWidget(this->GetWidget
<NWidgetBase
>(widget
)->GetCurrentRect(), pt
, 0, INT8_MAX
, 0, vol
)) {
830 if (widget
== WID_M_MUSIC_VOL
) {
831 MusicDriver::GetInstance()->SetVolume(vol
);
833 SetEffectVolume(vol
);
835 this->SetWidgetDirty(widget
);
836 SetWindowClassesDirty(WC_GAME_OPTIONS
);
839 if (click_count
> 0) this->mouse_capture_widget
= widget
;
843 case WID_M_SHUFFLE
: // toggle shuffle
844 if (_music
.IsShuffle()) {
849 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _music
.IsShuffle());
850 this->SetWidgetDirty(WID_M_SHUFFLE
);
853 case WID_M_PROGRAMME
: // show track selection
854 ShowMusicTrackSelection();
857 case WID_M_ALL
: case WID_M_OLD
: case WID_M_NEW
:
858 case WID_M_EZY
: case WID_M_CUSTOM1
: case WID_M_CUSTOM2
: // playlist
859 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_M_ALL
));
865 static constexpr NWidgetPart _nested_music_window_widgets
[] = {
866 NWidget(NWID_HORIZONTAL
),
867 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
868 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_MUSIC_JAZZ_JUKEBOX_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
869 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
870 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
873 NWidget(NWID_HORIZONTAL
),
874 NWidget(NWID_VERTICAL
),
875 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
876 NWidget(NWID_HORIZONTAL
),
877 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PREV
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_PREV
, STR_MUSIC_TOOLTIP_SKIP_TO_PREVIOUS_TRACK
),
878 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_NEXT
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_NEXT
, STR_MUSIC_TOOLTIP_SKIP_TO_NEXT_TRACK_IN_SELECTION
),
879 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_STOP
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_STOP_MUSIC
, STR_MUSIC_TOOLTIP_STOP_PLAYING_MUSIC
),
880 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PLAY
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_PLAY_MUSIC
, STR_MUSIC_TOOLTIP_START_PLAYING_MUSIC
),
882 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
884 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_SLIDERS
),
885 NWidget(NWID_HORIZONTAL
), SetPIP(4, 0, 4),
886 NWidget(NWID_VERTICAL
),
887 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_MUSIC_VOLUME
, STR_NULL
),
888 NWidget(WWT_EMPTY
, COLOUR_GREY
, WID_M_MUSIC_VOL
), SetMinimalSize(67, 0), SetPadding(2), SetMinimalTextLines(1, 0), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC
),
890 NWidget(NWID_VERTICAL
),
891 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_EFFECTS_VOLUME
, STR_NULL
),
892 NWidget(WWT_EMPTY
, COLOUR_GREY
, WID_M_EFFECT_VOL
), SetMinimalSize(67, 0), SetPadding(2), SetMinimalTextLines(1, 0), SetFill(1, 0), SetDataTip(0x0, STR_MUSIC_TOOLTIP_DRAG_SLIDERS_TO_SET_MUSIC
),
897 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_BACKGROUND
),
898 NWidget(NWID_HORIZONTAL
), SetPIP(6, 0, 6),
899 NWidget(NWID_VERTICAL
),
900 NWidget(NWID_SPACER
), SetFill(0, 1),
901 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_SHUFFLE
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_SHUFFLE
, STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE
),
902 NWidget(NWID_SPACER
), SetFill(0, 1),
904 NWidget(NWID_VERTICAL
), SetPadding(0, 0, 3, 3),
905 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK
), SetFill(0, 0), SetDataTip(STR_MUSIC_TRACK
, STR_NULL
),
906 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NR
), EndContainer(),
908 NWidget(NWID_VERTICAL
), SetPadding(0, 3, 3, 0),
909 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK_TITLE
), SetFill(1, 0), SetDataTip(STR_MUSIC_XTITLE
, STR_NULL
),
910 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NAME
), SetFill(1, 0), EndContainer(),
912 NWidget(NWID_VERTICAL
),
913 NWidget(NWID_SPACER
), SetFill(0, 1),
914 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_M_PROGRAMME
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_PROGRAM
, STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION
),
915 NWidget(NWID_SPACER
), SetFill(0, 1),
919 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
920 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
921 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
922 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
923 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
924 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_CUSTOM1
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_1
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_1_USER_DEFINED
),
925 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_CUSTOM2
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_CUSTOM_2
, STR_MUSIC_TOOLTIP_SELECT_CUSTOM_2_USER_DEFINED
),
929 static WindowDesc
_music_window_desc(
930 WDP_AUTO
, "music", 0, 0,
931 WC_MUSIC_WINDOW
, WC_NONE
,
933 _nested_music_window_widgets
936 void ShowMusicWindow()
938 AllocateWindowDescFront
<MusicWindow
>(_music_window_desc
, 0);