6765: Add play/stop button and duration label for editor sounds  r=def- a=Robyt3

Add button to play/stop audio preview and label showing selected sound duration in editor sound list and sound file browser.

Show error message instead of preview in editor image/sound file browser when selected file cannot be loaded.

Screenshots:
- ![screenshot_2023-06-25_21-53-14](https://github.com/ddnet/ddnet/assets/23437060/a2eb810c-e132-4fa8-9d0d-730b78088651)
- ![screenshot_2023-06-25_21-53-01](https://github.com/ddnet/ddnet/assets/23437060/1a742abc-51f9-4526-aada-d941d42e481d)
- ![screenshot_2023-06-25_21-53-05](https://github.com/ddnet/ddnet/assets/23437060/bf500772-ab6b-409f-8cc4-1bfbaeae55eb)


## Checklist

- [X] Tested the change ingame
- [X] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [X] Considered possible null pointers and out of bounds array indexing
- [X] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-06-26 08:48:05 +00:00 committed by GitHub
commit d840233951
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 141 additions and 48 deletions

View file

@ -576,7 +576,7 @@ int CSound::DecodeWV(int SampleID, const void *pData, unsigned DataSize)
return SampleID; return SampleID;
} }
int CSound::LoadOpus(const char *pFilename) int CSound::LoadOpus(const char *pFilename, int StorageType)
{ {
// don't waste memory on sound when we are stress testing // don't waste memory on sound when we are stress testing
#ifdef CONF_DEBUG #ifdef CONF_DEBUG
@ -600,7 +600,7 @@ int CSound::LoadOpus(const char *pFilename)
void *pData; void *pData;
unsigned DataSize; unsigned DataSize;
if(!m_pStorage->ReadFile(pFilename, IStorage::TYPE_ALL, &pData, &DataSize)) if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize))
{ {
dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename); dbg_msg("sound/opus", "failed to open file. filename='%s'", pFilename);
return -1; return -1;
@ -618,7 +618,7 @@ int CSound::LoadOpus(const char *pFilename)
return SampleID; return SampleID;
} }
int CSound::LoadWV(const char *pFilename) int CSound::LoadWV(const char *pFilename, int StorageType)
{ {
// don't waste memory on sound when we are stress testing // don't waste memory on sound when we are stress testing
#ifdef CONF_DEBUG #ifdef CONF_DEBUG
@ -642,7 +642,7 @@ int CSound::LoadWV(const char *pFilename)
void *pData; void *pData;
unsigned DataSize; unsigned DataSize;
if(!m_pStorage->ReadFile(pFilename, IStorage::TYPE_ALL, &pData, &DataSize)) if(!m_pStorage->ReadFile(pFilename, StorageType, &pData, &DataSize))
{ {
dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename); dbg_msg("sound/wv", "failed to open file. filename='%s'", pFilename);
return -1; return -1;

View file

@ -34,9 +34,9 @@ public:
bool IsSoundEnabled() override { return m_SoundEnabled; } bool IsSoundEnabled() override { return m_SoundEnabled; }
int LoadWV(const char *pFilename) override; int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override;
int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor) override;
int LoadOpus(const char *pFilename) override; int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) override;
int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override; int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor) override;
void UnloadSample(int SampleID) override; void UnloadSample(int SampleID) override;

View file

