mirror of
https://github.com/bulletphysics/bullet3
synced 2024-12-13 21:30:09 +00:00
408 lines
7.6 KiB
C++
408 lines
7.6 KiB
C++
/*
|
|
GWEN
|
|
Copyright (c) 2010 Facepunch Studios
|
|
See license in Gwen.h
|
|
*/
|
|
|
|
#include "Gwen/Gwen.h"
|
|
#include "Gwen/Controls/TextBox.h"
|
|
#include "Gwen/Skin.h"
|
|
#include "Gwen/Utility.h"
|
|
#include "Gwen/Platform.h"
|
|
|
|
#include <math.h>
|
|
#include <cmath>
|
|
|
|
using namespace Gwen;
|
|
using namespace Gwen::Controls;
|
|
|
|
GWEN_CONTROL_CONSTRUCTOR(TextBox)
|
|
{
|
|
SetSize(200, 20);
|
|
|
|
SetMouseInputEnabled(true);
|
|
SetKeyboardInputEnabled(true);
|
|
|
|
SetAlignment(Pos::Left | Pos::CenterV);
|
|
SetTextPadding(Padding(4, 2, 4, 2));
|
|
|
|
m_iCursorPos = 0;
|
|
m_iCursorEnd = 0;
|
|
m_bSelectAll = false;
|
|
|
|
SetTextColor(Gwen::Color(50, 50, 50, 255)); // TODO: From Skin
|
|
|
|
SetTabable(true);
|
|
|
|
AddAccelerator(L"Ctrl + c", &TextBox::OnCopy);
|
|
AddAccelerator(L"Ctrl + x", &TextBox::OnCut);
|
|
AddAccelerator(L"Ctrl + v", &TextBox::OnPaste);
|
|
AddAccelerator(L"Ctrl + a", &TextBox::OnSelectAll);
|
|
}
|
|
|
|
bool TextBox::OnChar(Gwen::UnicodeChar c)
|
|
{
|
|
if (c == '\t') return false;
|
|
|
|
Gwen::UnicodeString str;
|
|
str += c;
|
|
|
|
InsertText(str);
|
|
return true;
|
|
}
|
|
|
|
void TextBox::InsertText(const Gwen::UnicodeString& strInsert)
|
|
{
|
|
// TODO: Make sure fits (implement maxlength)
|
|
|
|
if (HasSelection())
|
|
{
|
|
EraseSelection();
|
|
}
|
|
|
|
if (m_iCursorPos > TextLength()) m_iCursorPos = TextLength();
|
|
|
|
if (!IsTextAllowed(strInsert, m_iCursorPos))
|
|
return;
|
|
|
|
UnicodeString str = GetText();
|
|
str.insert(m_iCursorPos, strInsert);
|
|
SetText(str);
|
|
|
|
m_iCursorPos += (int)strInsert.size();
|
|
m_iCursorEnd = m_iCursorPos;
|
|
|
|
RefreshCursorBounds();
|
|
}
|
|
|
|
void TextBox::Render(Skin::Base* skin)
|
|
{
|
|
if (ShouldDrawBackground())
|
|
skin->DrawTextBox(this);
|
|
|
|
if (!HasFocus()) return;
|
|
|
|
// Draw selection.. if selected..
|
|
if (m_iCursorPos != m_iCursorEnd)
|
|
{
|
|
skin->GetRender()->SetDrawColor(Gwen::Color(50, 170, 255, 200));
|
|
skin->GetRender()->DrawFilledRect(m_rectSelectionBounds);
|
|
}
|
|
|
|
// Draw caret
|
|
if (std::fmod(Gwen::Platform::GetTimeInSeconds() - m_fLastInputTime,
|
|
1.0f) > 0.5f)
|
|
skin->GetRender()->SetDrawColor(Gwen::Color(255, 255, 255, 255));
|
|
else
|
|
skin->GetRender()->SetDrawColor(Gwen::Color(0, 0, 0, 255));
|
|
|
|
skin->GetRender()->DrawFilledRect(m_rectCaretBounds);
|
|
}
|
|
|
|
void TextBox::RefreshCursorBounds()
|
|
{
|
|
m_fLastInputTime = Gwen::Platform::GetTimeInSeconds();
|
|
|
|
MakeCaratVisible();
|
|
|
|
Gwen::Point pA = GetCharacterPosition(m_iCursorPos);
|
|
Gwen::Point pB = GetCharacterPosition(m_iCursorEnd);
|
|
|
|
m_rectSelectionBounds.x = Utility::Min(pA.x, pB.x);
|
|
m_rectSelectionBounds.y = m_Text->Y() - 1;
|
|
m_rectSelectionBounds.w = Utility::Max(pA.x, pB.x) - m_rectSelectionBounds.x;
|
|
m_rectSelectionBounds.h = m_Text->Height() + 2;
|
|
|
|
m_rectCaretBounds.x = pA.x;
|
|
m_rectCaretBounds.y = m_Text->Y() - 1;
|
|
m_rectCaretBounds.w = 1;
|
|
m_rectCaretBounds.h = m_Text->Height() + 2;
|
|
|
|
Redraw();
|
|
}
|
|
|
|
void TextBox::OnPaste(Gwen::Controls::Base* /*pCtrl*/)
|
|
{
|
|
InsertText(Platform::GetClipboardText());
|
|
}
|
|
|
|
void TextBox::OnCopy(Gwen::Controls::Base* /*pCtrl*/)
|
|
{
|
|
if (!HasSelection()) return;
|
|
|
|
Platform::SetClipboardText(GetSelection());
|
|
}
|
|
|
|
void TextBox::OnCut(Gwen::Controls::Base* /*pCtrl*/)
|
|
{
|
|
if (!HasSelection()) return;
|
|
|
|
Platform::SetClipboardText(GetSelection());
|
|
EraseSelection();
|
|
}
|
|
|
|
void TextBox::OnSelectAll(Gwen::Controls::Base* /*pCtrl*/)
|
|
{
|
|
m_iCursorEnd = 0;
|
|
m_iCursorPos = TextLength();
|
|
|
|
RefreshCursorBounds();
|
|
}
|
|
|
|
void TextBox::OnMouseDoubleClickLeft(int /*x*/, int /*y*/)
|
|
{
|
|
OnSelectAll(this);
|
|
}
|
|
|
|
UnicodeString TextBox::GetSelection()
|
|
{
|
|
if (!HasSelection()) return L"";
|
|
|
|
int iStart = Utility::Min(m_iCursorPos, m_iCursorEnd);
|
|
int iEnd = Utility::Max(m_iCursorPos, m_iCursorEnd);
|
|
|
|
const UnicodeString& str = GetText();
|
|
return str.substr(iStart, iEnd - iStart);
|
|
}
|
|
|
|
bool TextBox::OnKeyReturn(bool bDown)
|
|
{
|
|
if (bDown) return true;
|
|
|
|
OnEnter();
|
|
|
|
// Try to move to the next control, as if tab had been pressed
|
|
OnKeyTab(true);
|
|
|
|
// If we still have focus, blur it.
|
|
if (HasFocus())
|
|
{
|
|
Blur();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TextBox::OnKeyBackspace(bool bDown)
|
|
{
|
|
if (!bDown) return true;
|
|
if (HasSelection())
|
|
{
|
|
EraseSelection();
|
|
return true;
|
|
}
|
|
|
|
if (m_iCursorPos == 0) return true;
|
|
|
|
DeleteText(m_iCursorPos - 1, 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TextBox::OnKeyDelete(bool bDown)
|
|
{
|
|
if (!bDown) return true;
|
|
if (HasSelection())
|
|
{
|
|
EraseSelection();
|
|
return true;
|
|
}
|
|
|
|
if (m_iCursorPos >= TextLength()) return true;
|
|
|
|
DeleteText(m_iCursorPos, 1);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool TextBox::OnKeyLeft(bool bDown)
|
|
{
|
|
if (!bDown) return true;
|
|
|
|
if (m_iCursorPos > 0)
|
|
m_iCursorPos--;
|
|
|
|
if (!Gwen::Input::IsShiftDown())
|
|
{
|
|
m_iCursorEnd = m_iCursorPos;
|
|
}
|
|
|
|
RefreshCursorBounds();
|
|
return true;
|
|
}
|
|
|
|
bool TextBox::OnKeyRight(bool bDown)
|
|
{
|
|
if (!bDown) return true;
|
|
|
|
if (m_iCursorPos < TextLength())
|
|
m_iCursorPos++;
|
|
|
|
if (!Gwen::Input::IsShiftDown())
|
|
{
|
|
m_iCursorEnd = m_iCursorPos;
|
|
}
|
|
|
|
RefreshCursorBounds();
|
|
return true;
|
|
}
|
|
|
|
bool TextBox::OnKeyHome(bool bDown)
|
|
{
|
|
if (!bDown) return true;
|
|
m_iCursorPos = 0;
|
|
|
|
if (!Gwen::Input::IsShiftDown())
|
|
{
|
|
m_iCursorEnd = m_iCursorPos;
|
|
}
|
|
|
|
RefreshCursorBounds();
|
|
return true;
|
|
}
|
|
|
|
bool TextBox::OnKeyEnd(bool /*bDown*/)
|
|
{
|
|
m_iCursorPos = TextLength();
|
|
|
|
if (!Gwen::Input::IsShiftDown())
|
|
{
|
|
m_iCursorEnd = m_iCursorPos;
|
|
}
|
|
|
|
RefreshCursorBounds();
|
|
return true;
|
|
}
|
|
|
|
void TextBox::SetCursorPos(int i)
|
|
{
|
|
if (m_iCursorPos == i) return;
|
|
|
|
m_iCursorPos = i;
|
|
RefreshCursorBounds();
|
|
}
|
|
|
|
void TextBox::SetCursorEnd(int i)
|
|
{
|
|
if (m_iCursorEnd == i) return;
|
|
|
|
m_iCursorEnd = i;
|
|
RefreshCursorBounds();
|
|
}
|
|
|
|
void TextBox::DeleteText(int iStartPos, int iLength)
|
|
{
|
|
UnicodeString str = GetText();
|
|
str.erase(iStartPos, iLength);
|
|
SetText(str);
|
|
|
|
if (m_iCursorPos > iStartPos)
|
|
{
|
|
SetCursorPos(m_iCursorPos - iLength);
|
|
}
|
|
|
|
SetCursorEnd(m_iCursorPos);
|
|
}
|
|
|
|
bool TextBox::HasSelection()
|
|
{
|
|
return m_iCursorPos != m_iCursorEnd;
|
|
}
|
|
|
|
void TextBox::EraseSelection()
|
|
{
|
|
int iStart = Utility::Min(m_iCursorPos, m_iCursorEnd);
|
|
int iEnd = Utility::Max(m_iCursorPos, m_iCursorEnd);
|
|
|
|
DeleteText(iStart, iEnd - iStart);
|
|
|
|
// Move the cursor to the start of the selection,
|
|
// since the end is probably outside of the string now.
|
|
m_iCursorPos = iStart;
|
|
m_iCursorEnd = iStart;
|
|
}
|
|
|
|
void TextBox::OnMouseClickLeft(int x, int y, bool bDown)
|
|
{
|
|
if (m_bSelectAll)
|
|
{
|
|
OnSelectAll(this);
|
|
m_bSelectAll = false;
|
|
return;
|
|
}
|
|
|
|
int iChar = m_Text->GetClosestCharacter(m_Text->CanvasPosToLocal(Gwen::Point(x, y)));
|
|
|
|
if (bDown)
|
|
{
|
|
SetCursorPos(iChar);
|
|
|
|
if (!Gwen::Input::IsShiftDown())
|
|
SetCursorEnd(iChar);
|
|
|
|
Gwen::MouseFocus = this;
|
|
}
|
|
else
|
|
{
|
|
if (Gwen::MouseFocus == this)
|
|
{
|
|
SetCursorPos(iChar);
|
|
Gwen::MouseFocus = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
void TextBox::OnMouseMoved(int x, int y, int /*deltaX*/, int /*deltaY*/)
|
|
{
|
|
if (Gwen::MouseFocus != this) return;
|
|
|
|
int iChar = m_Text->GetClosestCharacter(m_Text->CanvasPosToLocal(Gwen::Point(x, y)));
|
|
|
|
SetCursorPos(iChar);
|
|
}
|
|
|
|
void TextBox::MakeCaratVisible()
|
|
{
|
|
int iCaratPos = m_Text->GetCharacterPosition(m_iCursorPos).x;
|
|
|
|
// If the carat is already in a semi-good position, leave it.
|
|
{
|
|
int iRealCaratPos = iCaratPos + m_Text->X();
|
|
if (iRealCaratPos > Width() * 0.1f && iRealCaratPos < Width() * 0.9f)
|
|
return;
|
|
}
|
|
|
|
// The ideal position is for the carat to be right in the middle
|
|
int idealx = -iCaratPos + Width() * 0.5f;
|
|
;
|
|
|
|
// Don't show too much whitespace to the right
|
|
if (idealx + m_Text->Width() < Width() - m_rTextPadding.right)
|
|
idealx = -m_Text->Width() + (Width() - m_rTextPadding.right);
|
|
|
|
// Or the left
|
|
if (idealx > m_rTextPadding.left)
|
|
idealx = m_rTextPadding.left;
|
|
|
|
m_Text->SetPos(idealx, m_Text->Y());
|
|
}
|
|
|
|
void TextBox::Layout(Skin::Base* skin)
|
|
{
|
|
BaseClass::Layout(skin);
|
|
|
|
RefreshCursorBounds();
|
|
}
|
|
|
|
void TextBox::OnTextChanged()
|
|
{
|
|
if (m_iCursorPos > TextLength()) m_iCursorPos = TextLength();
|
|
if (m_iCursorEnd > TextLength()) m_iCursorEnd = TextLength();
|
|
|
|
onTextChanged.Call(this);
|
|
}
|
|
|
|
void TextBox::OnEnter()
|
|
{
|
|
onReturnPressed.Call(this);
|
|
} |