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