| 1 | #include "prompt.h" |
| 2 | |
| 3 | #include "editor.h" |
| 4 | |
| 5 | #include <engine/keys.h> |
| 6 | |
| 7 | #include <game/client/ui_listbox.h> |
| 8 | #include <game/editor/quick_action.h> |
| 9 | |
| 10 | static bool FuzzyMatch(const char *pHaystack, const char *pNeedle) |
| 11 | { |
| 12 | if(!pNeedle || !pNeedle[0]) |
| 13 | return false; |
| 14 | char aBuf[2] = {0}; |
| 15 | const char *pHit = pHaystack; |
| 16 | int NeedleLen = str_length(str: pNeedle); |
| 17 | for(int i = 0; i < NeedleLen; i++) |
| 18 | { |
| 19 | if(!pHit) |
| 20 | return false; |
| 21 | aBuf[0] = pNeedle[i]; |
| 22 | pHit = str_find_nocase(haystack: pHit, needle: aBuf); |
| 23 | if(pHit) |
| 24 | pHit++; |
| 25 | } |
| 26 | return pHit; |
| 27 | } |
| 28 | |
| 29 | bool CPrompt::IsActive() |
| 30 | { |
| 31 | return Editor()->m_Dialog == DIALOG_QUICK_PROMPT; |
| 32 | } |
| 33 | |
| 34 | void CPrompt::SetActive() |
| 35 | { |
| 36 | Editor()->m_Dialog = DIALOG_QUICK_PROMPT; |
| 37 | Ui()->ClosePopupMenus(); |
| 38 | Ui()->SetActiveItem(&m_PromptInput); |
| 39 | } |
| 40 | |
| 41 | void CPrompt::SetInactive() |
| 42 | { |
| 43 | m_ResetFilterResults = true; |
| 44 | m_PromptInput.Clear(); |
| 45 | if(Editor()->m_Dialog == DIALOG_QUICK_PROMPT) |
| 46 | { |
| 47 | Editor()->OnDialogClose(); |
| 48 | } |
| 49 | } |
| 50 | |
| 51 | bool CPrompt::OnInput(const IInput::CEvent &Event) |
| 52 | { |
| 53 | if(Editor()->m_Dialog == DIALOG_NONE && Input()->ModifierIsPressed() && Input()->KeyPress(Key: KEY_P)) |
| 54 | { |
| 55 | SetActive(); |
| 56 | } |
| 57 | return false; |
| 58 | } |
| 59 | |
| 60 | void CPrompt::OnInit(CEditor *pEditor) |
| 61 | { |
| 62 | CEditorComponent::OnInit(pEditor); |
| 63 | |
| 64 | #define REGISTER_QUICK_ACTION(name, text, callback, disabled, active, button_color, description) m_vQuickActions.emplace_back(&Editor()->m_QuickAction##name); |
| 65 | #include <game/editor/quick_actions.h> |
| 66 | #undef REGISTER_QUICK_ACTION |
| 67 | } |
| 68 | |
| 69 | void CPrompt::OnRender(CUIRect _) |
| 70 | { |
| 71 | if(!IsActive()) |
| 72 | return; |
| 73 | |
| 74 | if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ESCAPE)) |
| 75 | { |
| 76 | SetInactive(); |
| 77 | return; |
| 78 | } |
| 79 | |
| 80 | // Prevent UI elements below the prompt dialog from being activated. |
| 81 | Ui()->SetHotItem(this); |
| 82 | |
| 83 | static CListBox s_ListBox; |
| 84 | CUIRect Prompt, PromptBox; |
| 85 | CUIRect Suggestions; |
| 86 | |
| 87 | Ui()->MapScreen(); |
| 88 | CUIRect Overlay = *Ui()->Screen(); |
| 89 | |
| 90 | Overlay.Draw(Color: ColorRGBA(0, 0, 0, 0.33f), Corners: IGraphics::CORNER_NONE, Rounding: 0.0f); |
| 91 | CUIRect Background; |
| 92 | Overlay.VMargin(Cut: 150.0f, pOtherRect: &Background); |
| 93 | Background.HMargin(Cut: 50.0f, pOtherRect: &Background); |
| 94 | Background.Draw(Color: ColorRGBA(0, 0, 0, 0.80f), Corners: IGraphics::CORNER_ALL, Rounding: 5.0f); |
| 95 | |
| 96 | Background.Margin(Cut: 10.0f, pOtherRect: &Prompt); |
| 97 | |
| 98 | Prompt.VSplitMid(pLeft: nullptr, pRight: &PromptBox); |
| 99 | |
| 100 | Prompt.HSplitTop(Cut: 16.0f, pTop: &PromptBox, pBottom: &Suggestions); |
| 101 | PromptBox.Draw(Color: ColorRGBA(0, 0, 0, 0.75f), Corners: IGraphics::CORNER_ALL, Rounding: 2.0f); |
| 102 | Suggestions.y += 6.0f; |
| 103 | |
| 104 | if(Ui()->DoClearableEditBox(pLineInput: &m_PromptInput, pRect: &PromptBox, FontSize: 10.0f) || m_ResetFilterResults) |
| 105 | { |
| 106 | m_PromptSelectedIndex = 0; |
| 107 | m_vpFilteredPromptList.clear(); |
| 108 | if(m_ResetFilterResults && m_pLastAction && !m_pLastAction->Disabled()) |
| 109 | { |
| 110 | m_vpFilteredPromptList.push_back(x: m_pLastAction); |
| 111 | } |
| 112 | for(auto *pQuickAction : m_vQuickActions) |
| 113 | { |
| 114 | if(pQuickAction->Disabled()) |
| 115 | continue; |
| 116 | |
| 117 | if(m_PromptInput.IsEmpty() || FuzzyMatch(pHaystack: pQuickAction->Label(), pNeedle: m_PromptInput.GetString())) |
| 118 | { |
| 119 | if(!m_ResetFilterResults || pQuickAction != m_pLastAction) |
| 120 | m_vpFilteredPromptList.push_back(x: pQuickAction); |
| 121 | } |
| 122 | } |
| 123 | m_ResetFilterResults = false; |
| 124 | } |
| 125 | |
| 126 | s_ListBox.SetActive(!Ui()->IsPopupOpen()); |
| 127 | s_ListBox.DoStart(RowHeight: 15.0f, NumItems: m_vpFilteredPromptList.size(), ItemsPerRow: 1, RowsPerScroll: 5, SelectedIndex: m_PromptSelectedIndex, pRect: &Suggestions, Background: false); |
| 128 | |
| 129 | float LabelWidth = Overlay.w > 855.0f ? 200.0f : 100.0f; |
| 130 | |
| 131 | for(size_t i = 0; i < m_vpFilteredPromptList.size(); i++) |
| 132 | { |
| 133 | const CListboxItem Item = s_ListBox.DoNextItem(pId: m_vpFilteredPromptList[i]->ActionButtonId(), Selected: m_PromptSelectedIndex >= 0 && (size_t)m_PromptSelectedIndex == i); |
| 134 | if(!Item.m_Visible) |
| 135 | continue; |
| 136 | |
| 137 | CUIRect LabelColumn, DescColumn; |
| 138 | float Margin = 5.0f; |
| 139 | Item.m_Rect.VMargin(Cut: Margin, pOtherRect: &LabelColumn); |
| 140 | LabelColumn.VSplitLeft(Cut: LabelWidth, pLeft: &LabelColumn, pRight: &DescColumn); |
| 141 | DescColumn.VSplitLeft(Cut: Margin, pLeft: nullptr, pRight: &DescColumn); |
| 142 | |
| 143 | SLabelProperties Props; |
| 144 | Props.m_MaxWidth = LabelColumn.w; |
| 145 | Props.m_EllipsisAtEnd = true; |
| 146 | Ui()->DoLabel(pRect: &LabelColumn, pText: m_vpFilteredPromptList[i]->Label(), Size: 10.0f, Align: TEXTALIGN_ML, LabelProps: Props); |
| 147 | |
| 148 | Props.m_MaxWidth = DescColumn.w; |
| 149 | TextRender()->TextColor(Color: TextRender()->DefaultTextColor().WithAlpha(alpha: Item.m_Selected ? 1.0f : 0.8f)); |
| 150 | Ui()->DoLabel(pRect: &DescColumn, pText: m_vpFilteredPromptList[i]->Description(), Size: 10.0f, Align: TEXTALIGN_MR, LabelProps: Props); |
| 151 | TextRender()->TextColor(Color: TextRender()->DefaultTextColor()); |
| 152 | } |
| 153 | |
| 154 | const int NewSelected = s_ListBox.DoEnd(); |
| 155 | if(m_PromptSelectedIndex != NewSelected) |
| 156 | { |
| 157 | m_PromptSelectedIndex = NewSelected; |
| 158 | } |
| 159 | |
| 160 | if(s_ListBox.WasItemActivated()) |
| 161 | { |
| 162 | if(m_PromptSelectedIndex >= 0) |
| 163 | { |
| 164 | CQuickAction *pBtn = m_vpFilteredPromptList[m_PromptSelectedIndex]; |
| 165 | SetInactive(); |
| 166 | pBtn->Call(); |
| 167 | m_pLastAction = pBtn; |
| 168 | } |
| 169 | } |
| 170 | } |
| 171 | |