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