1#include "font_typer.h"
2
3#include "editor.h"
4
5#include <base/log.h>
6#include <base/str.h>
7#include <base/time.h>
8
9#include <engine/keys.h>
10
11#include <game/editor/editor_actions.h>
12
13#include <algorithm>
14
15using namespace std::chrono_literals;
16
17void CFontTyper::OnInit(CEditor *pEditor)
18{
19 CEditorComponent::OnInit(pEditor);
20
21 m_CursorTextTexture = pEditor->Graphics()->LoadTexture(pFilename: "editor/cursor_text.png", StorageType: IStorage::TYPE_ALL, Flags: 0);
22}
23
24void CFontTyper::SetTile(ivec2 Pos, unsigned char Index, const std::shared_ptr<CLayerTiles> &pLayer)
25{
26 CTile Tile = {
27 .m_Index: Index, // index
28 .m_Flags: 0, // flags
29 .m_Skip: 0, // skip
30 .m_Reserved: 0, // reserved
31 };
32 pLayer->SetTile(x: Pos.x, y: Pos.y, Tile);
33 m_TilesPlacedSinceActivate++;
34}
35
36bool CFontTyper::OnInput(const IInput::CEvent &Event)
37{
38 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
39 if(!pLayer)
40 {
41 if(IsActive())
42 TextModeOff();
43 return false;
44 }
45 if(pLayer->m_Image == -1)
46 return false;
47
48 if(!IsActive())
49 {
50 if(Event.m_Key == KEY_T && Input()->ModifierIsPressed() && !Ui()->IsPopupOpen() && Editor()->m_Dialog == DIALOG_NONE)
51 {
52 if(pLayer && pLayer->m_KnownTextModeLayer)
53 {
54 TextModeOn();
55 }
56 else
57 {
58 m_ConfirmActivatePopupContext.Reset();
59 m_ConfirmActivatePopupContext.YesNoButtons();
60 str_copy(dst&: m_ConfirmActivatePopupContext.m_aMessage, src: "Enable text mode? Pressing letters and numbers on your keyboard will place tiles.");
61 Ui()->ShowPopupConfirm(X: Ui()->MouseX(), Y: Ui()->MouseY(), pContext: &m_ConfirmActivatePopupContext);
62 }
63 }
64 return false;
65 }
66
67 // only handle key down and not also key up
68 if(!(Event.m_Flags & IInput::FLAG_PRESS))
69 return false;
70
71 // letters
72 if(Event.m_Key >= KEY_A && Event.m_Key <= KEY_Z)
73 {
74 SetTile(Pos: m_TextIndex, Index: Event.m_Key - KEY_A + LETTER_OFFSET, pLayer);
75 m_TextIndex.x++;
76 m_TextLineLen++;
77 }
78 // numbers
79 if(Event.m_Key >= KEY_1 && Event.m_Key <= KEY_0)
80 {
81 SetTile(Pos: m_TextIndex, Index: Event.m_Key - KEY_1 + NUMBER_OFFSET, pLayer);
82 m_TextIndex.x++;
83 m_TextLineLen++;
84 }
85 if(Event.m_Key >= KEY_KP_1 && Event.m_Key <= KEY_KP_0)
86 {
87 SetTile(Pos: m_TextIndex, Index: Event.m_Key - KEY_KP_1 + NUMBER_OFFSET, pLayer);
88 m_TextIndex.x++;
89 m_TextLineLen++;
90 }
91
92 // deletion
93 if(Event.m_Key == KEY_BACKSPACE)
94 {
95 m_TextIndex.x--;
96 m_TextLineLen--;
97 SetTile(Pos: m_TextIndex, Index: 0, pLayer);
98 }
99 // space
100 if(Event.m_Key == KEY_SPACE)
101 {
102 SetTile(Pos: m_TextIndex, Index: 0, pLayer);
103 m_TextIndex.x++;
104 m_TextLineLen++;
105 }
106 // newline
107 if(Event.m_Key == KEY_RETURN)
108 {
109 m_TextIndex.y++;
110 m_TextIndex.x -= m_TextLineLen;
111 m_TextLineLen = 0;
112 }
113 // arrow key navigation
114 if(Event.m_Key == KEY_LEFT)
115 {
116 m_TextIndex.x--;
117 m_TextLineLen--;
118 if(Input()->KeyIsPressed(Key: KEY_LCTRL))
119 {
120 while(pLayer->GetTile(x: m_TextIndex.x, y: m_TextIndex.y).m_Index)
121 {
122 if(m_TextIndex.x < 1 || m_TextIndex.x > pLayer->m_Width - 2)
123 break;
124 m_TextIndex.x--;
125 m_TextLineLen--;
126 }
127 }
128 }
129 if(Event.m_Key == KEY_RIGHT)
130 {
131 m_TextIndex.x++;
132 m_TextLineLen++;
133 if(Input()->KeyIsPressed(Key: KEY_LCTRL))
134 {
135 while(pLayer->GetTile(x: m_TextIndex.x, y: m_TextIndex.y).m_Index)
136 {
137 if(m_TextIndex.x < 1 || m_TextIndex.x > pLayer->m_Width - 2)
138 break;
139 m_TextIndex.x++;
140 m_TextLineLen++;
141 }
142 }
143 }
144 if(Event.m_Key == KEY_UP)
145 m_TextIndex.y--;
146 if(Event.m_Key == KEY_DOWN)
147 m_TextIndex.y++;
148 m_TextIndex.x = std::clamp(val: m_TextIndex.x, lo: 0, hi: pLayer->m_Width - 1);
149 m_TextIndex.y = std::clamp(val: m_TextIndex.y, lo: 0, hi: pLayer->m_Height - 1);
150 m_CursorRenderTime = time_get_nanoseconds() - 501ms;
151 float Dist = distance(
152 a: vec2(m_TextIndex.x, m_TextIndex.y),
153 b: (Editor()->MapView()->GetWorldOffset() + Editor()->MapView()->GetEditorOffset()) / 32);
154 Dist /= Editor()->MapView()->GetWorldZoom();
155 if(Dist > 10.0f)
156 {
157 Editor()->MapView()->SetWorldOffset(vec2(m_TextIndex.x, m_TextIndex.y) * 32 - Editor()->MapView()->GetEditorOffset());
158 }
159
160 return false;
161}
162
163void CFontTyper::TextModeOn()
164{
165 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
166 if(!pLayer)
167 return;
168 if(pLayer->m_Image == -1)
169 return;
170
171 SetCursor();
172 m_TilesPlacedSinceActivate = 0;
173 m_Active = true;
174 pLayer->m_KnownTextModeLayer = true;
175
176 // hack to not show picker when pressing space
177 Editor()->m_Dialog = DIALOG_PSEUDO_FONT_TYPER;
178}
179
180void CFontTyper::TextModeOff()
181{
182 if(Editor()->m_Dialog == DIALOG_PSEUDO_FONT_TYPER)
183 Editor()->m_Dialog = DIALOG_NONE;
184 if(m_TilesPlacedSinceActivate)
185 Map()->m_EditorHistory.RecordAction(pAction: std::make_shared<CEditorBrushDrawAction>(args: Map(), args&: Map()->m_SelectedGroup), pDisplay: "Font typer");
186 m_TilesPlacedSinceActivate = 0;
187 m_Active = false;
188 m_pLastLayer = nullptr;
189}
190
191void CFontTyper::SetCursor()
192{
193 m_TextIndex.x = (int)(Ui()->MouseWorldX() / 32);
194 m_TextIndex.y = (int)(Ui()->MouseWorldY() / 32);
195 m_TextLineLen = 0;
196 m_CursorRenderTime = time_get_nanoseconds() - 501ms;
197}
198
199void CFontTyper::OnRender(CUIRect View)
200{
201 if(m_ConfirmActivatePopupContext.m_Result == CUi::SConfirmPopupContext::CONFIRMED)
202 {
203 TextModeOn();
204 m_ConfirmActivatePopupContext.Reset();
205 }
206 if(m_ConfirmActivatePopupContext.m_Result != CUi::SConfirmPopupContext::UNSET)
207 m_ConfirmActivatePopupContext.Reset();
208
209 if(!IsActive())
210 return;
211
212 if(Ui()->ConsumeHotkey(Hotkey: CUi::HOTKEY_ESCAPE))
213 TextModeOff();
214 str_copy(dst&: Editor()->m_aTooltip, src: "Type on your keyboard to insert letters and numbers. Press Escape to end text mode.");
215
216 std::shared_ptr<CLayerTiles> pLayer = std::static_pointer_cast<CLayerTiles>(r: Map()->SelectedLayerType(Index: 0, Type: LAYERTYPE_TILES));
217 if(!pLayer)
218 return;
219
220 // exit if selected layer changes
221 if(m_pLastLayer && m_pLastLayer != pLayer)
222 {
223 TextModeOff();
224 m_pLastLayer = pLayer;
225 return;
226 }
227 // exit if dialog or edit box pops up
228 if(Editor()->m_Dialog != DIALOG_PSEUDO_FONT_TYPER || CLineInput::GetActiveInput())
229 {
230 TextModeOff();
231 return;
232 }
233 m_pLastLayer = pLayer;
234 m_TextIndex.x = std::clamp(val: m_TextIndex.x, lo: 0, hi: pLayer->m_Width - 1);
235 m_TextIndex.y = std::clamp(val: m_TextIndex.y, lo: 0, hi: pLayer->m_Height - 1);
236
237 const auto CurTime = time_get_nanoseconds();
238 if((CurTime - m_CursorRenderTime) > 1s)
239 m_CursorRenderTime = time_get_nanoseconds();
240 if((CurTime - m_CursorRenderTime) > 500ms)
241 {
242 std::shared_ptr<CLayerGroup> pGroup = Map()->SelectedGroup();
243 pGroup->MapScreen();
244 Graphics()->WrapClamp();
245 Graphics()->TextureSet(Texture: m_CursorTextTexture);
246 Graphics()->QuadsBegin();
247 Graphics()->SetColor(r: 1, g: 1, b: 1, a: 1);
248 IGraphics::CQuadItem QuadItem(m_TextIndex.x * 32, m_TextIndex.y * 32, 32.0f, 32.0f);
249 Graphics()->QuadsDrawTL(pArray: &QuadItem, Num: 1);
250 Graphics()->QuadsEnd();
251 Graphics()->WrapNormal();
252 Ui()->MapScreen();
253 }
254}
255