1#include "editor.h"
2
3#include <engine/textrender.h>
4
5#include <game/editor/mapitems/image.h>
6#include <game/editor/mapitems/sound.h>
7
8using namespace FontIcons;
9
10const ColorRGBA CEditor::ms_DefaultPropColor = ColorRGBA(1, 1, 1, 0.5f);
11
12int CEditor::DoProperties(CUIRect *pToolbox, CProperty *pProps, int *pIds, int *pNewVal, const std::vector<ColorRGBA> &vColors)
13{
14 auto Res = DoPropertiesWithState<int>(pToolBox: pToolbox, pProps, pIds, pNewVal, vColors);
15 return Res.m_Value;
16}
17
18template<typename E>
19SEditResult<E> CEditor::DoPropertiesWithState(CUIRect *pToolBox, CProperty *pProps, int *pIds, int *pNewVal, const std::vector<ColorRGBA> &vColors)
20{
21 int Change = -1;
22 EEditState State = EEditState::EDITING;
23
24 for(int i = 0; pProps[i].m_pName; i++)
25 {
26 const ColorRGBA *pColor = i >= (int)vColors.size() ? &ms_DefaultPropColor : &vColors[i];
27
28 CUIRect Slot;
29 pToolBox->HSplitTop(Cut: 13.0f, pTop: &Slot, pBottom: pToolBox);
30 CUIRect Label, Shifter;
31 Slot.VSplitMid(pLeft: &Label, pRight: &Shifter);
32 Shifter.HMargin(Cut: 1.0f, pOtherRect: &Shifter);
33 Ui()->DoLabel(pRect: &Label, pText: pProps[i].m_pName, Size: 10.0f, Align: TEXTALIGN_ML);
34
35 if(pProps[i].m_Type == PROPTYPE_INT)
36 {
37 CUIRect Inc, Dec;
38 char aBuf[64];
39
40 Shifter.VSplitRight(Cut: 10.0f, pLeft: &Shifter, pRight: &Inc);
41 Shifter.VSplitLeft(Cut: 10.0f, pLeft: &Dec, pRight: &Shifter);
42 str_from_int(value: pProps[i].m_Value, dst&: aBuf);
43 auto NewValueRes = UiDoValueSelector(pId: (char *)&pIds[i], pRect: &Shifter, pLabel: "", Current: pProps[i].m_Value, Min: pProps[i].m_Min, Max: pProps[i].m_Max, Step: 1, Scale: 1.0f, pToolTip: "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", IsDegree: false, IsHex: false, corners: 0, pColor);
44 int NewValue = NewValueRes.m_Value;
45 if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING)
46 {
47 *pNewVal = NewValue;
48 Change = i;
49 State = NewValueRes.m_State;
50 }
51 if(DoButton_FontIcon(pId: (char *)&pIds[i] + 1, pText: FONT_ICON_MINUS, Checked: 0, pRect: &Dec, Flags: 0, pToolTip: "Decrease", Corners: IGraphics::CORNER_L, FontSize: 7.0f))
52 {
53 *pNewVal = pProps[i].m_Value - 1;
54 Change = i;
55 State = EEditState::ONE_GO;
56 }
57 if(DoButton_FontIcon(pId: ((char *)&pIds[i]) + 2, pText: FONT_ICON_PLUS, Checked: 0, pRect: &Inc, Flags: 0, pToolTip: "Increase", Corners: IGraphics::CORNER_R, FontSize: 7.0f))
58 {
59 *pNewVal = pProps[i].m_Value + 1;
60 Change = i;
61 State = EEditState::ONE_GO;
62 }
63 }
64 else if(pProps[i].m_Type == PROPTYPE_BOOL)
65 {
66 CUIRect No, Yes;
67 Shifter.VSplitMid(pLeft: &No, pRight: &Yes);
68 if(DoButton_Ex(pId: &pIds[i], pText: "No", Checked: !pProps[i].m_Value, pRect: &No, Flags: 0, pToolTip: "", Corners: IGraphics::CORNER_L))
69 {
70 *pNewVal = 0;
71 Change = i;
72 State = EEditState::ONE_GO;
73 }
74 if(DoButton_Ex(pId: ((char *)&pIds[i]) + 1, pText: "Yes", Checked: pProps[i].m_Value, pRect: &Yes, Flags: 0, pToolTip: "", Corners: IGraphics::CORNER_R))
75 {
76 *pNewVal = 1;
77 Change = i;
78 State = EEditState::ONE_GO;
79 }
80 }
81 else if(pProps[i].m_Type == PROPTYPE_ANGLE_SCROLL)
82 {
83 CUIRect Inc, Dec;
84 Shifter.VSplitRight(Cut: 10.0f, pLeft: &Shifter, pRight: &Inc);
85 Shifter.VSplitLeft(Cut: 10.0f, pLeft: &Dec, pRight: &Shifter);
86 const bool Shift = Input()->ShiftIsPressed();
87 int Step = Shift ? 1 : 45;
88 int Value = pProps[i].m_Value;
89
90 auto NewValueRes = UiDoValueSelector(pId: &pIds[i], pRect: &Shifter, pLabel: "", Current: Value, Min: pProps[i].m_Min, Max: pProps[i].m_Max, Step: Shift ? 1 : 45, Scale: Shift ? 1.0f : 10.0f, pToolTip: "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", IsDegree: false, IsHex: false, corners: 0);
91 int NewValue = NewValueRes.m_Value;
92 if(DoButton_FontIcon(pId: &pIds[i] + 1, pText: FONT_ICON_MINUS, Checked: 0, pRect: &Dec, Flags: 0, pToolTip: "Decrease", Corners: IGraphics::CORNER_L, FontSize: 7.0f))
93 {
94 NewValue = (std::ceil(x: (pProps[i].m_Value / (float)Step)) - 1) * Step;
95 if(NewValue < 0)
96 NewValue += 360;
97 State = EEditState::ONE_GO;
98 }
99 if(DoButton_FontIcon(pId: &pIds[i] + 2, pText: FONT_ICON_PLUS, Checked: 0, pRect: &Inc, Flags: 0, pToolTip: "Increase", Corners: IGraphics::CORNER_R, FontSize: 7.0f))
100 {
101 NewValue = (pProps[i].m_Value + Step) / Step * Step;
102 State = EEditState::ONE_GO;
103 }
104
105 if(NewValue != pProps[i].m_Value || NewValueRes.m_State != EEditState::EDITING)
106 {
107 *pNewVal = NewValue % 360;
108 Change = i;
109 State = NewValueRes.m_State;
110 }
111 }
112 else if(pProps[i].m_Type == PROPTYPE_COLOR)
113 {
114 const auto &&SetColor = [&](ColorRGBA NewColor) {
115 const int NewValue = NewColor.PackAlphaLast();
116 if(NewValue != pProps[i].m_Value || m_ColorPickerPopupContext.m_State != EEditState::EDITING)
117 {
118 *pNewVal = NewValue;
119 Change = i;
120 State = m_ColorPickerPopupContext.m_State;
121 }
122 };
123 DoColorPickerButton(pId: &pIds[i], pRect: &Shifter, Color: ColorRGBA::UnpackAlphaLast<ColorRGBA>(Color: pProps[i].m_Value), SetColor);
124 }
125 else if(pProps[i].m_Type == PROPTYPE_IMAGE)
126 {
127 const char *pName;
128 if(pProps[i].m_Value < 0)
129 pName = "None";
130 else
131 pName = m_Map.m_vpImages[pProps[i].m_Value]->m_aName;
132
133 if(DoButton_Ex(pId: &pIds[i], pText: pName, Checked: 0, pRect: &Shifter, Flags: 0, pToolTip: nullptr, Corners: IGraphics::CORNER_ALL))
134 PopupSelectImageInvoke(Current: pProps[i].m_Value, x: Ui()->MouseX(), y: Ui()->MouseY());
135
136 int r = PopupSelectImageResult();
137 if(r >= -1)
138 {
139 *pNewVal = r;
140 Change = i;
141 State = EEditState::ONE_GO;
142 }
143 }
144 else if(pProps[i].m_Type == PROPTYPE_SHIFT)
145 {
146 CUIRect Left, Right, Up, Down;
147 Shifter.VSplitMid(pLeft: &Left, pRight: &Up, Spacing: 2.0f);
148 Left.VSplitLeft(Cut: 10.0f, pLeft: &Left, pRight: &Shifter);
149 Shifter.VSplitRight(Cut: 10.0f, pLeft: &Shifter, pRight: &Right);
150 Shifter.Draw(Color: ColorRGBA(1, 1, 1, 0.5f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f);
151 Ui()->DoLabel(pRect: &Shifter, pText: "X", Size: 10.0f, Align: TEXTALIGN_MC);
152 Up.VSplitLeft(Cut: 10.0f, pLeft: &Up, pRight: &Shifter);
153 Shifter.VSplitRight(Cut: 10.0f, pLeft: &Shifter, pRight: &Down);
154 Shifter.Draw(Color: ColorRGBA(1, 1, 1, 0.5f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f);
155 Ui()->DoLabel(pRect: &Shifter, pText: "Y", Size: 10.0f, Align: TEXTALIGN_MC);
156 if(DoButton_FontIcon(pId: &pIds[i], pText: FONT_ICON_MINUS, Checked: 0, pRect: &Left, Flags: 0, pToolTip: "Left", Corners: IGraphics::CORNER_L, FontSize: 7.0f))
157 {
158 *pNewVal = DIRECTION_LEFT;
159 Change = i;
160 State = EEditState::ONE_GO;
161 }
162 if(DoButton_FontIcon(pId: ((char *)&pIds[i]) + 3, pText: FONT_ICON_PLUS, Checked: 0, pRect: &Right, Flags: 0, pToolTip: "Right", Corners: IGraphics::CORNER_R, FontSize: 7.0f))
163 {
164 *pNewVal = DIRECTION_RIGHT;
165 Change = i;
166 State = EEditState::ONE_GO;
167 }
168 if(DoButton_FontIcon(pId: ((char *)&pIds[i]) + 1, pText: FONT_ICON_MINUS, Checked: 0, pRect: &Up, Flags: 0, pToolTip: "Up", Corners: IGraphics::CORNER_L, FontSize: 7.0f))
169 {
170 *pNewVal = DIRECTION_UP;
171 Change = i;
172 State = EEditState::ONE_GO;
173 }
174 if(DoButton_FontIcon(pId: ((char *)&pIds[i]) + 2, pText: FONT_ICON_PLUS, Checked: 0, pRect: &Down, Flags: 0, pToolTip: "Down", Corners: IGraphics::CORNER_R, FontSize: 7.0f))
175 {
176 *pNewVal = DIRECTION_DOWN;
177 Change = i;
178 State = EEditState::ONE_GO;
179 }
180 }
181 else if(pProps[i].m_Type == PROPTYPE_SOUND)
182 {
183 const char *pName;
184 if(pProps[i].m_Value < 0)
185 pName = "None";
186 else
187 pName = m_Map.m_vpSounds[pProps[i].m_Value]->m_aName;
188
189 if(DoButton_Ex(pId: &pIds[i], pText: pName, Checked: 0, pRect: &Shifter, Flags: 0, pToolTip: nullptr, Corners: IGraphics::CORNER_ALL))
190 PopupSelectSoundInvoke(Current: pProps[i].m_Value, x: Ui()->MouseX(), y: Ui()->MouseY());
191
192 int r = PopupSelectSoundResult();
193 if(r >= -1)
194 {
195 *pNewVal = r;
196 Change = i;
197 State = EEditState::ONE_GO;
198 }
199 }
200 else if(pProps[i].m_Type == PROPTYPE_AUTOMAPPER)
201 {
202 const char *pName;
203 if(pProps[i].m_Value < 0 || pProps[i].m_Min < 0 || pProps[i].m_Min >= (int)m_Map.m_vpImages.size())
204 pName = "None";
205 else
206 pName = m_Map.m_vpImages[pProps[i].m_Min]->m_AutoMapper.GetConfigName(Index: pProps[i].m_Value);
207
208 if(DoButton_Ex(pId: &pIds[i], pText: pName, Checked: 0, pRect: &Shifter, Flags: 0, pToolTip: nullptr, Corners: IGraphics::CORNER_ALL))
209 PopupSelectConfigAutoMapInvoke(Current: pProps[i].m_Value, x: Ui()->MouseX(), y: Ui()->MouseY());
210
211 int r = PopupSelectConfigAutoMapResult();
212 if(r >= -1)
213 {
214 *pNewVal = r;
215 Change = i;
216 State = EEditState::ONE_GO;
217 }
218 }
219 else if(pProps[i].m_Type == PROPTYPE_ENVELOPE)
220 {
221 CUIRect Inc, Dec;
222 char aBuf[8];
223 int CurValue = pProps[i].m_Value;
224
225 Shifter.VSplitRight(Cut: 10.0f, pLeft: &Shifter, pRight: &Inc);
226 Shifter.VSplitLeft(Cut: 10.0f, pLeft: &Dec, pRight: &Shifter);
227
228 if(CurValue <= 0)
229 str_copy(dst&: aBuf, src: "None:");
230 else if(m_Map.m_vpEnvelopes[CurValue - 1]->m_aName[0])
231 {
232 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:", m_Map.m_vpEnvelopes[CurValue - 1]->m_aName);
233 if(!str_endswith(str: aBuf, suffix: ":"))
234 {
235 aBuf[sizeof(aBuf) - 2] = ':';
236 aBuf[sizeof(aBuf) - 1] = '\0';
237 }
238 }
239 else
240 aBuf[0] = '\0';
241
242 auto NewValueRes = UiDoValueSelector(pId: (char *)&pIds[i], pRect: &Shifter, pLabel: aBuf, Current: CurValue, Min: 0, Max: m_Map.m_vpEnvelopes.size(), Step: 1, Scale: 1.0f, pToolTip: "Set Envelope", IsDegree: false, IsHex: false, corners: IGraphics::CORNER_NONE);
243 int NewVal = NewValueRes.m_Value;
244 if(NewVal != CurValue || NewValueRes.m_State != EEditState::EDITING)
245 {
246 *pNewVal = NewVal;
247 Change = i;
248 State = NewValueRes.m_State;
249 }
250
251 if(DoButton_FontIcon(pId: (char *)&pIds[i] + 1, pText: FONT_ICON_MINUS, Checked: 0, pRect: &Dec, Flags: 0, pToolTip: "Previous Envelope", Corners: IGraphics::CORNER_L, FontSize: 7.0f))
252 {
253 *pNewVal = pProps[i].m_Value - 1;
254 Change = i;
255 State = EEditState::ONE_GO;
256 }
257 if(DoButton_FontIcon(pId: ((char *)&pIds[i]) + 2, pText: FONT_ICON_PLUS, Checked: 0, pRect: &Inc, Flags: 0, pToolTip: "Next Envelope", Corners: IGraphics::CORNER_R, FontSize: 7.0f))
258 {
259 *pNewVal = pProps[i].m_Value + 1;
260 Change = i;
261 State = EEditState::ONE_GO;
262 }
263 }
264 }
265
266 return SEditResult<E>{State, static_cast<E>(Change)};
267}
268
269template SEditResult<ECircleShapeProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
270template SEditResult<ERectangleShapeProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
271template SEditResult<EGroupProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
272template SEditResult<ELayerProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
273template SEditResult<ELayerQuadsProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
274template SEditResult<ETilesProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
275template SEditResult<ETilesCommonProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
276template SEditResult<ELayerSoundsProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
277template SEditResult<EQuadProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
278template SEditResult<EQuadPointProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
279template SEditResult<ESoundProp> CEditor::DoPropertiesWithState(CUIRect *, CProperty *, int *, int *, const std::vector<ColorRGBA> &);
280