Index: ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.cpp
===================================================================
--- ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.cpp (revision 25692)
+++ ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.cpp (revision 25693)
@@ -1,69 +1,65 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "IGUIScrollBarOwner.h"
#include "gui/CGUI.h"
#include "gui/IGUIScrollBar.h"
#include "gui/ObjectBases/IGUIObject.h"
IGUIScrollBarOwner::IGUIScrollBarOwner(IGUIObject& pObject)
: m_pObject(pObject)
{
}
-IGUIScrollBarOwner::~IGUIScrollBarOwner()
-{
- for (IGUIScrollBar* const& sb : m_ScrollBars)
- delete sb;
-}
+IGUIScrollBarOwner::~IGUIScrollBarOwner() = default;
void IGUIScrollBarOwner::ResetStates()
{
- for (IGUIScrollBar* const& sb : m_ScrollBars)
- sb->SetBarPressed(false);
+ for (const std::unique_ptr& scrollBar : m_ScrollBars)
+ scrollBar->SetBarPressed(false);
}
-void IGUIScrollBarOwner::AddScrollBar(IGUIScrollBar* scrollbar)
+void IGUIScrollBarOwner::AddScrollBar(std::unique_ptr scrollbar)
{
scrollbar->SetHostObject(this);
- m_ScrollBars.push_back(scrollbar);
+ m_ScrollBars.emplace_back(std::move(scrollbar));
}
const SGUIScrollBarStyle* IGUIScrollBarOwner::GetScrollBarStyle(const CStr& style) const
{
return m_pObject.GetGUI().GetScrollBarStyle(style);
}
void IGUIScrollBarOwner::HandleMessage(SGUIMessage& msg)
{
- for (IGUIScrollBar* const& sb : m_ScrollBars)
- sb->HandleMessage(msg);
+ for (const std::unique_ptr& scrollBar : m_ScrollBars)
+ scrollBar->HandleMessage(msg);
}
void IGUIScrollBarOwner::Draw(CCanvas2D& canvas)
{
- for (IGUIScrollBar* const& sb : m_ScrollBars)
- sb->Draw(canvas);
+ for (const std::unique_ptr& scrollBar : m_ScrollBars)
+ scrollBar->Draw(canvas);
}
float IGUIScrollBarOwner::GetScrollBarPos(const int index) const
{
return m_ScrollBars[index]->GetPos();
}
Index: ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.h
===================================================================
--- ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.h (revision 25692)
+++ ps/trunk/source/gui/ObjectBases/IGUIScrollBarOwner.h (revision 25693)
@@ -1,96 +1,97 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_IGUISCROLLBAROWNER
#define INCLUDED_IGUISCROLLBAROWNER
+#include
#include
class CCanvas2D;
class CStr8;
struct SGUIMessage;
struct SGUIScrollBarStyle;
class IGUIScrollBar;
class IGUIObject;
/**
* Base-class this if you want an object to contain
* one, or several, scroll-bars.
*/
class IGUIScrollBarOwner
{
NONCOPYABLE(IGUIScrollBarOwner);
friend class IGUIScrollBar;
public:
IGUIScrollBarOwner(IGUIObject& m_pObject);
virtual ~IGUIScrollBarOwner();
virtual void Draw(CCanvas2D& canvas);
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* @see IGUIObject#ResetStates()
*/
virtual void ResetStates();
/**
* Interface for the m_ScrollBar to use.
*/
virtual const SGUIScrollBarStyle* GetScrollBarStyle(const CStr8& style) const;
/**
* Add a scroll-bar
*/
- virtual void AddScrollBar(IGUIScrollBar* scrollbar);
+ virtual void AddScrollBar(std::unique_ptr scrollbar);
/**
* Get Scroll Bar reference (it should be transparent it's actually
* pointers).
*/
virtual IGUIScrollBar& GetScrollBar(const int& index)
{
return *m_ScrollBars[index];
}
/**
* Get the position of the scroll bar at @param index.
* Equivalent to GetScrollbar(index).GetPos().
*/
virtual float GetScrollBarPos(const int index) const;
protected:
/**
* Predominately you will only have one, but you can have
* as many as you like.
*/
- std::vector m_ScrollBars;
+ std::vector> m_ScrollBars;
private:
/**
* Reference to the IGUIObject.
* Private, because we don't want to inherit it in multiple classes.
*/
IGUIObject& m_pObject;
};
#endif // INCLUDED_IGUISCROLLBAROWNER
Index: ps/trunk/source/gui/ObjectTypes/CInput.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CInput.cpp (revision 25692)
+++ ps/trunk/source/gui/ObjectTypes/CInput.cpp (revision 25693)
@@ -1,2108 +1,2108 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CInput.h"
#include "graphics/Canvas2D.h"
#include "graphics/FontMetrics.h"
#include "graphics/TextRenderer.h"
#include "gui/CGUI.h"
#include "gui/CGUIScrollBarVertical.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "ps/ConfigDB.h"
#include "ps/CStrInternStatic.h"
#include "ps/GameSetup/Config.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "renderer/Renderer.h"
#include
extern int g_yres;
const CStr CInput::EventNameTextEdit = "TextEdit";
const CStr CInput::EventNamePress = "Press";
const CStr CInput::EventNameTab = "Tab";
CInput::CInput(CGUI& pGUI)
:
IGUIObject(pGUI),
IGUIScrollBarOwner(*static_cast(this)),
m_iBufferPos(-1),
m_iBufferPos_Tail(-1),
m_SelectingText(),
m_HorizontalScroll(),
m_PrevTime(),
m_CursorVisState(true),
m_CursorBlinkRate(0.5),
m_ComposingText(),
m_GeneratedPlaceholderTextValid(false),
m_iComposedLength(),
m_iComposedPos(),
m_iInsertPos(),
m_BufferPosition(this, "buffer_position"),
m_BufferZone(this, "buffer_zone"),
m_Caption(this, "caption"),
m_Font(this, "font"),
m_MaskChar(this, "mask_char"),
m_Mask(this, "mask"),
m_MaxLength(this, "max_length"),
m_MultiLine(this, "multiline"),
m_Readonly(this, "readonly"),
m_ScrollBar(this, "scrollbar"),
m_ScrollBarStyle(this, "scrollbar_style"),
m_Sprite(this, "sprite"),
m_SpriteOverlay(this, "sprite_overlay"),
m_SpriteSelectArea(this, "sprite_selectarea"),
m_TextColor(this, "textcolor"),
m_TextColorSelected(this, "textcolor_selected"),
m_PlaceholderText(this, "placeholder_text"),
m_PlaceholderColor(this, "placeholder_color")
{
CFG_GET_VAL("gui.cursorblinkrate", m_CursorBlinkRate);
- CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
+ auto bar = std::make_unique(pGUI);
bar->SetRightAligned(true);
- AddScrollBar(bar);
+ AddScrollBar(std::move(bar));
}
CInput::~CInput()
{
}
void CInput::UpdateBufferPositionSetting()
{
m_BufferPosition.Set(m_iBufferPos, false);
}
void CInput::ClearComposedText()
{
m_Caption.GetMutable().erase(m_iInsertPos, m_iComposedLength);
m_iBufferPos = m_iInsertPos;
UpdateBufferPositionSetting();
m_iComposedLength = 0;
m_iComposedPos = 0;
}
InReaction CInput::ManuallyHandleKeys(const SDL_Event_* ev)
{
ENSURE(m_iBufferPos != -1);
// Get direct access to silently mutate m_Caption.
// (Messages don't currently need to be sent)
CStrW& caption = m_Caption.GetMutable();
switch (ev->ev.type)
{
case SDL_HOTKEYDOWN:
{
if (m_ComposingText)
return IN_HANDLED;
return ManuallyHandleHotkeyEvent(ev);
}
// SDL2 has a new method of text input that better supports Unicode and CJK
// see https://wiki.libsdl.org/Tutorials/TextInput
case SDL_TEXTINPUT:
{
if (m_Readonly)
return IN_PASS;
// Text has been committed, either single key presses or through an IME
std::wstring text = wstring_from_utf8(ev->ev.text.text);
// Check max length
if (m_MaxLength != 0 && caption.length() + text.length() > static_cast(m_MaxLength))
return IN_HANDLED;
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
if (m_ComposingText)
{
ClearComposedText();
m_ComposingText = false;
}
if (m_iBufferPos == static_cast(caption.length()))
caption.append(text);
else
caption.insert(m_iBufferPos, text);
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
m_iBufferPos += text.length();
UpdateBufferPositionSetting();
m_iBufferPos_Tail = -1;
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
return IN_HANDLED;
}
case SDL_TEXTEDITING:
{
if (m_Readonly)
return IN_PASS;
// Text is being composed with an IME
// TODO: indicate this by e.g. underlining the uncommitted text
const char* rawText = ev->ev.edit.text;
int rawLength = strlen(rawText);
std::wstring wtext = wstring_from_utf8(rawText);
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
// Remember cursor position when text composition begins
if (!m_ComposingText)
m_iInsertPos = m_iBufferPos;
else
{
// Composed text is replaced each time
ClearComposedText();
}
m_ComposingText = ev->ev.edit.start != 0 || rawLength != 0;
if (m_ComposingText)
{
caption.insert(m_iInsertPos, wtext);
// The text buffer is limited to SDL_TEXTEDITINGEVENT_TEXT_SIZE bytes, yet start
// increases without limit, so don't let it advance beyond the composed text length
m_iComposedLength = wtext.length();
m_iComposedPos = ev->ev.edit.start < m_iComposedLength ? ev->ev.edit.start : m_iComposedLength;
m_iBufferPos = m_iInsertPos + m_iComposedPos;
// TODO: composed text selection - what does ev.edit.length do?
m_iBufferPos_Tail = -1;
}
UpdateBufferPositionSetting();
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
return IN_HANDLED;
}
case SDL_KEYDOWN:
case SDL_KEYUP:
{
// Since the GUI framework doesn't handle to set settings
// in Unicode (CStrW), we'll simply retrieve the actual
// pointer and edit that.
SDL_Keycode keyCode = ev->ev.key.keysym.sym;
// We have a probably printable key - we should return HANDLED so it can't trigger hotkeys.
// However, if Ctrl/Meta modifiers are active, just pass it through instead,
// assuming that we are indeed trying to trigger hotkeys (e.g. copy/paste).
// Escape & the "cancel" hotkey are also passed through to allow closing dialogs easily.
// See also similar logic in CConsole.cpp
// NB: this assumes that Ctrl/GUI aren't used in the Manually* functions below,
// as those code paths would obviously never be taken.
if (keyCode == SDLK_ESCAPE || EventWillFireHotkey(ev, "cancel") ||
g_scancodes[SDL_SCANCODE_LCTRL] || g_scancodes[SDL_SCANCODE_RCTRL] ||
g_scancodes[SDL_SCANCODE_LGUI] || g_scancodes[SDL_SCANCODE_RGUI])
return IN_PASS;
if (m_ComposingText)
return IN_HANDLED;
if (ev->ev.type == SDL_KEYDOWN)
{
ManuallyImmutableHandleKeyDownEvent(keyCode);
ManuallyMutableHandleKeyDownEvent(keyCode);
UpdateBufferPositionSetting();
}
return IN_HANDLED;
}
default:
{
return IN_PASS;
}
}
}
void CInput::ManuallyMutableHandleKeyDownEvent(const SDL_Keycode keyCode)
{
if (m_Readonly)
return;
wchar_t cooked = 0;
// Get direct access to silently mutate m_Caption.
// (Messages don't currently need to be sent)
CStrW& caption = m_Caption.GetMutable();
switch (keyCode)
{
case SDLK_TAB:
{
SendEvent(GUIM_TAB, EventNameTab);
// Don't send a textedit event, because it should only
// be sent if the GUI control changes the text
break;
}
case SDLK_BACKSPACE:
{
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
else
{
m_iBufferPos_Tail = -1;
if (caption.empty() || m_iBufferPos == 0)
break;
if (m_iBufferPos == static_cast(caption.length()))
caption = caption.Left(static_cast(caption.length()) - 1);
else
caption =
caption.Left(m_iBufferPos - 1) +
caption.Right(static_cast(caption.length()) - m_iBufferPos);
--m_iBufferPos;
UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
}
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
break;
}
case SDLK_DELETE:
{
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
else
{
if (caption.empty() || m_iBufferPos == static_cast(caption.length()))
break;
caption =
caption.Left(m_iBufferPos) +
caption.Right(static_cast(caption.length()) - (m_iBufferPos + 1));
UpdateText(m_iBufferPos, m_iBufferPos + 1, m_iBufferPos);
}
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
break;
}
case SDLK_KP_ENTER:
case SDLK_RETURN:
{
// 'Return' should do a Press event for single liners (e.g. submitting forms)
// otherwise a '\n' character will be added.
if (!m_MultiLine)
{
SendEvent(GUIM_PRESSED, EventNamePress);
break;
}
cooked = '\n'; // Change to '\n' and do default:
FALLTHROUGH;
}
default: // Insert a character
{
// Regular input is handled via SDL_TEXTINPUT, so we should ignore it here.
if (cooked == 0)
return;
// Check max length
if (m_MaxLength != 0 && caption.length() >= static_cast(m_MaxLength))
break;
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
m_iBufferPos_Tail = -1;
if (m_iBufferPos == static_cast(caption.length()))
caption += cooked;
else
caption =
caption.Left(m_iBufferPos) + cooked +
caption.Right(static_cast(caption.length()) - m_iBufferPos);
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos + 1);
++m_iBufferPos;
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
break;
}
}
}
void CInput::ManuallyImmutableHandleKeyDownEvent(const SDL_Keycode keyCode)
{
bool shiftKeyPressed = g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT];
const CStrW& caption = *m_Caption;
switch (keyCode)
{
case SDLK_HOME:
{
// If there's not a selection, we should create one now
if (!shiftKeyPressed)
{
// Make sure a selection isn't created.
m_iBufferPos_Tail = -1;
}
else if (!SelectingText())
{
// Place tail at the current point:
m_iBufferPos_Tail = m_iBufferPos;
}
m_iBufferPos = 0;
m_WantedX = 0.0f;
UpdateAutoScroll();
break;
}
case SDLK_END:
{
// If there's not a selection, we should create one now
if (!shiftKeyPressed)
{
// Make sure a selection isn't created.
m_iBufferPos_Tail = -1;
}
else if (!SelectingText())
{
// Place tail at the current point:
m_iBufferPos_Tail = m_iBufferPos;
}
m_iBufferPos = static_cast(caption.length());
m_WantedX = 0.0f;
UpdateAutoScroll();
break;
}
/**
* Conventions for Left/Right when text is selected:
*
* References:
*
* Visual Studio
* Visual Studio has the 'newer' approach, used by newer versions of
* things, and in newer applications. A left press will always place
* the pointer on the left edge of the selection, and then of course
* remove the selection. Right will do the exact same thing.
* If you have the pointer on the right edge and press right, it will
* in other words just remove the selection.
*
* Windows (eg. Notepad)
* A left press always takes the pointer a step to the left and
* removes the selection as if it were never there in the first place.
* Right of course does the same thing but to the right.
*
* I chose the Visual Studio convention. Used also in Word, gtk 2.0, MSN
* Messenger.
*/
case SDLK_LEFT:
{
m_WantedX = 0.f;
if (shiftKeyPressed || !SelectingText())
{
if (!shiftKeyPressed)
m_iBufferPos_Tail = -1;
else if (!SelectingText())
m_iBufferPos_Tail = m_iBufferPos;
if (m_iBufferPos > 0)
--m_iBufferPos;
}
else
{
if (m_iBufferPos_Tail < m_iBufferPos)
m_iBufferPos = m_iBufferPos_Tail;
m_iBufferPos_Tail = -1;
}
UpdateAutoScroll();
break;
}
case SDLK_RIGHT:
{
m_WantedX = 0.0f;
if (shiftKeyPressed || !SelectingText())
{
if (!shiftKeyPressed)
m_iBufferPos_Tail = -1;
else if (!SelectingText())
m_iBufferPos_Tail = m_iBufferPos;
if (m_iBufferPos < static_cast(caption.length()))
++m_iBufferPos;
}
else
{
if (m_iBufferPos_Tail > m_iBufferPos)
m_iBufferPos = m_iBufferPos_Tail;
m_iBufferPos_Tail = -1;
}
UpdateAutoScroll();
break;
}
/**
* Conventions for Up/Down when text is selected:
*
* References:
*
* Visual Studio
* Visual Studio has a very strange approach, down takes you below the
* selection to the next row, and up to the one prior to the whole
* selection. The weird part is that it is always aligned as the
* 'pointer'. I decided this is to much work for something that is
* a bit arbitrary
*
* Windows (eg. Notepad)
* Just like with left/right, the selection is destroyed and it moves
* just as if there never were a selection.
*
* I chose the Notepad convention even though I use the VS convention with
* left/right.
*/
case SDLK_UP:
{
if (!shiftKeyPressed)
m_iBufferPos_Tail = -1;
else if (!SelectingText())
m_iBufferPos_Tail = m_iBufferPos;
std::list::iterator current = m_CharacterPositions.begin();
while (current != m_CharacterPositions.end())
{
if (m_iBufferPos >= current->m_ListStart &&
m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
break;
++current;
}
float pos_x;
if (m_iBufferPos - current->m_ListStart == 0)
pos_x = 0.f;
else
pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
if (m_WantedX > pos_x)
pos_x = m_WantedX;
// Now change row:
if (current != m_CharacterPositions.begin())
{
--current;
// Find X-position:
m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
}
// else we can't move up
UpdateAutoScroll();
break;
}
case SDLK_DOWN:
{
if (!shiftKeyPressed)
m_iBufferPos_Tail = -1;
else if (!SelectingText())
m_iBufferPos_Tail = m_iBufferPos;
std::list::iterator current = m_CharacterPositions.begin();
while (current != m_CharacterPositions.end())
{
if (m_iBufferPos >= current->m_ListStart &&
m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
break;
++current;
}
float pos_x;
if (m_iBufferPos - current->m_ListStart == 0)
pos_x = 0.f;
else
pos_x = current->m_ListOfX[m_iBufferPos - current->m_ListStart - 1];
if (m_WantedX > pos_x)
pos_x = m_WantedX;
// Now change row:
// Add first, so we can check if it's .end()
++current;
if (current != m_CharacterPositions.end())
{
// Find X-position:
m_iBufferPos = current->m_ListStart + GetXTextPosition(current, pos_x, m_WantedX);
}
// else we can't move up
UpdateAutoScroll();
break;
}
case SDLK_PAGEUP:
{
GetScrollBar(0).ScrollMinusPlenty();
UpdateAutoScroll();
break;
}
case SDLK_PAGEDOWN:
{
GetScrollBar(0).ScrollPlusPlenty();
UpdateAutoScroll();
break;
}
default:
{
break;
}
}
}
void CInput::SetupGeneratedPlaceholderText()
{
m_GeneratedPlaceholderText = CGUIText(m_pGUI, m_PlaceholderText, m_Font, 0, m_BufferZone, EAlign::LEFT, this);
m_GeneratedPlaceholderTextValid = true;
}
InReaction CInput::ManuallyHandleHotkeyEvent(const SDL_Event_* ev)
{
bool shiftKeyPressed = g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT];
std::string hotkey = static_cast(ev->ev.user.data1);
// Get direct access to silently mutate m_Caption.
// (Messages don't currently need to be sent)
CStrW& caption = m_Caption.GetMutable();
if (hotkey == "paste")
{
if (m_Readonly)
return IN_PASS;
m_WantedX = 0.0f;
char* utf8_text = SDL_GetClipboardText();
if (!utf8_text)
return IN_HANDLED;
std::wstring text = wstring_from_utf8(utf8_text);
SDL_free(utf8_text);
// Check max length
if (m_MaxLength != 0 && caption.length() + text.length() > static_cast(m_MaxLength))
text = text.substr(0, static_cast(m_MaxLength) - caption.length());
if (SelectingText())
DeleteCurSelection();
if (m_iBufferPos == static_cast(caption.length()))
caption += text;
else
caption =
caption.Left(m_iBufferPos) + text +
caption.Right(static_cast(caption.length()) - m_iBufferPos);
UpdateText(m_iBufferPos, m_iBufferPos, m_iBufferPos+1);
m_iBufferPos += static_cast(text.size());
UpdateAutoScroll();
UpdateBufferPositionSetting();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
return IN_HANDLED;
}
else if (hotkey == "copy" || hotkey == "cut")
{
if (m_Readonly && hotkey == "cut")
return IN_PASS;
m_WantedX = 0.0f;
if (SelectingText())
{
int virtualFrom;
int virtualTo;
if (m_iBufferPos_Tail >= m_iBufferPos)
{
virtualFrom = m_iBufferPos;
virtualTo = m_iBufferPos_Tail;
}
else
{
virtualFrom = m_iBufferPos_Tail;
virtualTo = m_iBufferPos;
}
CStrW text = caption.Left(virtualTo).Right(virtualTo - virtualFrom);
SDL_SetClipboardText(text.ToUTF8().c_str());
if (hotkey == "cut")
{
DeleteCurSelection();
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
}
}
return IN_HANDLED;
}
else if (hotkey == "text.delete.left")
{
if (m_Readonly)
return IN_PASS;
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
if (!caption.empty() && m_iBufferPos != 0)
{
m_iBufferPos_Tail = m_iBufferPos;
CStrW searchString = caption.Left(m_iBufferPos);
// If we are starting in whitespace, adjust position until we get a non whitespace
while (m_iBufferPos > 0)
{
if (!iswspace(searchString[m_iBufferPos - 1]))
break;
m_iBufferPos--;
}
// If we end up on a punctuation char we just delete it (treat punct like a word)
if (iswpunct(searchString[m_iBufferPos - 1]))
m_iBufferPos--;
else
{
// Now we are on a non white space character, adjust position to char after next whitespace char is found
while (m_iBufferPos > 0)
{
if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
break;
m_iBufferPos--;
}
}
UpdateBufferPositionSetting();
DeleteCurSelection();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
}
UpdateAutoScroll();
return IN_HANDLED;
}
else if (hotkey == "text.delete.right")
{
if (m_Readonly)
return IN_PASS;
m_WantedX = 0.0f;
if (SelectingText())
DeleteCurSelection();
if (!caption.empty() && m_iBufferPos < static_cast(caption.length()))
{
// Delete the word to the right of the cursor
m_iBufferPos_Tail = m_iBufferPos;
// Delete chars to the right unit we hit whitespace
while (++m_iBufferPos < static_cast(caption.length()))
{
if (iswspace(caption[m_iBufferPos]) || iswpunct(caption[m_iBufferPos]))
break;
}
// Eliminate any whitespace behind the word we just deleted
while (m_iBufferPos < static_cast(caption.length()))
{
if (!iswspace(caption[m_iBufferPos]))
break;
++m_iBufferPos;
}
UpdateBufferPositionSetting();
DeleteCurSelection();
}
UpdateAutoScroll();
SendEvent(GUIM_TEXTEDIT, EventNameTextEdit);
return IN_HANDLED;
}
else if (hotkey == "text.move.left")
{
m_WantedX = 0.0f;
if (shiftKeyPressed || !SelectingText())
{
if (!shiftKeyPressed)
m_iBufferPos_Tail = -1;
else if (!SelectingText())
m_iBufferPos_Tail = m_iBufferPos;
if (!caption.empty() && m_iBufferPos != 0)
{
CStrW searchString = caption.Left(m_iBufferPos);
// If we are starting in whitespace, adjust position until we get a non whitespace
while (m_iBufferPos > 0)
{
if (!iswspace(searchString[m_iBufferPos - 1]))
break;
m_iBufferPos--;
}
// If we end up on a puctuation char we just select it (treat punct like a word)
if (iswpunct(searchString[m_iBufferPos - 1]))
m_iBufferPos--;
else
{
// Now we are on a non white space character, adjust position to char after next whitespace char is found
while (m_iBufferPos > 0)
{
if (iswspace(searchString[m_iBufferPos - 1]) || iswpunct(searchString[m_iBufferPos - 1]))
break;
m_iBufferPos--;
}
}
}
}
else
{
if (m_iBufferPos_Tail < m_iBufferPos)
m_iBufferPos = m_iBufferPos_Tail;
m_iBufferPos_Tail = -1;
}
UpdateBufferPositionSetting();
UpdateAutoScroll();
return IN_HANDLED;
}
else if (hotkey == "text.move.right")
{
m_WantedX = 0.0f;
if (shiftKeyPressed || !SelectingText())
{
if (!shiftKeyPressed)
m_iBufferPos_Tail = -1;
else if (!SelectingText())
m_iBufferPos_Tail = m_iBufferPos;
if (!caption.empty() && m_iBufferPos < static_cast(caption.length()))
{
// Select chars to the right until we hit whitespace
while (++m_iBufferPos < static_cast(caption.length()))
{
if (iswspace(caption[m_iBufferPos]) || iswpunct(caption[m_iBufferPos]))
break;
}
// Also select any whitespace following the word we just selected
while (m_iBufferPos < static_cast(caption.length()))
{
if (!iswspace(caption[m_iBufferPos]))
break;
++m_iBufferPos;
}
}
}
else
{
if (m_iBufferPos_Tail > m_iBufferPos)
m_iBufferPos = m_iBufferPos_Tail;
m_iBufferPos_Tail = -1;
}
UpdateBufferPositionSetting();
UpdateAutoScroll();
return IN_HANDLED;
}
return IN_PASS;
}
void CInput::ResetStates()
{
IGUIObject::ResetStates();
IGUIScrollBarOwner::ResetStates();
}
void CInput::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
IGUIScrollBarOwner::HandleMessage(Message);
// Cleans up operator[] usage.
const CStrW& caption = *m_Caption;
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
{
// Update scroll-bar
// TODO Gee: (2004-09-01) Is this really updated each time it should?
if (m_ScrollBar &&
(Message.value == "size" ||
Message.value == "z" ||
Message.value == "absolute"))
{
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
}
// Update scrollbar
if (Message.value == "scrollbar_style")
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
if (Message.value == "buffer_position")
{
m_iBufferPos = m_MaxLength != 0 ? std::min(m_MaxLength, m_BufferPosition) : m_BufferPosition;
m_iBufferPos_Tail = -1; // position change resets selection
}
if (Message.value == "size" ||
Message.value == "z" ||
Message.value == "font" ||
Message.value == "absolute" ||
Message.value == "caption" ||
Message.value == "scrollbar" ||
Message.value == "scrollbar_style")
{
UpdateText();
}
if (Message.value == "multiline")
{
if (!m_MultiLine)
GetScrollBar(0).SetLength(0.f);
else
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
UpdateText();
}
if (Message.value == "placeholder_text" ||
Message.value == "size" ||
Message.value == "font" ||
Message.value == "z" ||
Message.value == "text_valign")
{
m_GeneratedPlaceholderTextValid = false;
}
UpdateAutoScroll();
break;
}
case GUIM_MOUSE_PRESS_LEFT:
{
// Check if we're selecting the scrollbar
if (m_ScrollBar &&
m_MultiLine &&
GetScrollBar(0).GetStyle())
{
if (m_pGUI.GetMousePos().X > m_CachedActualSize.right - GetScrollBar(0).GetStyle()->m_Width)
break;
}
if (m_ComposingText)
break;
// Okay, this section is about pressing the mouse and
// choosing where the point should be placed. For
// instance, if we press between a and b, the point
// should of course be placed accordingly. Other
// special cases are handled like the input box norms.
if (g_scancodes[SDL_SCANCODE_LSHIFT] || g_scancodes[SDL_SCANCODE_RSHIFT])
m_iBufferPos = GetMouseHoveringTextPosition();
else
m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
m_SelectingText = true;
UpdateAutoScroll();
// If we immediately release the button it will just be seen as a click
// for the user though.
break;
}
case GUIM_MOUSE_DBLCLICK_LEFT:
{
if (m_ComposingText)
break;
if (caption.empty())
break;
m_iBufferPos = m_iBufferPos_Tail = GetMouseHoveringTextPosition();
if (m_iBufferPos >= (int)caption.length())
m_iBufferPos = m_iBufferPos_Tail = caption.length() - 1;
// See if we are clicking over whitespace
if (iswspace(caption[m_iBufferPos]))
{
// see if we are in a section of whitespace greater than one character
if ((m_iBufferPos + 1 < (int) caption.length() && iswspace(caption[m_iBufferPos + 1])) ||
(m_iBufferPos - 1 > 0 && iswspace(caption[m_iBufferPos - 1])))
{
//
// We are clicking in an area with more than one whitespace character
// so we select both the word to the left and then the word to the right
//
// [1] First the left
// skip the whitespace
while (m_iBufferPos > 0)
{
if (!iswspace(caption[m_iBufferPos - 1]))
break;
m_iBufferPos--;
}
// now go until we hit white space or punctuation
while (m_iBufferPos > 0)
{
if (iswspace(caption[m_iBufferPos - 1]))
break;
m_iBufferPos--;
if (iswpunct(caption[m_iBufferPos]))
break;
}
// [2] Then the right
// go right until we are not in whitespace
while (++m_iBufferPos_Tail < static_cast(caption.length()))
{
if (!iswspace(caption[m_iBufferPos_Tail]))
break;
}
if (m_iBufferPos_Tail == static_cast(caption.length()))
break;
// now go to the right until we hit whitespace or punctuation
while (++m_iBufferPos_Tail < static_cast(caption.length()))
{
if (iswspace(caption[m_iBufferPos_Tail]) || iswpunct(caption[m_iBufferPos_Tail]))
break;
}
}
else
{
// single whitespace so select word to the right
while (++m_iBufferPos_Tail < static_cast(caption.length()))
{
if (!iswspace(caption[m_iBufferPos_Tail]))
break;
}
if (m_iBufferPos_Tail == static_cast(caption.length()))
break;
// Don't include the leading whitespace
m_iBufferPos = m_iBufferPos_Tail;
// now go to the right until we hit whitespace or punctuation
while (++m_iBufferPos_Tail < static_cast(caption.length()))
{
if (iswspace(caption[m_iBufferPos_Tail]) || iswpunct(caption[m_iBufferPos_Tail]))
break;
}
}
}
else
{
// clicked on non-whitespace so select current word
// go until we hit white space or punctuation
while (m_iBufferPos > 0)
{
if (iswspace(caption[m_iBufferPos - 1]))
break;
m_iBufferPos--;
if (iswpunct(caption[m_iBufferPos]))
break;
}
// go to the right until we hit whitespace or punctuation
while (++m_iBufferPos_Tail < static_cast(caption.length()))
if (iswspace(caption[m_iBufferPos_Tail]) || iswpunct(caption[m_iBufferPos_Tail]))
break;
}
UpdateAutoScroll();
break;
}
case GUIM_MOUSE_RELEASE_LEFT:
{
if (m_SelectingText)
m_SelectingText = false;
break;
}
case GUIM_MOUSE_MOTION:
{
// If we just pressed down and started to move before releasing
// this is one way of selecting larger portions of text.
if (m_SelectingText)
{
// Actually, first we need to re-check that the mouse button is
// really pressed (it can be released while outside the control.
if (!g_mouse_buttons[SDL_BUTTON_LEFT])
m_SelectingText = false;
else
m_iBufferPos = GetMouseHoveringTextPosition();
UpdateAutoScroll();
}
break;
}
case GUIM_LOAD:
{
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
UpdateText();
UpdateAutoScroll();
break;
}
case GUIM_GOT_FOCUS:
{
m_iBufferPos = 0;
m_PrevTime = 0.0;
m_CursorVisState = false;
ResetActiveHotkeys();
// Tell the IME where to draw the candidate list
SDL_Rect rect;
rect.h = m_CachedActualSize.GetSize().Height;
rect.w = m_CachedActualSize.GetSize().Width;
rect.x = m_CachedActualSize.TopLeft().X;
rect.y = m_CachedActualSize.TopLeft().Y;
SDL_SetTextInputRect(&rect);
SDL_StartTextInput();
break;
}
case GUIM_LOST_FOCUS:
{
if (m_ComposingText)
{
// Simulate a final text editing event to clear the composition
SDL_Event_ evt;
evt.ev.type = SDL_TEXTEDITING;
evt.ev.edit.length = 0;
evt.ev.edit.start = 0;
evt.ev.edit.text[0] = 0;
ManuallyHandleKeys(&evt);
}
SDL_StopTextInput();
m_iBufferPos = -1;
m_iBufferPos_Tail = -1;
break;
}
default:
{
break;
}
}
UpdateBufferPositionSetting();
}
void CInput::UpdateCachedSize()
{
// If an ancestor's size changed, this will let us intercept the change and
// update our scrollbar positions
IGUIObject::UpdateCachedSize();
if (m_ScrollBar)
{
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
}
m_GeneratedPlaceholderTextValid = false;
}
void CInput::Draw(CCanvas2D& canvas)
{
if (m_CursorBlinkRate > 0.0)
{
// check if the cursor visibility state needs to be changed
double currTime = timer_Time();
if (currTime - m_PrevTime >= m_CursorBlinkRate)
{
m_CursorVisState = !m_CursorVisState;
m_PrevTime = currTime;
}
}
else
// should always be visible
m_CursorVisState = true;
CStrIntern font_name(m_Font->ToUTF8());
wchar_t mask_char = L'*';
if (m_Mask && m_MaskChar->length() > 0)
mask_char = (*m_MaskChar)[0];
m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize);
float scroll = 0.f;
if (m_ScrollBar && m_MultiLine)
scroll = GetScrollBar(0).GetPos();
CFontMetrics font(font_name);
// We'll have to setup clipping manually, since we're doing the rendering manually.
CRect cliparea(m_CachedActualSize);
// First we'll figure out the clipping area, which is the cached actual size
// substracted by an optional scrollbar
if (m_ScrollBar)
{
scroll = GetScrollBar(0).GetPos();
// substract scrollbar from cliparea
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
if (cliparea != CRect())
{
glEnable(GL_SCISSOR_TEST);
glScissor(
cliparea.left * g_GuiScale,
g_yres - cliparea.bottom * g_GuiScale,
cliparea.GetWidth() * g_GuiScale,
cliparea.GetHeight() * g_GuiScale);
}
// These are useful later.
int VirtualFrom, VirtualTo;
if (m_iBufferPos_Tail >= m_iBufferPos)
{
VirtualFrom = m_iBufferPos;
VirtualTo = m_iBufferPos_Tail;
}
else
{
VirtualFrom = m_iBufferPos_Tail;
VirtualTo = m_iBufferPos;
}
// Get the height of this font.
float h = (float)font.GetHeight();
float ls = (float)font.GetLineSpacing();
CTextRenderer textRenderer;
textRenderer.SetCurrentFont(font_name);
textRenderer.Translate(
(float)(int)(m_CachedActualSize.left) + m_BufferZone,
(float)(int)(m_CachedActualSize.top+h) + m_BufferZone);
// U+FE33: PRESENTATION FORM FOR VERTICAL LOW LINE
// (sort of like a | which is aligned to the left of most characters)
float buffered_y = -scroll + m_BufferZone;
// When selecting larger areas, we need to draw a rectangle box
// around it, and this is to keep track of where the box
// started, because we need to follow the iteration until we
// reach the end, before we can actually draw it.
bool drawing_box = false;
float box_x = 0.f;
float x_pointer = 0.f;
// If we have a selecting box (i.e. when you have selected letters, not just when
// the pointer is between two letters) we need to process all letters once
// before we do it the second time and render all the text. We can't do it
// in the same loop because text will have been drawn, so it will disappear when
// drawn behind the text that has already been drawn. Confusing, well it's necessary
// (I think).
if (SelectingText())
{
// Now m_iBufferPos_Tail can be of both sides of m_iBufferPos,
// just like you can select from right to left, as you can
// left to right. Is there a difference? Yes, the pointer
// be placed accordingly, so that if you select shift and
// expand this selection, it will expand on appropriate side.
// Anyway, since the drawing procedure needs "To" to be
// greater than from, we need virtual values that might switch
// place.
int virtualFrom = 0;
int virtualTo = 0;
if (m_iBufferPos_Tail >= m_iBufferPos)
{
virtualFrom = m_iBufferPos;
virtualTo = m_iBufferPos_Tail;
}
else
{
virtualFrom = m_iBufferPos_Tail;
virtualTo = m_iBufferPos;
}
bool done = false;
for (std::list::const_iterator it = m_CharacterPositions.begin();
it != m_CharacterPositions.end();
++it, buffered_y += ls, x_pointer = 0.f)
{
if (m_MultiLine && buffered_y > m_CachedActualSize.GetHeight())
break;
// We might as well use 'i' here to iterate, because we need it
// (often compared against ints, so don't make it size_t)
for (int i = 0; i < (int)it->m_ListOfX.size()+2; ++i)
{
if (it->m_ListStart + i == virtualFrom)
{
// we won't actually draw it now, because we don't
// know the width of each glyph to that position.
// we need to go along with the iteration, and
// make a mark where the box started:
drawing_box = true; // will turn false when finally rendered.
// Get current x position
box_x = x_pointer;
}
const bool at_end = (i == (int)it->m_ListOfX.size()+1);
if (drawing_box && (it->m_ListStart + i == virtualTo || at_end))
{
// Depending on if it's just a row change, or if it's
// the end of the select box, do slightly different things.
if (at_end)
{
if (it->m_ListStart + i != virtualFrom)
// and actually add a white space! yes, this is done in any common input
x_pointer += font.GetCharacterWidth(L' ');
}
else
{
drawing_box = false;
done = true;
}
CRect rect;
// Set 'rect' depending on if it's a multiline control, or a one-line control
if (m_MultiLine)
{
rect = CRect(
m_CachedActualSize.left + box_x + m_BufferZone,
m_CachedActualSize.top + buffered_y + (h - ls) / 2,
m_CachedActualSize.left + x_pointer + m_BufferZone,
m_CachedActualSize.top + buffered_y + (h + ls) / 2);
if (rect.bottom < m_CachedActualSize.top)
continue;
if (rect.top < m_CachedActualSize.top)
rect.top = m_CachedActualSize.top;
if (rect.bottom > m_CachedActualSize.bottom)
rect.bottom = m_CachedActualSize.bottom;
}
else // if one-line
{
rect = CRect(
m_CachedActualSize.left + box_x + m_BufferZone - m_HorizontalScroll,
m_CachedActualSize.top + buffered_y + (h - ls) / 2,
m_CachedActualSize.left + x_pointer + m_BufferZone - m_HorizontalScroll,
m_CachedActualSize.top + buffered_y + (h + ls) / 2);
if (rect.left < m_CachedActualSize.left)
rect.left = m_CachedActualSize.left;
if (rect.right > m_CachedActualSize.right)
rect.right = m_CachedActualSize.right;
}
m_pGUI.DrawSprite(m_SpriteSelectArea, canvas, rect);
}
if (i < (int)it->m_ListOfX.size())
{
if (!m_Mask)
x_pointer += font.GetCharacterWidth((*m_Caption)[it->m_ListStart + i]);
else
x_pointer += font.GetCharacterWidth(mask_char);
}
}
if (done)
break;
// If we're about to draw a box, and all of a sudden changes
// line, we need to draw that line's box, and then reset
// the box drawing to the beginning of the new line.
if (drawing_box)
box_x = 0.f;
}
}
// Reset some from previous run
buffered_y = -scroll;
// Setup initial color (then it might change and change back, when drawing selected area)
textRenderer.SetCurrentColor(m_TextColor);
bool using_selected_color = false;
for (std::list::const_iterator it = m_CharacterPositions.begin();
it != m_CharacterPositions.end();
++it, buffered_y += ls)
{
if (buffered_y + m_BufferZone >= -ls || !m_MultiLine)
{
if (m_MultiLine && buffered_y + m_BufferZone > m_CachedActualSize.GetHeight())
break;
const CVector2D savedTranslate = textRenderer.GetTranslate();
// Text must always be drawn in integer values. So we have to convert scroll
if (m_MultiLine)
textRenderer.Translate(0.f, -(float)(int)scroll);
else
textRenderer.Translate(-(float)(int)m_HorizontalScroll, 0.f);
// We might as well use 'i' here, because we need it
// (often compared against ints, so don't make it size_t)
for (int i = 0; i < (int)it->m_ListOfX.size()+1; ++i)
{
if (!m_MultiLine && i < (int)it->m_ListOfX.size())
{
if (it->m_ListOfX[i] - m_HorizontalScroll < -m_BufferZone)
{
// We still need to translate the OpenGL matrix
if (i == 0)
textRenderer.Translate(it->m_ListOfX[i], 0.f);
else
textRenderer.Translate(it->m_ListOfX[i] - it->m_ListOfX[i-1], 0.f);
continue;
}
}
// End of selected area, change back color
if (SelectingText() && it->m_ListStart + i == VirtualTo)
{
using_selected_color = false;
textRenderer.SetCurrentColor(m_TextColor);
}
// selecting only one, then we need only to draw a cursor.
if (i != (int)it->m_ListOfX.size() && it->m_ListStart + i == m_iBufferPos && m_CursorVisState)
textRenderer.Put(0.0f, 0.0f, L"_");
// Drawing selected area
if (SelectingText() &&
it->m_ListStart + i >= VirtualFrom &&
it->m_ListStart + i < VirtualTo &&
!using_selected_color)
{
using_selected_color = true;
textRenderer.SetCurrentColor(m_TextColorSelected);
}
if (i != (int)it->m_ListOfX.size())
{
if (!m_Mask)
textRenderer.PrintfAdvance(L"%lc", (*m_Caption)[it->m_ListStart + i]);
else
textRenderer.PrintfAdvance(L"%lc", mask_char);
}
// check it's now outside a one-liner, then we'll break
if (!m_MultiLine && i < (int)it->m_ListOfX.size() &&
it->m_ListOfX[i] - m_HorizontalScroll > m_CachedActualSize.GetWidth() - m_BufferZone)
break;
}
if (it->m_ListStart + (int)it->m_ListOfX.size() == m_iBufferPos)
{
textRenderer.SetCurrentColor(m_TextColor);
if (m_CursorVisState)
textRenderer.PutAdvance(L"_");
if (using_selected_color)
textRenderer.SetCurrentColor(m_TextColorSelected);
}
textRenderer.ResetTranslate(savedTranslate);
}
textRenderer.Translate(0.f, ls);
}
canvas.DrawText(textRenderer);
if (cliparea != CRect())
glDisable(GL_SCISSOR_TEST);
if (m_Caption->empty() && !m_PlaceholderText->GetRawString().empty())
DrawPlaceholderText(canvas, cliparea);
// Draw scrollbars on top of the content
if (m_ScrollBar && m_MultiLine)
IGUIScrollBarOwner::Draw(canvas);
// Draw the overlays last
m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize);
}
void CInput::DrawPlaceholderText(CCanvas2D& canvas, const CRect& clipping)
{
if (!m_GeneratedPlaceholderTextValid)
SetupGeneratedPlaceholderText();
m_GeneratedPlaceholderText.Draw(m_pGUI, canvas, m_PlaceholderColor, m_CachedActualSize.TopLeft(), clipping);
}
void CInput::UpdateText(int from, int to_before, int to_after)
{
CStrW& caption = m_Caption.GetMutable();
if (m_MaxLength != 0 && caption.length() > static_cast(m_MaxLength))
caption = caption.substr(0, m_MaxLength);
CStrIntern font_name(m_Font->ToUTF8());
wchar_t mask_char = L'*';
if (m_Mask && m_MaskChar->length() > 0)
mask_char = (*m_MaskChar)[0];
// Ensure positions are valid after caption changes
m_iBufferPos = std::min(m_iBufferPos, static_cast(caption.size()));
m_iBufferPos_Tail = std::min(m_iBufferPos_Tail, static_cast(caption.size()));
UpdateBufferPositionSetting();
if (font_name.empty())
{
// Destroy everything stored, there's no font, so there can be no data.
m_CharacterPositions.clear();
return;
}
SRow row;
row.m_ListStart = 0;
int to = 0; // make sure it's initialized
if (to_before == -1)
to = static_cast(caption.length());
CFontMetrics font(font_name);
std::list::iterator current_line;
// Used to ... TODO
int check_point_row_start = -1;
int check_point_row_end = -1;
// Reset
if (from == 0 && to_before == -1)
{
m_CharacterPositions.clear();
current_line = m_CharacterPositions.begin();
}
else
{
ENSURE(to_before != -1);
std::list::iterator destroy_row_from;
std::list::iterator destroy_row_to;
// Used to check if the above has been set to anything,
// previously a comparison like:
// destroy_row_from == std::list::iterator()
// ... was used, but it didn't work with GCC.
bool destroy_row_from_used = false;
bool destroy_row_to_used = false;
// Iterate, and remove everything between 'from' and 'to_before'
// actually remove the entire lines they are on, it'll all have
// to be redone. And when going along, we'll delete a row at a time
// when continuing to see how much more after 'to' we need to remake.
int i = 0;
for (std::list::iterator it = m_CharacterPositions.begin();
it != m_CharacterPositions.end();
++it, ++i)
{
if (!destroy_row_from_used && it->m_ListStart > from)
{
// Destroy the previous line, and all to 'to_before'
destroy_row_from = it;
--destroy_row_from;
destroy_row_from_used = true;
// For the rare case that we might remove characters to a word
// so that it suddenly fits on the previous row,
// we need to by standards re-do the whole previous line too
// (if one exists)
if (destroy_row_from != m_CharacterPositions.begin())
--destroy_row_from;
}
if (!destroy_row_to_used && it->m_ListStart > to_before)
{
destroy_row_to = it;
destroy_row_to_used = true;
// If it isn't the last row, we'll add another row to delete,
// just so we can see if the last restorted line is
// identical to what it was before. If it isn't, then we'll
// have to continue.
// 'check_point_row_start' is where we store how the that
// line looked.
if (destroy_row_to != m_CharacterPositions.end())
{
check_point_row_start = destroy_row_to->m_ListStart;
check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
if (destroy_row_to->m_ListOfX.empty())
++check_point_row_end;
}
++destroy_row_to;
break;
}
}
if (!destroy_row_from_used)
{
destroy_row_from = m_CharacterPositions.end();
--destroy_row_from;
// As usual, let's destroy another row back
if (destroy_row_from != m_CharacterPositions.begin())
--destroy_row_from;
current_line = destroy_row_from;
}
if (!destroy_row_to_used)
{
destroy_row_to = m_CharacterPositions.end();
check_point_row_start = -1;
}
// set 'from' to the row we'll destroy from
// and 'to' to the row we'll destroy to
from = destroy_row_from->m_ListStart;
if (destroy_row_to != m_CharacterPositions.end())
to = destroy_row_to->m_ListStart; // notice it will iterate [from, to), so it will never reach to.
else
to = static_cast(caption.length());
// Setup the first row
row.m_ListStart = destroy_row_from->m_ListStart;
std::list::iterator temp_it = destroy_row_to;
--temp_it;
current_line = m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
// If there has been a change in number of characters
// we need to change all m_ListStart that comes after
// the interval we just destroyed. We'll change all
// values with the delta change of the string length.
int delta = to_after - to_before;
if (delta != 0)
{
for (std::list::iterator it = current_line;
it != m_CharacterPositions.end();
++it)
it->m_ListStart += delta;
// Update our check point too!
check_point_row_start += delta;
check_point_row_end += delta;
if (to != static_cast(caption.length()))
to += delta;
}
}
int last_word_started = from;
float x_pos = 0.f;
//if (to_before != -1)
// return;
for (int i = from; i < to; ++i)
{
if (caption[i] == L'\n' && m_MultiLine)
{
if (i == to-1 && to != static_cast(caption.length()))
break; // it will be added outside
current_line = m_CharacterPositions.insert(current_line, row);
++current_line;
// Setup the next row:
row.m_ListOfX.clear();
row.m_ListStart = i+1;
x_pos = 0.f;
}
else
{
if (caption[i] == L' '/* || TODO Gee (2004-10-13): the '-' disappears, fix.
caption[i] == L'-'*/)
last_word_started = i+1;
if (!m_Mask)
x_pos += font.GetCharacterWidth(caption[i]);
else
x_pos += font.GetCharacterWidth(mask_char);
if (x_pos >= GetTextAreaWidth() && m_MultiLine)
{
// The following decides whether it will word-wrap a word,
// or if it's only one word on the line, where it has to
// break the word apart.
if (last_word_started == row.m_ListStart)
{
last_word_started = i;
row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started));
//row.m_ListOfX.push_back(x_pos);
//continue;
}
else
{
// regular word-wrap
row.m_ListOfX.resize(row.m_ListOfX.size() - (i-last_word_started+1));
}
// Now, create a new line:
// notice: when we enter a newline, you can stand with the cursor
// both before and after that character, being on different
// rows. With automatic word-wrapping, that is not possible. Which
// is intuitively correct.
current_line = m_CharacterPositions.insert(current_line, row);
++current_line;
// Setup the next row:
row.m_ListOfX.clear();
row.m_ListStart = last_word_started;
i = last_word_started-1;
x_pos = 0.f;
}
else
// Get width of this character:
row.m_ListOfX.push_back(x_pos);
}
// Check if it's the last iteration, and we're not revising the whole string
// because in that case, more word-wrapping might be needed.
// also check if the current line isn't the end
if (to_before != -1 && i == to-1 && current_line != m_CharacterPositions.end())
{
// check all rows and see if any existing
if (row.m_ListStart != check_point_row_start)
{
std::list::iterator destroy_row_from;
std::list::iterator destroy_row_to;
// Are used to check if the above has been set to anything,
// previously a comparison like:
// destroy_row_from == std::list::iterator()
// was used, but it didn't work with GCC.
bool destroy_row_from_used = false;
bool destroy_row_to_used = false;
// Iterate, and remove everything between 'from' and 'to_before'
// actually remove the entire lines they are on, it'll all have
// to be redone. And when going along, we'll delete a row at a time
// when continuing to see how much more after 'to' we need to remake.
for (std::list::iterator it = m_CharacterPositions.begin();
it != m_CharacterPositions.end();
++it)
{
if (!destroy_row_from_used && it->m_ListStart > check_point_row_start)
{
// Destroy the previous line, and all to 'to_before'
//if (i >= 2)
// destroy_row_from = it-2;
//else
// destroy_row_from = it-1;
destroy_row_from = it;
destroy_row_from_used = true;
//--destroy_row_from;
}
if (!destroy_row_to_used && it->m_ListStart > check_point_row_end)
{
destroy_row_to = it;
destroy_row_to_used = true;
// If it isn't the last row, we'll add another row to delete,
// just so we can see if the last restorted line is
// identical to what it was before. If it isn't, then we'll
// have to continue.
// 'check_point_row_start' is where we store how the that
// line looked.
if (destroy_row_to != m_CharacterPositions.end())
{
check_point_row_start = destroy_row_to->m_ListStart;
check_point_row_end = check_point_row_start + (int)destroy_row_to->m_ListOfX.size();
if (destroy_row_to->m_ListOfX.empty())
++check_point_row_end;
}
else
check_point_row_start = check_point_row_end = -1;
++destroy_row_to;
break;
}
}
if (!destroy_row_from_used)
{
destroy_row_from = m_CharacterPositions.end();
--destroy_row_from;
current_line = destroy_row_from;
}
if (!destroy_row_to_used)
{
destroy_row_to = m_CharacterPositions.end();
check_point_row_start = check_point_row_end = -1;
}
if (destroy_row_to != m_CharacterPositions.end())
to = destroy_row_to->m_ListStart; // notice it will iterate [from, to[, so it will never reach to.
else
to = static_cast(caption.length());
// Set current line, new rows will be added before current_line, so
// we'll choose the destroy_row_to, because it won't be deleted
// in the coming erase.
current_line = destroy_row_to;
m_CharacterPositions.erase(destroy_row_from, destroy_row_to);
}
// else, the for loop will end naturally.
}
}
// This is kind of special, when we renew a some lines, then the last
// one will sometimes end with a space (' '), that really should
// be omitted when word-wrapping. So we'll check if the last row
// we'll add has got the same value as the next row.
if (current_line != m_CharacterPositions.end())
{
if (row.m_ListStart + (int)row.m_ListOfX.size() == current_line->m_ListStart)
row.m_ListOfX.resize(row.m_ListOfX.size()-1);
}
// add the final row (even if empty)
m_CharacterPositions.insert(current_line, row);
if (m_ScrollBar)
{
GetScrollBar(0).SetScrollRange(m_CharacterPositions.size() * font.GetLineSpacing() + m_BufferZone * 2.f);
GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
}
}
int CInput::GetMouseHoveringTextPosition() const
{
if (m_CharacterPositions.empty())
return 0;
// Return position
int retPosition;
std::list::const_iterator current = m_CharacterPositions.begin();
CVector2D mouse = m_pGUI.GetMousePos();
if (m_MultiLine)
{
float scroll = 0.f;
if (m_ScrollBar)
scroll = GetScrollBarPos(0);
// Now get the height of the font.
// TODO: Get the real font
CFontMetrics font(CStrIntern(m_Font->ToUTF8()));
float spacing = (float)font.GetLineSpacing();
// Change mouse position relative to text.
mouse -= m_CachedActualSize.TopLeft();
mouse.X -= m_BufferZone;
mouse.Y += scroll - m_BufferZone;
int row = (int)((mouse.Y) / spacing);
if (row < 0)
row = 0;
if (row > (int)m_CharacterPositions.size()-1)
row = (int)m_CharacterPositions.size()-1;
// TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
// be able to get the specific element here. This is hopefully a temporary hack.
for (int i = 0; i < row; ++i)
++current;
}
else
{
// current is already set to begin,
// but we'll change the mouse.x to fit our horizontal scrolling
mouse -= m_CachedActualSize.TopLeft();
mouse.X -= m_BufferZone - m_HorizontalScroll;
// mouse.y is moot
}
retPosition = current->m_ListStart;
// Okay, now loop through the glyphs to find the appropriate X position
float dummy;
retPosition += GetXTextPosition(current, mouse.X, dummy);
return retPosition;
}
// Does not process horizontal scrolling, 'x' must be modified before inputted.
int CInput::GetXTextPosition(const std::list::const_iterator& current, const float& x, float& wanted) const
{
int ret = 0;
float previous = 0.f;
int i = 0;
for (std::vector::const_iterator it = current->m_ListOfX.begin();
it != current->m_ListOfX.end();
++it, ++i)
{
if (*it >= x)
{
if (x - previous >= *it - x)
ret += i+1;
else
ret += i;
break;
}
previous = *it;
}
// If a position wasn't found, we will assume the last
// character of that line.
if (i == (int)current->m_ListOfX.size())
{
ret += i;
wanted = x;
}
else
wanted = 0.f;
return ret;
}
void CInput::DeleteCurSelection()
{
int virtualFrom;
int virtualTo;
if (m_iBufferPos_Tail >= m_iBufferPos)
{
virtualFrom = m_iBufferPos;
virtualTo = m_iBufferPos_Tail;
}
else
{
virtualFrom = m_iBufferPos_Tail;
virtualTo = m_iBufferPos;
}
// Silently change.
m_Caption.Set(m_Caption->Left(virtualFrom) + m_Caption->Right(static_cast(m_Caption->length()) - virtualTo),
false);
UpdateText(virtualFrom, virtualTo, virtualFrom);
// Remove selection
m_iBufferPos_Tail = -1;
m_iBufferPos = virtualFrom;
UpdateBufferPositionSetting();
}
bool CInput::SelectingText() const
{
return m_iBufferPos_Tail != -1 &&
m_iBufferPos_Tail != m_iBufferPos;
}
float CInput::GetTextAreaWidth()
{
if (m_ScrollBar && GetScrollBar(0).GetStyle())
return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f - GetScrollBar(0).GetStyle()->m_Width;
return m_CachedActualSize.GetWidth() - m_BufferZone * 2.f;
}
void CInput::UpdateAutoScroll()
{
// Autoscrolling up and down
if (m_MultiLine)
{
if (!m_ScrollBar)
return;
const float scroll = GetScrollBar(0).GetPos();
// Now get the height of the font.
// TODO: Get the real font
CFontMetrics font(CStrIntern(m_Font->ToUTF8()));
float spacing = (float)font.GetLineSpacing();
//float height = font.GetHeight();
// TODO Gee (2004-11-21): Okay, I need a 'std::list' for some reasons, but I would really like to
// be able to get the specific element here. This is hopefully a temporary hack.
std::list::iterator current = m_CharacterPositions.begin();
int row = 0;
while (current != m_CharacterPositions.end())
{
if (m_iBufferPos >= current->m_ListStart &&
m_iBufferPos <= current->m_ListStart + (int)current->m_ListOfX.size())
break;
++current;
++row;
}
// If scrolling down
if (-scroll + static_cast(row + 1) * spacing + m_BufferZone * 2.f > m_CachedActualSize.GetHeight())
{
// Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
GetScrollBar(0).SetPos(static_cast(row + 1) * spacing - m_CachedActualSize.GetHeight() + m_BufferZone * 2.f);
}
// If scrolling up
else if (-scroll + (float)row * spacing < 0.f)
{
// Scroll so the selected row is shown completely, also with m_BufferZone length to the edge.
GetScrollBar(0).SetPos((float)row * spacing);
}
}
else // autoscrolling left and right
{
// Get X position of position:
if (m_CharacterPositions.empty())
return;
float x_position = 0.f;
float x_total = 0.f;
if (!m_CharacterPositions.begin()->m_ListOfX.empty())
{
// Get position of m_iBufferPos
if ((int)m_CharacterPositions.begin()->m_ListOfX.size() >= m_iBufferPos &&
m_iBufferPos > 0)
x_position = m_CharacterPositions.begin()->m_ListOfX[m_iBufferPos-1];
// Get complete length:
x_total = m_CharacterPositions.begin()->m_ListOfX[m_CharacterPositions.begin()->m_ListOfX.size()-1];
}
// Check if outside to the right
if (x_position - m_HorizontalScroll + m_BufferZone * 2.f > m_CachedActualSize.GetWidth())
m_HorizontalScroll = x_position - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
// Check if outside to the left
if (x_position - m_HorizontalScroll < 0.f)
m_HorizontalScroll = x_position;
// Check if the text doesn't even fill up to the right edge even though scrolling is done.
if (m_HorizontalScroll != 0.f &&
x_total - m_HorizontalScroll + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
m_HorizontalScroll = x_total - m_CachedActualSize.GetWidth() + m_BufferZone * 2.f;
// Now this is the fail-safe, if x_total isn't even the length of the control,
// remove all scrolling
if (x_total + m_BufferZone * 2.f < m_CachedActualSize.GetWidth())
m_HorizontalScroll = 0.f;
}
}
Index: ps/trunk/source/gui/ObjectTypes/CList.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CList.cpp (revision 25692)
+++ ps/trunk/source/gui/ObjectTypes/CList.cpp (revision 25693)
@@ -1,487 +1,487 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CList.h"
#include "gui/CGUI.h"
#include "gui/CGUIScrollBarVertical.h"
#include "gui/SettingTypes/CGUIColor.h"
#include "gui/SettingTypes/CGUIList.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/timer.h"
const CStr CList::EventNameSelectionChange = "SelectionChange";
const CStr CList::EventNameHoverChange = "HoverChange";
const CStr CList::EventNameMouseLeftClickItem = "MouseLeftClickItem";
const CStr CList::EventNameMouseLeftDoubleClickItem = "MouseLeftDoubleClickItem";
CList::CList(CGUI& pGUI)
: IGUIObject(pGUI),
IGUITextOwner(*static_cast(this)),
IGUIScrollBarOwner(*static_cast(this)),
m_Modified(false),
m_PrevSelectedItem(-1),
m_LastItemClickTime(0),
m_BufferZone(this, "buffer_zone"),
m_Font(this, "font"),
m_ScrollBar(this, "scrollbar", false),
m_ScrollBarStyle(this, "scrollbar_style"),
m_ScrollBottom(this, "scroll_bottom", false),
m_SoundDisabled(this, "sound_disabled"),
m_SoundSelected(this, "sound_selected"),
m_Sprite(this, "sprite"),
m_SpriteOverlay(this, "sprite_overlay"),
// Add sprite_disabled! TODO
m_SpriteSelectArea(this, "sprite_selectarea"),
m_SpriteSelectAreaOverlay(this, "sprite_selectarea_overlay"),
m_TextColor(this, "textcolor"),
m_TextColorSelected(this, "textcolor_selected"),
m_Selected(this, "selected", -1), // Index selected. -1 is none.
m_AutoScroll(this, "auto_scroll", false),
m_Hovered(this, "hovered", -1),
// Each list item has both a name (in 'list') and an associated data string (in 'list_data')
m_List(this, "list"),
m_ListData(this, "list_data")
{
// Add scroll-bar
- CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
+ auto bar = std::make_unique(pGUI);
bar->SetRightAligned(true);
- AddScrollBar(bar);
+ AddScrollBar(std::move(bar));
}
CList::~CList()
{
}
void CList::SetupText()
{
SetupText(false);
}
void CList::SetupText(bool append)
{
m_Modified = true;
if (!append)
// Delete all generated texts.
// TODO: try to be cleverer if we want to update items before the end.
m_GeneratedTexts.clear();
float width = GetListRect().GetWidth();
bool bottom = false;
if (m_ScrollBar && GetScrollBar(0).GetStyle())
{
if (m_ScrollBottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f)
bottom = true;
// remove scrollbar if applicable
width -= GetScrollBar(0).GetStyle()->m_Width;
}
// Generate texts
float buffered_y = 0.f;
if (append && !m_ItemsYPositions.empty())
buffered_y = m_ItemsYPositions.back();
m_ItemsYPositions.resize(m_List->m_Items.size() + 1);
for (size_t i = append ? m_List->m_Items.size() - 1 : 0; i < m_List->m_Items.size(); ++i)
{
CGUIText* text;
if (!m_List->m_Items[i].GetOriginalString().empty())
text = &AddText(m_List->m_Items[i], m_Font, width, m_BufferZone);
else
{
// Minimum height of a space character of the current font size
CGUIString align_string;
align_string.SetValue(L" ");
text = &AddText(align_string, m_Font, width, m_BufferZone);
}
m_ItemsYPositions[i] = buffered_y;
buffered_y += text->GetSize().Height;
}
m_ItemsYPositions[m_List->m_Items.size()] = buffered_y;
// Setup scrollbar
if (m_ScrollBar)
{
GetScrollBar(0).SetScrollRange(m_ItemsYPositions.back());
GetScrollBar(0).SetScrollSpace(GetListRect().GetHeight());
CRect rect = GetListRect();
GetScrollBar(0).SetX(rect.right);
GetScrollBar(0).SetY(rect.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(rect.bottom - rect.top);
if (bottom)
GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos());
}
}
void CList::ResetStates()
{
IGUIObject::ResetStates();
IGUIScrollBarOwner::ResetStates();
}
void CList::UpdateCachedSize()
{
IGUIObject::UpdateCachedSize();
IGUITextOwner::UpdateCachedSize();
}
void CList::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
IGUIScrollBarOwner::HandleMessage(Message);
//IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead!
m_Modified = false;
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
if (Message.value == "list")
SetupText();
// If selected is changed, call "SelectionChange"
if (Message.value == "selected")
{
// TODO: Check range
if (m_AutoScroll)
UpdateAutoScroll();
ScriptEvent(EventNameSelectionChange);
}
if (Message.value == "scrollbar")
SetupText();
// Update scrollbar
if (Message.value == "scrollbar_style")
{
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
SetupText();
}
break;
case GUIM_MOUSE_PRESS_LEFT:
{
if (!m_Enabled)
{
PlaySound(m_SoundDisabled);
break;
}
int hovered = GetHoveredItem();
if (hovered == -1)
break;
m_Selected.Set(hovered, true);
UpdateAutoScroll();
PlaySound(m_SoundSelected);
if (timer_Time() - m_LastItemClickTime < SELECT_DBLCLICK_RATE && hovered == m_PrevSelectedItem)
this->SendMouseEvent(GUIM_MOUSE_DBLCLICK_LEFT_ITEM, EventNameMouseLeftDoubleClickItem);
else
this->SendMouseEvent(GUIM_MOUSE_PRESS_LEFT_ITEM, EventNameMouseLeftClickItem);
m_LastItemClickTime = timer_Time();
m_PrevSelectedItem = hovered;
break;
}
case GUIM_MOUSE_LEAVE:
{
if (m_Hovered == -1)
break;
m_Hovered.Set(-1, true);
ScriptEvent(EventNameHoverChange);
break;
}
case GUIM_MOUSE_OVER:
{
int hovered = GetHoveredItem();
if (hovered == m_Hovered)
break;
m_Hovered.Set(hovered, true);
ScriptEvent(EventNameHoverChange);
break;
}
case GUIM_LOAD:
{
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
break;
}
default:
break;
}
IGUITextOwner::HandleMessage(Message);
}
InReaction CList::ManuallyHandleKeys(const SDL_Event_* ev)
{
InReaction result = IN_PASS;
if (ev->ev.type == SDL_KEYDOWN)
{
int szChar = ev->ev.key.keysym.sym;
switch (szChar)
{
case SDLK_HOME:
SelectFirstElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_END:
SelectLastElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_UP:
SelectPrevElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_DOWN:
SelectNextElement();
UpdateAutoScroll();
result = IN_HANDLED;
break;
case SDLK_PAGEUP:
GetScrollBar(0).ScrollMinusPlenty();
result = IN_HANDLED;
break;
case SDLK_PAGEDOWN:
GetScrollBar(0).ScrollPlusPlenty();
result = IN_HANDLED;
break;
default: // Do nothing
result = IN_PASS;
}
}
return result;
}
void CList::Draw(CCanvas2D& canvas)
{
DrawList(canvas, m_Selected, m_Sprite, m_SpriteOverlay, m_SpriteSelectArea, m_SpriteSelectAreaOverlay, m_TextColor);
}
void CList::DrawList(CCanvas2D& canvas, const int& selected, const CGUISpriteInstance& sprite, const CGUISpriteInstance& spriteOverlay,
const CGUISpriteInstance& spriteSelectArea, const CGUISpriteInstance& spriteSelectAreaOverlay, const CGUIColor& textColor)
{
CRect rect = GetListRect();
m_pGUI.DrawSprite(sprite, canvas, rect);
float scroll = 0.f;
if (m_ScrollBar)
scroll = GetScrollBar(0).GetPos();
bool drawSelected = false;
CRect rectSel;
if (selected >= 0 && selected+1 < (int)m_ItemsYPositions.size())
{
// Get rectangle of selection:
rectSel = CRect(
rect.left, rect.top + m_ItemsYPositions[selected] - scroll,
rect.right, rect.top + m_ItemsYPositions[selected+1] - scroll);
if (rectSel.top <= rect.bottom &&
rectSel.bottom >= rect.top)
{
if (rectSel.bottom > rect.bottom)
rectSel.bottom = rect.bottom;
if (rectSel.top < rect.top)
rectSel.top = rect.top;
if (m_ScrollBar)
{
// Remove any overlapping area of the scrollbar.
if (rectSel.right > GetScrollBar(0).GetOuterRect().left &&
rectSel.right <= GetScrollBar(0).GetOuterRect().right)
rectSel.right = GetScrollBar(0).GetOuterRect().left;
if (rectSel.left >= GetScrollBar(0).GetOuterRect().left &&
rectSel.left < GetScrollBar(0).GetOuterRect().right)
rectSel.left = GetScrollBar(0).GetOuterRect().right;
}
m_pGUI.DrawSprite(spriteSelectArea, canvas, rectSel);
drawSelected = true;
}
}
for (size_t i = 0; i < m_List->m_Items.size(); ++i)
{
if (m_ItemsYPositions[i+1] - scroll < 0 ||
m_ItemsYPositions[i] - scroll > rect.GetHeight())
continue;
// Clipping area (we'll have to substract the scrollbar)
CRect cliparea = GetListRect();
if (m_ScrollBar)
{
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
DrawText(canvas, i, textColor, rect.TopLeft() - CVector2D(0.f, scroll - m_ItemsYPositions[i]), cliparea);
}
// Draw scrollbars on top of the content
if (m_ScrollBar)
IGUIScrollBarOwner::Draw(canvas);
// Draw the overlays last
m_pGUI.DrawSprite(spriteOverlay, canvas, rect);
if (drawSelected)
m_pGUI.DrawSprite(spriteSelectAreaOverlay, canvas, rectSel);
}
void CList::AddItem(const CGUIString& str, const CGUIString& data)
{
// Do not send a settings-changed message
m_List.GetMutable().m_Items.push_back(str);
m_ListData.GetMutable().m_Items.push_back(data);
SetupText(true);
}
void CList::AddItem(const CGUIString& strAndData)
{
AddItem(strAndData, strAndData);
}
bool CList::HandleAdditionalChildren(const XMBData& xmb, const XMBElement& child)
{
int elmt_item = xmb.GetElementID("item");
if (child.GetNodeName() == elmt_item)
{
CGUIString vlist;
vlist.SetValue(child.GetText().FromUTF8());
AddItem(vlist, vlist);
return true;
}
return false;
}
void CList::SelectNextElement()
{
if (m_Selected != static_cast(m_List->m_Items.size()) - 1)
{
m_Selected.Set(m_Selected + 1, true);
PlaySound(m_SoundSelected);
}
}
void CList::SelectPrevElement()
{
if (m_Selected > 0)
{
m_Selected.Set(m_Selected - 1, true);
PlaySound(m_SoundSelected);
}
}
void CList::SelectFirstElement()
{
if (m_Selected >= 0)
m_Selected.Set(0, true);
}
void CList::SelectLastElement()
{
const int index = static_cast(m_List->m_Items.size()) - 1;
if (m_Selected != index)
m_Selected.Set(index, true);
}
void CList::UpdateAutoScroll()
{
// No scrollbar, no scrolling (at least it's not made to work properly).
if (!m_ScrollBar || m_Selected < 0 || static_cast(m_Selected) >= m_ItemsYPositions.size())
return;
float scroll = GetScrollBar(0).GetPos();
// Check upper boundary
if (m_ItemsYPositions[m_Selected] < scroll)
{
GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected]);
return; // this means, if it wants to align both up and down at the same time
// this will have precedence.
}
// Check lower boundary
CRect rect = GetListRect();
if (m_ItemsYPositions[m_Selected+1]-rect.GetHeight() > scroll)
GetScrollBar(0).SetPos(m_ItemsYPositions[m_Selected+1]-rect.GetHeight());
}
int CList::GetHoveredItem()
{
const float scroll = m_ScrollBar ? GetScrollBar(0).GetPos() : 0.f;
const CRect& rect = GetListRect();
CVector2D mouse = m_pGUI.GetMousePos();
mouse.Y += scroll;
// Mouse is over scrollbar
if (m_ScrollBar && GetScrollBar(0).IsVisible() &&
mouse.X >= GetScrollBar(0).GetOuterRect().left &&
mouse.X <= GetScrollBar(0).GetOuterRect().right)
return -1;
for (size_t i = 0; i < m_List->m_Items.size(); ++i)
if (mouse.Y >= rect.top + m_ItemsYPositions[i] &&
mouse.Y < rect.top + m_ItemsYPositions[i + 1])
return i;
return -1;
}
Index: ps/trunk/source/gui/ObjectTypes/CText.cpp
===================================================================
--- ps/trunk/source/gui/ObjectTypes/CText.cpp (revision 25692)
+++ ps/trunk/source/gui/ObjectTypes/CText.cpp (revision 25693)
@@ -1,226 +1,226 @@
/* Copyright (C) 2021 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CText.h"
#include "gui/CGUI.h"
#include "gui/CGUIScrollBarVertical.h"
#include "gui/CGUIText.h"
CText::CText(CGUI& pGUI)
: IGUIObject(pGUI),
IGUIScrollBarOwner(*static_cast(this)),
IGUITextOwner(*static_cast(this)),
m_BufferZone(this, "buffer_zone"),
m_Caption(this, "caption"),
m_Clip(this, "clip", true),
m_Font(this, "font"),
m_ScrollBar(this, "scrollbar", false),
m_ScrollBarStyle(this, "scrollbar_style"),
m_ScrollBottom(this, "scroll_bottom"),
m_ScrollTop(this, "scroll_top"),
m_Sprite(this, "sprite"),
m_SpriteOverlay(this, "sprite_overlay"),
m_TextColor(this, "textcolor"),
m_TextColorDisabled(this, "textcolor_disabled")
{
// Add scroll-bar
- CGUIScrollBarVertical* bar = new CGUIScrollBarVertical(pGUI);
+ auto bar = std::make_unique(pGUI);
bar->SetRightAligned(true);
- AddScrollBar(bar);
+ AddScrollBar(std::move(bar));
// Add text
AddText();
}
CText::~CText()
{
}
void CText::SetupText()
{
if (m_GeneratedTexts.empty())
return;
float width = m_CachedActualSize.GetWidth();
// remove scrollbar if applicable
if (m_ScrollBar && GetScrollBar(0).GetStyle())
width -= GetScrollBar(0).GetStyle()->m_Width;
m_GeneratedTexts[0] = CGUIText(m_pGUI, m_Caption, m_Font, width, m_BufferZone, m_TextAlign, this);
if (!m_ScrollBar)
CalculateTextPosition(m_CachedActualSize, m_TextPos, m_GeneratedTexts[0]);
// Setup scrollbar
if (m_ScrollBar)
{
// If we are currently scrolled to the bottom of the text,
// then add more lines of text, update the scrollbar so we
// stick to the bottom.
// (Use 1.5px delta so this triggers the first time caption is set)
bool bottom = false;
if (m_ScrollBottom && GetScrollBar(0).GetPos() > GetScrollBar(0).GetMaxPos() - 1.5f)
bottom = true;
GetScrollBar(0).SetScrollRange(m_GeneratedTexts[0].GetSize().Height);
GetScrollBar(0).SetScrollSpace(m_CachedActualSize.GetHeight());
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
if (bottom)
GetScrollBar(0).SetPos(GetScrollBar(0).GetMaxPos());
if (m_ScrollTop)
GetScrollBar(0).SetPos(0.0f);
}
}
void CText::ResetStates()
{
IGUIObject::ResetStates();
IGUIScrollBarOwner::ResetStates();
}
void CText::UpdateCachedSize()
{
IGUIObject::UpdateCachedSize();
IGUITextOwner::UpdateCachedSize();
}
CSize2D CText::GetTextSize()
{
UpdateText();
return m_GeneratedTexts[0].GetSize();
}
const CStrW& CText::GetTooltipText() const
{
for (const CGUIText& text : m_GeneratedTexts)
for (const CGUIText::STextCall& textChunk : text.GetTextCalls())
{
if (textChunk.m_Tooltip.empty())
continue;
CRect area(textChunk.m_Pos - CVector2D(0.f, textChunk.m_Size.Height), textChunk.m_Size);
if (area.PointInside(m_pGUI.GetMousePos() - m_CachedActualSize.TopLeft()))
return textChunk.m_Tooltip;
}
return m_Tooltip;
}
void CText::HandleMessage(SGUIMessage& Message)
{
IGUIObject::HandleMessage(Message);
IGUIScrollBarOwner::HandleMessage(Message);
//IGUITextOwner::HandleMessage(Message); <== placed it after the switch instead!
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
if (Message.value == "scrollbar")
SetupText();
// Update scrollbar
if (Message.value == "scrollbar_style")
{
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
SetupText();
}
break;
case GUIM_MOUSE_WHEEL_DOWN:
{
GetScrollBar(0).ScrollPlus();
// Since the scroll was changed, let's simulate a mouse movement
// to check if scrollbar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
break;
}
case GUIM_MOUSE_WHEEL_UP:
{
GetScrollBar(0).ScrollMinus();
// Since the scroll was changed, let's simulate a mouse movement
// to check if scrollbar now is hovered
SGUIMessage msg(GUIM_MOUSE_MOTION);
HandleMessage(msg);
break;
}
case GUIM_LOAD:
{
GetScrollBar(0).SetX(m_CachedActualSize.right);
GetScrollBar(0).SetY(m_CachedActualSize.top);
GetScrollBar(0).SetZ(GetBufferedZ());
GetScrollBar(0).SetLength(m_CachedActualSize.bottom - m_CachedActualSize.top);
GetScrollBar(0).SetScrollBarStyle(m_ScrollBarStyle);
break;
}
default:
break;
}
IGUITextOwner::HandleMessage(Message);
}
void CText::Draw(CCanvas2D& canvas)
{
m_pGUI.DrawSprite(m_Sprite, canvas, m_CachedActualSize);
float scroll = 0.f;
if (m_ScrollBar)
scroll = GetScrollBar(0).GetPos();
// Clipping area (we'll have to subtract the scrollbar)
CRect cliparea;
if (m_Clip)
{
cliparea = m_CachedActualSize;
if (m_ScrollBar)
{
// subtract scrollbar from cliparea
if (cliparea.right > GetScrollBar(0).GetOuterRect().left &&
cliparea.right <= GetScrollBar(0).GetOuterRect().right)
cliparea.right = GetScrollBar(0).GetOuterRect().left;
if (cliparea.left >= GetScrollBar(0).GetOuterRect().left &&
cliparea.left < GetScrollBar(0).GetOuterRect().right)
cliparea.left = GetScrollBar(0).GetOuterRect().right;
}
}
const CGUIColor& color = m_Enabled ? m_TextColor : m_TextColorDisabled;
if (m_ScrollBar)
DrawText(canvas, 0, color, m_CachedActualSize.TopLeft() - CVector2D(0.f, scroll), cliparea);
else
DrawText(canvas, 0, color, m_TextPos, cliparea);
// Draw scrollbars on top of the content
if (m_ScrollBar)
IGUIScrollBarOwner::Draw(canvas);
// Draw the overlays last
m_pGUI.DrawSprite(m_SpriteOverlay, canvas, m_CachedActualSize);
}