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