Chat console improvements (#124)
This commit is contained in:
parent
0019930f27
commit
a3f08fe31f
@ -35,6 +35,7 @@ ChatBuffer::ChatBuffer(u32 scrollback):
|
||||
if (m_scrollback == 0)
|
||||
m_scrollback = 1;
|
||||
m_empty_formatted_line.first = true;
|
||||
m_empty_formatted_line.line_index = 0;
|
||||
}
|
||||
|
||||
void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text)
|
||||
@ -47,7 +48,7 @@ void ChatBuffer::addLine(const std::wstring &name, const std::wstring &text)
|
||||
if (m_rows > 0) {
|
||||
// m_formatted is valid and must be kept valid
|
||||
bool scrolled_at_bottom = (m_scroll == getBottomScrollPos());
|
||||
u32 num_added = formatChatLine(line, m_cols, m_formatted);
|
||||
u32 num_added = formatChatLine(line, m_unformatted.size() - 1, m_cols, m_formatted);
|
||||
if (scrolled_at_bottom)
|
||||
m_scroll += num_added;
|
||||
}
|
||||
@ -172,7 +173,7 @@ void ChatBuffer::reformat(u32 cols, u32 rows)
|
||||
{
|
||||
if (i == restore_scroll_unformatted)
|
||||
restore_scroll_formatted = m_formatted.size();
|
||||
formatChatLine(m_unformatted[i], cols, m_formatted);
|
||||
formatChatLine(m_unformatted[i], i, cols, m_formatted);
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,7 +224,7 @@ void ChatBuffer::scrollBottom()
|
||||
m_scroll = getBottomScrollPos();
|
||||
}
|
||||
|
||||
u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
|
||||
u32 ChatBuffer::formatChatLine(const ChatLine& line, int line_index, u32 cols,
|
||||
std::vector<ChatFormattedLine>& destination) const
|
||||
{
|
||||
u32 num_added = 0;
|
||||
@ -266,6 +267,7 @@ u32 ChatBuffer::formatChatLine(const ChatLine& line, u32 cols,
|
||||
//EnrichedString line_text(line.text);
|
||||
|
||||
next_line.first = true;
|
||||
next_line.line_index = line_index;
|
||||
bool text_processing = false;
|
||||
|
||||
// Produce fragments and layout them into lines
|
||||
|
@ -67,6 +67,8 @@ struct ChatFormattedLine
|
||||
std::vector<ChatFormattedFragment> fragments;
|
||||
// true if first line of one formatted ChatLine
|
||||
bool first;
|
||||
// Line index in ChatLine buffer
|
||||
int line_index;
|
||||
};
|
||||
|
||||
class ChatBuffer
|
||||
@ -111,6 +113,9 @@ public:
|
||||
// Scroll to top of buffer (oldest)
|
||||
void scrollTop();
|
||||
|
||||
s32 getScrollPos() { return m_scroll; }
|
||||
u32 getColsCount() { return m_cols; }
|
||||
|
||||
// Functions for keeping track of whether the lines were modified by any
|
||||
// preceding operations
|
||||
// If they were not changed, getLineCount() and getLine() output the same as
|
||||
@ -121,11 +126,11 @@ public:
|
||||
// Format a chat line for the given number of columns.
|
||||
// Appends the formatted lines to the destination array and
|
||||
// returns the number of formatted lines.
|
||||
u32 formatChatLine(const ChatLine& line, u32 cols,
|
||||
u32 formatChatLine(const ChatLine& line, int line_index, u32 cols,
|
||||
std::vector<ChatFormattedLine>& destination) const;
|
||||
|
||||
void resize(u32 scrollback);
|
||||
protected:
|
||||
|
||||
s32 getTopScrollPos() const;
|
||||
s32 getBottomScrollPos() const;
|
||||
|
||||
|
@ -954,10 +954,6 @@ private:
|
||||
#ifdef HAVE_TOUCHSCREENGUI
|
||||
bool m_cache_touchtarget;
|
||||
#endif
|
||||
|
||||
#if defined(__ANDROID__) || defined(__IOS__)
|
||||
bool m_android_chat_open = false;
|
||||
#endif
|
||||
};
|
||||
|
||||
Game::Game() :
|
||||
@ -1885,8 +1881,8 @@ void Game::processUserInput(f32 dtime)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!guienv->hasFocus(gui_chat_console) && gui_chat_console->isOpen()) {
|
||||
gui_chat_console->closeConsoleAtOnce();
|
||||
if (!gui_chat_console->hasFocus() && gui_chat_console->isOpen()) {
|
||||
gui_chat_console->closeConsole();
|
||||
}
|
||||
|
||||
// Input handler step() (used by the random input generator)
|
||||
@ -1924,7 +1920,7 @@ void Game::processKeyInput()
|
||||
openInventory();
|
||||
} else if (input->cancelPressed()) {
|
||||
#if defined(__ANDROID__) || defined(__IOS__)
|
||||
m_android_chat_open = false;
|
||||
gui_chat_console->setAndroidChatOpen(false);
|
||||
#endif
|
||||
if (!gui_chat_console->isOpenInhibited()) {
|
||||
showPauseMenu();
|
||||
@ -2137,11 +2133,20 @@ void Game::openConsole(float scale, const wchar_t *line)
|
||||
{
|
||||
assert(scale > 0.0f && scale <= 1.0f);
|
||||
|
||||
if (gui_chat_console->getAndroidChatOpen())
|
||||
return;
|
||||
|
||||
#if defined(__ANDROID__) || defined(__IOS__)
|
||||
if (!porting::hasRealKeyboard()) {
|
||||
porting::showInputDialog("", "", 2);
|
||||
m_android_chat_open = true;
|
||||
} else {
|
||||
gui_chat_console->setAndroidChatOpen(true);
|
||||
}
|
||||
#endif
|
||||
#if defined(__ANDROID__)
|
||||
return;
|
||||
#elif defined(__IOS__)
|
||||
if (!g_settings->getBool("device_is_tablet"))
|
||||
return;
|
||||
#endif
|
||||
if (gui_chat_console->isOpenInhibited())
|
||||
return;
|
||||
@ -2150,18 +2155,19 @@ void Game::openConsole(float scale, const wchar_t *line)
|
||||
gui_chat_console->setCloseOnEnter(true);
|
||||
gui_chat_console->replaceAndAddToHistory(line);
|
||||
}
|
||||
#if defined(__ANDROID__) || defined(__IOS__)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if defined(__ANDROID__) || defined(__IOS__)
|
||||
void Game::handleAndroidChatInput()
|
||||
{
|
||||
if (m_android_chat_open && porting::getInputDialogState() == 0) {
|
||||
if (gui_chat_console->getAndroidChatOpen() &&
|
||||
porting::getInputDialogState() == 0) {
|
||||
std::string text = porting::getInputDialogValue();
|
||||
client->typeChatMessage(utf8_to_wide(text));
|
||||
m_android_chat_open = false;
|
||||
gui_chat_console->setAndroidChatOpen(false);
|
||||
if (!text.empty() && gui_chat_console->isOpen()) {
|
||||
gui_chat_console->closeConsole();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
@ -20,6 +20,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
|
||||
#include "util/numeric.h"
|
||||
#include "inputhandler.h"
|
||||
#include "gui/guiChatConsole.h"
|
||||
#include "gui/mainmenumanager.h"
|
||||
#include "hud.h"
|
||||
|
||||
@ -115,6 +116,13 @@ bool MyEventReceiver::OnEvent(const SEvent &event)
|
||||
}
|
||||
#endif
|
||||
|
||||
GUIChatConsole* chat_console = GUIChatConsole::getChatConsole();
|
||||
if (chat_console && chat_console->isOpen()) {
|
||||
bool result = chat_console->preprocessEvent(event);
|
||||
if (result)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
React to nothing here if a menu is active
|
||||
*/
|
||||
|
@ -45,6 +45,7 @@ inline u32 clamp_u8(s32 value)
|
||||
return (u32) MYMIN(MYMAX(value, 0), 255);
|
||||
}
|
||||
|
||||
GUIChatConsole* GUIChatConsole::m_chat_console = nullptr;
|
||||
|
||||
GUIChatConsole::GUIChatConsole(
|
||||
gui::IGUIEnvironment* env,
|
||||
@ -61,6 +62,8 @@ GUIChatConsole::GUIChatConsole(
|
||||
m_menumgr(menumgr),
|
||||
m_animate_time_old(porting::getTimeMs())
|
||||
{
|
||||
m_chat_console = this;
|
||||
|
||||
// load background settings
|
||||
s32 console_alpha = g_settings->getS32("console_alpha");
|
||||
m_background_color.setAlpha(clamp_u8(console_alpha));
|
||||
@ -93,12 +96,19 @@ GUIChatConsole::GUIChatConsole(
|
||||
m_fontsize.X = MYMAX(m_fontsize.X, 1);
|
||||
m_fontsize.Y = MYMAX(m_fontsize.Y, 1);
|
||||
|
||||
createVScrollBar();
|
||||
|
||||
// set default cursor options
|
||||
setCursor(true, true, 2.0, 0.1);
|
||||
}
|
||||
|
||||
GUIChatConsole::~GUIChatConsole()
|
||||
{
|
||||
m_chat_console = nullptr;
|
||||
|
||||
removeChild(m_vscrollbar);
|
||||
delete m_vscrollbar;
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
||||
if (porting::hasRealKeyboard() && SDL_IsTextInputActive())
|
||||
SDL_StopTextInput();
|
||||
@ -114,10 +124,17 @@ void GUIChatConsole::openConsole(f32 scale)
|
||||
|
||||
m_open = true;
|
||||
m_desired_height_fraction = scale;
|
||||
|
||||
if (g_settings->getU32("fps_max") < 60) {
|
||||
m_desired_height_fraction *= m_screensize.Y;
|
||||
m_height = m_desired_height_fraction;
|
||||
}
|
||||
|
||||
m_desired_height = scale * m_screensize.Y;
|
||||
reformatConsole();
|
||||
m_animate_time_old = porting::getTimeMs();
|
||||
IGUIElement::setVisible(true);
|
||||
m_vscrollbar->setVisible(true);
|
||||
Environment->setFocus(this);
|
||||
m_menumgr->createdMenu(this);
|
||||
|
||||
@ -143,17 +160,16 @@ void GUIChatConsole::closeConsole()
|
||||
Environment->removeFocus(this);
|
||||
m_menumgr->deletingMenu(this);
|
||||
|
||||
if (g_settings->getU32("fps_max") < 60) {
|
||||
m_height = 0;
|
||||
recalculateConsolePosition();
|
||||
}
|
||||
|
||||
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
||||
if (porting::hasRealKeyboard() && SDL_IsTextInputActive())
|
||||
SDL_StopTextInput();
|
||||
#endif
|
||||
}
|
||||
|
||||
void GUIChatConsole::closeConsoleAtOnce()
|
||||
{
|
||||
closeConsole();
|
||||
m_height = 0;
|
||||
recalculateConsolePosition();
|
||||
#ifdef HAVE_TOUCHSCREENGUI
|
||||
if (g_touchscreengui && g_touchscreengui->isActive())
|
||||
g_touchscreengui->show();
|
||||
@ -197,6 +213,8 @@ void GUIChatConsole::draw()
|
||||
if(!IsVisible)
|
||||
return;
|
||||
|
||||
updateVScrollBar();
|
||||
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
|
||||
// Check screen size
|
||||
@ -230,19 +248,24 @@ void GUIChatConsole::draw()
|
||||
|
||||
void GUIChatConsole::reformatConsole()
|
||||
{
|
||||
s32 cols = m_screensize.X / m_fontsize.X - 2; // make room for a margin (looks better)
|
||||
s32 cols = (m_screensize.X - m_scrollbar_width) / m_fontsize.X - 2; // make room for a margin (looks better)
|
||||
s32 rows = m_desired_height / m_fontsize.Y - 1; // make room for the input prompt
|
||||
if (cols <= 0 || rows <= 0)
|
||||
cols = rows = 0;
|
||||
recalculateConsolePosition();
|
||||
m_chat_backend->reformat(cols, rows);
|
||||
|
||||
updateVScrollBar();
|
||||
}
|
||||
|
||||
void GUIChatConsole::recalculateConsolePosition()
|
||||
{
|
||||
core::rect<s32> rect(0, 0, m_screensize.X, m_height);
|
||||
DesiredRect = rect;
|
||||
recalculateAbsolutePosition(false);
|
||||
recalculateAbsolutePosition(true);
|
||||
|
||||
irr::core::rect<s32> scrollbarrect(m_screensize.X - m_scrollbar_width, 0, m_screensize.X, m_height);
|
||||
m_vscrollbar->setRelativePosition(scrollbarrect);
|
||||
}
|
||||
|
||||
void GUIChatConsole::animate(u32 msec)
|
||||
@ -253,8 +276,10 @@ void GUIChatConsole::animate(u32 msec)
|
||||
// Set invisible if close animation finished (reset by openConsole)
|
||||
// This function (animate()) is never called once its visibility becomes false so do not
|
||||
// actually set visible to false before the inhibited period is over
|
||||
if (!m_open && m_height == 0 && m_open_inhibited == 0)
|
||||
if (!m_open && m_height == 0 && m_open_inhibited == 0) {
|
||||
m_vscrollbar->setVisible(false);
|
||||
IGUIElement::setVisible(false);
|
||||
}
|
||||
|
||||
if (m_height != goal)
|
||||
{
|
||||
@ -338,6 +363,55 @@ void GUIChatConsole::drawText()
|
||||
if (y + line_height < 0)
|
||||
continue;
|
||||
|
||||
s32 scroll_pos = buf.getScrollPos();
|
||||
ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end;
|
||||
ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin;
|
||||
if (real_mark_begin != real_mark_end &&
|
||||
(s32)row + scroll_pos >= real_mark_begin.row + real_mark_begin.scroll &&
|
||||
(s32)row + scroll_pos <= real_mark_end.row + real_mark_end.scroll) {
|
||||
ChatFormattedFragment fragment_first = line.fragments[0];
|
||||
|
||||
if ((s32)row + scroll_pos == real_mark_begin.row + real_mark_begin.scroll &&
|
||||
real_mark_begin.fragment < line.fragments.size()) {
|
||||
fragment_first = line.fragments[real_mark_begin.fragment];
|
||||
}
|
||||
|
||||
ChatFormattedFragment fragment_last = line.fragments[line.fragments.size() - 1];
|
||||
|
||||
if ((s32)row + scroll_pos == real_mark_end.row + real_mark_end.scroll &&
|
||||
real_mark_end.fragment < line.fragments.size()) {
|
||||
fragment_last = line.fragments[real_mark_end.fragment];
|
||||
}
|
||||
|
||||
s32 x_begin = (fragment_first.column + 1) * m_fontsize.X;
|
||||
s32 text_size = m_font->getDimension(fragment_last.text.c_str()).Width;
|
||||
s32 x_end = (fragment_last.column + 1) * m_fontsize.X + text_size;
|
||||
|
||||
if ((s32)row + scroll_pos == real_mark_begin.row + real_mark_begin.scroll) {
|
||||
irr::core::stringw text = fragment_first.text.c_str();
|
||||
text = text.subString(0, real_mark_begin.character);
|
||||
s32 text_size = m_font->getDimension(text.c_str()).Width;
|
||||
x_begin = (fragment_first.column + 1) * m_fontsize.X + text_size;
|
||||
|
||||
if (real_mark_begin.x_max)
|
||||
x_begin = x_end;
|
||||
}
|
||||
|
||||
if ((s32)row + scroll_pos == real_mark_end.row + real_mark_end.scroll &&
|
||||
(real_mark_end.character < fragment_last.text.size()) &&
|
||||
!real_mark_end.x_max) {
|
||||
irr::core::stringw text = fragment_last.text.c_str();
|
||||
text = text.subString(0, real_mark_end.character);
|
||||
s32 text_size = m_font->getDimension(text.c_str()).Width;
|
||||
x_end = (fragment_last.column + 1) * m_fontsize.X + text_size;
|
||||
}
|
||||
|
||||
core::rect<s32> destrect(x_begin, y, x_end, y + m_fontsize.Y);
|
||||
video::IVideoDriver* driver = Environment->getVideoDriver();
|
||||
IGUISkin* skin = Environment->getSkin();
|
||||
driver->draw2DRectangle(skin->getColor(EGDC_HIGH_LIGHT), destrect, &AbsoluteClippingRect);
|
||||
}
|
||||
|
||||
for (const ChatFormattedFragment &fragment : line.fragments) {
|
||||
s32 x = (fragment.column + 1) * m_fontsize.X;
|
||||
core::rect<s32> destrect(
|
||||
@ -423,9 +497,166 @@ void GUIChatConsole::drawPrompt()
|
||||
|
||||
}
|
||||
|
||||
|
||||
ChatSelection GUIChatConsole::getCursorPos(s32 x, s32 y)
|
||||
{
|
||||
ChatSelection selection;
|
||||
|
||||
if (m_font == NULL)
|
||||
return selection;
|
||||
|
||||
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
||||
selection.scroll = buf.getScrollPos();
|
||||
selection.initialized = true;
|
||||
|
||||
s32 line_height = m_fontsize.Y;
|
||||
s32 y_min = m_height - m_desired_height;
|
||||
s32 y_max = buf.getRows() * line_height + y_min;
|
||||
|
||||
if (y <= y_min) {
|
||||
selection.row = 0;
|
||||
} else if (y >= y_max) {
|
||||
selection.row = buf.getRows() - 1;
|
||||
} else {
|
||||
for (u32 row = 0; row < buf.getRows(); row++) {
|
||||
s32 y1 = row * line_height + m_height - m_desired_height;
|
||||
s32 y2 = y1 + line_height;
|
||||
|
||||
if (y1 + line_height < 0)
|
||||
return selection;
|
||||
|
||||
if (y >= y1 && y <= y2) {
|
||||
selection.row = row;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ChatFormattedLine line = buf.getFormattedLine(selection.row);
|
||||
selection.row_buf = line.line_index;
|
||||
int current_row = selection.row;
|
||||
|
||||
while (!line.first) {
|
||||
current_row--;
|
||||
line = buf.getFormattedLine(current_row);
|
||||
selection.line++;
|
||||
}
|
||||
|
||||
line = buf.getFormattedLine(selection.row);
|
||||
|
||||
if (line.fragments.empty())
|
||||
return selection;
|
||||
|
||||
const ChatFormattedFragment &fragment_first = line.fragments[0];
|
||||
const ChatFormattedFragment &fragment_last = line.fragments[line.fragments.size() - 1];
|
||||
s32 x_min = (fragment_first.column + 1) * m_fontsize.X;
|
||||
s32 text_size = m_font->getDimension(fragment_last.text.c_str()).Width;
|
||||
s32 x_max = (fragment_last.column + 1) * m_fontsize.X + text_size;
|
||||
|
||||
if (x < x_min) {
|
||||
x = x_min;
|
||||
} else if (x > x_max) {
|
||||
x = x_max;
|
||||
selection.x_max = true;
|
||||
}
|
||||
|
||||
for (unsigned int i = 0; i < line.fragments.size(); i++) {
|
||||
const ChatFormattedFragment &fragment = line.fragments[i];
|
||||
s32 fragment_x = (fragment.column + 1) * m_fontsize.X;
|
||||
|
||||
if (x < fragment_x)
|
||||
continue;
|
||||
|
||||
if (i < line.fragments.size() - 1) {
|
||||
const ChatFormattedFragment &fragment_next = line.fragments[i + 1];
|
||||
s32 fragment_next_x = (fragment_next.column + 1) * m_fontsize.X;
|
||||
|
||||
if (x >= fragment_next_x)
|
||||
continue;
|
||||
}
|
||||
|
||||
s32 index = m_font->getCharacterFromPos(fragment.text.c_str(), x - fragment_x);
|
||||
|
||||
selection.fragment = i;
|
||||
selection.character = index > -1 ? index : fragment.text.size() - 1;
|
||||
return selection;
|
||||
}
|
||||
|
||||
return selection;
|
||||
}
|
||||
|
||||
irr::core::stringc GUIChatConsole::getSelectedText()
|
||||
{
|
||||
if (m_font == NULL)
|
||||
return "";
|
||||
|
||||
if (m_mark_begin == m_mark_end)
|
||||
return "";
|
||||
|
||||
bool add_to_string = false;
|
||||
irr::core::stringw text = "";
|
||||
|
||||
ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end;
|
||||
ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin;
|
||||
|
||||
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
||||
|
||||
for (int row = real_mark_begin.row_buf; row < real_mark_end.row_buf + 1; row++) {
|
||||
const ChatLine& line = buf.getLine(row);
|
||||
|
||||
std::vector<ChatFormattedLine> formatted_lines;
|
||||
buf.formatChatLine(line, 0, buf.getColsCount(), formatted_lines);
|
||||
|
||||
for (unsigned int i = 0; i < formatted_lines.size(); i++) {
|
||||
const ChatFormattedLine &line = formatted_lines[i];
|
||||
|
||||
for (unsigned int j = 0; j < line.fragments.size(); j++) {
|
||||
const ChatFormattedFragment &fragment = line.fragments[j];
|
||||
|
||||
for (unsigned int k = 0; k < fragment.text.size(); k++) {
|
||||
if (!add_to_string &&
|
||||
row == real_mark_begin.row_buf &&
|
||||
i == real_mark_begin.line &&
|
||||
j == real_mark_begin.fragment &&
|
||||
k == real_mark_begin.character) {
|
||||
add_to_string = true;
|
||||
|
||||
if (real_mark_begin.x_max)
|
||||
continue;
|
||||
}
|
||||
|
||||
if (add_to_string) {
|
||||
if (row == real_mark_end.row_buf &&
|
||||
i == real_mark_end.line &&
|
||||
j == real_mark_end.fragment &&
|
||||
k == real_mark_end.character) {
|
||||
if (real_mark_end.x_max)
|
||||
text += fragment.text.c_str()[k];
|
||||
|
||||
irr::core::stringc text_c;
|
||||
text_c = wide_to_utf8(text.c_str()).c_str();
|
||||
return text_c;
|
||||
}
|
||||
|
||||
text += fragment.text.c_str()[k];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (row < real_mark_end.row_buf) {
|
||||
text += L"\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
irr::core::stringc text_c;
|
||||
text_c = wide_to_utf8(text.c_str()).c_str();
|
||||
return text_c;
|
||||
}
|
||||
|
||||
|
||||
bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
{
|
||||
|
||||
ChatPrompt &prompt = m_chat_backend->getPrompt();
|
||||
|
||||
if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
|
||||
@ -441,7 +672,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
}
|
||||
|
||||
if (event.KeyInput.Key == KEY_ESCAPE || event.KeyInput.Key == KEY_CANCEL) {
|
||||
closeConsoleAtOnce();
|
||||
closeConsole();
|
||||
m_close_on_enter = false;
|
||||
// inhibit open so the_game doesn't reopen immediately
|
||||
m_open_inhibited = 1; // so the ESCAPE button doesn't open the "pause menu"
|
||||
@ -449,11 +680,17 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_PRIOR)
|
||||
{
|
||||
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
||||
s32 rows = -(s32)buf.getRows();
|
||||
m_vscrollbar->setPos(m_vscrollbar->getPos() + rows);
|
||||
m_chat_backend->scrollPageUp();
|
||||
return true;
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_NEXT)
|
||||
{
|
||||
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
||||
s32 rows = buf.getRows();
|
||||
m_vscrollbar->setPos(m_vscrollbar->getPos() + rows);
|
||||
m_chat_backend->scrollPageDown();
|
||||
return true;
|
||||
}
|
||||
@ -463,7 +700,7 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
std::wstring text = prompt.replace(L"");
|
||||
m_client->typeChatMessage(text);
|
||||
if (m_close_on_enter) {
|
||||
closeConsoleAtOnce();
|
||||
closeConsole();
|
||||
m_close_on_enter = false;
|
||||
}
|
||||
return true;
|
||||
@ -558,6 +795,12 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
}
|
||||
else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
|
||||
{
|
||||
if (m_mark_begin != m_mark_end) {
|
||||
irr::core::stringc text = getSelectedText();
|
||||
Environment->getOSOperator()->copyToClipboard(text.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ctrl-C pressed
|
||||
// Copy text to clipboard
|
||||
if (prompt.getCursorLength() <= 0)
|
||||
@ -649,8 +892,9 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
for (u32 i = 0; i < text.size(); i++)
|
||||
prompt.input(text[i]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
else if(event.EventType == EET_MOUSE_INPUT_EVENT)
|
||||
@ -659,6 +903,91 @@ bool GUIChatConsole::OnEvent(const SEvent& event)
|
||||
{
|
||||
s32 rows = myround(-3.0 * event.MouseInput.Wheel);
|
||||
m_chat_backend->scroll(rows);
|
||||
m_vscrollbar->setPos(m_vscrollbar->getPos() + rows);
|
||||
} else if (event.MouseInput.Event == EMIE_LMOUSE_PRESSED_DOWN) {
|
||||
m_mouse_marking = true;
|
||||
m_mark_begin = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
||||
m_mark_end = m_mark_begin;
|
||||
} else if (event.MouseInput.Event == EMIE_LMOUSE_LEFT_UP) {
|
||||
if (m_mouse_marking) {
|
||||
m_mark_end = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
||||
m_mouse_marking = false;
|
||||
|
||||
if (m_mark_begin == m_mark_end) {
|
||||
m_mark_begin.reset();
|
||||
m_mark_end.reset();
|
||||
}
|
||||
}
|
||||
} else if (event.MouseInput.Event == EMIE_MOUSE_MOVED) {
|
||||
if (m_mouse_marking) {
|
||||
m_mark_end = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#ifdef HAVE_TOUCHSCREENGUI
|
||||
else if (event.EventType == EET_TOUCH_INPUT_EVENT) {
|
||||
if (event.TouchInput.Event == irr::ETIE_PRESSED_DOWN) {
|
||||
m_mouse_marking = false;
|
||||
m_long_press = false;
|
||||
m_cursor_press_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
||||
|
||||
ChatSelection real_mark_begin = m_mark_end > m_mark_begin ? m_mark_begin : m_mark_end;
|
||||
ChatSelection real_mark_end = m_mark_end > m_mark_begin ? m_mark_end : m_mark_begin;
|
||||
|
||||
if (m_cursor_press_pos < real_mark_begin || m_cursor_press_pos > real_mark_end) {
|
||||
m_mark_begin = m_cursor_press_pos;
|
||||
m_mark_end = m_cursor_press_pos;
|
||||
m_mouse_marking = true;
|
||||
}
|
||||
} else if (event.TouchInput.Event == irr::ETIE_LEFT_UP) {
|
||||
ChatSelection cursor_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
||||
|
||||
if (!m_long_press && m_cursor_press_pos == cursor_pos) {
|
||||
m_mark_begin.reset();
|
||||
m_mark_end.reset();
|
||||
}
|
||||
|
||||
m_cursor_press_pos.reset();
|
||||
m_mouse_marking = false;
|
||||
m_long_press = false;
|
||||
} else if (event.TouchInput.Event == irr::ETIE_MOVED) {
|
||||
ChatSelection cursor_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
||||
|
||||
if (!m_mouse_marking && !m_long_press && m_cursor_press_pos.initialized &&
|
||||
m_cursor_press_pos != cursor_pos) {
|
||||
m_mark_begin = m_cursor_press_pos;
|
||||
m_mark_end = m_cursor_press_pos;
|
||||
m_mouse_marking = true;
|
||||
}
|
||||
|
||||
if (m_mouse_marking) {
|
||||
m_mark_end = cursor_pos;
|
||||
}
|
||||
} else if (event.TouchInput.Event == irr::ETIE_PRESSED_LONG) {
|
||||
if (!m_mouse_marking) {
|
||||
m_long_press = true;
|
||||
if (m_mark_begin != m_mark_end) {
|
||||
irr::core::stringc text = getSelectedText();
|
||||
Environment->getOSOperator()->copyToClipboard(text.c_str());
|
||||
#ifdef __ANDROID__
|
||||
SDL_AndroidShowToast(
|
||||
"Copied to clipboard", 2,
|
||||
-1, 0, 0);
|
||||
#elif __IOS__
|
||||
porting::showToast("Copied to clipboard");
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
else if (event.EventType == EET_GUI_EVENT) {
|
||||
if (event.GUIEvent.EventType == EGET_SCROLL_BAR_CHANGED) {
|
||||
updateVScrollBar();
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,9 +998,174 @@ void GUIChatConsole::setVisible(bool visible)
|
||||
{
|
||||
m_open = visible;
|
||||
IGUIElement::setVisible(visible);
|
||||
m_vscrollbar->setVisible(visible);
|
||||
if (!visible) {
|
||||
m_height = 0;
|
||||
recalculateConsolePosition();
|
||||
}
|
||||
}
|
||||
|
||||
//! create a vertical scroll bar
|
||||
void GUIChatConsole::createVScrollBar()
|
||||
{
|
||||
IGUISkin *skin = 0;
|
||||
if (Environment)
|
||||
skin = Environment->getSkin();
|
||||
|
||||
m_scrollbar_width = skin ? skin->getSize(gui::EGDS_SCROLLBAR_SIZE) : 16;
|
||||
m_scrollbar_width *= 2;
|
||||
|
||||
irr::core::rect<s32> scrollbarrect(m_screensize.X - m_scrollbar_width,
|
||||
0, m_screensize.X, m_height);
|
||||
m_vscrollbar = new GUIScrollBar(Environment, getParent(), -1,
|
||||
scrollbarrect, false, true);
|
||||
|
||||
m_vscrollbar->setVisible(false);
|
||||
m_vscrollbar->setMax(0);
|
||||
m_vscrollbar->setPos(0);
|
||||
m_vscrollbar->setPageSize(0);
|
||||
m_vscrollbar->setSmallStep(1);
|
||||
m_vscrollbar->setLargeStep(1);
|
||||
m_vscrollbar->setArrowsVisible(GUIScrollBar::ArrowVisibility::SHOW);
|
||||
|
||||
ITextureSource *tsrc = m_client->getTextureSource();
|
||||
m_vscrollbar->setTextures({
|
||||
tsrc->getTexture("gui/scrollbar_bg.png"),
|
||||
tsrc->getTexture("gui/scrollbar_slider_long.png"),
|
||||
tsrc->getTexture("gui/scrollbar_up.png"),
|
||||
tsrc->getTexture("gui/scrollbar_down.png"),
|
||||
});
|
||||
|
||||
addChild(m_vscrollbar);
|
||||
}
|
||||
|
||||
void GUIChatConsole::updateVScrollBar()
|
||||
{
|
||||
if (!m_vscrollbar)
|
||||
return;
|
||||
|
||||
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
||||
|
||||
if (m_bottom_scroll_pos != buf.getBottomScrollPos()) {
|
||||
m_bottom_scroll_pos = buf.getBottomScrollPos();
|
||||
|
||||
if (buf.getBottomScrollPos() > 0) {
|
||||
buf.scrollAbsolute(m_bottom_scroll_pos);
|
||||
m_vscrollbar->setMax(m_bottom_scroll_pos);
|
||||
m_vscrollbar->setPos(m_bottom_scroll_pos);
|
||||
} else {
|
||||
m_vscrollbar->setMax(0);
|
||||
m_vscrollbar->setPos(0);
|
||||
}
|
||||
}
|
||||
|
||||
s32 page_size = (m_bottom_scroll_pos + buf.getRows() + 1) * m_fontsize.Y;
|
||||
if (m_vscrollbar->getPageSize() != page_size) {
|
||||
m_vscrollbar->setPageSize(page_size);
|
||||
}
|
||||
|
||||
if (m_vscrollbar->getPos() != buf.getScrollPos()) {
|
||||
if (buf.getScrollPos() >= 0) {
|
||||
s32 deltaScrollY = m_vscrollbar->getPos() - buf.getScrollPos();
|
||||
m_chat_backend->scroll(deltaScrollY);
|
||||
}
|
||||
}
|
||||
|
||||
if (IsVisible) {
|
||||
if (m_vscrollbar->isVisible() && m_vscrollbar->getMax() == 0)
|
||||
m_vscrollbar->setVisible(false);
|
||||
else if (!m_vscrollbar->isVisible() && m_vscrollbar->getMax() > 0)
|
||||
m_vscrollbar->setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool GUIChatConsole::hasFocus()
|
||||
{
|
||||
if (Environment->hasFocus(this))
|
||||
return true;
|
||||
|
||||
if (Environment->hasFocus(m_vscrollbar))
|
||||
return true;
|
||||
|
||||
const core::list<gui::IGUIElement*> &children = m_vscrollbar->getChildren();
|
||||
|
||||
for (gui::IGUIElement *it : children) {
|
||||
if (Environment->hasFocus(it))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GUIChatConsole::convertToMouseEvent(
|
||||
SEvent &mouse_event, SEvent touch_event) const noexcept
|
||||
{
|
||||
#ifdef HAVE_TOUCHSCREENGUI
|
||||
mouse_event = {};
|
||||
mouse_event.EventType = EET_MOUSE_INPUT_EVENT;
|
||||
mouse_event.MouseInput.X = touch_event.TouchInput.X;
|
||||
mouse_event.MouseInput.Y = touch_event.TouchInput.Y;
|
||||
switch (touch_event.TouchInput.Event) {
|
||||
case ETIE_PRESSED_DOWN:
|
||||
mouse_event.MouseInput.Event = EMIE_LMOUSE_PRESSED_DOWN;
|
||||
mouse_event.MouseInput.ButtonStates = EMBSM_LEFT;
|
||||
break;
|
||||
case ETIE_MOVED:
|
||||
mouse_event.MouseInput.Event = EMIE_MOUSE_MOVED;
|
||||
mouse_event.MouseInput.ButtonStates = EMBSM_LEFT;
|
||||
break;
|
||||
case ETIE_LEFT_UP:
|
||||
mouse_event.MouseInput.Event = EMIE_LMOUSE_LEFT_UP;
|
||||
mouse_event.MouseInput.ButtonStates = 0;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
#else
|
||||
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool GUIChatConsole::preprocessEvent(SEvent event)
|
||||
{
|
||||
updateVScrollBar();
|
||||
|
||||
#ifdef HAVE_TOUCHSCREENGUI
|
||||
if (event.EventType == irr::EET_TOUCH_INPUT_EVENT) {
|
||||
const core::position2di p(event.TouchInput.X, event.TouchInput.Y);
|
||||
|
||||
u32 row = m_chat_backend->getConsoleBuffer().getRows();
|
||||
s32 prompt_y = row * m_fontsize.Y + m_height - m_desired_height;
|
||||
|
||||
if (m_vscrollbar->isPointInside(p) || !isPointInside(p)) {
|
||||
SEvent mouse_event = {};
|
||||
bool success = convertToMouseEvent(mouse_event, event);
|
||||
if (success) {
|
||||
Environment->postEventFromUser(mouse_event);
|
||||
}
|
||||
}
|
||||
#if defined(__ANDROID__) || defined(__IOS__)
|
||||
else if (!porting::hasRealKeyboard() &&
|
||||
event.TouchInput.Y >= prompt_y &&
|
||||
event.TouchInput.Y <= m_height) {
|
||||
if (event.TouchInput.Event == ETIE_PRESSED_DOWN &&
|
||||
!m_android_chat_open) {
|
||||
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
||||
porting::showInputDialog("", "", 2);
|
||||
m_android_chat_open = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
OnEvent(event);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -23,6 +23,75 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "modalMenu.h"
|
||||
#include "chat.h"
|
||||
#include "config.h"
|
||||
#include "guiScrollBar.h"
|
||||
|
||||
struct ChatSelection
|
||||
{
|
||||
ChatSelection() : initialized(false), scroll(0), row(0), row_buf(0),
|
||||
line(0), fragment(0), character(0), x_max(false) {};
|
||||
|
||||
void reset() {
|
||||
initialized = false;
|
||||
scroll = 0;
|
||||
row = 0;
|
||||
row_buf = 0;
|
||||
line = 0;
|
||||
fragment = 0;
|
||||
character = 0;
|
||||
x_max = false;
|
||||
}
|
||||
|
||||
bool operator== (const ChatSelection &other) const {
|
||||
return (row + scroll == other.row + other.scroll &&
|
||||
row_buf == other.row_buf &&
|
||||
line == other.line &&
|
||||
fragment == other.fragment &&
|
||||
character == other.character &&
|
||||
x_max == other.x_max);
|
||||
}
|
||||
|
||||
bool operator< (const ChatSelection &other) const {
|
||||
if (row + scroll != other.row + other.scroll)
|
||||
return (row + scroll < other.row + other.scroll);
|
||||
if (row_buf != other.row_buf)
|
||||
return (row_buf < other.row_buf);
|
||||
if (line != other.line)
|
||||
return (line < other.line);
|
||||
if (fragment != other.fragment)
|
||||
return (fragment < other.fragment);
|
||||
if (character != other.character)
|
||||
return (character < other.character);
|
||||
if (x_max != other.x_max)
|
||||
return (x_max < other.x_max);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool operator> (const ChatSelection &other) {
|
||||
return other < *this;
|
||||
}
|
||||
|
||||
bool operator<= (const ChatSelection &other) {
|
||||
return !(*this > other);
|
||||
}
|
||||
|
||||
bool operator>= (const ChatSelection &other) {
|
||||
return !(*this < other);
|
||||
}
|
||||
|
||||
bool operator!= (const ChatSelection &other) const {
|
||||
return !this->operator==(other);
|
||||
}
|
||||
|
||||
bool initialized;
|
||||
int scroll;
|
||||
int row;
|
||||
int row_buf;
|
||||
unsigned int line;
|
||||
unsigned int fragment;
|
||||
unsigned int character;
|
||||
bool x_max;
|
||||
};
|
||||
|
||||
class Client;
|
||||
|
||||
@ -50,8 +119,6 @@ public:
|
||||
// Close the console, equivalent to openConsole(0).
|
||||
// This doesn't close immediately but initiates an animation.
|
||||
void closeConsole();
|
||||
// Close the console immediately, without animation.
|
||||
void closeConsoleAtOnce();
|
||||
// Set whether to close the console after the user presses enter.
|
||||
void setCloseOnEnter(bool close) { m_close_on_enter = close; }
|
||||
|
||||
@ -72,6 +139,18 @@ public:
|
||||
|
||||
virtual void setVisible(bool visible);
|
||||
|
||||
bool hasFocus();
|
||||
|
||||
bool convertToMouseEvent(
|
||||
SEvent &mouse_event, SEvent touch_event) const noexcept;
|
||||
|
||||
bool preprocessEvent(SEvent event);
|
||||
|
||||
bool getAndroidChatOpen() { return m_android_chat_open; }
|
||||
void setAndroidChatOpen(bool value) { m_android_chat_open = value; }
|
||||
|
||||
static GUIChatConsole* getChatConsole() { return m_chat_console; }
|
||||
|
||||
private:
|
||||
void reformatConsole();
|
||||
void recalculateConsolePosition();
|
||||
@ -82,7 +161,14 @@ private:
|
||||
void drawText();
|
||||
void drawPrompt();
|
||||
|
||||
ChatSelection getCursorPos(s32 x, s32 y);
|
||||
irr::core::stringc getSelectedText();
|
||||
void createVScrollBar();
|
||||
void updateVScrollBar();
|
||||
|
||||
private:
|
||||
static GUIChatConsole* m_chat_console;
|
||||
|
||||
ChatBackend* m_chat_backend;
|
||||
Client* m_client;
|
||||
IMenuManager* m_menumgr;
|
||||
@ -124,4 +210,16 @@ private:
|
||||
// font
|
||||
gui::IGUIFont *m_font = nullptr;
|
||||
v2u32 m_fontsize;
|
||||
|
||||
ChatSelection m_mark_begin;
|
||||
ChatSelection m_mark_end;
|
||||
bool m_mouse_marking = false;
|
||||
bool m_long_press = false;
|
||||
ChatSelection m_cursor_press_pos;
|
||||
|
||||
u32 m_scrollbar_width = 0;
|
||||
GUIScrollBar *m_vscrollbar = nullptr;
|
||||
s32 m_bottom_scroll_pos = 0;
|
||||
|
||||
bool m_android_chat_open = false;
|
||||
};
|
||||
|
@ -17,11 +17,12 @@ the arrow buttons where there is insufficient space.
|
||||
GUIScrollBar::GUIScrollBar(IGUIEnvironment *environment, IGUIElement *parent, s32 id,
|
||||
core::rect<s32> rectangle, bool horizontal, bool auto_scale) :
|
||||
IGUIElement(EGUIET_ELEMENT, environment, parent, id, rectangle),
|
||||
up_button(nullptr), down_button(nullptr), is_dragging(false),
|
||||
is_horizontal(horizontal), is_auto_scaling(auto_scale),
|
||||
dragged_by_slider(false), tray_clicked(false), scroll_pos(0),
|
||||
draw_center(0), thumb_size(0), min_pos(0), max_pos(100), small_step(10),
|
||||
large_step(50), drag_offset(0), page_size(100), border_size(0)
|
||||
up_button(nullptr), down_button(nullptr), bg_image(nullptr),
|
||||
slider_image(nullptr), is_dragging(false), is_horizontal(horizontal),
|
||||
is_auto_scaling(auto_scale), dragged_by_slider(false),
|
||||
tray_clicked(false), scroll_pos(0), draw_center(0), thumb_size(0),
|
||||
min_pos(0), max_pos(100), small_step(10), large_step(50),
|
||||
drag_offset(0), page_size(100), border_size(0)
|
||||
{
|
||||
refreshControls();
|
||||
setNotClipped(false);
|
||||
@ -217,9 +218,13 @@ void GUIScrollBar::draw()
|
||||
if (is_horizontal)
|
||||
rect = {h, 0, w - h, h};
|
||||
|
||||
gui::IGUIImage *e = Environment->addImage(rect, this);
|
||||
e->setImage(m_textures[0]);
|
||||
e->setScaleImage(true);
|
||||
if (!bg_image) {
|
||||
bg_image = Environment->addImage(rect, this);
|
||||
bg_image->setImage(m_textures[0]);
|
||||
bg_image->setScaleImage(true);
|
||||
} else {
|
||||
bg_image->setRelativePosition(rect);
|
||||
}
|
||||
} else {
|
||||
skin->draw2DRectangle(this, skin->getColor(EGDC_SCROLLBAR),
|
||||
slider_rect, &AbsoluteClippingRect);
|
||||
@ -247,9 +252,13 @@ void GUIScrollBar::draw()
|
||||
if (is_horizontal)
|
||||
rect = {draw_center - (w / 2), 0, draw_center + w - (w / 2), h};
|
||||
|
||||
gui::IGUIImage *e = Environment->addImage(core::rect<s32>(rect), this);
|
||||
e->setImage(m_textures[1]);
|
||||
e->setScaleImage(true);
|
||||
if (!slider_image) {
|
||||
slider_image = Environment->addImage(core::rect<s32>(rect), this);
|
||||
slider_image->setImage(m_textures[1]);
|
||||
slider_image->setScaleImage(true);
|
||||
} else {
|
||||
slider_image->setRelativePosition(rect);
|
||||
}
|
||||
} else {
|
||||
skin->draw3DButtonPaneStandard(this, slider_rect, &AbsoluteClippingRect);
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ public:
|
||||
s32 getLargeStep() const { return large_step; }
|
||||
s32 getSmallStep() const { return small_step; }
|
||||
s32 getPos() const;
|
||||
s32 getPageSize() const { return page_size; }
|
||||
|
||||
void setMax(const s32 &max);
|
||||
void setMin(const s32 &min);
|
||||
@ -57,6 +58,8 @@ private:
|
||||
|
||||
IGUIButton *up_button;
|
||||
IGUIButton *down_button;
|
||||
gui::IGUIImage *bg_image;
|
||||
gui::IGUIImage *slider_image;
|
||||
ArrowVisibility arrow_visibility = DEFAULT;
|
||||
bool is_dragging;
|
||||
bool is_horizontal;
|
||||
|
BIN
textures/base/pack/gui/scrollbar_bg.png
Normal file
BIN
textures/base/pack/gui/scrollbar_bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 78 B |
BIN
textures/base/pack/gui/scrollbar_down.png
Normal file
BIN
textures/base/pack/gui/scrollbar_down.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 480 B |
BIN
textures/base/pack/gui/scrollbar_slider_long.png
Normal file
BIN
textures/base/pack/gui/scrollbar_slider_long.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 425 B |
BIN
textures/base/pack/gui/scrollbar_up.png
Normal file
BIN
textures/base/pack/gui/scrollbar_up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 487 B |
Loading…
x
Reference in New Issue
Block a user