| 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 | |
| 14 | using namespace std::chrono_literals; |
| 15 | |
| 16 | void 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 | |
| 23 | void 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 | |
| 35 | bool 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 | |
| 162 | void 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 | |
| 179 | void 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 | |
| 190 | void 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 | |
| 198 | void 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 | |