@ -6,6 +6,7 @@
#include "kernel.h" #include "kernel.h"
#include <engine/shared/video.h> #include <engine/shared/video.h>
#include <engine/storage.h>
class ISound : public IInterface class ISound : public IInterface
{ {
@ -63,8 +64,8 @@ public:
virtual bool IsSoundEnabled() = 0; virtual bool IsSoundEnabled() = 0;
virtual int LoadWV(const char *pFilename) = 0; virtual int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0;
virtual int LoadOpus(const char *pFilename) = 0; virtual int LoadOpus(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 0;
virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; virtual int LoadWVFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0;
virtual int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0; virtual int LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 0;
virtual void UnloadSample(int SampleID) = 0; virtual void UnloadSample(int SampleID) = 0;

View file

@ -875,7 +875,7 @@ bool CEditor::CallbackSaveCopyMap(const char *pFileName, int StorageType, void *
} }
} }
void CEditor::DoToolbar(CUIRect ToolBar) void CEditor::DoToolbarLayers(CUIRect ToolBar)
{ {
const bool ModPressed = Input()->ModifierIsPressed(); const bool ModPressed = Input()->ModifierIsPressed();
const bool ShiftPressed = Input()->ShiftIsPressed(); const bool ShiftPressed = Input()->ShiftIsPressed();
@ -1268,6 +1268,41 @@ void CEditor::DoToolbar(CUIRect ToolBar)
} }
} }
void CEditor::DoToolbarSounds(CUIRect ToolBar)
{
CUIRect ToolBarTop, ToolBarBottom, Button;
ToolBar.HSplitMid(&ToolBarTop, &ToolBarBottom, 5.0f);
if(m_SelectedSound >= 0 && (size_t)m_SelectedSound < m_Map.m_vpSounds.size())
{
const CEditorSound *pSelectedSound = m_Map.m_vpSounds[m_SelectedSound];
// play/stop button
{
ToolBarBottom.VSplitLeft(ToolBarBottom.h, &Button, &ToolBarBottom);
static int s_PlayStopButton;
if(DoButton_FontIcon(&s_PlayStopButton, Sound()->IsPlaying(pSelectedSound->m_SoundID) ? FONT_ICON_STOP : FONT_ICON_PLAY, 0, &Button, 0, "Play/stop audio preview", IGraphics::CORNER_ALL) ||
(m_Dialog == DIALOG_NONE && m_EditBoxActive == 0 && Input()->KeyPress(KEY_SPACE)))
{
if(Sound()->IsPlaying(pSelectedSound->m_SoundID))
Sound()->Stop(pSelectedSound->m_SoundID);
else
Sound()->Play(CSounds::CHN_GUI, pSelectedSound->m_SoundID, 0);
}
}
// duration
{
ToolBarBottom.VSplitLeft(5.0f, nullptr, &ToolBarBottom);
char aDuration[32];
char aDurationLabel[64];
str_time_float(Sound()->GetSampleDuration(pSelectedSound->m_SoundID), TIME_HOURS, aDuration, sizeof(aDuration));
str_format(aDurationLabel, sizeof(aDurationLabel), "Duration: %s", aDuration);
UI()->DoLabel(&ToolBarBottom, aDurationLabel, 12.0f, TEXTALIGN_ML);
}
}
}
static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation) static void Rotate(const CPoint *pCenter, CPoint *pPoint, float Rotation)
{ {
int x = pPoint->x - pCenter->x; int x = pPoint->x - pCenter->x;
@ -4575,7 +4610,7 @@ void CEditor::RenderFileDialog()
// GUI coordsys // GUI coordsys
UI()->MapScreen(); UI()->MapScreen();
CUIRect View = *UI()->Screen(); CUIRect View = *UI()->Screen();
CUIRect Preview; CUIRect Preview = {0.0f, 0.0f, 0.0f, 0.0f};
float Width = View.w, Height = View.h; float Width = View.w, Height = View.h;
View.Draw(ColorRGBA(0, 0, 0, 0.25f), 0, 0); View.Draw(ColorRGBA(0, 0, 0, 0.25f), 0, 0);
@ -4594,7 +4629,7 @@ void CEditor::RenderFileDialog()
View.HSplitBottom(14.0f, &View, &FileBox); View.HSplitBottom(14.0f, &View, &FileBox);
FileBox.VSplitLeft(55.0f, &FileBoxLabel, &FileBox); FileBox.VSplitLeft(55.0f, &FileBoxLabel, &FileBox);
View.HSplitBottom(10.0f, &View, nullptr); // some spacing View.HSplitBottom(10.0f, &View, nullptr); // some spacing
if(m_FileDialogFileType == CEditor::FILETYPE_IMG) if(m_FileDialogFileType == CEditor::FILETYPE_IMG || m_FileDialogFileType == CEditor::FILETYPE_SOUND)
View.VSplitMid(&View, &Preview); View.VSplitMid(&View, &Preview);
// title // title
@ -4745,7 +4780,7 @@ void CEditor::RenderFileDialog()
m_aFilesSelectedName[0] = '\0'; m_aFilesSelectedName[0] = '\0';
UpdateFileNameInput(); UpdateFileNameInput();
s_ListBox.ScrollToSelected(); s_ListBox.ScrollToSelected();
m_PreviewImageState = PREVIEWIMAGE_UNLOADED; m_FilePreviewState = PREVIEW_UNLOADED;
} }
} }
@ -4753,46 +4788,97 @@ void CEditor::RenderFileDialog()
if(m_FilesSelectedIndex > -1) if(m_FilesSelectedIndex > -1)
{ {
if(m_FileDialogFileType == CEditor::FILETYPE_IMG && m_PreviewImageState == PREVIEWIMAGE_UNLOADED && m_FilesSelectedIndex > -1) if(m_FilePreviewState == PREVIEW_UNLOADED)
{ {
if(str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".png")) if(m_FileDialogFileType == CEditor::FILETYPE_IMG && str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".png"))
{ {
char aBuffer[IO_MAX_PATH_LENGTH]; char aBuffer[IO_MAX_PATH_LENGTH];
str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename); str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, IStorage::TYPE_ALL)) if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType))
{ {
Graphics()->UnloadTexture(&m_FilePreviewImage); Graphics()->UnloadTexture(&m_FilePreviewImage);
m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, 0); m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, m_FilePreviewImageInfo.m_Format, 0);
Graphics()->FreePNG(&m_FilePreviewImageInfo); Graphics()->FreePNG(&m_FilePreviewImageInfo);
m_PreviewImageState = PREVIEWIMAGE_LOADED; m_FilePreviewState = PREVIEW_LOADED;
} }
else else
{ {
m_PreviewImageState = PREVIEWIMAGE_ERROR; m_FilePreviewState = PREVIEW_ERROR;
} }
} }
else if(m_FileDialogFileType == CEditor::FILETYPE_SOUND && str_endswith(m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename, ".opus"))
{
char aBuffer[IO_MAX_PATH_LENGTH];
str_format(aBuffer, sizeof(aBuffer), "%s/%s", m_pFileDialogPath, m_vpFilteredFileList[m_FilesSelectedIndex]->m_aFilename);
Sound()->UnloadSample(m_FilePreviewSound);
m_FilePreviewSound = Sound()->LoadOpus(aBuffer, m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType);
m_FilePreviewState = m_FilePreviewSound == -1 ? PREVIEW_ERROR : PREVIEW_LOADED;
}
} }
if(m_FileDialogFileType == CEditor::FILETYPE_IMG && m_PreviewImageState == PREVIEWIMAGE_LOADED)
{
int w = m_FilePreviewImageInfo.m_Width;
int h = m_FilePreviewImageInfo.m_Height;
if(m_FilePreviewImageInfo.m_Width > Preview.w) // NOLINT(clang-analyzer-core.UndefinedBinaryOperatorResult)
{
h = m_FilePreviewImageInfo.m_Height * Preview.w / m_FilePreviewImageInfo.m_Width;
w = Preview.w;
}
if(h > Preview.h)
{
w = w * Preview.h / h;
h = Preview.h;
}
Graphics()->TextureSet(m_FilePreviewImage); if(m_FileDialogFileType == CEditor::FILETYPE_IMG)
Graphics()->BlendNormal(); {
Graphics()->QuadsBegin(); Preview.Margin(10.0f, &Preview);
IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, w, h); if(m_FilePreviewState == PREVIEW_LOADED)
Graphics()->QuadsDrawTL(&QuadItem, 1); {
Graphics()->QuadsEnd(); int w = m_FilePreviewImageInfo.m_Width;
int h = m_FilePreviewImageInfo.m_Height;
if(m_FilePreviewImageInfo.m_Width > Preview.w)
{
h = m_FilePreviewImageInfo.m_Height * Preview.w / m_FilePreviewImageInfo.m_Width;
w = Preview.w;
}
if(h > Preview.h)
{
w = w * Preview.h / h;
h = Preview.h;
}
Graphics()->TextureSet(m_FilePreviewImage);
Graphics()->BlendNormal();
Graphics()->QuadsBegin();
IGraphics::CQuadItem QuadItem(Preview.x, Preview.y, w, h);
Graphics()->QuadsDrawTL(&QuadItem, 1);
Graphics()->QuadsEnd();
}
else if(m_FilePreviewState == PREVIEW_ERROR)
{
SLabelProperties Props;
Props.m_MaxWidth = Preview.w;
UI()->DoLabel(&Preview, "Failed to load the image (check the local console for details).", 12.0f, TEXTALIGN_TL, Props);
}
}
else if(m_FileDialogFileType == CEditor::FILETYPE_SOUND)
{
Preview.Margin(10.0f, &Preview);
if(m_FilePreviewState == PREVIEW_LOADED)
{
CUIRect Button;
Preview.HSplitTop(20.0f, &Preview, nullptr);
Preview.VSplitLeft(Preview.h, &Button, &Preview);
Preview.VSplitLeft(Preview.h / 4.0f, nullptr, &Preview);
static int s_PlayStopButton;
if(DoButton_FontIcon(&s_PlayStopButton, Sound()->IsPlaying(m_FilePreviewSound) ? FONT_ICON_STOP : FONT_ICON_PLAY, 0, &Button, 0, "Play/stop audio preview", IGraphics::CORNER_ALL))
{
if(Sound()->IsPlaying(m_FilePreviewSound))
Sound()->Stop(m_FilePreviewSound);
else
Sound()->Play(CSounds::CHN_GUI, m_FilePreviewSound, 0);
}
char aDuration[32];
char aDurationLabel[64];
str_time_float(Sound()->GetSampleDuration(m_FilePreviewSound), TIME_HOURS, aDuration, sizeof(aDuration));
str_format(aDurationLabel, sizeof(aDurationLabel), "Duration: %s", aDuration);
UI()->DoLabel(&Preview, aDurationLabel, 12.0f, TEXTALIGN_ML);
}
else if(m_FilePreviewState == PREVIEW_ERROR)
{
SLabelProperties Props;
Props.m_MaxWidth = Preview.w;
UI()->DoLabel(&Preview, "Failed to load the sound (check the local console for details). Make sure you enabled sounds in the settings.", 12.0f, TEXTALIGN_TL, Props);
}
} }
} }
@ -4858,7 +4944,7 @@ void CEditor::RenderFileDialog()
UpdateFileNameInput(); UpdateFileNameInput();
if(!WasChanged) // ensure that changed flag is not set if it wasn't previously set, as this would reset the selection after DoEditBox is called if(!WasChanged) // ensure that changed flag is not set if it wasn't previously set, as this would reset the selection after DoEditBox is called
m_FileDialogFileNameInput.WasChanged(); // this clears the changed flag m_FileDialogFileNameInput.WasChanged(); // this clears the changed flag
m_PreviewImageState = PREVIEWIMAGE_UNLOADED; m_FilePreviewState = PREVIEW_UNLOADED;
} }
const float ButtonSpacing = ButtonBar.w > 600.0f ? 40.0f : 10.0f; const float ButtonSpacing = ButtonBar.w > 600.0f ? 40.0f : 10.0f;
@ -5053,7 +5139,7 @@ void CEditor::FilelistPopulate(int StorageType, bool KeepSelection)
else else
m_aFilesSelectedName[0] = '\0'; m_aFilesSelectedName[0] = '\0';
} }
m_PreviewImageState = PREVIEWIMAGE_UNLOADED; m_FilePreviewState = PREVIEW_UNLOADED;
} }
void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText, void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle, const char *pButtonText,
@ -5072,7 +5158,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
m_aFileDialogCurrentLink[0] = 0; m_aFileDialogCurrentLink[0] = 0;
m_pFileDialogPath = m_aFileDialogCurrentFolder; m_pFileDialogPath = m_aFileDialogCurrentFolder;
m_FileDialogFileType = FileType; m_FileDialogFileType = FileType;
m_PreviewImageState = PREVIEWIMAGE_UNLOADED; m_FilePreviewState = PREVIEW_UNLOADED;
m_FileDialogOpening = true; m_FileDialogOpening = true;
if(pDefaultName) if(pDefaultName)
@ -6180,7 +6266,9 @@ void CEditor::Render()
// do the toolbar // do the toolbar
if(m_Mode == MODE_LAYERS) if(m_Mode == MODE_LAYERS)
DoToolbar(ToolBar); DoToolbarLayers(ToolBar);
else if(m_Mode == MODE_SOUNDS)
DoToolbarSounds(ToolBar);
if(m_Dialog == DIALOG_NONE && m_EditBoxActive == 0) if(m_Dialog == DIALOG_NONE && m_EditBoxActive == 0)
{ {

View file

@ -710,11 +710,11 @@ class CEditor : public IEditor
int GetTextureUsageFlag(); int GetTextureUsageFlag();
enum EPreviewImageState enum EPreviewState
{ {
PREVIEWIMAGE_UNLOADED, PREVIEW_UNLOADED,
PREVIEWIMAGE_LOADED, PREVIEW_LOADED,
PREVIEWIMAGE_ERROR, PREVIEW_ERROR,
}; };
public: public:
@ -775,7 +775,8 @@ public:
m_FilesSelectedIndex = -1; m_FilesSelectedIndex = -1;
m_FilePreviewImage.Invalidate(); m_FilePreviewImage.Invalidate();
m_PreviewImageState = PREVIEWIMAGE_UNLOADED; m_FilePreviewSound = -1;
m_FilePreviewState = PREVIEW_UNLOADED;
m_SelectEntitiesImage = "DDNet"; m_SelectEntitiesImage = "DDNet";
@ -945,8 +946,10 @@ public:
int m_FileDialogFileType; int m_FileDialogFileType;
int m_FilesSelectedIndex; int m_FilesSelectedIndex;
CLineInputBuffered<IO_MAX_PATH_LENGTH> m_FileDialogNewFolderNameInput; CLineInputBuffered<IO_MAX_PATH_LENGTH> m_FileDialogNewFolderNameInput;
IGraphics::CTextureHandle m_FilePreviewImage; IGraphics::CTextureHandle m_FilePreviewImage;
EPreviewImageState m_PreviewImageState; int m_FilePreviewSound;
EPreviewState m_FilePreviewState;
CImageInfo m_FilePreviewImageInfo; CImageInfo m_FilePreviewImageInfo;
bool m_FileDialogOpening; bool m_FileDialogOpening;
@ -1212,7 +1215,8 @@ public:
void DoSoundSource(CSoundSource *pSource, int Index); void DoSoundSource(CSoundSource *pSource, int Index);
void DoMapEditor(CUIRect View); void DoMapEditor(CUIRect View);
void DoToolbar(CUIRect Toolbar); void DoToolbarLayers(CUIRect Toolbar);
void DoToolbarSounds(CUIRect Toolbar);
void DoQuad(CQuad *pQuad, int Index); void DoQuad(CQuad *pQuad, int Index);
ColorRGBA GetButtonColor(const void *pID, int Checked); ColorRGBA GetButtonColor(const void *pID, int Checked);