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
10static 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
29bool CPrompt::IsActive()
30{
31 return Editor()->m_Dialog == DIALOG_QUICK_PROMPT;
32}
33
34void CPrompt::SetActive()
35{
36 Editor()->m_Dialog = DIALOG_QUICK_PROMPT;
37 Ui()->ClosePopupMenus();
38 Ui()->SetActiveItem(&m_PromptInput);
39}
40
41void 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
51bool 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
60void 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
69void 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