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. */
13 #include "base_media_base.h"
14 #include "music/music_driver.hpp"
15 #include "window_gui.h"
16 #include "strings_func.h"
17 #include "window_func.h"
18 #include "sound_func.h"
20 #include "zoom_func.h"
21 #include "core/random_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 "widgets/dropdown_func.h"
28 #include "widgets/dropdown_type.h"
30 #include "widgets/music_widget.h"
32 #include "table/strings.h"
33 #include "table/sprites.h"
35 #include "safeguards.h"
39 struct PlaylistEntry
: MusicSongInfo
{
40 const MusicSet
*set
; ///< music set the song comes from
41 uint set_index
; ///< index of song in set
43 PlaylistEntry(const MusicSet
*set
, uint set_index
) : MusicSongInfo(set
->songinfo
[set_index
]), set(set
), set_index(set_index
) { }
44 bool IsValid() const { return !StrEmpty(this->songname
); }
46 typedef std::vector
<PlaylistEntry
> Playlist
;
48 enum PlaylistChoices
{
59 Playlist active_playlist
; ///< current play order of songs, including any shuffle
60 Playlist displayed_playlist
; ///< current playlist as displayed in GUI, never in shuffled order
61 Playlist music_set
; ///< all songs in current music set, in set order
63 PlaylistChoices selected_playlist
;
65 void BuildPlaylists();
67 void ChangePlaylist(PlaylistChoices pl
);
68 void ChangeMusicSet(const std::string
&set_name
);
78 bool IsPlaying() const;
79 bool IsShuffle() const;
80 PlaylistEntry
GetCurrentSong() const;
82 bool IsCustomPlaylist() const;
83 void PlaylistAdd(size_t song_index
);
84 void PlaylistRemove(size_t song_index
);
88 void ChangePlaylistPosition(int ofs
);
89 int playlist_position
;
91 void SaveCustomPlaylist(PlaylistChoices pl
);
93 Playlist standard_playlists
[PLCH_MAX
];
99 /** Rebuild all playlists for the current music set */
100 void MusicSystem::BuildPlaylists()
102 const MusicSet
*set
= BaseMusic::GetUsedSet();
104 /* Clear current playlists */
105 for (size_t i
= 0; i
< lengthof(this->standard_playlists
); ++i
) this->standard_playlists
[i
].clear();
106 this->music_set
.clear();
108 /* Build standard playlists, and a list of available music */
109 for (uint i
= 0; i
< NUM_SONGS_AVAILABLE
; i
++) {
110 PlaylistEntry
entry(set
, i
);
111 if (!entry
.IsValid()) continue;
113 this->music_set
.push_back(entry
);
115 /* Add theme song to theme-only playlist */
116 if (i
== 0) this->standard_playlists
[PLCH_THEMEONLY
].push_back(entry
);
118 /* Don't add the theme song to standard playlists */
120 this->standard_playlists
[PLCH_ALLMUSIC
].push_back(entry
);
121 uint theme
= (i
- 1) / NUM_SONGS_CLASS
;
122 this->standard_playlists
[PLCH_OLDSTYLE
+ theme
].push_back(entry
);
126 /* Load custom playlists
127 * Song index offsets are 1-based, zero indicates invalid/end-of-list value */
128 for (uint i
= 0; i
< NUM_SONGS_PLAYLIST
; i
++) {
129 if (_settings_client
.music
.custom_1
[i
] > 0) {
130 PlaylistEntry
entry(set
, _settings_client
.music
.custom_1
[i
] - 1);
131 if (entry
.IsValid()) this->standard_playlists
[PLCH_CUSTOM1
].push_back(entry
);
133 if (_settings_client
.music
.custom_2
[i
] > 0) {
134 PlaylistEntry
entry(set
, _settings_client
.music
.custom_2
[i
] - 1);
135 if (entry
.IsValid()) this->standard_playlists
[PLCH_CUSTOM2
].push_back(entry
);
141 * Switch to another playlist, or reload the current one.
142 * @param pl Playlist to select
144 void MusicSystem::ChangePlaylist(PlaylistChoices pl
)
146 assert(pl
< PLCH_MAX
&& pl
>= PLCH_ALLMUSIC
);
148 this->displayed_playlist
= this->standard_playlists
[pl
];
149 this->active_playlist
= this->displayed_playlist
;
150 this->selected_playlist
= pl
;
151 this->playlist_position
= 0;
153 if (this->selected_playlist
!= PLCH_THEMEONLY
) _settings_client
.music
.playlist
= this->selected_playlist
;
155 if (_settings_client
.music
.shuffle
) {
157 /* Shuffle() will also Play() if necessary, only start once */
158 } else if (_settings_client
.music
.playing
) {
162 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
163 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
167 * Change to named music set, and reset playback.
168 * @param set_name Name of music set to select
170 void MusicSystem::ChangeMusicSet(const std::string
&set_name
)
172 BaseMusic::SetSet(set_name
);
173 BaseMusic::ini_set
= set_name
;
175 this->BuildPlaylists();
176 this->ChangePlaylist(this->selected_playlist
);
178 InvalidateWindowData(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
, 0, true);
181 /** Enable shuffle mode and restart playback */
182 void MusicSystem::Shuffle()
184 _settings_client
.music
.shuffle
= true;
186 this->active_playlist
= this->displayed_playlist
;
187 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
188 size_t shuffle_index
= InteractiveRandom() % (this->active_playlist
.size() - i
);
189 std::swap(this->active_playlist
[i
], this->active_playlist
[i
+ shuffle_index
]);
192 if (_settings_client
.music
.playing
) this->Play();
194 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
197 /** Disable shuffle and restart playback */
198 void MusicSystem::Unshuffle()
200 _settings_client
.music
.shuffle
= false;
201 this->active_playlist
= this->displayed_playlist
;
203 if (_settings_client
.music
.playing
) this->Play();
205 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
208 /** Start/restart playback at current song */
209 void MusicSystem::Play()
211 /* Always set the playing flag, even if there is no music */
212 _settings_client
.music
.playing
= true;
213 MusicDriver::GetInstance()->StopSong();
214 /* Make sure playlist_position is a valid index, if playlist has changed etc. */
215 this->ChangePlaylistPosition(0);
217 /* If there is no music, don't try to play it */
218 if (this->active_playlist
.empty()) return;
220 MusicSongInfo song
= this->active_playlist
[this->playlist_position
];
221 if (_game_mode
== GM_MENU
&& this->selected_playlist
== PLCH_THEMEONLY
) song
.loop
= true;
222 MusicDriver::GetInstance()->PlaySong(song
);
224 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
227 /** Stop playback and set flag that we don't intend to play music */
228 void MusicSystem::Stop()
230 MusicDriver::GetInstance()->StopSong();
231 _settings_client
.music
.playing
= false;
233 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
236 /** Skip to next track */
237 void MusicSystem::Next()
239 this->ChangePlaylistPosition(+1);
240 if (_settings_client
.music
.playing
) this->Play();
242 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
245 /** Skip to previous track */
246 void MusicSystem::Prev()
248 this->ChangePlaylistPosition(-1);
249 if (_settings_client
.music
.playing
) this->Play();
251 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
254 /** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */
255 void MusicSystem::CheckStatus()
257 if ((_game_mode
== GM_MENU
) != (this->selected_playlist
== PLCH_THEMEONLY
)) {
258 /* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */
259 this->ChangePlaylist((_game_mode
== GM_MENU
) ? PLCH_THEMEONLY
: (PlaylistChoices
)_settings_client
.music
.playlist
);
261 if (this->active_playlist
.empty()) return;
262 /* If we were supposed to be playing, but music has stopped, move to next song */
263 if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next();
266 /** Is the player getting music right now? */
267 bool MusicSystem::IsPlaying() const
269 return _settings_client
.music
.playing
&& !this->active_playlist
.empty();
272 /** Is shuffle mode enabled? */
273 bool MusicSystem::IsShuffle() const
275 return _settings_client
.music
.shuffle
;
278 /** Return the current song, or a dummy if none */
279 MusicSystem::PlaylistEntry
MusicSystem::GetCurrentSong() const
281 if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0);
282 return this->active_playlist
[this->playlist_position
];
285 /** Is one of the custom playlists selected? */
286 bool MusicSystem::IsCustomPlaylist() const
288 return (this->selected_playlist
== PLCH_CUSTOM1
) || (this->selected_playlist
== PLCH_CUSTOM2
);
292 * Append a song to a custom playlist.
293 * Always adds to the currently active playlist.
294 * @param song_index Index of song in the current music set to add
296 void MusicSystem::PlaylistAdd(size_t song_index
)
298 if (!this->IsCustomPlaylist()) return;
300 /* Pick out song from the music set */
301 if (song_index
>= this->music_set
.size()) return;
302 PlaylistEntry entry
= this->music_set
[song_index
];
304 /* Check for maximum length */
305 if (this->standard_playlists
[this->selected_playlist
].size() >= NUM_SONGS_PLAYLIST
) return;
307 /* Add it to the appropriate playlist, and the display */
308 this->standard_playlists
[this->selected_playlist
].push_back(entry
);
309 this->displayed_playlist
.push_back(entry
);
311 /* Add it to the active playlist, if playback is shuffled select a random position to add at */
312 if (this->active_playlist
.empty()) {
313 this->active_playlist
.push_back(entry
);
314 if (this->IsPlaying()) this->Play();
315 } else if (this->IsShuffle()) {
316 /* Generate a random position between 0 and n (inclusive, new length) to insert at */
317 size_t maxpos
= this->displayed_playlist
.size();
318 size_t newpos
= InteractiveRandom() % maxpos
;
319 this->active_playlist
.insert(this->active_playlist
.begin() + newpos
, entry
);
320 /* Make sure to shift up the current playback position if the song was inserted before it */
321 if ((int)newpos
<= this->playlist_position
) this->playlist_position
++;
323 this->active_playlist
.push_back(entry
);
326 this->SaveCustomPlaylist(this->selected_playlist
);
328 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
332 * Remove a song from a custom playlist.
333 * @param song_index Index in the custom playlist to remove.
335 void MusicSystem::PlaylistRemove(size_t song_index
)
337 if (!this->IsCustomPlaylist()) return;
339 Playlist
&pl
= this->standard_playlists
[this->selected_playlist
];
340 if (song_index
>= pl
.size()) return;
342 /* Remove from "simple" playlists */
343 PlaylistEntry song
= pl
[song_index
];
344 pl
.erase(pl
.begin() + song_index
);
345 this->displayed_playlist
.erase(this->displayed_playlist
.begin() + song_index
);
347 /* Find in actual active playlist (may be shuffled) and remove,
348 * if it's the current song restart playback */
349 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
350 Playlist::iterator s2
= this->active_playlist
.begin() + i
;
351 if (s2
->filename
== song
.filename
&& s2
->cat_index
== song
.cat_index
) {
352 this->active_playlist
.erase(s2
);
353 if ((int)i
== this->playlist_position
&& this->IsPlaying()) this->Play();
358 this->SaveCustomPlaylist(this->selected_playlist
);
360 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
364 * Remove all songs from the current custom playlist.
365 * Effectively stops playback too.
367 void MusicSystem::PlaylistClear()
369 if (!this->IsCustomPlaylist()) return;
371 this->standard_playlists
[this->selected_playlist
].clear();
372 this->ChangePlaylist(this->selected_playlist
);
374 this->SaveCustomPlaylist(this->selected_playlist
);
378 * Change playlist position pointer by the given offset, making sure to keep it within valid range.
379 * If the playlist is empty, position is always set to 0.
380 * @param ofs Amount to move playlist position by.
382 void MusicSystem::ChangePlaylistPosition(int ofs
)
384 if (this->active_playlist
.empty()) {
385 this->playlist_position
= 0;
387 this->playlist_position
+= ofs
;
388 while (this->playlist_position
>= (int)this->active_playlist
.size()) this->playlist_position
-= (int)this->active_playlist
.size();
389 while (this->playlist_position
< 0) this->playlist_position
+= (int)this->active_playlist
.size();
394 * Save a custom playlist to settings after modification.
395 * @param pl Playlist to store back
397 void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl
)
400 if (pl
== PLCH_CUSTOM1
) {
401 settings_pl
= _settings_client
.music
.custom_1
;
402 } else if (pl
== PLCH_CUSTOM2
) {
403 settings_pl
= _settings_client
.music
.custom_2
;
409 MemSetT(settings_pl
, 0, NUM_SONGS_PLAYLIST
);
411 for (Playlist::const_iterator song
= this->standard_playlists
[pl
].begin(); song
!= this->standard_playlists
[pl
].end(); ++song
) {
412 /* Music set indices in the settings playlist are 1-based, 0 means unused slot */
413 settings_pl
[num
++] = (byte
)song
->set_index
+ 1;
419 * Check music playback status and start/stop/song-finished.
420 * Called from main loop.
424 _music
.CheckStatus();
428 * Change the configured music set and reset playback
429 * @param index Index of music set to switch to
431 void ChangeMusicSet(int index
)
433 if (BaseMusic::GetIndexOfUsedSet() == index
) return;
434 _music
.ChangeMusicSet(BaseMusic::GetSet(index
)->name
);
438 * Prepare the music system for use.
439 * Called from \c InitializeGame
441 void InitializeMusic()
443 _music
.BuildPlaylists();
447 struct MusicTrackSelectionWindow
: public Window
{
448 MusicTrackSelectionWindow(WindowDesc
*desc
, WindowNumber number
) : Window(desc
)
450 this->InitNested(number
);
451 this->LowerWidget(WID_MTS_LIST_LEFT
);
452 this->LowerWidget(WID_MTS_LIST_RIGHT
);
453 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
454 this->LowerWidget(WID_MTS_ALL
+ _settings_client
.music
.playlist
);
457 void SetStringParameters(int widget
) const override
460 case WID_MTS_PLAYLIST
:
461 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ _settings_client
.music
.playlist
);
463 case WID_MTS_CAPTION
:
464 SetDParamStr(0, BaseMusic::GetUsedSet()->name
.c_str());
470 * Some data on this window has become invalid.
471 * @param data Information about the changed data.
472 * @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.
474 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
476 if (!gui_scope
) return;
477 for (int i
= 0; i
< 6; i
++) {
478 this->SetWidgetLoweredState(WID_MTS_ALL
+ i
, i
== _settings_client
.music
.playlist
);
480 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
484 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
487 case WID_MTS_PLAYLIST
: {
488 Dimension d
= {0, 0};
490 for (int i
= 0; i
< 6; i
++) {
491 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ i
);
492 d
= maxdim(d
, GetStringBoundingBox(STR_PLAYLIST_PROGRAM
));
494 d
.width
+= padding
.width
;
495 d
.height
+= padding
.height
;
496 *size
= maxdim(*size
, d
);
500 case WID_MTS_LIST_LEFT
: case WID_MTS_LIST_RIGHT
: {
501 Dimension d
= {0, 0};
503 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
504 SetDParam(0, song
->tracknr
);
506 SetDParamStr(2, song
->songname
);
507 Dimension d2
= GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME
);
508 d
.width
= std::max(d
.width
, d2
.width
);
509 d
.height
+= d2
.height
;
511 d
.width
+= padding
.width
;
512 d
.height
+= padding
.height
;
513 *size
= maxdim(*size
, d
);
519 void DrawWidget(const Rect
&r
, int widget
) const override
522 case WID_MTS_LIST_LEFT
: {
523 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.bottom
- 1, PC_BLACK
);
525 int y
= r
.top
+ WD_FRAMERECT_TOP
;
526 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
527 SetDParam(0, song
->tracknr
);
529 SetDParamStr(2, song
->songname
);
530 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_PLAYLIST_TRACK_NAME
);
531 y
+= FONT_HEIGHT_SMALL
;
536 case WID_MTS_LIST_RIGHT
: {
537 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.bottom
- 1, PC_BLACK
);
539 int y
= r
.top
+ WD_FRAMERECT_TOP
;
540 for (MusicSystem::Playlist::const_iterator song
= _music
.active_playlist
.begin(); song
!= _music
.active_playlist
.end(); ++song
) {
541 SetDParam(0, song
->tracknr
);
543 SetDParamStr(2, song
->songname
);
544 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_PLAYLIST_TRACK_NAME
);
545 y
+= FONT_HEIGHT_SMALL
;
552 void OnClick(Point pt
, int widget
, int click_count
) override
555 case WID_MTS_LIST_LEFT
: { // add to playlist
556 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, FONT_HEIGHT_SMALL
);
557 _music
.PlaylistAdd(y
);
561 case WID_MTS_LIST_RIGHT
: { // remove from playlist
562 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, FONT_HEIGHT_SMALL
);
563 _music
.PlaylistRemove(y
);
567 case WID_MTS_MUSICSET
: {
569 ShowDropDownList(this, BuildMusicSetDropDownList(&selected
), selected
, widget
, 0, true, false);
573 case WID_MTS_CLEAR
: // clear
574 _music
.PlaylistClear();
577 case WID_MTS_ALL
: case WID_MTS_OLD
: case WID_MTS_NEW
:
578 case WID_MTS_EZY
: case WID_MTS_CUSTOM1
: case WID_MTS_CUSTOM2
: // set playlist
579 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_MTS_ALL
));
584 void OnDropdownSelect(int widget
, int index
) override
587 case WID_MTS_MUSICSET
:
588 ChangeMusicSet(index
);
596 static const NWidgetPart _nested_music_track_selection_widgets
[] = {
597 NWidget(NWID_HORIZONTAL
),
598 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
599 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_MTS_CAPTION
), SetDataTip(STR_PLAYLIST_MUSIC_SELECTION_SETNAME
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
600 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_MTS_MUSICSET
), SetDataTip(STR_PLAYLIST_CHANGE_SET
, STR_PLAYLIST_TOOLTIP_CHANGE_SET
),
602 NWidget(WWT_PANEL
, COLOUR_GREY
),
603 NWidget(NWID_HORIZONTAL
), SetPIP(2, 4, 2),
605 NWidget(NWID_VERTICAL
),
606 NWidget(WWT_LABEL
, COLOUR_GREY
), SetDataTip(STR_PLAYLIST_TRACK_INDEX
, STR_NULL
),
607 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_LEFT
), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK
), EndContainer(),
608 NWidget(NWID_SPACER
), SetMinimalSize(0, 2),
610 /* Middle buttons. */
611 NWidget(NWID_VERTICAL
),
612 NWidget(NWID_SPACER
), SetMinimalSize(60, 30), // Space above the first button from the title bar.
613 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
614 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
615 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
616 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
617 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
),
618 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
),
619 NWidget(NWID_SPACER
), SetMinimalSize(0, 16), // Space above 'clear' button
620 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_MTS_CLEAR
), SetFill(1, 0), SetDataTip(STR_PLAYLIST_CLEAR
, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1
),
621 NWidget(NWID_SPACER
), SetFill(0, 1),
624 NWidget(NWID_VERTICAL
),
625 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_MTS_PLAYLIST
), SetDataTip(STR_PLAYLIST_PROGRAM
, STR_NULL
),
626 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_RIGHT
), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK
), EndContainer(),
627 NWidget(NWID_SPACER
), SetMinimalSize(0, 2),
633 static WindowDesc
_music_track_selection_desc(
634 WDP_AUTO
, "music_track", 0, 0,
635 WC_MUSIC_TRACK_SELECTION
, WC_NONE
,
637 _nested_music_track_selection_widgets
, lengthof(_nested_music_track_selection_widgets
)
640 static void ShowMusicTrackSelection()
642 AllocateWindowDescFront
<MusicTrackSelectionWindow
>(&_music_track_selection_desc
, 0);
645 struct MusicWindow
: public Window
{
646 static const int slider_width
= 3;
648 MusicWindow(WindowDesc
*desc
, WindowNumber number
) : Window(desc
)
650 this->InitNested(number
);
651 this->LowerWidget(_settings_client
.music
.playlist
+ WID_M_ALL
);
652 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _settings_client
.music
.shuffle
);
654 UpdateDisabledButtons();
657 void UpdateDisabledButtons()
659 /* Disable music control widgets if there is no music
660 * -- except Programme button! So you can still select a music set. */
661 this->SetWidgetsDisabledState(
662 BaseMusic::GetUsedSet()->num_available
== 0,
663 WID_M_PREV
, WID_M_NEXT
, WID_M_STOP
, WID_M_PLAY
, WID_M_SHUFFLE
,
664 WID_M_ALL
, WID_M_OLD
, WID_M_NEW
, WID_M_EZY
, WID_M_CUSTOM1
, WID_M_CUSTOM2
,
669 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
672 /* Make sure that WID_M_SHUFFLE and WID_M_PROGRAMME have the same size.
673 * This can't be done by using NC_EQUALSIZE as the WID_M_INFO is
674 * between those widgets and of different size. */
675 case WID_M_SHUFFLE
: case WID_M_PROGRAMME
: {
676 Dimension d
= maxdim(GetStringBoundingBox(STR_MUSIC_PROGRAM
), GetStringBoundingBox(STR_MUSIC_SHUFFLE
));
677 d
.width
+= padding
.width
;
678 d
.height
+= padding
.height
;
679 *size
= maxdim(*size
, d
);
683 case WID_M_TRACK_NR
: {
684 Dimension d
= GetStringBoundingBox(STR_MUSIC_TRACK_NONE
);
685 d
.width
+= WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
686 d
.height
+= WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
687 *size
= maxdim(*size
, d
);
691 case WID_M_TRACK_NAME
: {
692 Dimension d
= GetStringBoundingBox(STR_MUSIC_TITLE_NONE
);
693 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
694 SetDParamStr(0, song
->songname
);
695 d
= maxdim(d
, GetStringBoundingBox(STR_MUSIC_TITLE_NAME
));
697 d
.width
+= WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
698 d
.height
+= WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
699 *size
= maxdim(*size
, d
);
703 /* Hack-ish: set the proper widget data; only needs to be done once
704 * per (Re)Init as that's the only time the language changes. */
705 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;
706 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;
707 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;
711 void DrawWidget(const Rect
&r
, int widget
) const override
714 case WID_M_TRACK_NR
: {
715 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
, r
.bottom
, PC_BLACK
);
716 if (BaseMusic::GetUsedSet()->num_available
== 0) {
719 StringID str
= STR_MUSIC_TRACK_NONE
;
720 if (_music
.IsPlaying()) {
721 SetDParam(0, _music
.GetCurrentSong().tracknr
);
723 str
= STR_MUSIC_TRACK_DIGIT
;
725 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, str
);
729 case WID_M_TRACK_NAME
: {
730 GfxFillRect(r
.left
, r
.top
+ 1, r
.right
- 1, r
.bottom
, PC_BLACK
);
731 StringID str
= STR_MUSIC_TITLE_NONE
;
732 MusicSystem::PlaylistEntry
entry(_music
.GetCurrentSong());
733 if (BaseMusic::GetUsedSet()->num_available
== 0) {
734 str
= STR_MUSIC_TITLE_NOMUSIC
;
735 } else if (_music
.IsPlaying()) {
736 str
= STR_MUSIC_TITLE_NAME
;
737 SetDParamStr(0, entry
.songname
);
739 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, str
, TC_FROMSTRING
, SA_HOR_CENTER
);
743 case WID_M_MUSIC_VOL
: case WID_M_EFFECT_VOL
: {
744 /* Draw a wedge indicating low to high volume level. */
745 const int ha
= (r
.bottom
- r
.top
) / 5;
746 int wx1
= r
.left
, wx2
= r
.right
;
747 if (_current_text_dir
== TD_RTL
) std::swap(wx1
, wx2
);
748 const uint shadow
= _colour_gradient
[COLOUR_GREY
][3];
749 const uint fill
= _colour_gradient
[COLOUR_GREY
][6];
750 const uint light
= _colour_gradient
[COLOUR_GREY
][7];
751 const std::vector
<Point
> wedge
{ Point
{wx1
, r
.bottom
- ha
}, Point
{wx2
, r
.top
+ ha
}, Point
{wx2
, r
.bottom
- ha
} };
752 GfxFillPolygon(wedge
, fill
);
753 GfxDrawLine(wedge
[0].x
, wedge
[0].y
, wedge
[2].x
, wedge
[2].y
, light
);
754 GfxDrawLine(wedge
[1].x
, wedge
[1].y
, wedge
[2].x
, wedge
[2].y
, _current_text_dir
== TD_RTL
? shadow
: light
);
755 GfxDrawLine(wedge
[0].x
, wedge
[0].y
, wedge
[1].x
, wedge
[1].y
, shadow
);
756 /* Draw a slider handle indicating current volume level. */
757 const int sw
= ScaleGUITrad(slider_width
);
758 byte volume
= (widget
== WID_M_MUSIC_VOL
) ? _settings_client
.music
.music_vol
: _settings_client
.music
.effect_vol
;
759 if (_current_text_dir
== TD_RTL
) volume
= 127 - volume
;
760 const int x
= r
.left
+ (volume
* (r
.right
- r
.left
- sw
) / 127);
761 DrawFrameRect(x
, r
.top
, x
+ sw
, r
.bottom
, COLOUR_GREY
, FR_NONE
);
768 * Some data on this window has become invalid.
769 * @param data Information about the changed data.
770 * @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.
772 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
774 if (!gui_scope
) return;
775 for (int i
= 0; i
< 6; i
++) {
776 this->SetWidgetLoweredState(WID_M_ALL
+ i
, i
== _settings_client
.music
.playlist
);
779 UpdateDisabledButtons();
784 void OnClick(Point pt
, int widget
, int click_count
) override
787 case WID_M_PREV
: // skip to prev
791 case WID_M_NEXT
: // skip to next
795 case WID_M_STOP
: // stop playing
799 case WID_M_PLAY
: // start playing
803 case WID_M_MUSIC_VOL
: case WID_M_EFFECT_VOL
: { // volume sliders
804 int x
= pt
.x
- this->GetWidget
<NWidgetBase
>(widget
)->pos_x
;
806 byte
*vol
= (widget
== WID_M_MUSIC_VOL
) ? &_settings_client
.music
.music_vol
: &_settings_client
.music
.effect_vol
;
808 byte new_vol
= Clamp(x
* 127 / (int)this->GetWidget
<NWidgetBase
>(widget
)->current_x
, 0, 127);
809 if (_current_text_dir
== TD_RTL
) new_vol
= 127 - new_vol
;
810 /* Clamp to make sure min and max are properly settable */
811 if (new_vol
> 124) new_vol
= 127;
812 if (new_vol
< 3) new_vol
= 0;
813 if (new_vol
!= *vol
) {
815 if (widget
== WID_M_MUSIC_VOL
) MusicDriver::GetInstance()->SetVolume(new_vol
);
819 if (click_count
> 0) this->mouse_capture_widget
= widget
;
823 case WID_M_SHUFFLE
: // toggle shuffle
824 if (_music
.IsShuffle()) {
829 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _music
.IsShuffle());
830 this->SetWidgetDirty(WID_M_SHUFFLE
);
833 case WID_M_PROGRAMME
: // show track selection
834 ShowMusicTrackSelection();
837 case WID_M_ALL
: case WID_M_OLD
: case WID_M_NEW
:
838 case WID_M_EZY
: case WID_M_CUSTOM1
: case WID_M_CUSTOM2
: // playlist
839 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_M_ALL
));
845 static const NWidgetPart _nested_music_window_widgets
[] = {
846 NWidget(NWID_HORIZONTAL
),
847 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
848 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_MUSIC_JAZZ_JUKEBOX_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
849 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
850 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
853 NWidget(NWID_HORIZONTAL
),
854 NWidget(NWID_VERTICAL
),
855 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
856 NWidget(NWID_HORIZONTAL
),
857 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PREV
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_PREV
, STR_MUSIC_TOOLTIP_SKIP_TO_PREVIOUS_TRACK
),
858 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
),
859 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_STOP
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_STOP_MUSIC
, STR_MUSIC_TOOLTIP_STOP_PLAYING_MUSIC
),
860 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PLAY
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_PLAY_MUSIC
, STR_MUSIC_TOOLTIP_START_PLAYING_MUSIC
),
862 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
864 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_SLIDERS
),
865 NWidget(NWID_HORIZONTAL
), SetPIP(4, 0, 4),
866 NWidget(NWID_VERTICAL
),
867 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_MUSIC_VOLUME
, STR_NULL
),
868 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
),
870 NWidget(NWID_VERTICAL
),
871 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_EFFECTS_VOLUME
, STR_NULL
),
872 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
),
877 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_BACKGROUND
),
878 NWidget(NWID_HORIZONTAL
), SetPIP(6, 0, 6),
879 NWidget(NWID_VERTICAL
),
880 NWidget(NWID_SPACER
), SetFill(0, 1),
881 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_SHUFFLE
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_SHUFFLE
, STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE
),
882 NWidget(NWID_SPACER
), SetFill(0, 1),
884 NWidget(NWID_VERTICAL
), SetPadding(0, 0, 3, 3),
885 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK
), SetFill(0, 0), SetDataTip(STR_MUSIC_TRACK
, STR_NULL
),
886 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NR
), EndContainer(),
888 NWidget(NWID_VERTICAL
), SetPadding(0, 3, 3, 0),
889 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK_TITLE
), SetFill(1, 0), SetDataTip(STR_MUSIC_XTITLE
, STR_NULL
),
890 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NAME
), SetFill(1, 0), EndContainer(),
892 NWidget(NWID_VERTICAL
),
893 NWidget(NWID_SPACER
), SetFill(0, 1),
894 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_M_PROGRAMME
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_PROGRAM
, STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION
),
895 NWidget(NWID_SPACER
), SetFill(0, 1),
899 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
900 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
901 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
902 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
903 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
904 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
),
905 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
),
909 static WindowDesc
_music_window_desc(
910 WDP_AUTO
, "music", 0, 0,
911 WC_MUSIC_WINDOW
, WC_NONE
,
913 _nested_music_window_widgets
, lengthof(_nested_music_window_widgets
)
916 void ShowMusicWindow()
918 AllocateWindowDescFront
<MusicWindow
>(&_music_window_desc
, 0);