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