5749: Editor: added the possibility to duplicate layers r=def- a=archimede67

<!-- What is the motivation for the changes of this pull request -->

Suggested by Pulsar a few years ago, I finally had the motivation to do it. We can now duplicate any non-special layer (tiles, quads and sounds) thanks to a button just above the delete button.

![image](https://user-images.githubusercontent.com/13364635/185260070-cd5b4c8f-5827-457c-b505-176751003dbc.png)

For each duplicated layers, all the necessary properties are duplicated, for example name, color, flags, etc.
For a tile layer, all the placed tiles are copied. Same goes for a sound layer, all sources are copied. And for the quads layer, all the quads are duplicated.

## 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: Corantin H <archi0670@gmail.com>
This commit is contained in:
bors[bot] 2022-08-19 13:25:44 +00:00 committed by GitHub
commit 6a5d99daf1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 2 deletions

View file

@ -212,6 +212,17 @@ void CLayerGroup::DeleteLayer(int Index)
m_pMap->m_Modified = true; m_pMap->m_Modified = true;
} }
void CLayerGroup::DuplicateLayer(int Index)
{
if(Index < 0 || Index >= (int)m_vpLayers.size())
return;
auto *pDup = m_vpLayers[Index]->Duplicate();
m_vpLayers.insert(m_vpLayers.begin() + Index + 1, pDup);
m_pMap->m_Modified = true;
}
void CLayerGroup::GetSize(float *pWidth, float *pHeight) const void CLayerGroup::GetSize(float *pWidth, float *pHeight) const
{ {
*pWidth = 0; *pWidth = 0;
@ -745,6 +756,11 @@ int CEditor::FindSelectedQuadIndex(int Index) const
return -1; return -1;
} }
bool CEditor::IsSpecialLayer(const CLayer *pLayer) const
{
return m_Map.m_pGameLayer == pLayer || m_Map.m_pTeleLayer == pLayer || m_Map.m_pSpeedupLayer == pLayer || m_Map.m_pFrontLayer == pLayer || m_Map.m_pSwitchLayer == pLayer || m_Map.m_pTuneLayer == pLayer;
}
void CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUser) void CEditor::CallbackOpenMap(const char *pFileName, int StorageType, void *pUser)
{ {
CEditor *pEditor = (CEditor *)pUser; CEditor *pEditor = (CEditor *)pUser;
@ -3480,7 +3496,7 @@ void CEditor::RenderLayers(CUIRect ToolBox, CUIRect View)
else else
s_LayerPopupContext.m_vpLayers.clear(); s_LayerPopupContext.m_vpLayers.clear();
UiInvokePopupMenu(&s_LayerPopupContext, 0, UI()->MouseX(), UI()->MouseY(), 120, 300, PopupLayer, &s_LayerPopupContext); UiInvokePopupMenu(&s_LayerPopupContext, 0, UI()->MouseX(), UI()->MouseY(), 120, 320, PopupLayer, &s_LayerPopupContext);
} }
} }

View file

