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;
}
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
#ifdef CONF_DEBUG
@ -600,7 +600,7 @@ int CSound::LoadOpus(const char *pFilename)
void *pData;
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);
return -1;
@ -618,7 +618,7 @@ int CSound::LoadOpus(const char *pFilename)
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
#ifdef CONF_DEBUG
@ -642,7 +642,7 @@ int CSound::LoadWV(const char *pFilename)
void *pData;
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);
return -1;

View file

@ -34,9 +34,9 @@ public:
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 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;
void UnloadSample(int SampleID) override;

View file

@ -6,6 +6,7 @@
#include "kernel.h"
#include <engine/shared/video.h>
#include <engine/storage.h>
class ISound : public IInterface
{
@ -63,8 +64,8 @@ public:
virtual bool IsSoundEnabled() = 0;
virtual int LoadWV(const char *pFilename) = 0;
virtual int LoadOpus(const char *pFilename) = 0;
virtual int LoadWV(const char *pFilename, int StorageType = IStorage::TYPE_ALL) = 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 LoadOpusFromMem(const void *pData, unsigned DataSize, bool FromEditor = false) = 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 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)
{
int x = pPoint->x - pCenter->x;
@ -4575,7 +4610,7 @@ void CEditor::RenderFileDialog()
// GUI coordsys
UI()->MapScreen();
CUIRect View = *UI()->Screen();
CUIRect Preview;
CUIRect Preview = {0.0f, 0.0f, 0.0f, 0.0f};
float Width = View.w, Height = View.h;
View.Draw(ColorRGBA(0, 0, 0, 0.25f), 0, 0);
@ -4594,7 +4629,7 @@ void CEditor::RenderFileDialog()
View.HSplitBottom(14.0f, &View, &FileBox);
FileBox.VSplitLeft(55.0f, &FileBoxLabel, &FileBox);
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);
// title
@ -4745,7 +4780,7 @@ void CEditor::RenderFileDialog()
m_aFilesSelectedName[0] = '\0';
UpdateFileNameInput();
s_ListBox.ScrollToSelected();
m_PreviewImageState = PREVIEWIMAGE_UNLOADED;
m_FilePreviewState = PREVIEW_UNLOADED;
}
}
@ -4753,30 +4788,42 @@ void CEditor::RenderFileDialog()
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];
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);
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);
m_PreviewImageState = PREVIEWIMAGE_LOADED;
m_FilePreviewState = PREVIEW_LOADED;
}
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)
}
if(m_FileDialogFileType == CEditor::FILETYPE_IMG)
{
Preview.Margin(10.0f, &Preview);
if(m_FilePreviewState == PREVIEW_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)
if(m_FilePreviewImageInfo.m_Width > Preview.w)
{
h = m_FilePreviewImageInfo.m_Height * Preview.w / m_FilePreviewImageInfo.m_Width;
w = Preview.w;
@ -4794,6 +4841,45 @@ void CEditor::RenderFileDialog()
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);
}
}
}
s_ListBox.DoStart(15.0f, m_vpFilteredFileList.size(), 1, 5, m_FilesSelectedIndex, &View, false);
@ -4858,7 +4944,7 @@ void CEditor::RenderFileDialog()
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
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;
@ -5053,7 +5139,7 @@ void CEditor::FilelistPopulate(int StorageType, bool KeepSelection)
else
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,
@ -5072,7 +5158,7 @@ void CEditor::InvokeFileDialog(int StorageType, int FileType, const char *pTitle
m_aFileDialogCurrentLink[0] = 0;
m_pFileDialogPath = m_aFileDialogCurrentFolder;
m_FileDialogFileType = FileType;
m_PreviewImageState = PREVIEWIMAGE_UNLOADED;
m_FilePreviewState = PREVIEW_UNLOADED;
m_FileDialogOpening = true;
if(pDefaultName)
@ -6180,7 +6266,9 @@ void CEditor::Render()
// do the toolbar
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)
{

View file

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