| 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 | |
| 15 | using namespace std::chrono_literals; |
| 16 | |
| 17 | void 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 | |
| 26 | void 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 | |
| 33 | void 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 | |
| 45 | bool 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 | |
| 170 | void 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 | |
| 187 | void 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 | |
| 196 | void 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 | |
| 204 | void 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 | |
| 263 | bool CFontTyper::IsActive() const |
| 264 | { |
| 265 | return Map()->m_FontTyperState.m_Active; |
| 266 | } |
| 267 | |