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