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 char *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 char *set_name
)
172 BaseMusic::SetSet(set_name
);
174 free(BaseMusic::ini_set
);
175 BaseMusic::ini_set
= stredup(set_name
);
177 this->BuildPlaylists();
178 this->ChangePlaylist(this->selected_playlist
);
180 InvalidateWindowData(WC_GAME_OPTIONS
, WN_GAME_OPTIONS_GAME_OPTIONS
, 0, true);
183 /** Enable shuffle mode and restart playback */
184 void MusicSystem::Shuffle()
186 _settings_client
.music
.shuffle
= true;
188 this->active_playlist
= this->displayed_playlist
;
189 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
190 size_t shuffle_index
= InteractiveRandom() % (this->active_playlist
.size() - i
);
191 std::swap(this->active_playlist
[i
], this->active_playlist
[i
+ shuffle_index
]);
194 if (_settings_client
.music
.playing
) this->Play();
196 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
199 /** Disable shuffle and restart playback */
200 void MusicSystem::Unshuffle()
202 _settings_client
.music
.shuffle
= false;
203 this->active_playlist
= this->displayed_playlist
;
205 if (_settings_client
.music
.playing
) this->Play();
207 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
210 /** Start/restart playback at current song */
211 void MusicSystem::Play()
213 /* Always set the playing flag, even if there is no music */
214 _settings_client
.music
.playing
= true;
215 MusicDriver::GetInstance()->StopSong();
216 /* Make sure playlist_position is a valid index, if playlist has changed etc. */
217 this->ChangePlaylistPosition(0);
219 /* If there is no music, don't try to play it */
220 if (this->active_playlist
.empty()) return;
222 MusicSongInfo song
= this->active_playlist
[this->playlist_position
];
223 if (_game_mode
== GM_MENU
&& this->selected_playlist
== PLCH_THEMEONLY
) song
.loop
= true;
224 MusicDriver::GetInstance()->PlaySong(song
);
226 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
229 /** Stop playback and set flag that we don't intend to play music */
230 void MusicSystem::Stop()
232 MusicDriver::GetInstance()->StopSong();
233 _settings_client
.music
.playing
= false;
235 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
238 /** Skip to next track */
239 void MusicSystem::Next()
241 this->ChangePlaylistPosition(+1);
242 if (_settings_client
.music
.playing
) this->Play();
244 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
247 /** Skip to previous track */
248 void MusicSystem::Prev()
250 this->ChangePlaylistPosition(-1);
251 if (_settings_client
.music
.playing
) this->Play();
253 InvalidateWindowData(WC_MUSIC_WINDOW
, 0);
256 /** Check that music is playing if it should, and that appropriate playlist is active for game/main menu */
257 void MusicSystem::CheckStatus()
259 if ((_game_mode
== GM_MENU
) != (this->selected_playlist
== PLCH_THEMEONLY
)) {
260 /* Make sure the theme-only playlist is active when on the title screen, and not during gameplay */
261 this->ChangePlaylist((_game_mode
== GM_MENU
) ? PLCH_THEMEONLY
: (PlaylistChoices
)_settings_client
.music
.playlist
);
263 if (this->active_playlist
.empty()) return;
264 /* If we were supposed to be playing, but music has stopped, move to next song */
265 if (this->IsPlaying() && !MusicDriver::GetInstance()->IsSongPlaying()) this->Next();
268 /** Is the player getting music right now? */
269 bool MusicSystem::IsPlaying() const
271 return _settings_client
.music
.playing
&& !this->active_playlist
.empty();
274 /** Is shuffle mode enabled? */
275 bool MusicSystem::IsShuffle() const
277 return _settings_client
.music
.shuffle
;
280 /** Return the current song, or a dummy if none */
281 MusicSystem::PlaylistEntry
MusicSystem::GetCurrentSong() const
283 if (!this->IsPlaying()) return PlaylistEntry(BaseMusic::GetUsedSet(), 0);
284 return this->active_playlist
[this->playlist_position
];
287 /** Is one of the custom playlists selected? */
288 bool MusicSystem::IsCustomPlaylist() const
290 return (this->selected_playlist
== PLCH_CUSTOM1
) || (this->selected_playlist
== PLCH_CUSTOM2
);
294 * Append a song to a custom playlist.
295 * Always adds to the currently active playlist.
296 * @param song_index Index of song in the current music set to add
298 void MusicSystem::PlaylistAdd(size_t song_index
)
300 if (!this->IsCustomPlaylist()) return;
302 /* Pick out song from the music set */
303 if (song_index
>= this->music_set
.size()) return;
304 PlaylistEntry entry
= this->music_set
[song_index
];
306 /* Check for maximum length */
307 if (this->standard_playlists
[this->selected_playlist
].size() >= NUM_SONGS_PLAYLIST
) return;
309 /* Add it to the appropriate playlist, and the display */
310 this->standard_playlists
[this->selected_playlist
].push_back(entry
);
311 this->displayed_playlist
.push_back(entry
);
313 /* Add it to the active playlist, if playback is shuffled select a random position to add at */
314 if (this->active_playlist
.empty()) {
315 this->active_playlist
.push_back(entry
);
316 if (this->IsPlaying()) this->Play();
317 } else if (this->IsShuffle()) {
318 /* Generate a random position between 0 and n (inclusive, new length) to insert at */
319 size_t maxpos
= this->displayed_playlist
.size();
320 size_t newpos
= InteractiveRandom() % maxpos
;
321 this->active_playlist
.insert(this->active_playlist
.begin() + newpos
, entry
);
322 /* Make sure to shift up the current playback position if the song was inserted before it */
323 if ((int)newpos
<= this->playlist_position
) this->playlist_position
++;
325 this->active_playlist
.push_back(entry
);
328 this->SaveCustomPlaylist(this->selected_playlist
);
330 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
334 * Remove a song from a custom playlist.
335 * @param song_index Index in the custom playlist to remove.
337 void MusicSystem::PlaylistRemove(size_t song_index
)
339 if (!this->IsCustomPlaylist()) return;
341 Playlist
&pl
= this->standard_playlists
[this->selected_playlist
];
342 if (song_index
>= pl
.size()) return;
344 /* Remove from "simple" playlists */
345 PlaylistEntry song
= pl
[song_index
];
346 pl
.erase(pl
.begin() + song_index
);
347 this->displayed_playlist
.erase(this->displayed_playlist
.begin() + song_index
);
349 /* Find in actual active playlist (may be shuffled) and remove,
350 * if it's the current song restart playback */
351 for (size_t i
= 0; i
< this->active_playlist
.size(); i
++) {
352 Playlist::iterator s2
= this->active_playlist
.begin() + i
;
353 if (s2
->filename
== song
.filename
&& s2
->cat_index
== song
.cat_index
) {
354 this->active_playlist
.erase(s2
);
355 if ((int)i
== this->playlist_position
&& this->IsPlaying()) this->Play();
360 this->SaveCustomPlaylist(this->selected_playlist
);
362 InvalidateWindowData(WC_MUSIC_TRACK_SELECTION
, 0);
366 * Remove all songs from the current custom playlist.
367 * Effectively stops playback too.
369 void MusicSystem::PlaylistClear()
371 if (!this->IsCustomPlaylist()) return;
373 this->standard_playlists
[this->selected_playlist
].clear();
374 this->ChangePlaylist(this->selected_playlist
);
376 this->SaveCustomPlaylist(this->selected_playlist
);
380 * Change playlist position pointer by the given offset, making sure to keep it within valid range.
381 * If the playlist is empty, position is always set to 0.
382 * @param ofs Amount to move playlist position by.
384 void MusicSystem::ChangePlaylistPosition(int ofs
)
386 if (this->active_playlist
.empty()) {
387 this->playlist_position
= 0;
389 this->playlist_position
+= ofs
;
390 while (this->playlist_position
>= (int)this->active_playlist
.size()) this->playlist_position
-= (int)this->active_playlist
.size();
391 while (this->playlist_position
< 0) this->playlist_position
+= (int)this->active_playlist
.size();
396 * Save a custom playlist to settings after modification.
397 * @param pl Playlist to store back
399 void MusicSystem::SaveCustomPlaylist(PlaylistChoices pl
)
402 if (pl
== PLCH_CUSTOM1
) {
403 settings_pl
= _settings_client
.music
.custom_1
;
404 } else if (pl
== PLCH_CUSTOM2
) {
405 settings_pl
= _settings_client
.music
.custom_2
;
411 MemSetT(settings_pl
, 0, NUM_SONGS_PLAYLIST
);
413 for (Playlist::const_iterator song
= this->standard_playlists
[pl
].begin(); song
!= this->standard_playlists
[pl
].end(); ++song
) {
414 /* Music set indices in the settings playlist are 1-based, 0 means unused slot */
415 settings_pl
[num
++] = (byte
)song
->set_index
+ 1;
421 * Check music playback status and start/stop/song-finished.
422 * Called from main loop.
426 _music
.CheckStatus();
430 * Change the configured music set and reset playback
431 * @param index Index of music set to switch to
433 void ChangeMusicSet(int index
)
435 if (BaseMusic::GetIndexOfUsedSet() == index
) return;
436 const char *name
= BaseMusic::GetSet(index
)->name
;
437 _music
.ChangeMusicSet(name
);
441 * Prepare the music system for use.
442 * Called from \c InitializeGame
444 void InitializeMusic()
446 _music
.BuildPlaylists();
450 struct MusicTrackSelectionWindow
: public Window
{
451 MusicTrackSelectionWindow(WindowDesc
*desc
, WindowNumber number
) : Window(desc
)
453 this->InitNested(number
);
454 this->LowerWidget(WID_MTS_LIST_LEFT
);
455 this->LowerWidget(WID_MTS_LIST_RIGHT
);
456 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
457 this->LowerWidget(WID_MTS_ALL
+ _settings_client
.music
.playlist
);
460 void SetStringParameters(int widget
) const override
463 case WID_MTS_PLAYLIST
:
464 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ _settings_client
.music
.playlist
);
466 case WID_MTS_CAPTION
:
467 SetDParamStr(0, BaseMusic::GetUsedSet()->name
);
473 * Some data on this window has become invalid.
474 * @param data Information about the changed data.
475 * @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.
477 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
479 if (!gui_scope
) return;
480 for (int i
= 0; i
< 6; i
++) {
481 this->SetWidgetLoweredState(WID_MTS_ALL
+ i
, i
== _settings_client
.music
.playlist
);
483 this->SetWidgetDisabledState(WID_MTS_CLEAR
, _settings_client
.music
.playlist
<= 3);
487 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
490 case WID_MTS_PLAYLIST
: {
491 Dimension d
= {0, 0};
493 for (int i
= 0; i
< 6; i
++) {
494 SetDParam(0, STR_MUSIC_PLAYLIST_ALL
+ i
);
495 d
= maxdim(d
, GetStringBoundingBox(STR_PLAYLIST_PROGRAM
));
497 d
.width
+= padding
.width
;
498 d
.height
+= padding
.height
;
499 *size
= maxdim(*size
, d
);
503 case WID_MTS_LIST_LEFT
: case WID_MTS_LIST_RIGHT
: {
504 Dimension d
= {0, 0};
506 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
507 SetDParam(0, song
->tracknr
);
509 SetDParamStr(2, song
->songname
);
510 Dimension d2
= GetStringBoundingBox(STR_PLAYLIST_TRACK_NAME
);
511 d
.width
= max(d
.width
, d2
.width
);
512 d
.height
+= d2
.height
;
514 d
.width
+= padding
.width
;
515 d
.height
+= padding
.height
;
516 *size
= maxdim(*size
, d
);
522 void DrawWidget(const Rect
&r
, int widget
) const override
525 case WID_MTS_LIST_LEFT
: {
526 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.bottom
- 1, PC_BLACK
);
528 int y
= r
.top
+ WD_FRAMERECT_TOP
;
529 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
530 SetDParam(0, song
->tracknr
);
532 SetDParamStr(2, song
->songname
);
533 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_PLAYLIST_TRACK_NAME
);
534 y
+= FONT_HEIGHT_SMALL
;
539 case WID_MTS_LIST_RIGHT
: {
540 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.bottom
- 1, PC_BLACK
);
542 int y
= r
.top
+ WD_FRAMERECT_TOP
;
543 for (MusicSystem::Playlist::const_iterator song
= _music
.active_playlist
.begin(); song
!= _music
.active_playlist
.end(); ++song
) {
544 SetDParam(0, song
->tracknr
);
546 SetDParamStr(2, song
->songname
);
547 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, y
, STR_PLAYLIST_TRACK_NAME
);
548 y
+= FONT_HEIGHT_SMALL
;
555 void OnClick(Point pt
, int widget
, int click_count
) override
558 case WID_MTS_LIST_LEFT
: { // add to playlist
559 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, FONT_HEIGHT_SMALL
);
560 _music
.PlaylistAdd(y
);
564 case WID_MTS_LIST_RIGHT
: { // remove from playlist
565 int y
= this->GetRowFromWidget(pt
.y
, widget
, 0, FONT_HEIGHT_SMALL
);
566 _music
.PlaylistRemove(y
);
570 case WID_MTS_MUSICSET
: {
572 ShowDropDownList(this, BuildMusicSetDropDownList(&selected
), selected
, widget
, 0, true, false);
576 case WID_MTS_CLEAR
: // clear
577 _music
.PlaylistClear();
580 case WID_MTS_ALL
: case WID_MTS_OLD
: case WID_MTS_NEW
:
581 case WID_MTS_EZY
: case WID_MTS_CUSTOM1
: case WID_MTS_CUSTOM2
: // set playlist
582 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_MTS_ALL
));
587 void OnDropdownSelect(int widget
, int index
) override
590 case WID_MTS_MUSICSET
:
591 ChangeMusicSet(index
);
599 static const NWidgetPart _nested_music_track_selection_widgets
[] = {
600 NWidget(NWID_HORIZONTAL
),
601 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
602 NWidget(WWT_CAPTION
, COLOUR_GREY
, WID_MTS_CAPTION
), SetDataTip(STR_PLAYLIST_MUSIC_SELECTION_SETNAME
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
603 NWidget(WWT_DROPDOWN
, COLOUR_GREY
, WID_MTS_MUSICSET
), SetDataTip(STR_PLAYLIST_CHANGE_SET
, STR_PLAYLIST_TOOLTIP_CHANGE_SET
),
605 NWidget(WWT_PANEL
, COLOUR_GREY
),
606 NWidget(NWID_HORIZONTAL
), SetPIP(2, 4, 2),
608 NWidget(NWID_VERTICAL
),
609 NWidget(WWT_LABEL
, COLOUR_GREY
), SetDataTip(STR_PLAYLIST_TRACK_INDEX
, STR_NULL
),
610 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_LEFT
), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_ADD_TRACK
), EndContainer(),
611 NWidget(NWID_SPACER
), SetMinimalSize(0, 2),
613 /* Middle buttons. */
614 NWidget(NWID_VERTICAL
),
615 NWidget(NWID_SPACER
), SetMinimalSize(60, 30), // Space above the first button from the title bar.
616 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
617 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
618 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
619 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_MTS_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
620 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
),
621 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
),
622 NWidget(NWID_SPACER
), SetMinimalSize(0, 16), // Space above 'clear' button
623 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_MTS_CLEAR
), SetFill(1, 0), SetDataTip(STR_PLAYLIST_CLEAR
, STR_PLAYLIST_TOOLTIP_CLEAR_CURRENT_PROGRAM_CUSTOM1
),
624 NWidget(NWID_SPACER
), SetFill(0, 1),
627 NWidget(NWID_VERTICAL
),
628 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_MTS_PLAYLIST
), SetDataTip(STR_PLAYLIST_PROGRAM
, STR_NULL
),
629 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_MTS_LIST_RIGHT
), SetMinimalSize(180, 194), SetDataTip(0x0, STR_PLAYLIST_TOOLTIP_CLICK_TO_REMOVE_TRACK
), EndContainer(),
630 NWidget(NWID_SPACER
), SetMinimalSize(0, 2),
636 static WindowDesc
_music_track_selection_desc(
637 WDP_AUTO
, "music_track", 0, 0,
638 WC_MUSIC_TRACK_SELECTION
, WC_NONE
,
640 _nested_music_track_selection_widgets
, lengthof(_nested_music_track_selection_widgets
)
643 static void ShowMusicTrackSelection()
645 AllocateWindowDescFront
<MusicTrackSelectionWindow
>(&_music_track_selection_desc
, 0);
648 struct MusicWindow
: public Window
{
649 static const int slider_width
= 3;
651 MusicWindow(WindowDesc
*desc
, WindowNumber number
) : Window(desc
)
653 this->InitNested(number
);
654 this->LowerWidget(_settings_client
.music
.playlist
+ WID_M_ALL
);
655 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _settings_client
.music
.shuffle
);
657 UpdateDisabledButtons();
660 void UpdateDisabledButtons()
662 /* Disable music control widgets if there is no music
663 * -- except Programme button! So you can still select a music set. */
664 this->SetWidgetsDisabledState(
665 BaseMusic::GetUsedSet()->num_available
== 0,
666 WID_M_PREV
, WID_M_NEXT
, WID_M_STOP
, WID_M_PLAY
, WID_M_SHUFFLE
,
667 WID_M_ALL
, WID_M_OLD
, WID_M_NEW
, WID_M_EZY
, WID_M_CUSTOM1
, WID_M_CUSTOM2
,
672 void UpdateWidgetSize(int widget
, Dimension
*size
, const Dimension
&padding
, Dimension
*fill
, Dimension
*resize
) override
675 /* Make sure that WID_M_SHUFFLE and WID_M_PROGRAMME have the same size.
676 * This can't be done by using NC_EQUALSIZE as the WID_M_INFO is
677 * between those widgets and of different size. */
678 case WID_M_SHUFFLE
: case WID_M_PROGRAMME
: {
679 Dimension d
= maxdim(GetStringBoundingBox(STR_MUSIC_PROGRAM
), GetStringBoundingBox(STR_MUSIC_SHUFFLE
));
680 d
.width
+= padding
.width
;
681 d
.height
+= padding
.height
;
682 *size
= maxdim(*size
, d
);
686 case WID_M_TRACK_NR
: {
687 Dimension d
= GetStringBoundingBox(STR_MUSIC_TRACK_NONE
);
688 d
.width
+= WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
689 d
.height
+= WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
690 *size
= maxdim(*size
, d
);
694 case WID_M_TRACK_NAME
: {
695 Dimension d
= GetStringBoundingBox(STR_MUSIC_TITLE_NONE
);
696 for (MusicSystem::Playlist::const_iterator song
= _music
.music_set
.begin(); song
!= _music
.music_set
.end(); ++song
) {
697 SetDParamStr(0, song
->songname
);
698 d
= maxdim(d
, GetStringBoundingBox(STR_MUSIC_TITLE_NAME
));
700 d
.width
+= WD_FRAMERECT_LEFT
+ WD_FRAMERECT_RIGHT
;
701 d
.height
+= WD_FRAMERECT_TOP
+ WD_FRAMERECT_BOTTOM
;
702 *size
= maxdim(*size
, d
);
706 /* Hack-ish: set the proper widget data; only needs to be done once
707 * per (Re)Init as that's the only time the language changes. */
708 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;
709 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;
710 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;
714 void DrawWidget(const Rect
&r
, int widget
) const override
717 case WID_M_TRACK_NR
: {
718 GfxFillRect(r
.left
+ 1, r
.top
+ 1, r
.right
, r
.bottom
, PC_BLACK
);
719 if (BaseMusic::GetUsedSet()->num_available
== 0) {
722 StringID str
= STR_MUSIC_TRACK_NONE
;
723 if (_music
.IsPlaying()) {
724 SetDParam(0, _music
.GetCurrentSong().tracknr
);
726 str
= STR_MUSIC_TRACK_DIGIT
;
728 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, str
);
732 case WID_M_TRACK_NAME
: {
733 GfxFillRect(r
.left
, r
.top
+ 1, r
.right
- 1, r
.bottom
, PC_BLACK
);
734 StringID str
= STR_MUSIC_TITLE_NONE
;
735 MusicSystem::PlaylistEntry
entry(_music
.GetCurrentSong());
736 if (BaseMusic::GetUsedSet()->num_available
== 0) {
737 str
= STR_MUSIC_TITLE_NOMUSIC
;
738 } else if (_music
.IsPlaying()) {
739 str
= STR_MUSIC_TITLE_NAME
;
740 SetDParamStr(0, entry
.songname
);
742 DrawString(r
.left
+ WD_FRAMERECT_LEFT
, r
.right
- WD_FRAMERECT_RIGHT
, r
.top
+ WD_FRAMERECT_TOP
, str
, TC_FROMSTRING
, SA_HOR_CENTER
);
746 case WID_M_MUSIC_VOL
: case WID_M_EFFECT_VOL
: {
747 /* Draw a wedge indicating low to high volume level. */
748 const int ha
= (r
.bottom
- r
.top
) / 5;
749 int wx1
= r
.left
, wx2
= r
.right
;
750 if (_current_text_dir
== TD_RTL
) std::swap(wx1
, wx2
);
751 const uint shadow
= _colour_gradient
[COLOUR_GREY
][3];
752 const uint fill
= _colour_gradient
[COLOUR_GREY
][6];
753 const uint light
= _colour_gradient
[COLOUR_GREY
][7];
754 const std::vector
<Point
> wedge
{ Point
{wx1
, r
.bottom
- ha
}, Point
{wx2
, r
.top
+ ha
}, Point
{wx2
, r
.bottom
- ha
} };
755 GfxFillPolygon(wedge
, fill
);
756 GfxDrawLine(wedge
[0].x
, wedge
[0].y
, wedge
[2].x
, wedge
[2].y
, light
);
757 GfxDrawLine(wedge
[1].x
, wedge
[1].y
, wedge
[2].x
, wedge
[2].y
, _current_text_dir
== TD_RTL
? shadow
: light
);
758 GfxDrawLine(wedge
[0].x
, wedge
[0].y
, wedge
[1].x
, wedge
[1].y
, shadow
);
759 /* Draw a slider handle indicating current volume level. */
760 const int sw
= ScaleGUITrad(slider_width
);
761 byte volume
= (widget
== WID_M_MUSIC_VOL
) ? _settings_client
.music
.music_vol
: _settings_client
.music
.effect_vol
;
762 if (_current_text_dir
== TD_RTL
) volume
= 127 - volume
;
763 const int x
= r
.left
+ (volume
* (r
.right
- r
.left
- sw
) / 127);
764 DrawFrameRect(x
, r
.top
, x
+ sw
, r
.bottom
, COLOUR_GREY
, FR_NONE
);
771 * Some data on this window has become invalid.
772 * @param data Information about the changed data.
773 * @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.
775 void OnInvalidateData(int data
= 0, bool gui_scope
= true) override
777 if (!gui_scope
) return;
778 for (int i
= 0; i
< 6; i
++) {
779 this->SetWidgetLoweredState(WID_M_ALL
+ i
, i
== _settings_client
.music
.playlist
);
782 UpdateDisabledButtons();
787 void OnClick(Point pt
, int widget
, int click_count
) override
790 case WID_M_PREV
: // skip to prev
794 case WID_M_NEXT
: // skip to next
798 case WID_M_STOP
: // stop playing
802 case WID_M_PLAY
: // start playing
806 case WID_M_MUSIC_VOL
: case WID_M_EFFECT_VOL
: { // volume sliders
807 int x
= pt
.x
- this->GetWidget
<NWidgetBase
>(widget
)->pos_x
;
809 byte
*vol
= (widget
== WID_M_MUSIC_VOL
) ? &_settings_client
.music
.music_vol
: &_settings_client
.music
.effect_vol
;
811 byte new_vol
= Clamp(x
* 127 / (int)this->GetWidget
<NWidgetBase
>(widget
)->current_x
, 0, 127);
812 if (_current_text_dir
== TD_RTL
) new_vol
= 127 - new_vol
;
813 /* Clamp to make sure min and max are properly settable */
814 if (new_vol
> 124) new_vol
= 127;
815 if (new_vol
< 3) new_vol
= 0;
816 if (new_vol
!= *vol
) {
818 if (widget
== WID_M_MUSIC_VOL
) MusicDriver::GetInstance()->SetVolume(new_vol
);
822 if (click_count
> 0) this->mouse_capture_widget
= widget
;
826 case WID_M_SHUFFLE
: // toggle shuffle
827 if (_music
.IsShuffle()) {
832 this->SetWidgetLoweredState(WID_M_SHUFFLE
, _music
.IsShuffle());
833 this->SetWidgetDirty(WID_M_SHUFFLE
);
836 case WID_M_PROGRAMME
: // show track selection
837 ShowMusicTrackSelection();
840 case WID_M_ALL
: case WID_M_OLD
: case WID_M_NEW
:
841 case WID_M_EZY
: case WID_M_CUSTOM1
: case WID_M_CUSTOM2
: // playlist
842 _music
.ChangePlaylist((MusicSystem::PlaylistChoices
)(widget
- WID_M_ALL
));
848 static const NWidgetPart _nested_music_window_widgets
[] = {
849 NWidget(NWID_HORIZONTAL
),
850 NWidget(WWT_CLOSEBOX
, COLOUR_GREY
),
851 NWidget(WWT_CAPTION
, COLOUR_GREY
), SetDataTip(STR_MUSIC_JAZZ_JUKEBOX_CAPTION
, STR_TOOLTIP_WINDOW_TITLE_DRAG_THIS
),
852 NWidget(WWT_SHADEBOX
, COLOUR_GREY
),
853 NWidget(WWT_STICKYBOX
, COLOUR_GREY
),
856 NWidget(NWID_HORIZONTAL
),
857 NWidget(NWID_VERTICAL
),
858 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
859 NWidget(NWID_HORIZONTAL
),
860 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PREV
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_SKIP_TO_PREV
, STR_MUSIC_TOOLTIP_SKIP_TO_PREVIOUS_TRACK
),
861 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
),
862 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_STOP
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_STOP_MUSIC
, STR_MUSIC_TOOLTIP_STOP_PLAYING_MUSIC
),
863 NWidget(WWT_PUSHIMGBTN
, COLOUR_GREY
, WID_M_PLAY
), SetMinimalSize(22, 22), SetDataTip(SPR_IMG_PLAY_MUSIC
, STR_MUSIC_TOOLTIP_START_PLAYING_MUSIC
),
865 NWidget(WWT_PANEL
, COLOUR_GREY
, -1), SetFill(1, 1), EndContainer(),
867 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_SLIDERS
),
868 NWidget(NWID_HORIZONTAL
), SetPIP(4, 0, 4),
869 NWidget(NWID_VERTICAL
),
870 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_MUSIC_VOLUME
, STR_NULL
),
871 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
),
873 NWidget(NWID_VERTICAL
),
874 NWidget(WWT_LABEL
, COLOUR_GREY
, -1), SetFill(1, 0), SetDataTip(STR_MUSIC_EFFECTS_VOLUME
, STR_NULL
),
875 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
),
880 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_BACKGROUND
),
881 NWidget(NWID_HORIZONTAL
), SetPIP(6, 0, 6),
882 NWidget(NWID_VERTICAL
),
883 NWidget(NWID_SPACER
), SetFill(0, 1),
884 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_SHUFFLE
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_SHUFFLE
, STR_MUSIC_TOOLTIP_TOGGLE_PROGRAM_SHUFFLE
),
885 NWidget(NWID_SPACER
), SetFill(0, 1),
887 NWidget(NWID_VERTICAL
), SetPadding(0, 0, 3, 3),
888 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK
), SetFill(0, 0), SetDataTip(STR_MUSIC_TRACK
, STR_NULL
),
889 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NR
), EndContainer(),
891 NWidget(NWID_VERTICAL
), SetPadding(0, 3, 3, 0),
892 NWidget(WWT_LABEL
, COLOUR_GREY
, WID_M_TRACK_TITLE
), SetFill(1, 0), SetDataTip(STR_MUSIC_XTITLE
, STR_NULL
),
893 NWidget(WWT_PANEL
, COLOUR_GREY
, WID_M_TRACK_NAME
), SetFill(1, 0), EndContainer(),
895 NWidget(NWID_VERTICAL
),
896 NWidget(NWID_SPACER
), SetFill(0, 1),
897 NWidget(WWT_PUSHTXTBTN
, COLOUR_GREY
, WID_M_PROGRAMME
), SetMinimalSize(50, 8), SetDataTip(STR_MUSIC_PROGRAM
, STR_MUSIC_TOOLTIP_SHOW_MUSIC_TRACK_SELECTION
),
898 NWidget(NWID_SPACER
), SetFill(0, 1),
902 NWidget(NWID_HORIZONTAL
, NC_EQUALSIZE
),
903 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_ALL
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_ALL
, STR_MUSIC_TOOLTIP_SELECT_ALL_TRACKS_PROGRAM
),
904 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_OLD
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_OLD_STYLE
, STR_MUSIC_TOOLTIP_SELECT_OLD_STYLE_MUSIC
),
905 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_NEW
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_NEW_STYLE
, STR_MUSIC_TOOLTIP_SELECT_NEW_STYLE_MUSIC
),
906 NWidget(WWT_TEXTBTN
, COLOUR_GREY
, WID_M_EZY
), SetFill(1, 0), SetDataTip(STR_MUSIC_PLAYLIST_EZY_STREET
, STR_MUSIC_TOOLTIP_SELECT_EZY_STREET_STYLE
),
907 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
),
908 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
),
912 static WindowDesc
_music_window_desc(
913 WDP_AUTO
, "music", 0, 0,
914 WC_MUSIC_WINDOW
, WC_NONE
,
916 _nested_music_window_widgets
, lengthof(_nested_music_window_widgets
)
919 void ShowMusicWindow()
921 AllocateWindowDescFront
<MusicWindow
>(&_music_window_desc
, 0);