/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */ #include #include "lineinput.h" #include "ui.h" IInput *CLineInput::ms_pInput = nullptr; ITextRender *CLineInput::ms_pTextRender = nullptr; IGraphics *CLineInput::ms_pGraphics = nullptr; IClient *CLineInput::ms_pClient = nullptr; CLineInput *CLineInput::ms_pActiveInput = nullptr; EInputPriority CLineInput::ms_ActiveInputPriority = EInputPriority::NONE; vec2 CLineInput::ms_CompositionWindowPosition = vec2(0.0f, 0.0f); float CLineInput::ms_CompositionLineHeight = 0.0f; char CLineInput::ms_aStars[128] = {'\0'}; void CLineInput::SetBuffer(char *pStr, size_t MaxSize, size_t MaxChars) { if(m_pStr && m_pStr == pStr) return; const char *pLastStr = m_pStr; m_pStr = pStr; m_MaxSize = MaxSize; m_MaxChars = MaxChars; m_WasChanged = m_pStr && pLastStr && m_WasChanged; if(!pLastStr) { m_ScrollOffset = m_ScrollOffsetChange = 0.0f; m_CaretPosition = vec2(0.0f, 0.0f); m_Hidden = false; m_pEmptyText = nullptr; m_MouseSelection.m_Selecting = false; } if(m_pStr && m_pStr != pLastStr) UpdateStrData(); } void CLineInput::Clear() { mem_zero(m_pStr, m_MaxSize); UpdateStrData(); } void CLineInput::Set(const char *pString) { str_copy(m_pStr, pString, m_MaxSize); UpdateStrData(); SetCursorOffset(m_Len); } void CLineInput::SetRange(const char *pString, size_t Begin, size_t End) { if(Begin > End) std::swap(Begin, End); Begin = clamp(Begin, 0, m_Len); End = clamp(End, 0, m_Len); size_t RemovedCharSize, RemovedCharCount; str_utf8_stats(m_pStr + Begin, End - Begin + 1, m_MaxChars, &RemovedCharSize, &RemovedCharCount); size_t AddedCharSize, AddedCharCount; str_utf8_stats(pString, m_MaxSize - m_Len + RemovedCharSize, m_MaxChars - m_NumChars + RemovedCharCount, &AddedCharSize, &AddedCharCount); if(RemovedCharSize || AddedCharSize) { if(AddedCharSize < RemovedCharSize) { if(AddedCharSize) mem_copy(m_pStr + Begin, pString, AddedCharSize); mem_move(m_pStr + Begin + AddedCharSize, m_pStr + Begin + RemovedCharSize, m_Len - Begin - AddedCharSize); } else if(AddedCharSize > RemovedCharSize) mem_move(m_pStr + End + AddedCharSize - RemovedCharSize, m_pStr + End, m_Len - End); if(AddedCharSize >= RemovedCharSize) mem_copy(m_pStr + Begin, pString, AddedCharSize); m_CursorPos = End - RemovedCharSize + AddedCharSize; m_Len += AddedCharSize - RemovedCharSize; m_NumChars += AddedCharCount - RemovedCharCount; m_WasChanged = true; m_pStr[m_Len] = '\0'; m_SelectionStart = m_SelectionEnd = m_CursorPos; } } void CLineInput::Insert(const char *pString, size_t Begin) { SetRange(pString, Begin, Begin); } void CLineInput::Append(const char *pString) { Insert(pString, m_Len); } void CLineInput::UpdateStrData() { str_utf8_stats(m_pStr, m_MaxSize, m_MaxChars, &m_Len, &m_NumChars); if(!in_range(m_CursorPos, 0, m_Len)) SetCursorOffset(m_CursorPos); if(!in_range(m_SelectionStart, 0, m_Len) || !in_range(m_SelectionEnd, 0, m_Len)) SetSelection(m_SelectionStart, m_SelectionEnd); } const char *CLineInput::GetDisplayedString() { if(!IsHidden()) return m_pStr; const size_t NumStars = minimum(GetNumChars(), sizeof(ms_aStars) - 1); for(size_t i = 0; i < NumStars; ++i) ms_aStars[i] = '*'; ms_aStars[NumStars] = '\0'; return ms_aStars; } void CLineInput::MoveCursor(EMoveDirection Direction, bool MoveWord, const char *pStr, size_t MaxSize, size_t *pCursorPos) { // Check whether cursor position is initially on space or non-space character. // When forwarding, check character to the right of the cursor position. // When rewinding, check character to the left of the cursor position (rewind first). size_t PeekCursorPos = Direction == FORWARD ? *pCursorPos : str_utf8_rewind(pStr, *pCursorPos); const char *pTemp = pStr + PeekCursorPos; bool AnySpace = str_utf8_isspace(str_utf8_decode(&pTemp)); bool AnyWord = !AnySpace; while(true) { if(Direction == FORWARD) *pCursorPos = str_utf8_forward(pStr, *pCursorPos); else *pCursorPos = str_utf8_rewind(pStr, *pCursorPos); if(!MoveWord || *pCursorPos <= 0 || *pCursorPos >= MaxSize) break; PeekCursorPos = Direction == FORWARD ? *pCursorPos : str_utf8_rewind(pStr, *pCursorPos); pTemp = pStr + PeekCursorPos; const bool CurrentSpace = str_utf8_isspace(str_utf8_decode(&pTemp)); const bool CurrentWord = !CurrentSpace; if(Direction == FORWARD && AnySpace && !CurrentSpace) break; // Forward: Stop when next (right) character is non-space after seeing at least one space character. else if(Direction == REWIND && AnyWord && !CurrentWord) break; // Rewind: Stop when next (left) character is space after seeing at least one non-space character. AnySpace |= CurrentSpace; AnyWord |= CurrentWord; } } void CLineInput::SetCursorOffset(size_t Offset) { m_SelectionStart = m_SelectionEnd = m_LastCompositionCursorPos = m_CursorPos = clamp(Offset, 0, m_Len); m_WasChanged = true; } void CLineInput::SetSelection(size_t Start, size_t End) { if(Start > End) std::swap(Start, End); m_SelectionStart = clamp(Start, 0, m_Len); m_SelectionEnd = clamp(End, 0, m_Len); m_WasChanged = true; } size_t CLineInput::OffsetFromActualToDisplay(size_t ActualOffset) const { if(!IsHidden()) return ActualOffset; size_t DisplayOffset = 0; size_t CurrentOffset = 0; while(CurrentOffset < ActualOffset) { const size_t PrevOffset = CurrentOffset; CurrentOffset = str_utf8_forward(m_pStr, CurrentOffset); if(CurrentOffset == PrevOffset) break; DisplayOffset++; } return DisplayOffset; } size_t CLineInput::OffsetFromDisplayToActual(size_t DisplayOffset) const { if(!IsHidden()) return DisplayOffset; size_t ActualOffset = 0; for(size_t i = 0; i < DisplayOffset; i++) { const size_t PrevOffset = ActualOffset; ActualOffset = str_utf8_forward(m_pStr, ActualOffset); if(ActualOffset == PrevOffset) break; } return ActualOffset; } bool CLineInput::ProcessInput(const IInput::CEvent &Event) { // update derived attributes to handle external changes to the buffer UpdateStrData(); const size_t OldCursorPos = m_CursorPos; const bool Selecting = Input()->ShiftIsPressed(); const size_t SelectionLength = GetSelectionLength(); bool KeyHandled = false; if((Event.m_Flags & IInput::FLAG_TEXT) && !(KEY_LCTRL <= Event.m_Key && Event.m_Key <= KEY_RGUI)) { SetRange(Event.m_aText, m_SelectionStart, m_SelectionEnd); } if(Event.m_Flags & IInput::FLAG_PRESS) { const bool ModPressed = Input()->ModifierIsPressed(); const bool AltPressed = Input()->AltIsPressed(); #ifdef CONF_PLATFORM_MACOSX const bool MoveWord = AltPressed && !ModPressed; #else const bool MoveWord = ModPressed && !AltPressed; #endif if(Event.m_Key == KEY_BACKSPACE) { if(SelectionLength && !MoveWord) { SetRange("", m_SelectionStart, m_SelectionEnd); } else { // If in MoveWord-mode, backspace will delete the word before the selection if(SelectionLength) m_SelectionEnd = m_CursorPos = m_SelectionStart; if(m_CursorPos > 0) { size_t NewCursorPos = m_CursorPos; MoveCursor(REWIND, MoveWord, m_pStr, m_Len, &NewCursorPos); SetRange("", NewCursorPos, m_CursorPos); } m_SelectionStart = m_SelectionEnd = m_CursorPos; } } else if(Event.m_Key == KEY_DELETE) { if(SelectionLength && !MoveWord) { SetRange("", m_SelectionStart, m_SelectionEnd); } else { // If in MoveWord-mode, delete will delete the word after the selection if(SelectionLength) m_SelectionStart = m_CursorPos = m_SelectionEnd; if(m_CursorPos < m_Len) { size_t EndCursorPos = m_CursorPos; MoveCursor(FORWARD, MoveWord, m_pStr, m_Len, &EndCursorPos); SetRange("", m_CursorPos, EndCursorPos); } m_SelectionStart = m_SelectionEnd = m_CursorPos; } } else if(Event.m_Key == KEY_LEFT) { if(SelectionLength && !Selecting) { m_CursorPos = m_SelectionStart; } else if(m_CursorPos > 0) { MoveCursor(REWIND, MoveWord, m_pStr, m_Len, &m_CursorPos); if(Selecting) { if(m_SelectionStart == OldCursorPos) // expand start first m_SelectionStart = m_CursorPos; else if(m_SelectionEnd == OldCursorPos) m_SelectionEnd = m_CursorPos; } } if(!Selecting) m_SelectionStart = m_SelectionEnd = m_CursorPos; } else if(Event.m_Key == KEY_RIGHT) { if(SelectionLength && !Selecting) { m_CursorPos = m_SelectionEnd; } else if(m_CursorPos < m_Len) { MoveCursor(FORWARD, MoveWord, m_pStr, m_Len, &m_CursorPos); if(Selecting) { if(m_SelectionEnd == OldCursorPos) // expand end first m_SelectionEnd = m_CursorPos; else if(m_SelectionStart == OldCursorPos) m_SelectionStart = m_CursorPos; } } if(!Selecting) m_SelectionStart = m_SelectionEnd = m_CursorPos; } else if(Event.m_Key == KEY_HOME) { if(Selecting) { if(SelectionLength && m_CursorPos == m_SelectionEnd) m_SelectionEnd = m_SelectionStart; } else m_SelectionEnd = 0; m_CursorPos = 0; m_SelectionStart = 0; } else if(Event.m_Key == KEY_END) { if(Selecting) { if(SelectionLength && m_CursorPos == m_SelectionStart) m_SelectionStart = m_SelectionEnd; } else m_SelectionStart = m_Len; m_CursorPos = m_Len; m_SelectionEnd = m_Len; } else if(ModPressed && !AltPressed && Event.m_Key == KEY_V) { const char *pClipboardText = Input()->GetClipboardText(); if(pClipboardText) { std::string ClipboardText = Input()->GetClipboardText(); if(m_pfnClipboardLineCallback) { // Split clipboard text into multiple lines. Send all complete lines to callback. // The lineinput is set to the last clipboard line. bool FirstLine = true; size_t i, Begin = 0; for(i = 0; i < ClipboardText.length(); i++) { if(ClipboardText[i] == '\n') { if(i == Begin) { Begin++; continue; } std::string Line = ClipboardText.substr(Begin, i - Begin + 1); if(FirstLine) { str_sanitize_cc(Line.data()); SetRange(Line.c_str(), m_SelectionStart, m_SelectionEnd); FirstLine = false; Line = GetString(); } Begin = i + 1; m_pfnClipboardLineCallback(Line.c_str()); } } std::string Line = ClipboardText.substr(Begin, i - Begin + 1); str_sanitize_cc(Line.data()); if(FirstLine) SetRange(Line.c_str(), m_SelectionStart, m_SelectionEnd); else Set(Line.c_str()); } else { str_sanitize_cc(ClipboardText.data()); SetRange(ClipboardText.c_str(), m_SelectionStart, m_SelectionEnd); } } KeyHandled = true; } else if(ModPressed && !AltPressed && (Event.m_Key == KEY_C || Event.m_Key == KEY_X) && SelectionLength) { char *pSelection = m_pStr + m_SelectionStart; const char TempChar = pSelection[SelectionLength]; pSelection[SelectionLength] = '\0'; Input()->SetClipboardText(pSelection); pSelection[SelectionLength] = TempChar; if(Event.m_Key == KEY_X) SetRange("", m_SelectionStart, m_SelectionEnd); KeyHandled = true; } else if(ModPressed && !AltPressed && Event.m_Key == KEY_A) { m_SelectionStart = 0; m_SelectionEnd = m_CursorPos = m_Len; } } m_WasChanged |= OldCursorPos != m_CursorPos; m_WasChanged |= SelectionLength != GetSelectionLength(); return m_WasChanged || KeyHandled; } STextBoundingBox CLineInput::Render(const CUIRect *pRect, float FontSize, int Align, bool Changed, float LineWidth) { const char *pDisplayStr = GetDisplayedString(); const bool HasComposition = Input()->HasComposition(); if(pDisplayStr[0] == '\0' && !HasComposition && m_pEmptyText != nullptr) { pDisplayStr = m_pEmptyText; m_MouseSelection.m_Selecting = false; TextRender()->TextColor(1.0f, 1.0f, 1.0f, 0.75f); } CTextCursor Cursor; if(IsActive()) { const size_t CursorOffset = GetCursorOffset(); const size_t DisplayCursorOffset = OffsetFromActualToDisplay(CursorOffset); const size_t CompositionStart = CursorOffset + Input()->GetCompositionCursor(); const size_t DisplayCompositionStart = OffsetFromActualToDisplay(CompositionStart); const size_t CaretOffset = HasComposition ? DisplayCompositionStart : DisplayCursorOffset; std::string DisplayStrBuffer; if(HasComposition) { const std::string DisplayStr = std::string(pDisplayStr); DisplayStrBuffer = DisplayStr.substr(0, DisplayCursorOffset) + Input()->GetComposition() + DisplayStr.substr(DisplayCursorOffset); pDisplayStr = DisplayStrBuffer.c_str(); } const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth); const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align); TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = LineWidth; Cursor.m_ForceCursorRendering = Changed; Cursor.m_PressMouse.x = m_MouseSelection.m_PressMouse.x; Cursor.m_ReleaseMouse.x = m_MouseSelection.m_ReleaseMouse.x; if(LineWidth < 0.0f) { // Using a Y position that's always inside the line input makes it so the selection does not reset when // the mouse is moved outside the line input while selecting, which would otherwise be very inconvenient. // This is a single line cursor, so we don't need the Y position to support selection over multiple lines. Cursor.m_PressMouse.y = CursorPos.y + BoundingBox.m_H / 2.0f; Cursor.m_ReleaseMouse.y = CursorPos.y + BoundingBox.m_H / 2.0f; } else { Cursor.m_PressMouse.y = m_MouseSelection.m_PressMouse.y; Cursor.m_ReleaseMouse.y = m_MouseSelection.m_ReleaseMouse.y; } if(HasComposition) { // We need to track the last composition cursor position separately, because the composition // cursor movement does not cause an input event that would set the Changed variable. Cursor.m_ForceCursorRendering |= m_LastCompositionCursorPos != CaretOffset; m_LastCompositionCursorPos = CaretOffset; const size_t DisplayCompositionEnd = DisplayCursorOffset + Input()->GetCompositionLength(); Cursor.m_CursorMode = TEXT_CURSOR_CURSOR_MODE_SET; TextRender()->UTF8OffToDecodedOff(pDisplayStr, CaretOffset, Cursor.m_CursorCharacter); Cursor.m_CalculateSelectionMode = TEXT_CURSOR_SELECTION_MODE_SET; Cursor.m_SelectionHeightFactor = 0.1f; TextRender()->UTF8OffToDecodedOff(pDisplayStr, DisplayCursorOffset, Cursor.m_SelectionStart); TextRender()->UTF8OffToDecodedOff(pDisplayStr, DisplayCompositionEnd, Cursor.m_SelectionEnd); TextRender()->TextSelectionColor(1.0f, 1.0f, 1.0f, 0.8f); TextRender()->TextEx(&Cursor, pDisplayStr); TextRender()->TextSelectionColor(TextRender()->DefaultTextSelectionColor()); } else if(GetSelectionLength()) { const size_t Start = OffsetFromActualToDisplay(GetSelectionStart()); const size_t End = OffsetFromActualToDisplay(GetSelectionEnd()); Cursor.m_CursorMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_CURSOR_MODE_CALCULATE : TEXT_CURSOR_CURSOR_MODE_SET; TextRender()->UTF8OffToDecodedOff(pDisplayStr, CaretOffset, Cursor.m_CursorCharacter); Cursor.m_CalculateSelectionMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_SET; TextRender()->UTF8OffToDecodedOff(pDisplayStr, Start, Cursor.m_SelectionStart); TextRender()->UTF8OffToDecodedOff(pDisplayStr, End, Cursor.m_SelectionEnd); TextRender()->TextEx(&Cursor, pDisplayStr); } else { Cursor.m_CursorMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_CURSOR_MODE_CALCULATE : TEXT_CURSOR_CURSOR_MODE_SET; TextRender()->UTF8OffToDecodedOff(pDisplayStr, CaretOffset, Cursor.m_CursorCharacter); Cursor.m_CalculateSelectionMode = m_MouseSelection.m_Selecting ? TEXT_CURSOR_SELECTION_MODE_CALCULATE : TEXT_CURSOR_SELECTION_MODE_NONE; TextRender()->TextEx(&Cursor, pDisplayStr); } if(Cursor.m_CursorMode == TEXT_CURSOR_CURSOR_MODE_CALCULATE) { int NewCursorOffset; TextRender()->DecodedOffToUTF8Off(pDisplayStr, Cursor.m_CursorCharacter, NewCursorOffset); if(NewCursorOffset >= 0) { SetCursorOffset(OffsetFromDisplayToActual(NewCursorOffset)); } } if(Cursor.m_CalculateSelectionMode == TEXT_CURSOR_SELECTION_MODE_CALCULATE) { int NewSelectionStart, NewSelectionEnd; TextRender()->DecodedOffToUTF8Off(pDisplayStr, Cursor.m_SelectionStart, NewSelectionStart); TextRender()->DecodedOffToUTF8Off(pDisplayStr, Cursor.m_SelectionEnd, NewSelectionEnd); if(NewSelectionStart >= 0 && NewSelectionEnd >= 0) { SetSelection(OffsetFromDisplayToActual(NewSelectionStart), OffsetFromDisplayToActual(NewSelectionEnd)); } } m_CaretPosition = Cursor.m_CursorRenderedPosition; STextBoundingBox CaretBoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, DisplayCursorOffset, LineWidth); CaretBoundingBox.MoveBy(CursorPos); SetCompositionWindowPosition(vec2(CaretBoundingBox.Right(), CaretBoundingBox.Bottom()), CaretBoundingBox.m_H); } else { const STextBoundingBox BoundingBox = TextRender()->TextBoundingBox(FontSize, pDisplayStr, -1, LineWidth); const vec2 CursorPos = CUI::CalcAlignedCursorPos(pRect, BoundingBox.Size(), Align); TextRender()->SetCursor(&Cursor, CursorPos.x, CursorPos.y, FontSize, TEXTFLAG_RENDER); Cursor.m_LineWidth = LineWidth; TextRender()->TextEx(&Cursor, pDisplayStr); } TextRender()->TextColor(TextRender()->DefaultTextColor()); return Cursor.BoundingBox(); } void CLineInput::RenderCandidates() { if(!Input()->HasComposition() || !Input()->GetCandidateCount()) return; const float FontSize = 7.0f; const float Padding = 1.0f; const float Margin = 4.0f; const float Height = 300.0f; const float Width = Height * Graphics()->ScreenAspect(); const int ScreenWidth = Graphics()->ScreenWidth(); const int ScreenHeight = Graphics()->ScreenHeight(); Graphics()->MapScreen(0.0f, 0.0f, Width, Height); // Determine longest candidate width float LongestCandidateWidth = 0.0f; for(int i = 0; i < Input()->GetCandidateCount(); ++i) LongestCandidateWidth = maximum(LongestCandidateWidth, TextRender()->TextWidth(FontSize, Input()->GetCandidate(i))); const float NumOffset = 8.0f; const float RectWidth = LongestCandidateWidth + Margin + NumOffset + 2.0f * Padding; const float RectHeight = Input()->GetCandidateCount() * (FontSize + 2.0f * Padding) + Margin; vec2 Position = ms_CompositionWindowPosition / vec2(ScreenWidth, ScreenHeight) * vec2(Width, Height); Position.y += Margin; // Move candidate window left if needed if(Position.x + RectWidth + Margin > Width) Position.x -= Position.x + RectWidth + Margin - Width; // Move candidate window up if needed if(Position.y + RectHeight + Margin > Height) Position.y -= RectHeight + ms_CompositionLineHeight / ScreenHeight * Height + 2.0f * Margin; Graphics()->TextureClear(); Graphics()->QuadsBegin(); Graphics()->BlendNormal(); // Draw window shadow Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.8f); IGraphics::CQuadItem Quad = IGraphics::CQuadItem(Position.x + 0.75f, Position.y + 0.75f, RectWidth, RectHeight); Graphics()->QuadsDrawTL(&Quad, 1); // Draw window background Graphics()->SetColor(0.15f, 0.15f, 0.15f, 1.0f); Quad = IGraphics::CQuadItem(Position.x, Position.y, RectWidth, RectHeight); Graphics()->QuadsDrawTL(&Quad, 1); // Draw selected entry highlight Graphics()->SetColor(0.1f, 0.4f, 0.8f, 1.0f); Quad = IGraphics::CQuadItem(Position.x + Margin / 4.0f, Position.y + Margin / 2.0f + Input()->GetCandidateSelectedIndex() * (FontSize + 2.0f * Padding), RectWidth - Margin / 2.0f, FontSize + 2.0f * Padding); Graphics()->QuadsDrawTL(&Quad, 1); Graphics()->QuadsEnd(); // Draw candidates for(int i = 0; i < Input()->GetCandidateCount(); ++i) { char aBuf[3]; str_format(aBuf, sizeof(aBuf), "%d.", (i + 1) % 10); const float PosX = Position.x + Margin / 2.0f + Padding; const float PosY = Position.y + Margin / 2.0f + i * (FontSize + 2.0f * Padding) + Padding; TextRender()->TextColor(0.6f, 0.6f, 0.6f, 1.0f); TextRender()->Text(PosX, PosY, FontSize, aBuf); TextRender()->TextColor(1.0f, 1.0f, 1.0f, 1.0f); TextRender()->Text(PosX + NumOffset, PosY, FontSize, Input()->GetCandidate(i)); } } void CLineInput::SetCompositionWindowPosition(vec2 Anchor, float LineHeight) { float ScreenX0, ScreenY0, ScreenX1, ScreenY1; const int ScreenWidth = Graphics()->ScreenWidth(); const int ScreenHeight = Graphics()->ScreenHeight(); Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); const vec2 ScreenScale = vec2(ScreenWidth / (ScreenX1 - ScreenX0), ScreenHeight / (ScreenY1 - ScreenY0)); ms_CompositionWindowPosition = Anchor * ScreenScale; ms_CompositionLineHeight = LineHeight * ScreenScale.y; Input()->SetCompositionWindowPosition(ms_CompositionWindowPosition.x, ms_CompositionWindowPosition.y - ms_CompositionLineHeight, ms_CompositionLineHeight); } void CLineInput::Activate(EInputPriority Priority) { if(IsActive()) return; if(ms_ActiveInputPriority != EInputPriority::NONE && Priority < ms_ActiveInputPriority) return; // do not replace a higher priority input if(ms_pActiveInput) ms_pActiveInput->OnDeactivate(); ms_pActiveInput = this; ms_pActiveInput->OnActivate(); ms_ActiveInputPriority = Priority; } void CLineInput::Deactivate() { if(!IsActive()) return; ms_pActiveInput->OnDeactivate(); ms_pActiveInput = nullptr; ms_ActiveInputPriority = EInputPriority::NONE; } void CLineInput::OnActivate() { Input()->StartTextInput(); } void CLineInput::OnDeactivate() { Input()->StopTextInput(); m_MouseSelection.m_Selecting = false; } void CLineInputNumber::SetInteger(int Number, int Base) { char aBuf[32]; switch(Base) { case 10: str_format(aBuf, sizeof(aBuf), "%d", Number); break; case 16: str_format(aBuf, sizeof(aBuf), "%06X", Number); break; default: dbg_assert(false, "Base unsupported"); return; } if(str_comp(aBuf, GetDisplayedString()) != 0) Set(aBuf); } int CLineInputNumber::GetInteger(int Base) const { return str_toint_base(GetString(), Base); } void CLineInputNumber::SetFloat(float Number) { char aBuf[32]; str_format(aBuf, sizeof(aBuf), "%.3f", Number); if(str_comp(aBuf, GetDisplayedString()) != 0) Set(aBuf); } float CLineInputNumber::GetFloat() const { return str_tofloat(GetString()); }