| 1 | #include "menus_ingame_touch_controls.h" |
| 2 | |
| 3 | #include <base/color.h> |
| 4 | #include <base/math.h> |
| 5 | #include <base/system.h> |
| 6 | |
| 7 | #include <engine/external/json-parser/json.h> |
| 8 | #include <engine/graphics.h> |
| 9 | #include <engine/shared/jsonwriter.h> |
| 10 | #include <engine/shared/localization.h> |
| 11 | #include <engine/textrender.h> |
| 12 | |
| 13 | #include <game/client/components/touch_controls.h> |
| 14 | #include <game/client/gameclient.h> |
| 15 | #include <game/client/lineinput.h> |
| 16 | #include <game/client/ui.h> |
| 17 | #include <game/client/ui_listbox.h> |
| 18 | #include <game/client/ui_rect.h> |
| 19 | #include <game/client/ui_scrollregion.h> |
| 20 | #include <game/localization.h> |
| 21 | |
| 22 | #include <algorithm> |
| 23 | #include <memory> |
| 24 | #include <string> |
| 25 | |
| 26 | static const constexpr float MAINMARGIN = 10.0f; |
| 27 | static const constexpr float SUBMARGIN = 5.0f; |
| 28 | static const constexpr float ROWSIZE = 25.0f; |
| 29 | static const constexpr float ROWGAP = 5.0f; |
| 30 | static const constexpr float FONTSIZE = 15.0f; |
| 31 | |
| 32 | const CMenusIngameTouchControls::CBehaviorFactoryEditor CMenusIngameTouchControls::[] = { |
| 33 | {.m_pId: CTouchControls::CIngameMenuTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CIngameMenuTouchButtonBehavior>(); }}, |
| 34 | {.m_pId: CTouchControls::CExtraMenuTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CExtraMenuTouchButtonBehavior>(args: 0); }}, |
| 35 | {.m_pId: CTouchControls::CEmoticonTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CEmoticonTouchButtonBehavior>(); }}, |
| 36 | {.m_pId: CTouchControls::CSpectateTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CSpectateTouchButtonBehavior>(); }}, |
| 37 | {.m_pId: CTouchControls::CSwapActionTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CSwapActionTouchButtonBehavior>(); }}, |
| 38 | {.m_pId: CTouchControls::CUseActionTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CUseActionTouchButtonBehavior>(); }}, |
| 39 | {.m_pId: CTouchControls::CJoystickActionTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CJoystickActionTouchButtonBehavior>(); }}, |
| 40 | {.m_pId: CTouchControls::CJoystickAimTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CJoystickAimTouchButtonBehavior>(); }}, |
| 41 | {.m_pId: CTouchControls::CJoystickFireTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CJoystickFireTouchButtonBehavior>(); }}, |
| 42 | {.m_pId: CTouchControls::CJoystickHookTouchButtonBehavior::BEHAVIOR_ID, .m_Factory: []() { return std::make_unique<CTouchControls::CJoystickHookTouchButtonBehavior>(); }}}; |
| 43 | |
| 44 | void CMenusIngameTouchControls::(CUIRect MainView) |
| 45 | { |
| 46 | if(!GameClient()->m_TouchControls.IsButtonEditing()) |
| 47 | { |
| 48 | RenderTouchButtonBrowser(MainView); |
| 49 | return; |
| 50 | } |
| 51 | // Used to decide if need to update the Samplebutton. |
| 52 | bool Changed = false; |
| 53 | CUIRect Functional, LeftButton, MiddleButton, RightButton, EditBox, Block; |
| 54 | MainView.h = 600.0f - 40.0f - MainView.y; |
| 55 | MainView.Draw(Color: CMenus::ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
| 56 | MainView.VMargin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 57 | MainView.HSplitTop(Cut: MAINMARGIN, pTop: nullptr, pBottom: &MainView); |
| 58 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &MainView); |
| 59 | |
| 60 | MainView.HSplitBottom(Cut: 2 * ROWSIZE + 2 * ROWGAP + MAINMARGIN, pTop: &Block, pBottom: &Functional); |
| 61 | Functional.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Functional); |
| 62 | Functional.HSplitBottom(Cut: MAINMARGIN, pTop: &Functional, pBottom: nullptr); |
| 63 | |
| 64 | // Choosing which to edit. |
| 65 | EditBox.VSplitLeft(Cut: EditBox.w / 3.0f, pLeft: &RightButton, pRight: &EditBox); |
| 66 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 67 | |
| 68 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: m_aEditElementIds.data(), pText: Localize(pStr: "Layout" ), Checked: m_EditElement == EElementType::LAYOUT, pRect: &RightButton, Corners: IGraphics::CORNER_TL, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 5.0f)) |
| 69 | { |
| 70 | m_EditElement = EElementType::LAYOUT; |
| 71 | } |
| 72 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: &m_aEditElementIds[1], pText: Localize(pStr: "Visibility" ), Checked: m_EditElement == EElementType::VISIBILITY, pRect: &LeftButton, Corners: IGraphics::CORNER_NONE, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 5.0f)) |
| 73 | { |
| 74 | m_EditElement = EElementType::VISIBILITY; |
| 75 | } |
| 76 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: &m_aEditElementIds[2], pText: Localize(pStr: "Behavior" ), Checked: m_EditElement == EElementType::BEHAVIOR, pRect: &MiddleButton, Corners: IGraphics::CORNER_TR, pAnimator: nullptr, pDefaultColor: nullptr, pActiveColor: nullptr, pHoverColor: nullptr, EdgeRounding: 5.0f)) |
| 77 | { |
| 78 | m_EditElement = EElementType::BEHAVIOR; |
| 79 | } |
| 80 | |
| 81 | // Edit blocks. |
| 82 | Block.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_B, Rounding: 5.0f); |
| 83 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 84 | Block.VMargin(Cut: SUBMARGIN, pOtherRect: &Block); |
| 85 | switch(m_EditElement) |
| 86 | { |
| 87 | case EElementType::LAYOUT: Changed |= RenderLayoutSettingBlock(Block); break; |
| 88 | case EElementType::VISIBILITY: Changed |= RenderVisibilitySettingBlock(Block); break; |
| 89 | case EElementType::BEHAVIOR: Changed |= RenderBehaviorSettingBlock(Block); break; |
| 90 | default: dbg_assert_failed("Unknown m_EditElement = %d." , (int)m_EditElement); |
| 91 | } |
| 92 | |
| 93 | // Save & Cancel & Hint. |
| 94 | Functional.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Functional); |
| 95 | const float ButtonWidth = (EditBox.w - SUBMARGIN * 2.0f) / 3.0f; |
| 96 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &LeftButton, pRight: &EditBox); |
| 97 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 98 | static CButtonContainer s_ConfirmButton; |
| 99 | // After touching this button, the button is then added into the button vector. Or it is still virtual. |
| 100 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ConfirmButton, pText: Localize(pStr: "Save changes" ), Checked: UnsavedChanges() ? 0 : 1, pRect: &LeftButton)) |
| 101 | { |
| 102 | if(UnsavedChanges()) |
| 103 | { |
| 104 | m_pOldSelectedButton = GameClient()->m_TouchControls.SelectedButton(); |
| 105 | if(CheckCachedSettings()) |
| 106 | { |
| 107 | if(m_pOldSelectedButton == nullptr) |
| 108 | { |
| 109 | m_pOldSelectedButton = GameClient()->m_TouchControls.NewButton(); |
| 110 | } |
| 111 | SaveCachedSettingsToTarget(pTargetButton: m_pOldSelectedButton); |
| 112 | GameClient()->m_TouchControls.SetSelectedButton(m_pOldSelectedButton); |
| 113 | GameClient()->m_TouchControls.SetEditingChanges(true); |
| 114 | SetUnsavedChanges(false); |
| 115 | } |
| 116 | } |
| 117 | } |
| 118 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &LeftButton, pRight: &EditBox); |
| 119 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &MiddleButton); |
| 120 | if(UnsavedChanges()) |
| 121 | { |
| 122 | TextRender()->TextColor(Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)); |
| 123 | Ui()->DoLabel(pRect: &LeftButton, pText: Localize(pStr: "Unsaved changes" ), Size: 14.0f, Align: TEXTALIGN_MC); |
| 124 | TextRender()->TextColor(Color: TextRender()->DefaultTextColor()); |
| 125 | } |
| 126 | |
| 127 | static CButtonContainer s_CancelButton; |
| 128 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_CancelButton, pText: Localize(pStr: "Discard changes" ), Checked: UnsavedChanges() ? 0 : 1, pRect: &MiddleButton)) |
| 129 | { |
| 130 | // Since the settings are canceled, reset the cached settings to m_pSelectedButton though selected button didn't change. |
| 131 | // Reset changes to default if the button is still virtual. |
| 132 | if(UnsavedChanges()) |
| 133 | { |
| 134 | CacheAllSettingsFromTarget(pTargetButton: GameClient()->m_TouchControls.SelectedButton()); |
| 135 | Changed = true; |
| 136 | if(!GameClient()->m_TouchControls.NoRealButtonSelected()) |
| 137 | { |
| 138 | SetUnsavedChanges(false); |
| 139 | } |
| 140 | } |
| 141 | // Cancel does nothing if nothing is unsaved. |
| 142 | } |
| 143 | |
| 144 | // Functional Buttons. |
| 145 | Functional.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Functional); |
| 146 | Functional.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Functional); |
| 147 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &LeftButton, pRight: &EditBox); |
| 148 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 149 | static CButtonContainer s_RemoveButton; |
| 150 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_RemoveButton, pText: Localize(pStr: "Delete" ), Checked: 0, pRect: &LeftButton)) |
| 151 | { |
| 152 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Delete button" ), pMessage: Localize(pStr: "Are you sure that you want to delete this button?" ), pConfirmButtonLabel: Localize(pStr: "Delete" ), pCancelButtonLabel: Localize(pStr: "Cancel" ), pfnConfirmButtonCallback: &CMenus::PopupConfirmDeleteButton); |
| 153 | } |
| 154 | |
| 155 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &LeftButton, pRight: &EditBox); |
| 156 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &MiddleButton); |
| 157 | // Create a new button with current cached settings. New button will be automatically moved to nearest empty space. |
| 158 | static CButtonContainer s_CopyPasteButton; |
| 159 | bool Checked = GameClient()->m_TouchControls.NoRealButtonSelected(); |
| 160 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_CopyPasteButton, pText: Localize(pStr: "Duplicate" ), Checked: UnsavedChanges() || Checked ? 1 : 0, pRect: &LeftButton)) |
| 161 | { |
| 162 | if(Checked) |
| 163 | { |
| 164 | GameClient()->m_Menus.PopupMessage(pTitle: Localize(pStr: "New button already created" ), pMessage: Localize(pStr: "A new button has already been created, please save or delete it before creating another one." ), pButtonLabel: Localize(pStr: "Ok" )); |
| 165 | } |
| 166 | else if(UnsavedChanges()) |
| 167 | { |
| 168 | GameClient()->m_Menus.PopupMessage(pTitle: Localize(pStr: "Unsaved changes" ), pMessage: Localize(pStr: "Please save your changes before duplicating a button." ), pButtonLabel: Localize(pStr: "Ok" )); |
| 169 | } |
| 170 | else |
| 171 | { |
| 172 | CTouchControls::CUnitRect FreeRect = GameClient()->m_TouchControls.UpdatePosition(MyRect: GameClient()->m_TouchControls.ShownRect().value(), Shape: m_CachedShape, Ignore: true); |
| 173 | if(FreeRect.m_X == -1) |
| 174 | { |
| 175 | FreeRect.m_W = CTouchControls::BUTTON_SIZE_MINIMUM; |
| 176 | FreeRect.m_H = CTouchControls::BUTTON_SIZE_MINIMUM; |
| 177 | FreeRect = GameClient()->m_TouchControls.UpdatePosition(MyRect: FreeRect, Shape: m_CachedShape, Ignore: true); |
| 178 | if(FreeRect.m_X == -1) |
| 179 | { |
| 180 | GameClient()->m_Menus.PopupMessage(pTitle: Localize(pStr: "No space for button" ), pMessage: Localize(pStr: "There is not enough space available to place another button." ), pButtonLabel: Localize(pStr: "Ok" )); |
| 181 | } |
| 182 | else |
| 183 | { |
| 184 | GameClient()->m_Menus.PopupMessage(pTitle: Localize(pStr: "No space for button" ), pMessage: Localize(pStr: "There is not enough space available to place another button with this size. The button has been resized." ), pButtonLabel: Localize(pStr: "Ok" )); |
| 185 | } |
| 186 | } |
| 187 | if(FreeRect.m_X != -1) // FreeRect might change. Don't use else here. |
| 188 | { |
| 189 | ResetButtonPointers(); |
| 190 | SetPosInputs(FreeRect); |
| 191 | Changed = true; |
| 192 | SetUnsavedChanges(true); |
| 193 | } |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | // Deselect a button. |
| 198 | static CButtonContainer s_DeselectButton; |
| 199 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_DeselectButton, pText: Localize(pStr: "Deselect" ), Checked: 0, pRect: &MiddleButton)) |
| 200 | { |
| 201 | m_pOldSelectedButton = GameClient()->m_TouchControls.SelectedButton(); |
| 202 | m_pNewSelectedButton = nullptr; |
| 203 | if(UnsavedChanges()) |
| 204 | { |
| 205 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Unsaved changes" ), pMessage: Localize(pStr: "You'll lose unsaved changes after deselecting." ), pConfirmButtonLabel: Localize(pStr: "Deselect" ), pCancelButtonLabel: Localize(pStr: "Cancel" ), pfnConfirmButtonCallback: &CMenus::PopupCancelDeselectButton); |
| 206 | } |
| 207 | else |
| 208 | { |
| 209 | GameClient()->m_Menus.PopupCancelDeselectButton(); |
| 210 | } |
| 211 | } |
| 212 | |
| 213 | // This ensures m_pSampleButton being updated always. |
| 214 | if(Changed) |
| 215 | { |
| 216 | UpdateSampleButton(); |
| 217 | } |
| 218 | } |
| 219 | |
| 220 | bool CMenusIngameTouchControls::(CUIRect Block) |
| 221 | { |
| 222 | bool Changed = false; |
| 223 | CUIRect EditBox, LeftButton, RightButton, PosX, PosY, PosW, PosH; |
| 224 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 225 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 226 | EditBox.VSplitMid(pLeft: &PosX, pRight: &EditBox); |
| 227 | if(Ui()->DoClearableEditBox(pLineInput: &m_InputX, pRect: &EditBox, FontSize: FONTSIZE)) |
| 228 | { |
| 229 | InputPosFunction(pInput: &m_InputX); |
| 230 | Changed = true; |
| 231 | } |
| 232 | |
| 233 | // Auto check if the input value contains char that is not digit. If so delete it. |
| 234 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 235 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 236 | EditBox.VSplitMid(pLeft: &PosY, pRight: &EditBox); |
| 237 | if(Ui()->DoClearableEditBox(pLineInput: &m_InputY, pRect: &EditBox, FontSize: FONTSIZE)) |
| 238 | { |
| 239 | InputPosFunction(pInput: &m_InputY); |
| 240 | Changed = true; |
| 241 | } |
| 242 | |
| 243 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 244 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 245 | EditBox.VSplitMid(pLeft: &PosW, pRight: &EditBox); |
| 246 | if(Ui()->DoClearableEditBox(pLineInput: &m_InputW, pRect: &EditBox, FontSize: FONTSIZE)) |
| 247 | { |
| 248 | InputPosFunction(pInput: &m_InputW); |
| 249 | Changed = true; |
| 250 | } |
| 251 | |
| 252 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 253 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 254 | EditBox.VSplitMid(pLeft: &PosH, pRight: &EditBox); |
| 255 | if(Ui()->DoClearableEditBox(pLineInput: &m_InputH, pRect: &EditBox, FontSize: FONTSIZE)) |
| 256 | { |
| 257 | InputPosFunction(pInput: &m_InputH); |
| 258 | Changed = true; |
| 259 | } |
| 260 | int X = m_InputX.GetInteger(); |
| 261 | int Y = m_InputY.GetInteger(); |
| 262 | int W = m_InputW.GetInteger(); |
| 263 | int H = m_InputH.GetInteger(); |
| 264 | auto DoValidatedLabel = [&](const CUIRect &LabelBlock, const char *pLabel, int Size, bool Valid) { |
| 265 | if(pLabel == nullptr) |
| 266 | return; |
| 267 | if(!Valid) |
| 268 | TextRender()->TextColor(Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)); |
| 269 | Ui()->DoLabel(pRect: &LabelBlock, pText: pLabel, Size, Align: TEXTALIGN_ML); |
| 270 | if(!Valid) |
| 271 | TextRender()->TextColor(Color: TextRender()->DefaultTextColor()); |
| 272 | }; |
| 273 | |
| 274 | DoValidatedLabel(PosX, "X:" , FONTSIZE, 0 <= X && X + W <= CTouchControls::BUTTON_SIZE_SCALE); |
| 275 | DoValidatedLabel(PosY, "Y:" , FONTSIZE, 0 <= Y && Y + H <= CTouchControls::BUTTON_SIZE_SCALE); |
| 276 | // There're strings without ":" below, so don't localize these with ":" together. |
| 277 | char aBuf[64]; |
| 278 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Width" )); |
| 279 | DoValidatedLabel(PosW, aBuf, FONTSIZE, CTouchControls::BUTTON_SIZE_MINIMUM <= W && W <= CTouchControls::BUTTON_SIZE_MAXIMUM); |
| 280 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Height" )); |
| 281 | DoValidatedLabel(PosH, aBuf, FONTSIZE, CTouchControls::BUTTON_SIZE_MINIMUM <= H && H < +CTouchControls::BUTTON_SIZE_MAXIMUM); |
| 282 | |
| 283 | // Drop down menu for shapes |
| 284 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 285 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 286 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &RightButton); |
| 287 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Shape" )); |
| 288 | const char *apShapes[] = {Localize(pStr: "Rectangle" , pContext: "Touch button shape" ), Localize(pStr: "Circle" , pContext: "Touch button shape" )}; |
| 289 | static_assert(std::size(apShapes) == (size_t)CTouchControls::EButtonShape::NUM_SHAPES, "Insufficient shape names" ); |
| 290 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 291 | static CUi::SDropDownState s_ButtonShapeDropDownState; |
| 292 | static CScrollRegion s_ButtonShapeDropDownScrollRegion; |
| 293 | s_ButtonShapeDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_ButtonShapeDropDownScrollRegion; |
| 294 | const CTouchControls::EButtonShape NewButtonShape = (CTouchControls::EButtonShape)Ui()->DoDropDown(pRect: &RightButton, CurSelection: (int)m_CachedShape, pStrs: apShapes, Num: (int)CTouchControls::EButtonShape::NUM_SHAPES, State&: s_ButtonShapeDropDownState); |
| 295 | if(NewButtonShape != m_CachedShape) |
| 296 | { |
| 297 | m_CachedShape = NewButtonShape; |
| 298 | SetUnsavedChanges(true); |
| 299 | Changed = true; |
| 300 | } |
| 301 | return Changed; |
| 302 | } |
| 303 | |
| 304 | bool CMenusIngameTouchControls::(CUIRect Block) |
| 305 | { |
| 306 | const char *apLabelTypes[] = {Localize(pStr: "Plain" , pContext: "Touch button label type" ), Localize(pStr: "Localized" , pContext: "Touch button label type" ), Localize(pStr: "Icon" , pContext: "Touch button label type" )}; |
| 307 | static_assert(std::size(apLabelTypes) == (size_t)CTouchControls::CButtonLabel::EType::NUM_TYPES, "Insufficient label type names" ); |
| 308 | bool Changed = false; |
| 309 | CUIRect EditBox, LeftButton, MiddleButton, RightButton; |
| 310 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 311 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 312 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 313 | char aBuf[128]; |
| 314 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Behavior type" )); |
| 315 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 316 | static CUi::SDropDownState s_ButtonBehaviorDropDownState; |
| 317 | static CScrollRegion s_ButtonBehaviorDropDownScrollRegion; |
| 318 | s_ButtonBehaviorDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_ButtonBehaviorDropDownScrollRegion; |
| 319 | const char *apBehaviors[] = {Localize(pStr: "Bind" , pContext: "Touch button behavior" ), Localize(pStr: "Bind Toggle" , pContext: "Touch button behavior" ), Localize(pStr: "Predefined" , pContext: "Touch button behavior" )}; |
| 320 | const EBehaviorType NewButtonBehavior = (EBehaviorType)Ui()->DoDropDown(pRect: &MiddleButton, CurSelection: (int)m_EditBehaviorType, pStrs: apBehaviors, Num: std::size(apBehaviors), State&: s_ButtonBehaviorDropDownState); |
| 321 | if(NewButtonBehavior != m_EditBehaviorType) |
| 322 | { |
| 323 | m_EditBehaviorType = NewButtonBehavior; |
| 324 | if(m_EditBehaviorType == EBehaviorType::BIND) |
| 325 | { |
| 326 | m_vBehaviorElements[0]->UpdateInputs(); |
| 327 | } |
| 328 | SetUnsavedChanges(true); |
| 329 | Changed = true; |
| 330 | } |
| 331 | if(m_EditBehaviorType != EBehaviorType::BIND_TOGGLE) |
| 332 | { |
| 333 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 334 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 335 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 336 | if(m_EditBehaviorType == EBehaviorType::BIND) |
| 337 | { |
| 338 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Command" )); |
| 339 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 340 | if(Ui()->DoClearableEditBox(pLineInput: &m_vBehaviorElements[0]->m_InputCommand, pRect: &MiddleButton, FontSize: 10.0f)) |
| 341 | { |
| 342 | m_vBehaviorElements[0]->UpdateCommand(); |
| 343 | SetUnsavedChanges(true); |
| 344 | Changed = true; |
| 345 | } |
| 346 | } |
| 347 | else if(m_EditBehaviorType == EBehaviorType::PREDEFINED) |
| 348 | { |
| 349 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Type" )); |
| 350 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 351 | static CUi::SDropDownState s_ButtonPredefinedDropDownState; |
| 352 | static CScrollRegion s_ButtonPredefinedDropDownScrollRegion; |
| 353 | const char **apPredefinedNames = PredefinedNames(); |
| 354 | s_ButtonPredefinedDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_ButtonPredefinedDropDownScrollRegion; |
| 355 | const EPredefinedType NewPredefined = (EPredefinedType)Ui()->DoDropDown(pRect: &MiddleButton, CurSelection: (int)m_PredefinedBehaviorType, pStrs: apPredefinedNames, Num: std::size(BEHAVIOR_FACTORIES_EDITOR), State&: s_ButtonPredefinedDropDownState); |
| 356 | if(NewPredefined != m_PredefinedBehaviorType) |
| 357 | { |
| 358 | m_PredefinedBehaviorType = NewPredefined; |
| 359 | SetUnsavedChanges(true); |
| 360 | Changed = true; |
| 361 | } |
| 362 | } |
| 363 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 364 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 365 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 366 | if(m_EditBehaviorType == EBehaviorType::BIND) |
| 367 | { |
| 368 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Label" )); |
| 369 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 370 | if(Ui()->DoClearableEditBox(pLineInput: &m_vBehaviorElements[0]->m_InputLabel, pRect: &MiddleButton, FontSize: 10.0f)) |
| 371 | { |
| 372 | m_vBehaviorElements[0]->UpdateLabel(); |
| 373 | SetUnsavedChanges(true); |
| 374 | Changed = true; |
| 375 | } |
| 376 | } |
| 377 | else if(m_EditBehaviorType == EBehaviorType::PREDEFINED && m_PredefinedBehaviorType == EPredefinedType::EXTRA_MENU) // Extra menu type, needs to input number. |
| 378 | { |
| 379 | // Increase & Decrease button share 1/2 width, the rest is for label. |
| 380 | EditBox.VSplitLeft(Cut: ROWSIZE, pLeft: &LeftButton, pRight: &MiddleButton); |
| 381 | static CButtonContainer ; |
| 382 | if(Ui()->DoButton_FontIcon(pButtonContainer: &s_ExtraMenuDecreaseButton, pText: FontIcons::FONT_ICON_MINUS, Checked: 0, pRect: &LeftButton, Flags: BUTTONFLAG_LEFT)) |
| 383 | { |
| 384 | if(m_CachedExtraMenuNumber > 0) |
| 385 | { |
| 386 | // Menu Number also counts from 1, but written as 0. |
| 387 | m_CachedExtraMenuNumber--; |
| 388 | SetUnsavedChanges(true); |
| 389 | Changed = true; |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | MiddleButton.VSplitRight(Cut: ROWSIZE, pLeft: &LeftButton, pRight: &MiddleButton); |
| 394 | Ui()->DoLabel(pRect: &LeftButton, pText: std::to_string(val: m_CachedExtraMenuNumber + 1).c_str(), Size: FONTSIZE, Align: TEXTALIGN_MC); |
| 395 | static CButtonContainer ; |
| 396 | if(Ui()->DoButton_FontIcon(pButtonContainer: &s_ExtraMenuIncreaseButton, pText: FontIcons::FONT_ICON_PLUS, Checked: 0, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT)) |
| 397 | { |
| 398 | if(m_CachedExtraMenuNumber < CTouchControls::MAX_EXTRA_MENU_NUMBER - 1) |
| 399 | { |
| 400 | m_CachedExtraMenuNumber++; |
| 401 | SetUnsavedChanges(true); |
| 402 | Changed = true; |
| 403 | } |
| 404 | } |
| 405 | } |
| 406 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 407 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 408 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 409 | if(m_EditBehaviorType == EBehaviorType::BIND) |
| 410 | { |
| 411 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Label type" )); |
| 412 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 413 | CTouchControls::CButtonLabel::EType NewButtonLabelType = m_vBehaviorElements[0]->m_CachedCommands.m_LabelType; |
| 414 | MiddleButton.VSplitLeft(Cut: MiddleButton.w / 3.0f, pLeft: &LeftButton, pRight: &MiddleButton); |
| 415 | MiddleButton.VSplitMid(pLeft: &MiddleButton, pRight: &RightButton); |
| 416 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &m_vBehaviorElements[0]->m_aLabelTypeRadios[0], pText: apLabelTypes[0], Checked: NewButtonLabelType == CTouchControls::CButtonLabel::EType::PLAIN ? 1 : 0, pRect: &LeftButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_L)) |
| 417 | NewButtonLabelType = CTouchControls::CButtonLabel::EType::PLAIN; |
| 418 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &m_vBehaviorElements[0]->m_aLabelTypeRadios[1], pText: apLabelTypes[1], Checked: NewButtonLabelType == CTouchControls::CButtonLabel::EType::LOCALIZED ? 1 : 0, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_NONE)) |
| 419 | NewButtonLabelType = CTouchControls::CButtonLabel::EType::LOCALIZED; |
| 420 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &m_vBehaviorElements[0]->m_aLabelTypeRadios[2], pText: apLabelTypes[2], Checked: NewButtonLabelType == CTouchControls::CButtonLabel::EType::ICON ? 1 : 0, pRect: &RightButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_R)) |
| 421 | NewButtonLabelType = CTouchControls::CButtonLabel::EType::ICON; |
| 422 | if(NewButtonLabelType != m_vBehaviorElements[0]->m_CachedCommands.m_LabelType) |
| 423 | { |
| 424 | Changed = true; |
| 425 | SetUnsavedChanges(true); |
| 426 | m_vBehaviorElements[0]->m_CachedCommands.m_LabelType = NewButtonLabelType; |
| 427 | } |
| 428 | } |
| 429 | } |
| 430 | else |
| 431 | { |
| 432 | static CScrollRegion s_BindToggleScrollRegion; |
| 433 | CScrollRegionParams ScrollParam; |
| 434 | ScrollParam.m_ScrollUnit = 90.0f; |
| 435 | vec2 ScrollOffset(0.0f, 0.0f); |
| 436 | s_BindToggleScrollRegion.Begin(pClipRect: &Block, pOutOffset: &ScrollOffset, pParams: &ScrollParam); |
| 437 | Block.y += ScrollOffset.y; |
| 438 | for(unsigned CommandIndex = 0; CommandIndex < m_vBehaviorElements.size(); CommandIndex++) |
| 439 | { |
| 440 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 441 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 442 | { |
| 443 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_T, Rounding: 5.0f); |
| 444 | EditBox.VSplitMid(pLeft: &EditBox, pRight: &RightButton); |
| 445 | RightButton.VSplitLeft(Cut: ScrollParam.m_ScrollbarWidth / 2.0f, pLeft: nullptr, pRight: &RightButton); |
| 446 | EditBox.VSplitLeft(Cut: ROWSIZE, pLeft: &MiddleButton, pRight: &EditBox); |
| 447 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &LeftButton); |
| 448 | Ui()->DoLabel(pRect: &LeftButton, pText: Localize(pStr: "Add command" ), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 449 | if(Ui()->DoButton_FontIcon(pButtonContainer: &m_vBehaviorElements[CommandIndex]->m_BindToggleAddButtons, pText: FontIcons::FONT_ICON_PLUS, Checked: 0, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT)) |
| 450 | { |
| 451 | m_vBehaviorElements.emplace(position: m_vBehaviorElements.begin() + CommandIndex, args: std::make_unique<CBehaviorElements>()); |
| 452 | m_vBehaviorElements[CommandIndex]->UpdateInputs(); |
| 453 | Changed = true; |
| 454 | SetUnsavedChanges(true); |
| 455 | } |
| 456 | RightButton.VSplitLeft(Cut: ROWSIZE, pLeft: &MiddleButton, pRight: &RightButton); |
| 457 | RightButton.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &LeftButton); |
| 458 | Ui()->DoLabel(pRect: &LeftButton, pText: Localize(pStr: "Delete command" ), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 459 | if(Ui()->DoButton_FontIcon(pButtonContainer: &m_vBehaviorElements[CommandIndex]->m_BindToggleDeleteButtons, pText: FontIcons::FONT_ICON_TRASH, Checked: 0, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT)) |
| 460 | { |
| 461 | if(m_vBehaviorElements.size() > 2) |
| 462 | { |
| 463 | if(Ui()->CheckActiveItem(pId: &m_vBehaviorElements[CommandIndex]->m_InputCommand) || Ui()->CheckActiveItem(pId: &m_vBehaviorElements[CommandIndex]->m_InputLabel)) |
| 464 | Ui()->SetActiveItem(nullptr); |
| 465 | m_vBehaviorElements.erase(position: m_vBehaviorElements.begin() + CommandIndex); |
| 466 | continue; |
| 467 | } |
| 468 | else |
| 469 | { |
| 470 | m_vBehaviorElements[CommandIndex]->Reset(); |
| 471 | } |
| 472 | SetUnsavedChanges(true); |
| 473 | Changed = true; |
| 474 | } |
| 475 | } |
| 476 | Block.HSplitTop(Cut: ROWGAP, pTop: &EditBox, pBottom: &Block); |
| 477 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 478 | { |
| 479 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
| 480 | } |
| 481 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 482 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 483 | { |
| 484 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
| 485 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 486 | MiddleButton.VSplitLeft(Cut: ScrollParam.m_ScrollbarWidth / 2.0f, pLeft: nullptr, pRight: &MiddleButton); |
| 487 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Command" )); |
| 488 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 489 | if(Ui()->DoClearableEditBox(pLineInput: &m_vBehaviorElements[CommandIndex]->m_InputCommand, pRect: &MiddleButton, FontSize: 10.0f)) |
| 490 | { |
| 491 | m_vBehaviorElements[CommandIndex]->UpdateCommand(); |
| 492 | SetUnsavedChanges(true); |
| 493 | Changed = true; |
| 494 | } |
| 495 | } |
| 496 | Block.HSplitTop(Cut: ROWGAP, pTop: &EditBox, pBottom: &Block); |
| 497 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 498 | { |
| 499 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
| 500 | } |
| 501 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 502 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 503 | { |
| 504 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
| 505 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 506 | MiddleButton.VSplitLeft(Cut: ScrollParam.m_ScrollbarWidth / 2.0f, pLeft: nullptr, pRight: &MiddleButton); |
| 507 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Label" )); |
| 508 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 509 | if(Ui()->DoClearableEditBox(pLineInput: &m_vBehaviorElements[CommandIndex]->m_InputLabel, pRect: &MiddleButton, FontSize: 10.0f)) |
| 510 | { |
| 511 | m_vBehaviorElements[CommandIndex]->UpdateLabel(); |
| 512 | SetUnsavedChanges(true); |
| 513 | Changed = true; |
| 514 | } |
| 515 | } |
| 516 | Block.HSplitTop(Cut: ROWGAP, pTop: &EditBox, pBottom: &Block); |
| 517 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 518 | { |
| 519 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
| 520 | } |
| 521 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 522 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 523 | { |
| 524 | EditBox.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_B, Rounding: 5.0f); |
| 525 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 526 | MiddleButton.VSplitLeft(Cut: ScrollParam.m_ScrollbarWidth / 2.0f, pLeft: nullptr, pRight: &MiddleButton); |
| 527 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s:" , Localize(pStr: "Label type" )); |
| 528 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 529 | CTouchControls::CButtonLabel::EType NewButtonLabelType = m_vBehaviorElements[CommandIndex]->m_CachedCommands.m_LabelType; |
| 530 | MiddleButton.VSplitLeft(Cut: MiddleButton.w / 3.0f, pLeft: &LeftButton, pRight: &MiddleButton); |
| 531 | MiddleButton.VSplitMid(pLeft: &MiddleButton, pRight: &RightButton); |
| 532 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &m_vBehaviorElements[CommandIndex]->m_aLabelTypeRadios[0], pText: apLabelTypes[0], Checked: NewButtonLabelType == CTouchControls::CButtonLabel::EType::PLAIN ? 1 : 0, pRect: &LeftButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_L)) |
| 533 | NewButtonLabelType = CTouchControls::CButtonLabel::EType::PLAIN; |
| 534 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &m_vBehaviorElements[CommandIndex]->m_aLabelTypeRadios[1], pText: apLabelTypes[1], Checked: NewButtonLabelType == CTouchControls::CButtonLabel::EType::LOCALIZED ? 1 : 0, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_NONE)) |
| 535 | NewButtonLabelType = CTouchControls::CButtonLabel::EType::LOCALIZED; |
| 536 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &m_vBehaviorElements[CommandIndex]->m_aLabelTypeRadios[2], pText: apLabelTypes[2], Checked: NewButtonLabelType == CTouchControls::CButtonLabel::EType::PLAIN ? 1 : 0, pRect: &RightButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_R)) |
| 537 | NewButtonLabelType = CTouchControls::CButtonLabel::EType::ICON; |
| 538 | if(NewButtonLabelType != m_vBehaviorElements[CommandIndex]->m_CachedCommands.m_LabelType) |
| 539 | { |
| 540 | Changed = true; |
| 541 | SetUnsavedChanges(true); |
| 542 | m_vBehaviorElements[CommandIndex]->m_CachedCommands.m_LabelType = NewButtonLabelType; |
| 543 | } |
| 544 | } |
| 545 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 546 | } |
| 547 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 548 | if(s_BindToggleScrollRegion.AddRect(Rect: EditBox)) |
| 549 | { |
| 550 | EditBox.VSplitLeft(Cut: ROWSIZE, pLeft: &MiddleButton, pRight: &EditBox); |
| 551 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &LeftButton); |
| 552 | Ui()->DoLabel(pRect: &LeftButton, pText: Localize(pStr: "Add command" ), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 553 | static CButtonContainer s_FinalAddButton; |
| 554 | if(Ui()->DoButton_FontIcon(pButtonContainer: &s_FinalAddButton, pText: FontIcons::FONT_ICON_PLUS, Checked: 0, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT)) |
| 555 | { |
| 556 | m_vBehaviorElements.emplace_back(args: std::make_unique<CBehaviorElements>()); |
| 557 | Changed = true; |
| 558 | SetUnsavedChanges(true); |
| 559 | } |
| 560 | } |
| 561 | s_BindToggleScrollRegion.End(); |
| 562 | } |
| 563 | return Changed; |
| 564 | } |
| 565 | |
| 566 | bool CMenusIngameTouchControls::(CUIRect Block) |
| 567 | { |
| 568 | // Visibilities time. This is button's visibility, not virtual. |
| 569 | bool Changed = false; |
| 570 | CUIRect EditBox, LeftButton, MiddleButton, RightButton; |
| 571 | |
| 572 | // Block.HSplitTop(ROWGAP, nullptr, &Block); |
| 573 | static CScrollRegion s_VisibilityScrollRegion; |
| 574 | CScrollRegionParams ScrollParam; |
| 575 | ScrollParam.m_ScrollUnit = 90.0f; |
| 576 | vec2 ScrollOffset(0.0f, 0.0f); |
| 577 | s_VisibilityScrollRegion.Begin(pClipRect: &Block, pOutOffset: &ScrollOffset, pParams: &ScrollParam); |
| 578 | Block.y += ScrollOffset.y; |
| 579 | |
| 580 | static std::vector<CButtonContainer> s_avVisibilitySelector[(int)CTouchControls::EButtonVisibility::NUM_VISIBILITIES]; |
| 581 | if(s_avVisibilitySelector[0].empty()) |
| 582 | std::for_each_n(first: s_avVisibilitySelector, n: (int)CTouchControls::EButtonVisibility::NUM_VISIBILITIES, f: [](auto &Element) { |
| 583 | Element.resize(3); |
| 584 | }); |
| 585 | const char **ppVisibilities = VisibilityNames(); |
| 586 | for(unsigned Current = 0; Current < (unsigned)CTouchControls::EButtonVisibility::NUM_VISIBILITIES; ++Current) |
| 587 | { |
| 588 | Block.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &Block); |
| 589 | Block.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &Block); |
| 590 | if(s_VisibilityScrollRegion.AddRect(Rect: EditBox)) |
| 591 | { |
| 592 | EditBox.VSplitMid(pLeft: &LeftButton, pRight: &MiddleButton); |
| 593 | MiddleButton.VSplitLeft(Cut: ScrollParam.m_ScrollbarWidth / 2.0f, pLeft: nullptr, pRight: &MiddleButton); |
| 594 | if(Current < (unsigned)CTouchControls::EButtonVisibility::EXTRA_MENU_1) |
| 595 | Ui()->DoLabel(pRect: &LeftButton, pText: ppVisibilities[Current], Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 596 | else |
| 597 | { |
| 598 | unsigned = Current - (unsigned)CTouchControls::EButtonVisibility::EXTRA_MENU_1 + 1; |
| 599 | char aBuf[64]; |
| 600 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %d" , ppVisibilities[(int)CTouchControls::EButtonVisibility::EXTRA_MENU_1], ExtraMenuNumber); |
| 601 | Ui()->DoLabel(pRect: &LeftButton, pText: aBuf, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 602 | } |
| 603 | MiddleButton.VSplitLeft(Cut: MiddleButton.w / 3.0f, pLeft: &LeftButton, pRight: &MiddleButton); |
| 604 | MiddleButton.VSplitMid(pLeft: &MiddleButton, pRight: &RightButton); |
| 605 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_avVisibilitySelector[Current][(int)EVisibilityType::INCLUDE], pText: Localize(pStr: "Included" , pContext: "Touch button visibility preview" ), Checked: m_aCachedVisibilities[Current] == EVisibilityType::INCLUDE, pRect: &LeftButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_L)) |
| 606 | { |
| 607 | m_aCachedVisibilities[Current] = EVisibilityType::INCLUDE; |
| 608 | SetUnsavedChanges(true); |
| 609 | Changed = true; |
| 610 | } |
| 611 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_avVisibilitySelector[Current][(int)EVisibilityType::EXCLUDE], pText: Localize(pStr: "Excluded" , pContext: "Touch button visibility preview" ), Checked: m_aCachedVisibilities[Current] == EVisibilityType::EXCLUDE, pRect: &MiddleButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_NONE)) |
| 612 | { |
| 613 | m_aCachedVisibilities[Current] = EVisibilityType::EXCLUDE; |
| 614 | SetUnsavedChanges(true); |
| 615 | Changed = true; |
| 616 | } |
| 617 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_avVisibilitySelector[Current][(int)EVisibilityType::IGNORE], pText: Localize(pStr: "Ignore" , pContext: "Touch button visibility preview" ), Checked: m_aCachedVisibilities[Current] == EVisibilityType::IGNORE, pRect: &RightButton, Flags: BUTTONFLAG_LEFT, pImageName: nullptr, Corners: IGraphics::CORNER_R)) |
| 618 | { |
| 619 | m_aCachedVisibilities[Current] = EVisibilityType::IGNORE; |
| 620 | SetUnsavedChanges(true); |
| 621 | Changed = true; |
| 622 | } |
| 623 | } |
| 624 | } |
| 625 | s_VisibilityScrollRegion.End(); |
| 626 | return Changed; |
| 627 | } |
| 628 | |
| 629 | void CMenusIngameTouchControls::(CUIRect MainView) |
| 630 | { |
| 631 | CUIRect LeftButton, MiddleButton, RightButton, EditBox, LabelRect, CommandRect, X, Y, W, H; |
| 632 | MainView.h = 600.0f - 40.0f - MainView.y; |
| 633 | MainView.Draw(Color: CMenus::ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
| 634 | MainView.Margin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 635 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &RightButton, pBottom: &MainView); |
| 636 | Ui()->DoLabel(pRect: &RightButton, pText: Localize(pStr: "Long press on a touch button to select it." ), Size: 15.0f, Align: TEXTALIGN_MC); |
| 637 | MainView.HSplitTop(Cut: MAINMARGIN, pTop: nullptr, pBottom: &MainView); |
| 638 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &MainView); |
| 639 | EditBox.VSplitLeft(Cut: (EditBox.w - SUBMARGIN) / 2.0f, pLeft: &LeftButton, pRight: &EditBox); |
| 640 | static CButtonContainer s_NewButton; |
| 641 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_NewButton, pText: Localize(pStr: "New button" ), Checked: 0, pRect: &LeftButton)) |
| 642 | NewVirtualButton(); |
| 643 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &MiddleButton); |
| 644 | static CButtonContainer s_SelectedButton; |
| 645 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_SelectedButton, pText: Localize(pStr: "Select button by touch" ), Checked: 0, pRect: &MiddleButton)) |
| 646 | GameClient()->m_Menus.SetActive(false); |
| 647 | |
| 648 | MainView.HSplitBottom(Cut: ROWSIZE, pTop: &MainView, pBottom: &EditBox); |
| 649 | MainView.HSplitBottom(Cut: ROWGAP, pTop: &MainView, pBottom: nullptr); |
| 650 | EditBox.VSplitLeft(Cut: ROWSIZE, pLeft: &LeftButton, pRight: &EditBox); |
| 651 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
| 652 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); |
| 653 | Ui()->DoLabel(pRect: &LeftButton, pText: FontIcons::FONT_ICON_MAGNIFYING_GLASS, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 654 | TextRender()->SetRenderFlags(0); |
| 655 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
| 656 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 657 | EditBox.VSplitLeft(Cut: EditBox.w / 3.0f, pLeft: &LeftButton, pRight: &EditBox); |
| 658 | char aBufSearch[64]; |
| 659 | str_format(buffer: aBufSearch, buffer_size: sizeof(aBufSearch), format: "%s:" , Localize(pStr: "Search" )); |
| 660 | Ui()->DoLabel(pRect: &LeftButton, pText: aBufSearch, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 661 | |
| 662 | if(Ui()->DoClearableEditBox(pLineInput: &m_FilterInput, pRect: &EditBox, FontSize: FONTSIZE)) |
| 663 | m_NeedFilter = true; |
| 664 | |
| 665 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 666 | // Makes the header labels looks better. |
| 667 | MainView.HSplitTop(Cut: FONTSIZE / CUi::ms_FontmodHeight, pTop: &EditBox, pBottom: &MainView); |
| 668 | static CListBox s_PreviewListBox; |
| 669 | EditBox.Draw(Color: ColorRGBA(1.0f, 1.0f, 1.0f, 0.25f), Corners: IGraphics::CORNER_T, Rounding: 5.0f); |
| 670 | EditBox.VSplitRight(Cut: s_PreviewListBox.ScrollbarWidthMax(), pLeft: &EditBox, pRight: nullptr); |
| 671 | EditBox.VSplitLeft(Cut: ROWSIZE, pLeft: nullptr, pRight: &EditBox); |
| 672 | |
| 673 | float PosWidth = EditBox.w * 4.0f / 9.0f; // Total pos width. |
| 674 | const float ButtonWidth = (EditBox.w - PosWidth - SUBMARGIN) / 2.0f; |
| 675 | PosWidth = (PosWidth - SUBMARGIN * 4.0f) / 4.0f; // Divided pos width. |
| 676 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &LabelRect, pRight: &EditBox); |
| 677 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 678 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &CommandRect, pRight: &EditBox); |
| 679 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 680 | EditBox.VSplitLeft(Cut: ButtonWidth, pLeft: &RightButton, pRight: &EditBox); |
| 681 | RightButton.VSplitLeft(Cut: PosWidth, pLeft: &X, pRight: &RightButton); |
| 682 | RightButton.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &RightButton); |
| 683 | RightButton.VSplitLeft(Cut: PosWidth, pLeft: &Y, pRight: &RightButton); |
| 684 | RightButton.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &RightButton); |
| 685 | RightButton.VSplitLeft(Cut: PosWidth, pLeft: &W, pRight: &RightButton); |
| 686 | RightButton.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &RightButton); |
| 687 | RightButton.VSplitLeft(Cut: PosWidth, pLeft: &H, pRight: &RightButton); |
| 688 | RightButton.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &RightButton); |
| 689 | const std::array<std::pair<CUIRect *, const char *>, (unsigned)ESortType::NUM_SORTS> = { |
| 690 | ._M_elems: {{&LabelRect, Localize(pStr: "Label" )}, |
| 691 | {&X, "X" }, |
| 692 | {&Y, "Y" }, |
| 693 | {&W, Localize(pStr: "Width" )}, |
| 694 | {&H, Localize(pStr: "Height" )}}}; |
| 695 | std::array<std::function<bool(CTouchControls::CTouchButton *, CTouchControls::CTouchButton *)>, (unsigned)ESortType::NUM_SORTS> aSortFunctions = { |
| 696 | [](CTouchControls::CTouchButton *pLhs, CTouchControls::CTouchButton *pRhs) { |
| 697 | if(pLhs->m_VisibilityCached == pRhs->m_VisibilityCached) |
| 698 | return str_comp(a: pLhs->m_pBehavior->GetLabel().m_pLabel, b: pRhs->m_pBehavior->GetLabel().m_pLabel) < 0; |
| 699 | return pLhs->m_VisibilityCached; |
| 700 | }, |
| 701 | [](CTouchControls::CTouchButton *pLhs, CTouchControls::CTouchButton *pRhs) { |
| 702 | if(pLhs->m_VisibilityCached == pRhs->m_VisibilityCached) |
| 703 | return pLhs->m_UnitRect.m_X < pRhs->m_UnitRect.m_X; |
| 704 | return pLhs->m_VisibilityCached; |
| 705 | }, |
| 706 | [](CTouchControls::CTouchButton *pLhs, CTouchControls::CTouchButton *pRhs) { |
| 707 | if(pLhs->m_VisibilityCached == pRhs->m_VisibilityCached) |
| 708 | return pLhs->m_UnitRect.m_Y < pRhs->m_UnitRect.m_Y; |
| 709 | return pLhs->m_VisibilityCached; |
| 710 | }, |
| 711 | [](CTouchControls::CTouchButton *pLhs, CTouchControls::CTouchButton *pRhs) { |
| 712 | if(pLhs->m_VisibilityCached == pRhs->m_VisibilityCached) |
| 713 | return pLhs->m_UnitRect.m_W < pRhs->m_UnitRect.m_W; |
| 714 | return pLhs->m_VisibilityCached; |
| 715 | }, |
| 716 | [](CTouchControls::CTouchButton *pLhs, CTouchControls::CTouchButton *pRhs) { |
| 717 | if(pLhs->m_VisibilityCached == pRhs->m_VisibilityCached) |
| 718 | return pLhs->m_UnitRect.m_H < pRhs->m_UnitRect.m_H; |
| 719 | return pLhs->m_VisibilityCached; |
| 720 | }}; |
| 721 | for(unsigned = (unsigned)ESortType::LABEL; HeaderIndex < (unsigned)ESortType::NUM_SORTS; HeaderIndex++) |
| 722 | { |
| 723 | if(GameClient()->m_Menus.DoButton_GridHeader(pId: &m_aSortHeaderIds[HeaderIndex], pText: "" , |
| 724 | Checked: (unsigned)m_SortType == HeaderIndex, pRect: aHeaderDatas[HeaderIndex].first)) |
| 725 | { |
| 726 | if(m_SortType != (ESortType)HeaderIndex) |
| 727 | { |
| 728 | m_NeedSort = true; |
| 729 | m_SortType = (ESortType)HeaderIndex; |
| 730 | } |
| 731 | } |
| 732 | Ui()->DoLabel(pRect: aHeaderDatas[HeaderIndex].first, pText: aHeaderDatas[HeaderIndex].second, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 733 | } |
| 734 | // Can't sort buttons basing on command, that's meaning less and too slow. |
| 735 | Ui()->DoLabel(pRect: &CommandRect, pText: Localize(pStr: "Command" ), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 736 | |
| 737 | if(m_NeedUpdatePreview) |
| 738 | { |
| 739 | m_NeedUpdatePreview = false; |
| 740 | m_vpButtons = GameClient()->m_TouchControls.GetButtonsEditor(); |
| 741 | m_NeedSort = true; |
| 742 | m_NeedFilter = true; |
| 743 | } |
| 744 | |
| 745 | if(m_NeedFilter || m_NeedSort) |
| 746 | { |
| 747 | // Sorting can be done directly. |
| 748 | if(m_NeedSort) |
| 749 | { |
| 750 | m_NeedSort = false; |
| 751 | m_NeedFilter = true; |
| 752 | std::sort(first: m_vpButtons.begin(), last: m_vpButtons.end(), comp: aSortFunctions[(unsigned)m_SortType]); |
| 753 | } |
| 754 | |
| 755 | m_vpMutableButtons = m_vpButtons; |
| 756 | |
| 757 | // Filtering should be done separately. |
| 758 | if(m_NeedFilter && !m_FilterInput.IsEmpty()) |
| 759 | { |
| 760 | // Both label and command will be considered. |
| 761 | const auto DeleteIt = std::remove_if(first: m_vpMutableButtons.begin(), last: m_vpMutableButtons.end(), pred: [&](CTouchControls::CTouchButton *pButton) { |
| 762 | if(str_utf8_find_nocase(haystack: pButton->m_pBehavior->GetLabel().m_pLabel, needle: m_FilterInput.GetString()) != nullptr) |
| 763 | return false; |
| 764 | std::string Command = DetermineTouchButtonCommandLabel(pButton); |
| 765 | if(str_utf8_find_nocase(haystack: Command.c_str(), needle: m_FilterInput.GetString()) != nullptr) |
| 766 | return false; |
| 767 | return true; |
| 768 | }); |
| 769 | m_vpMutableButtons.erase(first: DeleteIt, last: m_vpMutableButtons.end()); |
| 770 | } |
| 771 | m_NeedFilter = false; |
| 772 | } |
| 773 | |
| 774 | if(!m_vpMutableButtons.empty()) |
| 775 | { |
| 776 | s_PreviewListBox.SetActive(true); |
| 777 | s_PreviewListBox.DoStart(RowHeight: ROWSIZE, NumItems: m_vpMutableButtons.size(), ItemsPerRow: 1, RowsPerScroll: 4, SelectedIndex: m_SelectedPreviewButtonIndex, pRect: &MainView, Background: true, BackgroundCorners: IGraphics::CORNER_B); |
| 778 | for(unsigned ButtonIndex = 0; ButtonIndex < m_vpMutableButtons.size(); ButtonIndex++) |
| 779 | { |
| 780 | CTouchControls::CTouchButton *pButton = m_vpMutableButtons[ButtonIndex]; |
| 781 | const CListboxItem ListItem = s_PreviewListBox.DoNextItem(pId: &m_vpMutableButtons[ButtonIndex], Selected: m_SelectedPreviewButtonIndex == (int)ButtonIndex); |
| 782 | if(ListItem.m_Visible) |
| 783 | { |
| 784 | EditBox = ListItem.m_Rect; |
| 785 | EditBox.VSplitLeft(Cut: ROWSIZE, pLeft: &LeftButton, pRight: &EditBox); |
| 786 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
| 787 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); |
| 788 | Ui()->DoLabel(pRect: &LeftButton, pText: m_vpMutableButtons[ButtonIndex]->m_VisibilityCached ? FontIcons::FONT_ICON_EYE : FontIcons::FONT_ICON_EYE_SLASH, Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 789 | TextRender()->SetRenderFlags(0); |
| 790 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
| 791 | EditBox.VSplitLeft(Cut: LabelRect.w, pLeft: &LeftButton, pRight: &EditBox); |
| 792 | const char *pLabel = pButton->m_pBehavior->GetLabel().m_pLabel; |
| 793 | const auto LabelType = pButton->m_pBehavior->GetLabel().m_Type; |
| 794 | if(LabelType == CTouchControls::CButtonLabel::EType::PLAIN) |
| 795 | { |
| 796 | SLabelProperties Props; |
| 797 | Props.m_MaxWidth = LeftButton.w; |
| 798 | Props.m_EnableWidthCheck = false; |
| 799 | Props.m_EllipsisAtEnd = true; |
| 800 | Ui()->DoLabel(pRect: &LeftButton, pText: pLabel, Size: FONTSIZE, Align: TEXTALIGN_ML, LabelProps: Props); |
| 801 | } |
| 802 | else if(LabelType == CTouchControls::CButtonLabel::EType::LOCALIZED) |
| 803 | { |
| 804 | pLabel = Localize(pStr: pLabel); |
| 805 | SLabelProperties Props; |
| 806 | Props.m_MaxWidth = LeftButton.w; |
| 807 | Props.m_EnableWidthCheck = false; |
| 808 | Props.m_EllipsisAtEnd = true; |
| 809 | Ui()->DoLabel(pRect: &LeftButton, pText: pLabel, Size: FONTSIZE, Align: TEXTALIGN_ML, LabelProps: Props); |
| 810 | } |
| 811 | else if(LabelType == CTouchControls::CButtonLabel::EType::ICON) |
| 812 | { |
| 813 | SLabelProperties Props; |
| 814 | Props.m_MaxWidth = LeftButton.w; |
| 815 | Props.m_EnableWidthCheck = false; |
| 816 | Props.m_EllipsisAtEnd = true; |
| 817 | TextRender()->SetFontPreset(EFontPreset::ICON_FONT); |
| 818 | TextRender()->SetRenderFlags(ETextRenderFlags::TEXT_RENDER_FLAG_ONLY_ADVANCE_WIDTH | ETextRenderFlags::TEXT_RENDER_FLAG_NO_X_BEARING | ETextRenderFlags::TEXT_RENDER_FLAG_NO_Y_BEARING); |
| 819 | Ui()->DoLabel(pRect: &LeftButton, pText: pLabel, Size: FONTSIZE, Align: TEXTALIGN_ML, LabelProps: Props); |
| 820 | TextRender()->SetRenderFlags(0); |
| 821 | TextRender()->SetFontPreset(EFontPreset::DEFAULT_FONT); |
| 822 | } |
| 823 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 824 | EditBox.VSplitLeft(Cut: CommandRect.w, pLeft: &LeftButton, pRight: &EditBox); |
| 825 | std::string Command = DetermineTouchButtonCommandLabel(pButton); |
| 826 | SLabelProperties Props; |
| 827 | Props.m_MaxWidth = LeftButton.w; |
| 828 | Props.m_EnableWidthCheck = false; |
| 829 | Props.m_EllipsisAtEnd = true; |
| 830 | Ui()->DoLabel(pRect: &LeftButton, pText: Command.c_str(), Size: FONTSIZE, Align: TEXTALIGN_ML, LabelProps: Props); |
| 831 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 832 | EditBox.VSplitLeft(Cut: X.w, pLeft: &LeftButton, pRight: &EditBox); |
| 833 | Ui()->DoLabel(pRect: &LeftButton, pText: std::to_string(val: pButton->m_UnitRect.m_X).c_str(), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 834 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 835 | EditBox.VSplitLeft(Cut: Y.w, pLeft: &LeftButton, pRight: &EditBox); |
| 836 | Ui()->DoLabel(pRect: &LeftButton, pText: std::to_string(val: pButton->m_UnitRect.m_Y).c_str(), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 837 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 838 | EditBox.VSplitLeft(Cut: W.w, pLeft: &LeftButton, pRight: &EditBox); |
| 839 | Ui()->DoLabel(pRect: &LeftButton, pText: std::to_string(val: pButton->m_UnitRect.m_W).c_str(), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 840 | EditBox.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 841 | EditBox.VSplitLeft(Cut: H.w, pLeft: &LeftButton, pRight: &EditBox); |
| 842 | Ui()->DoLabel(pRect: &LeftButton, pText: std::to_string(val: pButton->m_UnitRect.m_H).c_str(), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 843 | } |
| 844 | } |
| 845 | |
| 846 | m_SelectedPreviewButtonIndex = s_PreviewListBox.DoEnd(); |
| 847 | if(s_PreviewListBox.WasItemActivated()) |
| 848 | { |
| 849 | GameClient()->m_TouchControls.SetSelectedButton(m_vpMutableButtons[m_SelectedPreviewButtonIndex]); |
| 850 | CacheAllSettingsFromTarget(pTargetButton: m_vpMutableButtons[m_SelectedPreviewButtonIndex]); |
| 851 | SetUnsavedChanges(false); |
| 852 | UpdateSampleButton(); |
| 853 | } |
| 854 | } |
| 855 | else |
| 856 | { |
| 857 | // Copied from menus_browser.cpp |
| 858 | MainView.HMargin(Cut: (MainView.h - (16.0f + 18.0f + 8.0f)) / 2.0f, pOtherRect: &LeftButton); |
| 859 | LeftButton.HSplitTop(Cut: 16.0f, pTop: &LeftButton, pBottom: &MiddleButton); |
| 860 | MiddleButton.HSplitTop(Cut: 8.0f, pTop: nullptr, pBottom: &MiddleButton); |
| 861 | MiddleButton.VMargin(Cut: (MiddleButton.w - 200.0f) / 2.0f, pOtherRect: &MiddleButton); |
| 862 | Ui()->DoLabel(pRect: &LeftButton, pText: Localize(pStr: "No buttons match your filter criteria" ), Size: 16.0f, Align: TEXTALIGN_MC); |
| 863 | static CButtonContainer s_ResetButton; |
| 864 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ResetButton, pText: Localize(pStr: "Reset filter" ), Checked: 0, pRect: &MiddleButton)) |
| 865 | { |
| 866 | m_FilterInput.Clear(); |
| 867 | m_NeedUpdatePreview = true; |
| 868 | } |
| 869 | } |
| 870 | } |
| 871 | |
| 872 | void CMenusIngameTouchControls::(CUIRect SelectingTab) |
| 873 | { |
| 874 | CUIRect LeftButton; |
| 875 | SelectingTab.VSplitLeft(Cut: SelectingTab.w / 4.0f, pLeft: &LeftButton, pRight: &SelectingTab); |
| 876 | static CButtonContainer s_FileTab; |
| 877 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: &s_FileTab, pText: Localize(pStr: "File" ), Checked: m_CurrentMenu == EMenuType::MENU_FILE, pRect: &LeftButton, Corners: IGraphics::CORNER_TL)) |
| 878 | m_CurrentMenu = EMenuType::MENU_FILE; |
| 879 | SelectingTab.VSplitLeft(Cut: SelectingTab.w / 3.0f, pLeft: &LeftButton, pRight: &SelectingTab); |
| 880 | static CButtonContainer s_ButtonTab; |
| 881 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: &s_ButtonTab, pText: Localize(pStr: "Buttons" ), Checked: m_CurrentMenu == EMenuType::MENU_BUTTONS, pRect: &LeftButton, Corners: IGraphics::CORNER_NONE)) |
| 882 | m_CurrentMenu = EMenuType::MENU_BUTTONS; |
| 883 | SelectingTab.VSplitLeft(Cut: SelectingTab.w / 2.0f, pLeft: &LeftButton, pRight: &SelectingTab); |
| 884 | static CButtonContainer ; |
| 885 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: &s_SettingsMenuTab, pText: Localize(pStr: "Settings" ), Checked: m_CurrentMenu == EMenuType::MENU_SETTINGS, pRect: &LeftButton, Corners: IGraphics::CORNER_NONE)) |
| 886 | m_CurrentMenu = EMenuType::MENU_SETTINGS; |
| 887 | SelectingTab.VSplitLeft(Cut: SelectingTab.w / 1.0f, pLeft: &LeftButton, pRight: &SelectingTab); |
| 888 | static CButtonContainer s_PreviewTab; |
| 889 | if(GameClient()->m_Menus.DoButton_MenuTab(pButtonContainer: &s_PreviewTab, pText: Localize(pStr: "Preview" ), Checked: m_CurrentMenu == EMenuType::MENU_PREVIEW, pRect: &LeftButton, Corners: IGraphics::CORNER_TR)) |
| 890 | m_CurrentMenu = EMenuType::MENU_PREVIEW; |
| 891 | } |
| 892 | |
| 893 | void CMenusIngameTouchControls::(CUIRect MainView) |
| 894 | { |
| 895 | CUIRect EditBox, Row, Label, Button; |
| 896 | MainView.h = 2 * MAINMARGIN + 4 * ROWSIZE + 3 * ROWGAP; |
| 897 | MainView.Draw(Color: CMenus::ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
| 898 | MainView.VMargin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 899 | MainView.HSplitTop(Cut: MAINMARGIN, pTop: nullptr, pBottom: &MainView); |
| 900 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &MainView); |
| 901 | static CButtonContainer s_ActiveColorPicker; |
| 902 | ColorHSLA ColorTest = GameClient()->m_Menus.DoLine_ColorPicker(pResetId: &s_ActiveColorPicker, LineSize: ROWSIZE, LabelSize: 15.0f, BottomMargin: 5.0f, pMainRect: &EditBox, pText: Localize(pStr: "Active color" ), pColorValue: &m_ColorActive, DefaultColor: GameClient()->m_TouchControls.DefaultBackgroundColorActive(), CheckBoxSpacing: false, pCheckBoxValue: nullptr, Alpha: true); |
| 903 | GameClient()->m_TouchControls.SetBackgroundColorActive(color_cast<ColorRGBA>(hsl: ColorHSLA(m_ColorActive, true))); |
| 904 | if(color_cast<ColorRGBA>(hsl: ColorTest) != GameClient()->m_TouchControls.BackgroundColorActive()) |
| 905 | GameClient()->m_TouchControls.SetEditingChanges(true); |
| 906 | |
| 907 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 908 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &MainView); |
| 909 | static CButtonContainer s_InactiveColorPicker; |
| 910 | ColorTest = GameClient()->m_Menus.DoLine_ColorPicker(pResetId: &s_InactiveColorPicker, LineSize: ROWSIZE, LabelSize: 15.0f, BottomMargin: 5.0f, pMainRect: &EditBox, pText: Localize(pStr: "Inactive color" ), pColorValue: &m_ColorInactive, DefaultColor: GameClient()->m_TouchControls.DefaultBackgroundColorInactive(), CheckBoxSpacing: false, pCheckBoxValue: nullptr, Alpha: true); |
| 911 | GameClient()->m_TouchControls.SetBackgroundColorInactive(color_cast<ColorRGBA>(hsl: ColorHSLA(m_ColorInactive, true))); |
| 912 | if(color_cast<ColorRGBA>(hsl: ColorTest) != GameClient()->m_TouchControls.BackgroundColorInactive()) |
| 913 | GameClient()->m_TouchControls.SetEditingChanges(true); |
| 914 | |
| 915 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 916 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &Row, pBottom: &MainView); |
| 917 | Row.VSplitMid(pLeft: &Label, pRight: &Button); |
| 918 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Direct touch input while ingame" ), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 919 | |
| 920 | const char *apIngameTouchModes[(int)CTouchControls::EDirectTouchIngameMode::NUM_STATES] = {Localize(pStr: "Disabled" , pContext: "Direct touch input" ), Localize(pStr: "Active action" , pContext: "Direct touch input" ), Localize(pStr: "Aim" , pContext: "Direct touch input" ), Localize(pStr: "Fire" , pContext: "Direct touch input" ), Localize(pStr: "Hook" , pContext: "Direct touch input" )}; |
| 921 | const CTouchControls::EDirectTouchIngameMode OldDirectTouchIngame = GameClient()->m_TouchControls.DirectTouchIngame(); |
| 922 | static CUi::SDropDownState s_DirectTouchIngameDropDownState; |
| 923 | static CScrollRegion s_DirectTouchIngameDropDownScrollRegion; |
| 924 | s_DirectTouchIngameDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_DirectTouchIngameDropDownScrollRegion; |
| 925 | const CTouchControls::EDirectTouchIngameMode NewDirectTouchIngame = (CTouchControls::EDirectTouchIngameMode)Ui()->DoDropDown(pRect: &Button, CurSelection: (int)OldDirectTouchIngame, pStrs: apIngameTouchModes, Num: std::size(apIngameTouchModes), State&: s_DirectTouchIngameDropDownState); |
| 926 | if(OldDirectTouchIngame != NewDirectTouchIngame) |
| 927 | { |
| 928 | GameClient()->m_TouchControls.SetDirectTouchIngame(NewDirectTouchIngame); |
| 929 | } |
| 930 | |
| 931 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 932 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &Row, pBottom: &MainView); |
| 933 | Row.VSplitMid(pLeft: &Label, pRight: &Button); |
| 934 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Direct touch input while spectating" ), Size: FONTSIZE, Align: TEXTALIGN_ML); |
| 935 | |
| 936 | const char *apSpectateTouchModes[(int)CTouchControls::EDirectTouchSpectateMode::NUM_STATES] = {Localize(pStr: "Disabled" , pContext: "Direct touch input" ), Localize(pStr: "Aim" , pContext: "Direct touch input" )}; |
| 937 | const CTouchControls::EDirectTouchSpectateMode OldDirectTouchSpectate = GameClient()->m_TouchControls.DirectTouchSpectate(); |
| 938 | static CUi::SDropDownState s_DirectTouchSpectateDropDownState; |
| 939 | static CScrollRegion s_DirectTouchSpectateDropDownScrollRegion; |
| 940 | s_DirectTouchSpectateDropDownState.m_SelectionPopupContext.m_pScrollRegion = &s_DirectTouchSpectateDropDownScrollRegion; |
| 941 | const CTouchControls::EDirectTouchSpectateMode NewDirectTouchSpectate = (CTouchControls::EDirectTouchSpectateMode)Ui()->DoDropDown(pRect: &Button, CurSelection: (int)OldDirectTouchSpectate, pStrs: apSpectateTouchModes, Num: std::size(apSpectateTouchModes), State&: s_DirectTouchSpectateDropDownState); |
| 942 | if(OldDirectTouchSpectate != NewDirectTouchSpectate) |
| 943 | { |
| 944 | GameClient()->m_TouchControls.SetDirectTouchSpectate(NewDirectTouchSpectate); |
| 945 | } |
| 946 | } |
| 947 | |
| 948 | void CMenusIngameTouchControls::(CUIRect MainView) |
| 949 | { |
| 950 | CUIRect EditBox; |
| 951 | MainView.h = 600.0f - 40.0f - MainView.y; |
| 952 | MainView.Draw(Color: CMenus::ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
| 953 | MainView.VMargin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 954 | MainView.HMargin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 955 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &EditBox, pBottom: &MainView); |
| 956 | Ui()->DoLabel(pRect: &EditBox, pText: Localize(pStr: "Preview button visibility while the editor is active." ), Size: FONTSIZE, Align: TEXTALIGN_MC); |
| 957 | MainView.HSplitBottom(Cut: ROWSIZE, pTop: &MainView, pBottom: &EditBox); |
| 958 | MainView.HSplitBottom(Cut: ROWGAP, pTop: &MainView, pBottom: nullptr); |
| 959 | EditBox.VSplitLeft(Cut: MAINMARGIN, pLeft: nullptr, pRight: &EditBox); |
| 960 | static CButtonContainer s_PreviewAllCheckBox; |
| 961 | bool Preview = GameClient()->m_TouchControls.PreviewAllButtons(); |
| 962 | if(GameClient()->m_Menus.DoButton_CheckBox(pId: &s_PreviewAllCheckBox, pText: Localize(pStr: "Show all buttons" ), Checked: Preview ? 1 : 0, pRect: &EditBox)) |
| 963 | { |
| 964 | GameClient()->m_TouchControls.SetPreviewAllButtons(!Preview); |
| 965 | } |
| 966 | MainView.Draw(Color: ColorRGBA(0.0f, 0.0f, 0.0f, 0.15f), Corners: IGraphics::CORNER_ALL, Rounding: 10.0f); |
| 967 | MainView.VMargin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 968 | MainView.HMargin(Cut: ROWGAP, pOtherRect: &MainView); |
| 969 | static CScrollRegion s_VirtualVisibilityScrollRegion; |
| 970 | CScrollRegionParams ScrollParam; |
| 971 | ScrollParam.m_ScrollUnit = 90.0f; |
| 972 | vec2 ScrollOffset(0.0f, 0.0f); |
| 973 | s_VirtualVisibilityScrollRegion.Begin(pClipRect: &MainView, pOutOffset: &ScrollOffset, pParams: &ScrollParam); |
| 974 | MainView.y += ScrollOffset.y; |
| 975 | std::array<bool, (size_t)CTouchControls::EButtonVisibility::NUM_VISIBILITIES> aVirtualVisibilities = GameClient()->m_TouchControls.VirtualVisibilities(); |
| 976 | const char **ppVisibilities = VisibilityNames(); |
| 977 | for(unsigned Current = 0; Current < (unsigned)CTouchControls::EButtonVisibility::NUM_VISIBILITIES; ++Current) |
| 978 | { |
| 979 | MainView.HSplitTop(Cut: ROWSIZE + ROWGAP, pTop: &EditBox, pBottom: &MainView); |
| 980 | if(s_VirtualVisibilityScrollRegion.AddRect(Rect: EditBox)) |
| 981 | { |
| 982 | EditBox.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &EditBox); |
| 983 | if(Current < (unsigned)CTouchControls::EButtonVisibility::EXTRA_MENU_1) |
| 984 | { |
| 985 | if(GameClient()->m_Menus.DoButton_CheckBox(pId: &m_aVisibilityIds[Current], pText: ppVisibilities[Current], Checked: aVirtualVisibilities[Current] == 1, pRect: &EditBox)) |
| 986 | GameClient()->m_TouchControls.ReverseVirtualVisibilities(Number: Current); |
| 987 | } |
| 988 | else |
| 989 | { |
| 990 | unsigned = Current - (unsigned)CTouchControls::EButtonVisibility::EXTRA_MENU_1 + 1; |
| 991 | char aBuf[64]; |
| 992 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "%s %d" , ppVisibilities[(int)CTouchControls::EButtonVisibility::EXTRA_MENU_1], ExtraMenuNumber); |
| 993 | if(GameClient()->m_Menus.DoButton_CheckBox(pId: &m_aVisibilityIds[Current], pText: aBuf, Checked: aVirtualVisibilities[Current] == 1, pRect: &EditBox)) |
| 994 | GameClient()->m_TouchControls.ReverseVirtualVisibilities(Number: Current); |
| 995 | } |
| 996 | } |
| 997 | } |
| 998 | s_VirtualVisibilityScrollRegion.End(); |
| 999 | } |
| 1000 | |
| 1001 | void CMenusIngameTouchControls::(CUIRect MainView) |
| 1002 | { |
| 1003 | CUIRect Label, Button, Row; |
| 1004 | MainView.h = 2 * MAINMARGIN + 4 * ROWSIZE + 3 * ROWGAP; |
| 1005 | MainView.Draw(Color: CMenus::ms_ColorTabbarActive, Corners: IGraphics::CORNER_B, Rounding: 10.0f); |
| 1006 | MainView.Margin(Cut: MAINMARGIN, pOtherRect: &MainView); |
| 1007 | |
| 1008 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &Row, pBottom: &MainView); |
| 1009 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 1010 | Row.VSplitLeft(Cut: Row.h, pLeft: nullptr, pRight: &Row); |
| 1011 | Row.VSplitRight(Cut: Row.h, pLeft: &Row, pRight: &Button); |
| 1012 | Row.VMargin(Cut: 5.0f, pOtherRect: &Label); |
| 1013 | Ui()->DoLabel(pRect: &Label, pText: Localize(pStr: "Edit touch controls" ), Size: 20.0f, Align: TEXTALIGN_MC); |
| 1014 | |
| 1015 | static CButtonContainer s_OpenHelpButton; |
| 1016 | if(Ui()->DoButton_FontIcon(pButtonContainer: &s_OpenHelpButton, pText: FontIcons::FONT_ICON_QUESTION, Checked: 0, pRect: &Button, Flags: BUTTONFLAG_LEFT)) |
| 1017 | { |
| 1018 | Client()->ViewLink(pLink: Localize(pStr: "https://wiki.ddnet.org/wiki/Touch_controls" )); |
| 1019 | } |
| 1020 | |
| 1021 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &Row, pBottom: &MainView); |
| 1022 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 1023 | |
| 1024 | Row.VSplitLeft(Cut: (Row.w - SUBMARGIN) / 2.0f, pLeft: &Button, pRight: &Row); |
| 1025 | static CButtonContainer s_SaveConfigurationButton; |
| 1026 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_SaveConfigurationButton, pText: Localize(pStr: "Save changes" ), Checked: GameClient()->m_TouchControls.HasEditingChanges() ? 0 : 1, pRect: &Button)) |
| 1027 | { |
| 1028 | if(GameClient()->m_TouchControls.SaveConfigurationToFile()) |
| 1029 | { |
| 1030 | GameClient()->m_TouchControls.SetEditingChanges(false); |
| 1031 | } |
| 1032 | else |
| 1033 | { |
| 1034 | SWarning Warning(Localize(pStr: "Error saving touch controls" ), Localize(pStr: "Could not save touch controls to file. See local console for details." )); |
| 1035 | Warning.m_AutoHide = false; |
| 1036 | Client()->AddWarning(Warning); |
| 1037 | } |
| 1038 | } |
| 1039 | |
| 1040 | Row.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &Button); |
| 1041 | if(GameClient()->m_TouchControls.HasEditingChanges()) |
| 1042 | { |
| 1043 | TextRender()->TextColor(Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f)); |
| 1044 | Ui()->DoLabel(pRect: &Button, pText: Localize(pStr: "Unsaved changes" ), Size: 14.0f, Align: TEXTALIGN_MC); |
| 1045 | TextRender()->TextColor(Color: TextRender()->DefaultTextColor()); |
| 1046 | } |
| 1047 | |
| 1048 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &Row, pBottom: &MainView); |
| 1049 | MainView.HSplitTop(Cut: ROWGAP, pTop: nullptr, pBottom: &MainView); |
| 1050 | |
| 1051 | Row.VSplitLeft(Cut: (Row.w - SUBMARGIN) / 2.0f, pLeft: &Button, pRight: &Row); |
| 1052 | static CButtonContainer s_DiscardChangesButton; |
| 1053 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_DiscardChangesButton, pText: Localize(pStr: "Discard changes" ), Checked: GameClient()->m_TouchControls.HasEditingChanges() ? 0 : 1, pRect: &Button)) |
| 1054 | { |
| 1055 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Discard changes" ), |
| 1056 | pMessage: Localize(pStr: "Are you sure that you want to discard the current changes to the touch controls?" ), |
| 1057 | pConfirmButtonLabel: Localize(pStr: "Yes" ), pCancelButtonLabel: Localize(pStr: "No" ), |
| 1058 | pfnConfirmButtonCallback: &CMenus::PopupConfirmDiscardTouchControlsChanges); |
| 1059 | } |
| 1060 | |
| 1061 | Row.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &Button); |
| 1062 | static CButtonContainer s_ResetButton; |
| 1063 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ResetButton, pText: Localize(pStr: "Reset to defaults" ), Checked: 0, pRect: &Button)) |
| 1064 | { |
| 1065 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Reset to defaults" ), |
| 1066 | pMessage: Localize(pStr: "Are you sure that you want to reset the touch controls to default?" ), |
| 1067 | pConfirmButtonLabel: Localize(pStr: "Yes" ), pCancelButtonLabel: Localize(pStr: "No" ), |
| 1068 | pfnConfirmButtonCallback: &CMenus::PopupConfirmResetTouchControls); |
| 1069 | } |
| 1070 | |
| 1071 | MainView.HSplitTop(Cut: ROWSIZE, pTop: &Row, pBottom: &MainView); |
| 1072 | MainView.HSplitTop(Cut: MAINMARGIN, pTop: nullptr, pBottom: &MainView); |
| 1073 | |
| 1074 | Row.VSplitLeft(Cut: (Row.w - SUBMARGIN) / 2.0f, pLeft: &Button, pRight: &Row); |
| 1075 | static CButtonContainer s_ClipboardImportButton; |
| 1076 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ClipboardImportButton, pText: Localize(pStr: "Import from clipboard" ), Checked: 0, pRect: &Button)) |
| 1077 | { |
| 1078 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Import from clipboard" ), |
| 1079 | pMessage: Localize(pStr: "Are you sure that you want to import the touch controls from the clipboard? This will overwrite your current touch controls." ), |
| 1080 | pConfirmButtonLabel: Localize(pStr: "Yes" ), pCancelButtonLabel: Localize(pStr: "No" ), |
| 1081 | pfnConfirmButtonCallback: &CMenus::PopupConfirmImportTouchControlsClipboard); |
| 1082 | } |
| 1083 | |
| 1084 | Row.VSplitLeft(Cut: SUBMARGIN, pLeft: nullptr, pRight: &Button); |
| 1085 | static CButtonContainer s_ClipboardExportButton; |
| 1086 | if(GameClient()->m_Menus.DoButton_Menu(pButtonContainer: &s_ClipboardExportButton, pText: Localize(pStr: "Export to clipboard" ), Checked: 0, pRect: &Button)) |
| 1087 | { |
| 1088 | GameClient()->m_TouchControls.SaveConfigurationToClipboard(); |
| 1089 | } |
| 1090 | } |
| 1091 | |
| 1092 | // Check if CTouchControls need CMenus to open any popups. |
| 1093 | void CMenusIngameTouchControls::(CTouchControls::CPopupParam ) |
| 1094 | { |
| 1095 | m_pOldSelectedButton = PopupParam.m_pOldSelectedButton; |
| 1096 | m_pNewSelectedButton = PopupParam.m_pNewSelectedButton; |
| 1097 | m_CloseMenu = !PopupParam.m_KeepMenuOpen; |
| 1098 | switch(PopupParam.m_PopupType) |
| 1099 | { |
| 1100 | case CTouchControls::EPopupType::BUTTON_CHANGED: ChangeSelectedButtonWhileHavingUnsavedChanges(); break; |
| 1101 | case CTouchControls::EPopupType::NO_SPACE: NoSpaceForOverlappingButton(); break; |
| 1102 | case CTouchControls::EPopupType::BUTTON_INVISIBLE: SelectedButtonNotVisible(); break; |
| 1103 | // The NUM_POPUPS will not call the function. |
| 1104 | default: dbg_assert_failed("Unknown popup type = %d." , (int)PopupParam.m_PopupType); |
| 1105 | } |
| 1106 | } |
| 1107 | |
| 1108 | void CMenusIngameTouchControls::() const |
| 1109 | { |
| 1110 | // Both old and new button pointer can be nullptr. |
| 1111 | // Saving settings to the old selected button(nullptr = create), then switch to new selected button(new = haven't created). |
| 1112 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Unsaved changes" ), pMessage: Localize(pStr: "Save all changes before switching selected button?" ), pConfirmButtonLabel: Localize(pStr: "Save" ), pCancelButtonLabel: Localize(pStr: "Discard" ), pfnConfirmButtonCallback: &CMenus::PopupConfirmChangeSelectedButton, ConfirmNextPopup: CMenus::POPUP_NONE, pfnCancelButtonCallback: &CMenus::PopupCancelChangeSelectedButton); |
| 1113 | } |
| 1114 | |
| 1115 | void CMenusIngameTouchControls::() const |
| 1116 | { |
| 1117 | GameClient()->m_Menus.PopupMessage(pTitle: Localize(pStr: "No space for button" ), pMessage: Localize(pStr: "There is not enough space available for the button. Check its visibilities and size." ), pButtonLabel: Localize(pStr: "Ok" )); |
| 1118 | } |
| 1119 | |
| 1120 | void CMenusIngameTouchControls::() |
| 1121 | { |
| 1122 | // Cancel shouldn't do anything but open ingame menu, the menu is already opened now. |
| 1123 | m_CloseMenu = false; |
| 1124 | GameClient()->m_Menus.PopupConfirm(pTitle: Localize(pStr: "Selected button not visible" ), pMessage: Localize(pStr: "The selected button is not visible. Do you want to deselect it or edit its visibility?" ), pConfirmButtonLabel: Localize(pStr: "Deselect" ), pCancelButtonLabel: Localize(pStr: "Edit" ), pfnConfirmButtonCallback: &CMenus::PopupConfirmSelectedNotVisible); |
| 1125 | } |
| 1126 | |
| 1127 | bool CMenusIngameTouchControls::() const |
| 1128 | { |
| 1129 | return GameClient()->m_TouchControls.HasUnsavedChanges(); |
| 1130 | } |
| 1131 | |
| 1132 | void CMenusIngameTouchControls::(bool UnsavedChanges) |
| 1133 | { |
| 1134 | GameClient()->m_TouchControls.SetUnsavedChanges(UnsavedChanges); |
| 1135 | } |
| 1136 | |
| 1137 | // Check if cached settings are legal. |
| 1138 | bool CMenusIngameTouchControls::() const |
| 1139 | { |
| 1140 | std::vector<const char *> vpErrors; |
| 1141 | char aBuf[256]; |
| 1142 | int X = m_InputX.GetInteger(); |
| 1143 | int Y = m_InputY.GetInteger(); |
| 1144 | int W = m_InputW.GetInteger(); |
| 1145 | int H = m_InputH.GetInteger(); |
| 1146 | // Illegal size settings. |
| 1147 | if(W < CTouchControls::BUTTON_SIZE_MINIMUM || W > CTouchControls::BUTTON_SIZE_MAXIMUM || H < CTouchControls::BUTTON_SIZE_MINIMUM || H > CTouchControls::BUTTON_SIZE_MAXIMUM) |
| 1148 | { |
| 1149 | str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: Localize(pStr: "Width and height are required to be within the range from %d to %d." ), CTouchControls::BUTTON_SIZE_MINIMUM, CTouchControls::BUTTON_SIZE_MAXIMUM); |
| 1150 | vpErrors.emplace_back(args&: aBuf); |
| 1151 | } |
| 1152 | if(X < 0 || Y < 0 || X + W > CTouchControls::BUTTON_SIZE_SCALE || Y + H > CTouchControls::BUTTON_SIZE_SCALE) |
| 1153 | { |
| 1154 | vpErrors.emplace_back(args: Localize(pStr: "Button position is outside of the screen." )); |
| 1155 | } |
| 1156 | if(GameClient()->m_TouchControls.IsRectOverlapping(MyRect: {.m_X: X, .m_Y: Y, .m_W: W, .m_H: H}, Shape: m_CachedShape)) |
| 1157 | { |
| 1158 | vpErrors.emplace_back(args: Localize(pStr: "The selected button is overlapping with other buttons." )); |
| 1159 | } |
| 1160 | if(!vpErrors.empty()) |
| 1161 | { |
| 1162 | char aErrorMessage[1024] = "" ; |
| 1163 | for(const char *pError : vpErrors) |
| 1164 | { |
| 1165 | if(aErrorMessage[0] != '\0') |
| 1166 | str_append(dst&: aErrorMessage, src: "\n" ); |
| 1167 | str_append(dst&: aErrorMessage, src: pError); |
| 1168 | } |
| 1169 | GameClient()->m_Menus.PopupMessage(pTitle: Localize(pStr: "Wrong button settings" ), pMessage: aErrorMessage, pButtonLabel: Localize(pStr: "Ok" )); |
| 1170 | return false; |
| 1171 | } |
| 1172 | else |
| 1173 | { |
| 1174 | return true; |
| 1175 | } |
| 1176 | } |
| 1177 | |
| 1178 | // All default settings are here. |
| 1179 | void CMenusIngameTouchControls::() |
| 1180 | { |
| 1181 | // Reset all cached values. |
| 1182 | m_EditBehaviorType = EBehaviorType::BIND; |
| 1183 | m_PredefinedBehaviorType = EPredefinedType::EXTRA_MENU; |
| 1184 | m_CachedExtraMenuNumber = 0; |
| 1185 | m_vBehaviorElements.clear(); |
| 1186 | m_vBehaviorElements.emplace_back(args: std::make_unique<CBehaviorElements>()); |
| 1187 | m_vBehaviorElements.emplace_back(args: std::make_unique<CBehaviorElements>()); |
| 1188 | m_aCachedVisibilities.fill(u: EVisibilityType::IGNORE); // 2 means don't have the visibility, true:1,false:0 |
| 1189 | m_aCachedVisibilities[(int)CTouchControls::EButtonVisibility::DEMO_PLAYER] = EVisibilityType::EXCLUDE; |
| 1190 | // These things can't be empty. std::stoi can't cast empty string. |
| 1191 | SetPosInputs({.m_X: 0, .m_Y: 0, .m_W: CTouchControls::BUTTON_SIZE_MINIMUM, .m_H: CTouchControls::BUTTON_SIZE_MINIMUM}); |
| 1192 | m_CachedShape = CTouchControls::EButtonShape::RECT; |
| 1193 | } |
| 1194 | |
| 1195 | // This is called when the Touch button editor is rendered as well when selectedbutton changes. Used for updating all cached settings. |
| 1196 | void CMenusIngameTouchControls::(CTouchControls::CTouchButton *pTargetButton) |
| 1197 | { |
| 1198 | ResetCachedSettings(); |
| 1199 | if(pTargetButton == nullptr) |
| 1200 | { |
| 1201 | return; // Nothing to cache. |
| 1202 | } |
| 1203 | // These values can't be null. The constructor has been updated. Default:{0,0,CTouchControls::BUTTON_SIZE_MINIMUM,CTouchControls::BUTTON_SIZE_MINIMUM}, shape = rect. |
| 1204 | SetPosInputs(pTargetButton->m_UnitRect); |
| 1205 | m_CachedShape = pTargetButton->m_Shape; |
| 1206 | for(const auto &Visibility : pTargetButton->m_vVisibilities) |
| 1207 | { |
| 1208 | dbg_assert((int)Visibility.m_Type < (int)CTouchControls::EButtonVisibility::NUM_VISIBILITIES, "Target button has out of bound visibility type: %d" , (int)Visibility.m_Type); |
| 1209 | m_aCachedVisibilities[(int)Visibility.m_Type] = Visibility.m_Parity ? EVisibilityType::INCLUDE : EVisibilityType::EXCLUDE; |
| 1210 | } |
| 1211 | |
| 1212 | // These are behavior values. |
| 1213 | if(pTargetButton->m_pBehavior != nullptr) |
| 1214 | { |
| 1215 | const char *pBehaviorType = pTargetButton->m_pBehavior->GetBehaviorType(); |
| 1216 | if(str_comp(a: pBehaviorType, b: CTouchControls::CBindTouchButtonBehavior::BEHAVIOR_TYPE) == 0) |
| 1217 | { |
| 1218 | m_EditBehaviorType = EBehaviorType::BIND; |
| 1219 | auto *pTargetBehavior = static_cast<CTouchControls::CBindTouchButtonBehavior *>(pTargetButton->m_pBehavior.get()); |
| 1220 | // m_LabelType must not be null. Default value is PLAIN. |
| 1221 | m_vBehaviorElements[0]->m_CachedCommands = {pTargetBehavior->GetLabel().m_pLabel, pTargetBehavior->GetLabel().m_Type, pTargetBehavior->GetCommand()}; |
| 1222 | m_vBehaviorElements[0]->UpdateInputs(); |
| 1223 | } |
| 1224 | else if(str_comp(a: pBehaviorType, b: CTouchControls::CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE) == 0) |
| 1225 | { |
| 1226 | m_EditBehaviorType = EBehaviorType::BIND_TOGGLE; |
| 1227 | auto *pTargetBehavior = static_cast<CTouchControls::CBindToggleTouchButtonBehavior *>(pTargetButton->m_pBehavior.get()); |
| 1228 | auto TargetCommands = pTargetBehavior->GetCommand(); |
| 1229 | // Can't use resize here :( |
| 1230 | while(m_vBehaviorElements.size() > maximum<size_t>(a: TargetCommands.size(), b: 2)) |
| 1231 | m_vBehaviorElements.pop_back(); |
| 1232 | while(m_vBehaviorElements.size() < maximum<size_t>(a: TargetCommands.size(), b: 2)) |
| 1233 | m_vBehaviorElements.emplace_back(args: std::make_unique<CBehaviorElements>()); |
| 1234 | for(unsigned CommandIndex = 0; CommandIndex < TargetCommands.size(); CommandIndex++) |
| 1235 | { |
| 1236 | m_vBehaviorElements[CommandIndex]->m_CachedCommands = TargetCommands[CommandIndex]; |
| 1237 | m_vBehaviorElements[CommandIndex]->UpdateInputs(); |
| 1238 | } |
| 1239 | } |
| 1240 | else if(str_comp(a: pBehaviorType, b: CTouchControls::CPredefinedTouchButtonBehavior::BEHAVIOR_TYPE) == 0) |
| 1241 | { |
| 1242 | m_EditBehaviorType = EBehaviorType::PREDEFINED; |
| 1243 | auto *pTargetBehavior = static_cast<CTouchControls::CPredefinedTouchButtonBehavior *>(pTargetButton->m_pBehavior.get()); |
| 1244 | const char *pPredefinedType = pTargetBehavior->GetPredefinedType(); |
| 1245 | if(pPredefinedType == nullptr) |
| 1246 | m_PredefinedBehaviorType = EPredefinedType::EXTRA_MENU; |
| 1247 | else |
| 1248 | m_PredefinedBehaviorType = (EPredefinedType)CalculatePredefinedType(pType: pPredefinedType); |
| 1249 | dbg_assert(m_PredefinedBehaviorType != EPredefinedType::NUM_PREDEFINEDTYPES, "Detected out of bound m_PredefinedBehaviorType. pPredefinedType = %s" , pPredefinedType); |
| 1250 | |
| 1251 | if(m_PredefinedBehaviorType == EPredefinedType::EXTRA_MENU) |
| 1252 | { |
| 1253 | auto * = static_cast<CTouchControls::CExtraMenuTouchButtonBehavior *>(pTargetButton->m_pBehavior.get()); |
| 1254 | m_CachedExtraMenuNumber = pExtraMenuBehavior->GetNumber(); |
| 1255 | } |
| 1256 | } |
| 1257 | else // Empty |
| 1258 | dbg_assert_failed("Detected out of bound value in m_EditBehaviorType" ); |
| 1259 | } |
| 1260 | } |
| 1261 | |
| 1262 | // Will override everything in the button. If nullptr is passed, a new button will be created. |
| 1263 | void CMenusIngameTouchControls::(CTouchControls::CTouchButton *pTargetButton) const |
| 1264 | { |
| 1265 | dbg_assert(pTargetButton != nullptr, "Target button to receive is nullptr." ); |
| 1266 | pTargetButton->m_UnitRect.m_W = std::clamp(val: m_InputW.GetInteger(), lo: CTouchControls::BUTTON_SIZE_MINIMUM, hi: CTouchControls::BUTTON_SIZE_MAXIMUM); |
| 1267 | pTargetButton->m_UnitRect.m_H = std::clamp(val: m_InputH.GetInteger(), lo: CTouchControls::BUTTON_SIZE_MINIMUM, hi: CTouchControls::BUTTON_SIZE_MAXIMUM); |
| 1268 | pTargetButton->m_UnitRect.m_X = std::clamp(val: m_InputX.GetInteger(), lo: 0, hi: CTouchControls::BUTTON_SIZE_SCALE - pTargetButton->m_UnitRect.m_W); |
| 1269 | pTargetButton->m_UnitRect.m_Y = std::clamp(val: m_InputY.GetInteger(), lo: 0, hi: CTouchControls::BUTTON_SIZE_SCALE - pTargetButton->m_UnitRect.m_H); |
| 1270 | pTargetButton->m_vVisibilities.clear(); |
| 1271 | for(unsigned Iterator = (unsigned)CTouchControls::EButtonVisibility::INGAME; Iterator < (unsigned)CTouchControls::EButtonVisibility::NUM_VISIBILITIES; ++Iterator) |
| 1272 | { |
| 1273 | if(m_aCachedVisibilities[Iterator] != EVisibilityType::IGNORE) |
| 1274 | pTargetButton->m_vVisibilities.emplace_back(args: (CTouchControls::EButtonVisibility)Iterator, args: m_aCachedVisibilities[Iterator] == EVisibilityType::INCLUDE); |
| 1275 | } |
| 1276 | pTargetButton->m_Shape = m_CachedShape; |
| 1277 | pTargetButton->UpdateScreenFromUnitRect(); |
| 1278 | |
| 1279 | // Make a new behavior class instead of modify the original one. |
| 1280 | if(m_EditBehaviorType == EBehaviorType::BIND) |
| 1281 | { |
| 1282 | pTargetButton->m_pBehavior = std::make_unique<CTouchControls::CBindTouchButtonBehavior>( |
| 1283 | args: m_vBehaviorElements[0]->m_CachedCommands.m_Label.c_str(), |
| 1284 | args&: m_vBehaviorElements[0]->m_CachedCommands.m_LabelType, |
| 1285 | args: m_vBehaviorElements[0]->m_CachedCommands.m_Command.c_str()); |
| 1286 | } |
| 1287 | else if(m_EditBehaviorType == EBehaviorType::BIND_TOGGLE) |
| 1288 | { |
| 1289 | std::vector<CTouchControls::CBindToggleTouchButtonBehavior::CCommand> vMovingBehavior; |
| 1290 | vMovingBehavior.reserve(n: m_vBehaviorElements.size()); |
| 1291 | for(const auto &Element : m_vBehaviorElements) |
| 1292 | vMovingBehavior.emplace_back(args&: Element->m_CachedCommands); |
| 1293 | pTargetButton->m_pBehavior = std::make_unique<CTouchControls::CBindToggleTouchButtonBehavior>(args: std::move(vMovingBehavior)); |
| 1294 | } |
| 1295 | else if(m_EditBehaviorType == EBehaviorType::PREDEFINED) |
| 1296 | { |
| 1297 | if(m_PredefinedBehaviorType == EPredefinedType::EXTRA_MENU) |
| 1298 | pTargetButton->m_pBehavior = std::make_unique<CTouchControls::CExtraMenuTouchButtonBehavior>(args: CTouchControls::CExtraMenuTouchButtonBehavior(m_CachedExtraMenuNumber)); |
| 1299 | else |
| 1300 | pTargetButton->m_pBehavior = BEHAVIOR_FACTORIES_EDITOR[(int)m_PredefinedBehaviorType].m_Factory(); |
| 1301 | } |
| 1302 | else |
| 1303 | { |
| 1304 | dbg_assert_failed("Invalid m_EditBehaviorType: %d" , static_cast<int>(m_EditBehaviorType)); |
| 1305 | } |
| 1306 | pTargetButton->UpdatePointers(); |
| 1307 | } |
| 1308 | |
| 1309 | // Used for setting the value of four position input box to the unitrect. |
| 1310 | void CMenusIngameTouchControls::(CTouchControls::CUnitRect MyRect) |
| 1311 | { |
| 1312 | m_InputX.SetInteger(Number: MyRect.m_X); |
| 1313 | m_InputY.SetInteger(Number: MyRect.m_Y); |
| 1314 | m_InputW.SetInteger(Number: MyRect.m_W); |
| 1315 | m_InputH.SetInteger(Number: MyRect.m_H); |
| 1316 | } |
| 1317 | |
| 1318 | // Used to make sure the input box is numbers only, also clamp the value. |
| 1319 | void CMenusIngameTouchControls::(CLineInputNumber *pInput) |
| 1320 | { |
| 1321 | int InputValue = pInput->GetInteger(); |
| 1322 | // Deal with the "-1" FindPositionXY give. |
| 1323 | InputValue = std::clamp(val: InputValue, lo: 0, hi: CTouchControls::BUTTON_SIZE_SCALE); |
| 1324 | pInput->SetInteger(Number: InputValue); |
| 1325 | SetUnsavedChanges(true); |
| 1326 | } |
| 1327 | |
| 1328 | // Update m_pSampleButton in CTouchControls. The Samplebutton is used for showing on screen. |
| 1329 | void CMenusIngameTouchControls::() |
| 1330 | { |
| 1331 | GameClient()->m_TouchControls.RemakeSampleButton(); |
| 1332 | SaveCachedSettingsToTarget(pTargetButton: GameClient()->m_TouchControls.SampleButton()); |
| 1333 | GameClient()->m_TouchControls.SetShownRect(GameClient()->m_TouchControls.SampleButton()->m_UnitRect); |
| 1334 | } |
| 1335 | |
| 1336 | // Not inline so there's no more includes in menus.h. A shortcut to the function in CTouchControls. |
| 1337 | void CMenusIngameTouchControls::() |
| 1338 | { |
| 1339 | GameClient()->m_TouchControls.ResetButtonPointers(); |
| 1340 | } |
| 1341 | |
| 1342 | // New button doesn't create a real button, instead it reset the Samplebutton to cache every setting. When saving a the Samplebutton then a real button will be created. |
| 1343 | void CMenusIngameTouchControls::() |
| 1344 | { |
| 1345 | CTouchControls::CUnitRect FreeRect = GameClient()->m_TouchControls.UpdatePosition(MyRect: {.m_X: 0, .m_Y: 0, .m_W: CTouchControls::BUTTON_SIZE_MINIMUM, .m_H: CTouchControls::BUTTON_SIZE_MINIMUM}, Shape: CTouchControls::EButtonShape::RECT, Ignore: true); |
| 1346 | ResetButtonPointers(); |
| 1347 | ResetCachedSettings(); |
| 1348 | SetPosInputs(FreeRect); |
| 1349 | UpdateSampleButton(); |
| 1350 | SetUnsavedChanges(true); |
| 1351 | } |
| 1352 | |
| 1353 | // Used for updating cached settings or something else only when opening the editor, to reduce lag. Issues come from CTouchControls. |
| 1354 | void CMenusIngameTouchControls::() |
| 1355 | { |
| 1356 | if(GameClient()->m_TouchControls.AnyIssueNotResolved()) |
| 1357 | { |
| 1358 | std::array<CTouchControls::CIssueParam, (unsigned)CTouchControls::EIssueType::NUM_ISSUES> aIssues = GameClient()->m_TouchControls.Issues(); |
| 1359 | for(unsigned Current = 0; Current < (unsigned)CTouchControls::EIssueType::NUM_ISSUES; Current++) |
| 1360 | { |
| 1361 | if(aIssues[Current].m_Resolved == true) |
| 1362 | continue; |
| 1363 | switch(Current) |
| 1364 | { |
| 1365 | case(int)CTouchControls::EIssueType::CACHE_SETTINGS: CacheAllSettingsFromTarget(pTargetButton: aIssues[Current].m_pTargetButton); break; |
| 1366 | case(int)CTouchControls::EIssueType::SAVE_SETTINGS: |
| 1367 | { |
| 1368 | if(aIssues[Current].m_pTargetButton == nullptr) |
| 1369 | aIssues[Current].m_pTargetButton = GameClient()->m_TouchControls.NewButton(); |
| 1370 | SaveCachedSettingsToTarget(pTargetButton: aIssues[Current].m_pTargetButton); |
| 1371 | break; |
| 1372 | } |
| 1373 | case(int)CTouchControls::EIssueType::CACHE_POSITION: SetPosInputs(aIssues[Current].m_pTargetButton->m_UnitRect); break; |
| 1374 | default: dbg_assert_failed("Unknown Issue type: %d" , Current); |
| 1375 | } |
| 1376 | } |
| 1377 | } |
| 1378 | } |
| 1379 | |
| 1380 | // Turn predefined behavior strings like "joystick-hook" into integers according to the enum. |
| 1381 | int CMenusIngameTouchControls::(const char *pType) const |
| 1382 | { |
| 1383 | int IntegerType; |
| 1384 | for(IntegerType = (int)EPredefinedType::EXTRA_MENU; IntegerType < (int)EPredefinedType::NUM_PREDEFINEDTYPES; IntegerType++) |
| 1385 | { |
| 1386 | if(str_comp(a: pType, b: BEHAVIOR_FACTORIES_EDITOR[IntegerType].m_pId) == 0) |
| 1387 | return IntegerType; |
| 1388 | } |
| 1389 | dbg_assert_failed("Unknown predefined type %s." , pType == nullptr ? "nullptr" : pType); |
| 1390 | } |
| 1391 | |
| 1392 | std::string CMenusIngameTouchControls::DetermineTouchButtonCommandLabel(CTouchControls::CTouchButton *pButton) const |
| 1393 | { |
| 1394 | const char *pBehaviorType = pButton->m_pBehavior->GetBehaviorType(); |
| 1395 | if(str_comp(a: pBehaviorType, b: CTouchControls::CBindTouchButtonBehavior::BEHAVIOR_TYPE) == 0) |
| 1396 | { |
| 1397 | return static_cast<CTouchControls::CBindTouchButtonBehavior *>(pButton->m_pBehavior.get())->GetCommand(); |
| 1398 | } |
| 1399 | else if(str_comp(a: pBehaviorType, b: CTouchControls::CBindToggleTouchButtonBehavior::BEHAVIOR_TYPE) == 0) |
| 1400 | { |
| 1401 | const auto *pBehavior = static_cast<CTouchControls::CBindToggleTouchButtonBehavior *>(pButton->m_pBehavior.get()); |
| 1402 | return pBehavior->GetCommand()[pBehavior->GetActiveCommandIndex()].m_Command; |
| 1403 | } |
| 1404 | else if(str_comp(a: pBehaviorType, b: CTouchControls::CPredefinedTouchButtonBehavior::BEHAVIOR_TYPE) == 0) |
| 1405 | { |
| 1406 | auto *pTargetBehavior = static_cast<CTouchControls::CPredefinedTouchButtonBehavior *>(pButton->m_pBehavior.get()); |
| 1407 | const char *pPredefinedType = pTargetBehavior->GetPredefinedType(); |
| 1408 | const char **apPredefinedNames = PredefinedNames(); |
| 1409 | std::string Command = apPredefinedNames[CalculatePredefinedType(pType: pPredefinedType)]; |
| 1410 | if(str_comp(a: pPredefinedType, b: CTouchControls::CExtraMenuTouchButtonBehavior::BEHAVIOR_ID) == 0) |
| 1411 | { |
| 1412 | const auto * = static_cast<CTouchControls::CExtraMenuTouchButtonBehavior *>(pTargetBehavior); |
| 1413 | Command.append(s: " " ); |
| 1414 | Command.append(str: std::to_string(val: pExtraMenuBehavior->GetNumber())); |
| 1415 | } |
| 1416 | return Command; |
| 1417 | } |
| 1418 | else |
| 1419 | { |
| 1420 | dbg_assert_failed("Detected unknown behavior type in CMenusIngameTouchControls::GetCommand()" ); |
| 1421 | } |
| 1422 | } |
| 1423 | |
| 1424 | // Used for making json chars like \n or \uf3ce visible. |
| 1425 | std::string CMenusIngameTouchControls::CBehaviorElements::(const char *pLabel) const |
| 1426 | { |
| 1427 | json_settings JsonSettings{}; |
| 1428 | char aError[256]; |
| 1429 | char aJsonString[1048]; |
| 1430 | str_format(buffer: aJsonString, buffer_size: sizeof(aJsonString), format: "\"%s\"" , pLabel); |
| 1431 | json_value *pJsonLabel = json_parse_ex(settings: &JsonSettings, json: aJsonString, length: str_length(str: aJsonString), error: aError); |
| 1432 | if(pJsonLabel == nullptr || pJsonLabel->type != json_string) |
| 1433 | { |
| 1434 | return pLabel; |
| 1435 | } |
| 1436 | std::string ParsedString = pJsonLabel->u.string.ptr; |
| 1437 | json_value_free(pJsonLabel); |
| 1438 | return ParsedString; |
| 1439 | } |
| 1440 | |
| 1441 | CMenusIngameTouchControls::CBehaviorElements::() noexcept |
| 1442 | { |
| 1443 | Reset(); |
| 1444 | } |
| 1445 | |
| 1446 | CMenusIngameTouchControls::CBehaviorElements::(CBehaviorElements &&Other) noexcept : |
| 1447 | m_InputCommand(std::move(Other.m_InputCommand)), m_InputLabel(std::move(Other.m_InputLabel)), m_CachedCommands(std::move(Other.m_CachedCommands)) |
| 1448 | { |
| 1449 | } |
| 1450 | |
| 1451 | CMenusIngameTouchControls::CBehaviorElements::() |
| 1452 | { |
| 1453 | m_InputCommand.Deactivate(); |
| 1454 | m_InputLabel.Deactivate(); |
| 1455 | } |
| 1456 | |
| 1457 | CMenusIngameTouchControls::CBehaviorElements &CMenusIngameTouchControls::CBehaviorElements::(CBehaviorElements &&Other) noexcept |
| 1458 | { |
| 1459 | if(this == &Other) |
| 1460 | { |
| 1461 | return *this; |
| 1462 | } |
| 1463 | m_InputCommand = std::move(Other.m_InputCommand); |
| 1464 | m_InputLabel = std::move(Other.m_InputLabel); |
| 1465 | m_CachedCommands = std::move(Other.m_CachedCommands); |
| 1466 | return *this; |
| 1467 | } |
| 1468 | |
| 1469 | void CMenusIngameTouchControls::CBehaviorElements::() |
| 1470 | { |
| 1471 | m_InputLabel.Set(ParseLabel(pLabel: m_CachedCommands.m_Label.c_str()).c_str()); |
| 1472 | m_InputCommand.Set(m_CachedCommands.m_Command.c_str()); |
| 1473 | } |
| 1474 | |
| 1475 | void CMenusIngameTouchControls::CBehaviorElements::() |
| 1476 | { |
| 1477 | m_CachedCommands = {}; |
| 1478 | m_InputCommand.Clear(); |
| 1479 | m_InputLabel.Clear(); |
| 1480 | } |
| 1481 | |
| 1482 | const char **CMenusIngameTouchControls::() const |
| 1483 | { |
| 1484 | static const char *s_apVisibilities[8]; |
| 1485 | s_apVisibilities[0] = Localize(pStr: "Ingame" , pContext: "Touch button visibilities" ); |
| 1486 | s_apVisibilities[1] = Localize(pStr: "Zoom Allowed" , pContext: "Touch button visibilities" ); |
| 1487 | s_apVisibilities[2] = Localize(pStr: "Vote Active" , pContext: "Touch button visibilities" ); |
| 1488 | s_apVisibilities[3] = Localize(pStr: "Dummy Allowed" , pContext: "Touch button visibilities" ); |
| 1489 | s_apVisibilities[4] = Localize(pStr: "Dummy Connected" , pContext: "Touch button visibilities" ); |
| 1490 | s_apVisibilities[5] = Localize(pStr: "Rcon Authed" , pContext: "Touch button visibilities" ); |
| 1491 | s_apVisibilities[6] = Localize(pStr: "Demo Player" , pContext: "Touch button visibilities" ); |
| 1492 | s_apVisibilities[7] = Localize(pStr: "Extra Menu" , pContext: "Touch button visibilities" ); |
| 1493 | static_assert(std::size(s_apVisibilities) == (size_t)CTouchControls::EButtonVisibility::NUM_VISIBILITIES - CTouchControls::MAX_EXTRA_MENU_NUMBER + 1, "Insufficient visibility names" ); |
| 1494 | return s_apVisibilities; |
| 1495 | } |
| 1496 | |
| 1497 | const char **CMenusIngameTouchControls::() const |
| 1498 | { |
| 1499 | static const char *s_apPredefined[10]; |
| 1500 | s_apPredefined[0] = Localize(pStr: "Ingame Menu" , pContext: "Predefined touch button behaviors" ); |
| 1501 | s_apPredefined[1] = Localize(pStr: "Extra Menu" , pContext: "Predefined touch button behaviors" ); |
| 1502 | s_apPredefined[2] = Localize(pStr: "Emoticon" , pContext: "Predefined touch button behaviors" ); |
| 1503 | s_apPredefined[3] = Localize(pStr: "Spectate" , pContext: "Predefined touch button behaviors" ); |
| 1504 | s_apPredefined[4] = Localize(pStr: "Swap Action" , pContext: "Predefined touch button behaviors" ); |
| 1505 | s_apPredefined[5] = Localize(pStr: "Use Action" , pContext: "Predefined touch button behaviors" ); |
| 1506 | s_apPredefined[6] = Localize(pStr: "Joystick Action" , pContext: "Predefined touch button behaviors" ); |
| 1507 | s_apPredefined[7] = Localize(pStr: "Joystick Aim" , pContext: "Predefined touch button behaviors" ); |
| 1508 | s_apPredefined[8] = Localize(pStr: "Joystick Fire" , pContext: "Predefined touch button behaviors" ); |
| 1509 | s_apPredefined[9] = Localize(pStr: "Joystick Hook" , pContext: "Predefined touch button behaviors" ); |
| 1510 | dbg_assert(std::size(s_apPredefined) == std::size(BEHAVIOR_FACTORIES_EDITOR), "Insufficient predefined names" ); |
| 1511 | return s_apPredefined; |
| 1512 | } |
| 1513 | |