@ -127,6 +127,17 @@ public:
m_BrushRefCount = 0; m_BrushRefCount = 0;
} }
CLayer(const CLayer &Other)
{
str_copy(m_aName, Other.m_aName, sizeof(m_aName));
m_Flags = Other.m_Flags;
m_pEditor = Other.m_pEditor;
m_Type = Other.m_Type;
m_BrushRefCount = 0;
m_Visible = true;
m_Readonly = false;
}
virtual ~CLayer() virtual ~CLayer()
{ {
} }
@ -147,6 +158,8 @@ public:
virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) {} virtual void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) {}
virtual void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) {} virtual void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) {}
virtual CLayer *Duplicate() const = 0;
virtual void GetSize(float *pWidth, float *pHeight) virtual void GetSize(float *pWidth, float *pHeight)
{ {
*pWidth = 0; *pWidth = 0;
@ -159,7 +172,6 @@ public:
bool m_Readonly; bool m_Readonly;
bool m_Visible; bool m_Visible;
int m_BrushRefCount; int m_BrushRefCount;
}; };
@ -198,6 +210,7 @@ public:
void GetSize(float *pWidth, float *pHeight) const; void GetSize(float *pWidth, float *pHeight) const;
void DeleteLayer(int Index); void DeleteLayer(int Index);
void DuplicateLayer(int Index);
int SwapLayers(int Index0, int Index1); int SwapLayers(int Index0, int Index1);
bool IsEmpty() const bool IsEmpty() const
@ -554,6 +567,7 @@ protected:
public: public:
CLayerTiles(int w, int h); CLayerTiles(int w, int h);
CLayerTiles(const CLayerTiles &Other);
~CLayerTiles(); ~CLayerTiles();
virtual CTile GetTile(int x, int y); virtual CTile GetTile(int x, int y);
@ -580,6 +594,8 @@ public:
void BrushFlipY() override; void BrushFlipY() override;
void BrushRotate(float Amount) override; void BrushRotate(float Amount) override;
CLayer *Duplicate() const override;
virtual void ShowInfo(); virtual void ShowInfo();
int RenderProperties(CUIRect *pToolbox) override; int RenderProperties(CUIRect *pToolbox) override;
@ -637,6 +653,7 @@ class CLayerQuads : public CLayer
{ {
public: public:
CLayerQuads(); CLayerQuads();
CLayerQuads(const CLayerQuads &Other);
~CLayerQuads(); ~CLayerQuads();
void Render(bool QuadPicker = false) override; void Render(bool QuadPicker = false) override;
@ -655,6 +672,7 @@ public:
void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) override; void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) override;
void GetSize(float *pWidth, float *pHeight) override; void GetSize(float *pWidth, float *pHeight) override;
CLayer *Duplicate() const override;
int m_Image; int m_Image;
std::vector<CQuad> m_vQuads; std::vector<CQuad> m_vQuads;
@ -842,6 +860,7 @@ public:
void DeleteSelectedQuads(); void DeleteSelectedQuads();
bool IsQuadSelected(int Index) const; bool IsQuadSelected(int Index) const;
int FindSelectedQuadIndex(int Index) const; int FindSelectedQuadIndex(int Index) const;
bool IsSpecialLayer(const CLayer *pLayer) const;
float ScaleFontSize(char *pText, int TextSize, float FontSize, int Width); float ScaleFontSize(char *pText, int TextSize, float FontSize, int Width);
int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f)); int DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIDs, int *pNewVal, ColorRGBA Color = ColorRGBA(1, 1, 1, 0.5f));
@ -1318,6 +1337,7 @@ class CLayerSounds : public CLayer
{ {
public: public:
CLayerSounds(); CLayerSounds();
CLayerSounds(const CLayerSounds &Other);
~CLayerSounds(); ~CLayerSounds();
void Render(bool Tileset = false) override; void Render(bool Tileset = false) override;
@ -1332,6 +1352,8 @@ public:
void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) override; void ModifyEnvelopeIndex(INDEX_MODIFY_FUNC pfnFunc) override;
void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) override; void ModifySoundIndex(INDEX_MODIFY_FUNC pfnFunc) override;
CLayer *Duplicate() const override;
int m_Sound; int m_Sound;
std::vector<CSoundSource> m_vSources; std::vector<CSoundSource> m_vSources;
}; };

View file

@ -14,6 +14,13 @@ CLayerQuads::CLayerQuads()
m_Image = -1; m_Image = -1;
} }
CLayerQuads::CLayerQuads(const CLayerQuads &Other) :
CLayer(Other)
{
m_Image = Other.m_Image;
m_vQuads = Other.m_vQuads;
}
CLayerQuads::~CLayerQuads() = default; CLayerQuads::~CLayerQuads() = default;
void CLayerQuads::Render(bool QuadPicker) void CLayerQuads::Render(bool QuadPicker)
@ -255,3 +262,8 @@ void CLayerQuads::ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func)
Func(&Quad.m_ColorEnv); Func(&Quad.m_ColorEnv);
} }
} }
CLayer *CLayerQuads::Duplicate() const
{
return new CLayerQuads(*this);
}

