1
0

Chat console improvements (#124)

This commit is contained in:
Deve 2023-03-24 10:05:35 +01:00 committed by GitHub
parent 0019930f27
commit a3f08fe31f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 670 additions and 45 deletions

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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;
};

View File

@ -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);
}

View File

@ -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;

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 487 B