View file

@ -12,6 +12,13 @@ CLayerSounds::CLayerSounds()
m_Sound = -1; m_Sound = -1;
} }
CLayerSounds::CLayerSounds(const CLayerSounds &Other) :
CLayer(Other)
{
m_Sound = Other.m_Sound;
m_vSources = Other.m_vSources;
}
CLayerSounds::~CLayerSounds() = default; CLayerSounds::~CLayerSounds() = default;
void CLayerSounds::Render(bool Tileset) void CLayerSounds::Render(bool Tileset)
@ -219,3 +226,8 @@ void CLayerSounds::ModifyEnvelopeIndex(INDEX_MODIFY_FUNC Func)
Func(&Source.m_PosEnv); Func(&Source.m_PosEnv);
} }
} }
CLayer *CLayerSounds::Duplicate() const
{
return new CLayerSounds(*this);
}

View file

@ -40,6 +40,33 @@ CLayerTiles::CLayerTiles(int w, int h)
mem_zero(m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile)); mem_zero(m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile));
} }
CLayerTiles::CLayerTiles(const CLayerTiles &Other) :
CLayer(Other)
{
m_Width = Other.m_Width;
m_Height = Other.m_Height;
m_pTiles = new CTile[m_Width * m_Height];
mem_copy(m_pTiles, Other.m_pTiles, (size_t)m_Width * m_Height * sizeof(CTile));
m_Image = Other.m_Image;
m_Texture = Other.m_Texture;
m_Game = Other.m_Game;
m_Color = Other.m_Color;
m_ColorEnv = Other.m_ColorEnv;
m_ColorEnvOffset = Other.m_ColorEnvOffset;
m_AutoMapperConfig = Other.m_AutoMapperConfig;
m_Seed = Other.m_Seed;
m_AutoAutoMap = Other.m_AutoAutoMap;
m_Tele = Other.m_Tele;
m_Speedup = Other.m_Speedup;
m_Front = Other.m_Front;
m_Switch = Other.m_Switch;
m_Tune = Other.m_Tune;
mem_copy(m_aFileName, Other.m_aFileName, IO_MAX_PATH_LENGTH);
}
CLayerTiles::~CLayerTiles() CLayerTiles::~CLayerTiles()
{ {
delete[] m_pTiles; delete[] m_pTiles;
@ -571,6 +598,11 @@ void CLayerTiles::BrushRotate(float Amount)
} }
} }
CLayer *CLayerTiles::Duplicate() const
{
return new CLayerTiles(*this);
}
void CLayerTiles::Resize(int NewW, int NewH) void CLayerTiles::Resize(int NewW, int NewH)
{ {
CTile *pNewData = new CTile[NewW * NewH]; CTile *pNewData = new CTile[NewW * NewH];

View file

@ -397,6 +397,21 @@ int CEditor::PopupLayer(CEditor *pEditor, CUIRect View, void *pContext)
return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers); return CLayerTiles::RenderCommonProperties(pPopup->m_CommonPropState, pEditor, &View, pPopup->m_vpLayers);
} }
// duplicate layer button
CUIRect DupButton;
static int s_DuplicationButton = 0;
View.HSplitBottom(4.0f, &View, nullptr);
View.HSplitBottom(12.0f, &View, &DupButton);
if(!pEditor->IsSpecialLayer(pEditor->GetSelectedLayer(0)))
{
if(pEditor->DoButton_Editor(&s_DuplicationButton, "Duplicate layer", 0, &DupButton, 0, "Duplicates the layer"))
{
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->DuplicateLayer(pEditor->m_vSelectedLayers[0]);
return 1;
}
}
// don't allow deletion of game layer // don't allow deletion of game layer
if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0) && if(pEditor->m_Map.m_pGameLayer != pEditor->GetSelectedLayer(0) &&
pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &Button, 0, "Deletes the layer")) pEditor->DoButton_Editor(&s_DeleteButton, "Delete layer", 0, &Button, 0, "Deletes the layer"))