* Some fixes for text selection in chat. - Reset selection on chat open - Reset selection on new message - Reset selection when window size changed * Don't move to bottom when user is scrolling chat * Some work on prompt selection * Make it working when text length is larger than max visible length * Handle touch events for prompt * Avoid moving chat history on new messages during scrolling. Also simplify selection marks comparing. * Move scroll to bottom on sending new message when console is opened without close on enter * Fixed copy to clipboard when chat buffer is full and also keep text selected when new message arrives * Some improvements for prompt selection * Delete selected text on new input or backspace key press * Fixed text selection and cursor pos for regular font * Fixed drawing prompt with newlines * Fixed empty prompt selection with ctrl+a * Enable chat console on android tablets
1609 lines
46 KiB
C++
1609 lines
46 KiB
C++
/*
|
|
Minetest
|
|
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 3.0 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program 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 Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License along
|
|
with this program; if not, write to the Free Software Foundation, Inc.,
|
|
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*/
|
|
|
|
#include "guiChatConsole.h"
|
|
#include "chat.h"
|
|
#include "client/client.h"
|
|
#include "debug.h"
|
|
#include "gettime.h"
|
|
#include "client/keycode.h"
|
|
#include "settings.h"
|
|
#include "porting.h"
|
|
#include "client/tile.h"
|
|
#include "client/fontengine.h"
|
|
#include "log.h"
|
|
#include "gettext.h"
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include "touchscreengui.h"
|
|
|
|
#if USE_FREETYPE
|
|
#include "irrlicht_changes/CGUITTFont.h"
|
|
#endif
|
|
|
|
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
|
#include <SDL.h>
|
|
#endif
|
|
|
|
inline u32 clamp_u8(s32 value)
|
|
{
|
|
return (u32) MYMIN(MYMAX(value, 0), 255);
|
|
}
|
|
|
|
GUIChatConsole* GUIChatConsole::m_chat_console = nullptr;
|
|
|
|
GUIChatConsole::GUIChatConsole(
|
|
gui::IGUIEnvironment* env,
|
|
gui::IGUIElement* parent,
|
|
s32 id,
|
|
ChatBackend* backend,
|
|
Client* client,
|
|
IMenuManager* menumgr
|
|
):
|
|
IGUIElement(gui::EGUIET_ELEMENT, env, parent, id,
|
|
core::rect<s32>(0,0,100,100)),
|
|
m_chat_backend(backend),
|
|
m_client(client),
|
|
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));
|
|
|
|
// load the background texture depending on settings
|
|
ITextureSource *tsrc = client->getTextureSource();
|
|
if (tsrc->isKnownSourceImage("background_chat.jpg")) {
|
|
m_background = tsrc->getTexture("background_chat.jpg");
|
|
m_background_color.setRed(255);
|
|
m_background_color.setGreen(255);
|
|
m_background_color.setBlue(255);
|
|
} else {
|
|
v3f console_color = g_settings->getV3F("console_color");
|
|
m_background_color.setRed(clamp_u8(myround(console_color.X)));
|
|
m_background_color.setGreen(clamp_u8(myround(console_color.Y)));
|
|
m_background_color.setBlue(clamp_u8(myround(console_color.Z)));
|
|
}
|
|
|
|
u16 chat_font_size = g_settings->getU16("chat_font_size");
|
|
m_font = g_fontengine->getFont(chat_font_size != 0 ?
|
|
chat_font_size : FONT_SIZE_UNSPECIFIED, FM_Mono);
|
|
|
|
if (!m_font) {
|
|
errorstream << "GUIChatConsole: Unable to load mono font" << std::endl;
|
|
} else {
|
|
core::dimension2d<u32> dim = m_font->getDimension(L"M");
|
|
m_fontsize = v2u32(dim.Width, dim.Height);
|
|
m_font->grab();
|
|
}
|
|
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();
|
|
#endif
|
|
|
|
if (m_font)
|
|
m_font->drop();
|
|
}
|
|
|
|
void GUIChatConsole::openConsole(f32 scale)
|
|
{
|
|
assert(scale > 0.0f && scale <= 1.0f);
|
|
|
|
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();
|
|
updateVScrollBar(false, true);
|
|
m_animate_time_old = porting::getTimeMs();
|
|
IGUIElement::setVisible(true);
|
|
m_vscrollbar->setVisible(true);
|
|
Environment->setFocus(this);
|
|
m_menumgr->createdMenu(this);
|
|
|
|
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
|
if (porting::hasRealKeyboard())
|
|
SDL_StartTextInput();
|
|
#endif
|
|
}
|
|
|
|
bool GUIChatConsole::isOpen() const
|
|
{
|
|
return m_open;
|
|
}
|
|
|
|
bool GUIChatConsole::isOpenInhibited() const
|
|
{
|
|
return m_open_inhibited > 0;
|
|
}
|
|
|
|
void GUIChatConsole::closeConsole()
|
|
{
|
|
m_open = false;
|
|
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
|
|
|
|
#ifdef HAVE_TOUCHSCREENGUI
|
|
if (g_touchscreengui && g_touchscreengui->isActive())
|
|
g_touchscreengui->show();
|
|
#endif
|
|
}
|
|
|
|
void GUIChatConsole::replaceAndAddToHistory(const std::wstring &line)
|
|
{
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
prompt.addToHistory(prompt.getLine());
|
|
prompt.replace(line);
|
|
}
|
|
|
|
|
|
void GUIChatConsole::setCursor(
|
|
bool visible, bool blinking, f32 blink_speed, f32 relative_height)
|
|
{
|
|
if (visible)
|
|
{
|
|
if (blinking)
|
|
{
|
|
// leave m_cursor_blink unchanged
|
|
m_cursor_blink_speed = blink_speed;
|
|
}
|
|
else
|
|
{
|
|
m_cursor_blink = 0x8000; // on
|
|
m_cursor_blink_speed = 0.0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
m_cursor_blink = 0; // off
|
|
m_cursor_blink_speed = 0.0;
|
|
}
|
|
m_cursor_height = relative_height;
|
|
}
|
|
|
|
void GUIChatConsole::draw()
|
|
{
|
|
if(!IsVisible)
|
|
return;
|
|
|
|
updateVScrollBar();
|
|
|
|
video::IVideoDriver* driver = Environment->getVideoDriver();
|
|
|
|
// Check screen size
|
|
v2u32 screensize = driver->getScreenSize();
|
|
if (screensize != m_screensize)
|
|
{
|
|
// screen size has changed
|
|
// scale current console height to new window size
|
|
if (m_screensize.Y != 0)
|
|
m_height = m_height * screensize.Y / m_screensize.Y;
|
|
m_screensize = screensize;
|
|
m_desired_height = m_desired_height_fraction * m_screensize.Y;
|
|
reformatConsole();
|
|
updateVScrollBar(true, false);
|
|
}
|
|
|
|
// Animation
|
|
u64 now = porting::getTimeMs();
|
|
animate(now - m_animate_time_old);
|
|
m_animate_time_old = now;
|
|
|
|
// Draw console elements if visible
|
|
if (m_height > 0)
|
|
{
|
|
drawBackground();
|
|
drawText();
|
|
drawPrompt();
|
|
}
|
|
|
|
gui::IGUIElement::draw();
|
|
}
|
|
|
|
void GUIChatConsole::reformatConsole()
|
|
{
|
|
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);
|
|
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
m_cursor_press_pos.reset();
|
|
m_history_marking = false;
|
|
m_prompt_marking = false;
|
|
m_long_press = false;
|
|
}
|
|
|
|
void GUIChatConsole::recalculateConsolePosition()
|
|
{
|
|
core::rect<s32> rect(0, 0, m_screensize.X, m_height);
|
|
DesiredRect = rect;
|
|
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)
|
|
{
|
|
// animate the console height
|
|
s32 goal = m_open ? m_desired_height : 0;
|
|
|
|
// 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) {
|
|
m_vscrollbar->setVisible(false);
|
|
IGUIElement::setVisible(false);
|
|
}
|
|
|
|
if (m_height != goal)
|
|
{
|
|
s32 max_change = msec * m_screensize.Y * (m_height_speed / 1000.0);
|
|
if (max_change == 0)
|
|
max_change = 1;
|
|
|
|
if (m_height < goal)
|
|
{
|
|
// increase height
|
|
if (m_height + max_change < goal)
|
|
m_height += max_change;
|
|
else
|
|
m_height = goal;
|
|
}
|
|
else
|
|
{
|
|
// decrease height
|
|
if (m_height > goal + max_change)
|
|
m_height -= max_change;
|
|
else
|
|
m_height = goal;
|
|
}
|
|
|
|
recalculateConsolePosition();
|
|
}
|
|
|
|
// blink the cursor
|
|
if (m_cursor_blink_speed != 0.0)
|
|
{
|
|
u32 blink_increase = 0x10000 * msec * (m_cursor_blink_speed / 1000.0);
|
|
if (blink_increase == 0)
|
|
blink_increase = 1;
|
|
m_cursor_blink = ((m_cursor_blink + blink_increase) & 0xffff);
|
|
}
|
|
|
|
// decrease open inhibit counter
|
|
if (m_open_inhibited > msec)
|
|
m_open_inhibited -= msec;
|
|
else
|
|
m_open_inhibited = 0;
|
|
}
|
|
|
|
void GUIChatConsole::drawBackground()
|
|
{
|
|
video::IVideoDriver* driver = Environment->getVideoDriver();
|
|
if (m_background != NULL)
|
|
{
|
|
core::rect<s32> sourcerect(0, -m_height, m_screensize.X, 0);
|
|
driver->draw2DImage(
|
|
m_background,
|
|
v2s32(0, 0),
|
|
sourcerect,
|
|
&AbsoluteClippingRect,
|
|
m_background_color,
|
|
false);
|
|
}
|
|
else
|
|
{
|
|
driver->draw2DRectangle(
|
|
m_background_color,
|
|
core::rect<s32>(0, 0, m_screensize.X, m_height),
|
|
&AbsoluteClippingRect);
|
|
}
|
|
}
|
|
|
|
void GUIChatConsole::drawText()
|
|
{
|
|
if (m_font == NULL)
|
|
return;
|
|
|
|
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
|
for (u32 row = 0; row < buf.getRows(); ++row)
|
|
{
|
|
const ChatFormattedLine& line = buf.getFormattedLine(row);
|
|
if (line.fragments.empty())
|
|
continue;
|
|
|
|
s32 line_height = m_fontsize.Y;
|
|
s32 y = row * line_height + m_height - m_desired_height;
|
|
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 &&
|
|
real_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY &&
|
|
real_mark_end.selection_type == ChatSelection::SELECTION_HISTORY &&
|
|
(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(
|
|
x, y, x + m_fontsize.X * fragment.text.size(), y + m_fontsize.Y);
|
|
|
|
#if USE_FREETYPE
|
|
if (m_font->getType() == irr::gui::EGFT_CUSTOM) {
|
|
// Draw colored text if FreeType is enabled
|
|
irr::gui::CGUITTFont *tmp = dynamic_cast<irr::gui::CGUITTFont *>(m_font);
|
|
tmp->draw(
|
|
fragment.text,
|
|
destrect,
|
|
false,
|
|
false,
|
|
&AbsoluteClippingRect);
|
|
} else
|
|
#endif
|
|
{
|
|
// Otherwise use standard text
|
|
m_font->draw(
|
|
fragment.text.c_str(),
|
|
destrect,
|
|
video::SColor(255, 255, 255, 255),
|
|
false,
|
|
false,
|
|
&AbsoluteClippingRect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void GUIChatConsole::drawPrompt()
|
|
{
|
|
if (!m_font)
|
|
return;
|
|
|
|
u32 row = m_chat_backend->getConsoleBuffer().getRows();
|
|
s32 line_height = m_fontsize.Y;
|
|
s32 y = row * line_height + m_height - m_desired_height;
|
|
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
std::wstring prompt_text = prompt.getVisiblePortion();
|
|
std::replace_if(prompt_text.begin(), prompt_text.end(),
|
|
[](wchar_t c) { return (c == L'\n' || c == L'\r'); }, L' ');
|
|
|
|
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 &&
|
|
real_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
real_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) {
|
|
std::wstring begin_text = L"]";
|
|
int begin_text_size = m_font->getDimension(begin_text.c_str()).Width;
|
|
int text_pos = m_fontsize.X + begin_text_size;
|
|
|
|
s32 x_begin = text_pos;
|
|
s32 text_size = m_font->getDimension(prompt_text.c_str()).Width;
|
|
s32 x_end = x_begin + text_size;
|
|
|
|
int current_scroll = prompt.getViewPosition();
|
|
if (real_mark_begin.character > 0) {
|
|
irr::core::stringw text = prompt_text.c_str();
|
|
int scroll_offset = real_mark_begin.scroll - current_scroll;
|
|
int length = scroll_offset + real_mark_begin.character;
|
|
length = MYMIN(MYMAX(length, 0), prompt_text.size() - 1);
|
|
text = text.subString(1, length);
|
|
s32 text_size = m_font->getDimension(text.c_str()).Width;
|
|
x_begin = text_pos + text_size;
|
|
}
|
|
|
|
if (real_mark_end.character < prompt_text.size() - 1) {
|
|
irr::core::stringw text = prompt_text.c_str();
|
|
int scroll_offset = real_mark_end.scroll - current_scroll;
|
|
int length = scroll_offset + real_mark_end.character;
|
|
if (real_mark_end.x_max)
|
|
length++;
|
|
length = MYMIN(MYMAX(length, 0), prompt_text.size() - 1);
|
|
text = text.subString(1, length);
|
|
s32 text_size = m_font->getDimension(text.c_str()).Width;
|
|
x_end = text_pos + 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);
|
|
}
|
|
|
|
core::rect<s32> destrect(
|
|
m_fontsize.X, y, prompt_text.size() * m_fontsize.X, y + m_fontsize.Y);
|
|
m_font->draw(
|
|
prompt_text.c_str(),
|
|
destrect,
|
|
video::SColor(255, 255, 255, 255),
|
|
false,
|
|
false,
|
|
&AbsoluteClippingRect);
|
|
|
|
// Draw the cursor during on periods
|
|
if ((m_cursor_blink & 0x8000) != 0)
|
|
{
|
|
s32 cursor_pos = prompt.getVisibleCursorPosition();
|
|
if (cursor_pos >= 0)
|
|
{
|
|
s32 cursor_len = prompt.getCursorLength();
|
|
video::IVideoDriver* driver = Environment->getVideoDriver();
|
|
std::wstring text = prompt_text.substr(0, cursor_pos);
|
|
s32 x = m_font->getDimension(text.c_str()).Width + m_fontsize.X;
|
|
|
|
core::rect<s32> destrect(
|
|
x,
|
|
y + m_fontsize.Y * (1.0 - m_cursor_height),
|
|
x + m_fontsize.X * MYMAX(cursor_len, 1),
|
|
y + m_fontsize.Y * (cursor_len ? m_cursor_height+1 : 1)
|
|
);
|
|
video::SColor cursor_color(255,255,255,255);
|
|
driver->draw2DRectangle(
|
|
cursor_color,
|
|
destrect,
|
|
&AbsoluteClippingRect);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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.selection_type = ChatSelection::SELECTION_HISTORY;
|
|
|
|
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.line_index = 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;
|
|
}
|
|
|
|
ChatSelection GUIChatConsole::getPromptCursorPos(s32 x, s32 y)
|
|
{
|
|
ChatSelection selection;
|
|
|
|
if (m_font == NULL)
|
|
return selection;
|
|
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
|
|
selection.selection_type = ChatSelection::SELECTION_PROMPT;
|
|
selection.scroll = prompt.getViewPosition();
|
|
|
|
std::wstring prompt_text = prompt.getVisiblePortion();
|
|
irr::core::stringw text = prompt_text.c_str();
|
|
text = text.subString(1, prompt_text.size() - 1);
|
|
|
|
std::wstring begin_text = L"]";
|
|
int begin_text_size = m_font->getDimension(begin_text.c_str()).Width;
|
|
int text_pos = m_fontsize.X + begin_text_size;
|
|
int pos = m_font->getCharacterFromPos(text.c_str(), x - text_pos);
|
|
|
|
if (pos == -1) {
|
|
selection.x_max = true;
|
|
selection.character = text.size() - 1;
|
|
} else {
|
|
selection.character = pos;
|
|
}
|
|
|
|
return selection;
|
|
}
|
|
|
|
ChatSelection GUIChatConsole::getCurrentPromptCursorPos()
|
|
{
|
|
ChatSelection selection;
|
|
|
|
if (m_font == NULL)
|
|
return selection;
|
|
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
|
|
selection.selection_type = ChatSelection::SELECTION_PROMPT;
|
|
selection.scroll = prompt.getViewPosition();
|
|
selection.character = prompt.getVisibleCursorPosition() - 1;
|
|
|
|
if ((unsigned int)selection.character > prompt.getLine().size() - selection.scroll - 1) {
|
|
selection.character--;
|
|
selection.x_max = true;
|
|
}
|
|
|
|
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();
|
|
|
|
const ChatLine& first_line = buf.getLine(0);
|
|
int first_line_index = first_line.line_index;
|
|
int mark_begin_row_buf = real_mark_begin.line_index - first_line_index;
|
|
int mark_end_row_buf = real_mark_end.line_index - first_line_index;
|
|
|
|
if (mark_begin_row_buf < 0 || mark_end_row_buf < 0)
|
|
return "";
|
|
|
|
for (int row = mark_begin_row_buf; row < mark_end_row_buf + 1; row++) {
|
|
const ChatLine& line = buf.getLine(row);
|
|
|
|
std::vector<ChatFormattedLine> formatted_lines;
|
|
buf.formatChatLine(line, 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 == 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 == 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 < 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;
|
|
}
|
|
|
|
irr::core::stringc GUIChatConsole::getPromptSelectedText()
|
|
{
|
|
if (m_font == NULL)
|
|
return "";
|
|
|
|
if (m_mark_begin == m_mark_end)
|
|
return "";
|
|
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
|
|
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;
|
|
|
|
std::wstring prompt_text = prompt.getLine();
|
|
|
|
if (real_mark_end.scroll + real_mark_end.character > prompt_text.size())
|
|
return "";
|
|
|
|
irr::core::stringw text = prompt_text.c_str();
|
|
int begin = real_mark_begin.scroll + real_mark_begin.character;
|
|
int length = real_mark_end.scroll + real_mark_end.character - begin;
|
|
if (real_mark_end.x_max)
|
|
length++;
|
|
text = text.subString(begin, length);
|
|
|
|
irr::core::stringc text_c;
|
|
text_c = wide_to_utf8(text.c_str()).c_str();
|
|
return text_c;
|
|
}
|
|
|
|
void GUIChatConsole::movePromptCursor(s32 x, s32 y)
|
|
{
|
|
ChatSelection selection = getPromptCursorPos(x, y);
|
|
|
|
int cursor_pos = selection.scroll + selection.character;
|
|
if (selection.x_max)
|
|
cursor_pos++;
|
|
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
prompt.setCursorPos(cursor_pos);
|
|
}
|
|
|
|
void GUIChatConsole::deletePromptSelection()
|
|
{
|
|
if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
m_mark_begin == m_mark_end)
|
|
return;
|
|
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
int scroll_pos = prompt.getViewPosition();
|
|
|
|
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;
|
|
|
|
int pos_begin = real_mark_begin.scroll + real_mark_begin.character;
|
|
int pos_end = real_mark_end.scroll + real_mark_end.character;
|
|
if (real_mark_end.x_max)
|
|
pos_end++;
|
|
|
|
std::wstring prompt_text = prompt.getLine();
|
|
std::wstring new_text;
|
|
new_text = prompt_text.substr(0, pos_begin);
|
|
new_text += prompt_text.substr(pos_end, prompt_text.size() - pos_end);
|
|
|
|
prompt.replace(new_text);
|
|
|
|
int cursor_pos = real_mark_begin.scroll + real_mark_begin.character;
|
|
prompt.setCursorPos(cursor_pos);
|
|
prompt.setViewPosition(scroll_pos);
|
|
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
}
|
|
|
|
bool GUIChatConsole::OnEvent(const SEvent& event)
|
|
{
|
|
ChatPrompt &prompt = m_chat_backend->getPrompt();
|
|
|
|
if(event.EventType == EET_KEY_INPUT_EVENT && event.KeyInput.PressedDown)
|
|
{
|
|
// Key input
|
|
if (KeyPress(event.KeyInput) == getKeySetting("keymap_console")) {
|
|
closeConsole();
|
|
|
|
// inhibit open so the_game doesn't reopen immediately
|
|
m_open_inhibited = 50;
|
|
m_close_on_enter = false;
|
|
return true;
|
|
}
|
|
|
|
if (event.KeyInput.Key == KEY_ESCAPE || event.KeyInput.Key == KEY_CANCEL) {
|
|
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"
|
|
return true;
|
|
}
|
|
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;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_RETURN)
|
|
{
|
|
prompt.addToHistory(prompt.getLine());
|
|
std::wstring text = prompt.replace(L"");
|
|
m_client->typeChatMessage(text);
|
|
if (m_close_on_enter) {
|
|
closeConsole();
|
|
m_close_on_enter = false;
|
|
} else {
|
|
updateVScrollBar(true, true);
|
|
}
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_UP)
|
|
{
|
|
// Up pressed
|
|
// Move back in history
|
|
prompt.historyPrev();
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_DOWN)
|
|
{
|
|
// Down pressed
|
|
// Move forward in history
|
|
prompt.historyNext();
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_LEFT || event.KeyInput.Key == KEY_RIGHT)
|
|
{
|
|
ChatSelection old_pos = getCurrentPromptCursorPos();
|
|
|
|
// Left/right pressed
|
|
// Move/select character/word to the left depending on control and shift keys
|
|
ChatPrompt::CursorOp op = ChatPrompt::CURSOROP_MOVE;
|
|
ChatPrompt::CursorOpDir dir = event.KeyInput.Key == KEY_LEFT ?
|
|
ChatPrompt::CURSOROP_DIR_LEFT :
|
|
ChatPrompt::CURSOROP_DIR_RIGHT;
|
|
ChatPrompt::CursorOpScope scope = event.KeyInput.Control ?
|
|
ChatPrompt::CURSOROP_SCOPE_WORD :
|
|
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
|
prompt.cursorOperation(op, dir, scope);
|
|
|
|
if (event.KeyInput.Shift) {
|
|
if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin = old_pos;
|
|
}
|
|
m_mark_end = getCurrentPromptCursorPos();
|
|
} else {
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_HOME)
|
|
{
|
|
ChatSelection old_pos = getCurrentPromptCursorPos();
|
|
|
|
// Home pressed
|
|
// move to beginning of line
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_MOVE,
|
|
ChatPrompt::CURSOROP_DIR_LEFT,
|
|
ChatPrompt::CURSOROP_SCOPE_LINE);
|
|
|
|
if (event.KeyInput.Shift) {
|
|
if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin = old_pos;
|
|
}
|
|
m_mark_end = getCurrentPromptCursorPos();
|
|
} else {
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_END)
|
|
{
|
|
ChatSelection old_pos = getCurrentPromptCursorPos();
|
|
|
|
// End pressed
|
|
// move to end of line
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_MOVE,
|
|
ChatPrompt::CURSOROP_DIR_RIGHT,
|
|
ChatPrompt::CURSOROP_SCOPE_LINE);
|
|
|
|
if (event.KeyInput.Shift) {
|
|
if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin = old_pos;
|
|
}
|
|
m_mark_end = getCurrentPromptCursorPos();
|
|
} else {
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_BACK)
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_begin != m_mark_end) {
|
|
deletePromptSelection();
|
|
return true;
|
|
}
|
|
|
|
// Backspace or Ctrl-Backspace pressed
|
|
// delete character / word to the left
|
|
ChatPrompt::CursorOpScope scope =
|
|
event.KeyInput.Control ?
|
|
ChatPrompt::CURSOROP_SCOPE_WORD :
|
|
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_DELETE,
|
|
ChatPrompt::CURSOROP_DIR_LEFT,
|
|
scope);
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_DELETE)
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_begin != m_mark_end) {
|
|
deletePromptSelection();
|
|
return true;
|
|
}
|
|
|
|
// Delete or Ctrl-Delete pressed
|
|
// delete character / word to the right
|
|
ChatPrompt::CursorOpScope scope =
|
|
event.KeyInput.Control ?
|
|
ChatPrompt::CURSOROP_SCOPE_WORD :
|
|
ChatPrompt::CURSOROP_SCOPE_CHARACTER;
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_DELETE,
|
|
ChatPrompt::CURSOROP_DIR_RIGHT,
|
|
scope);
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_KEY_A && event.KeyInput.Control)
|
|
{
|
|
if (prompt.getLine().size() > 0) {
|
|
ChatPrompt& prompt = m_chat_backend->getPrompt();
|
|
|
|
m_mark_begin.reset();
|
|
m_mark_begin.selection_type = ChatSelection::SELECTION_PROMPT;
|
|
m_mark_begin.scroll = 0;
|
|
m_mark_begin.character = 0;
|
|
|
|
m_mark_end.reset();
|
|
m_mark_end.selection_type = ChatSelection::SELECTION_PROMPT;
|
|
m_mark_end.scroll = 0;
|
|
m_mark_end.character = prompt.getLine().size() - 1;
|
|
m_mark_end.x_max = true;
|
|
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_MOVE,
|
|
ChatPrompt::CURSOROP_DIR_RIGHT,
|
|
ChatPrompt::CURSOROP_SCOPE_LINE);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_KEY_C && event.KeyInput.Control)
|
|
{
|
|
if (m_mark_begin != m_mark_end) {
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) {
|
|
irr::core::stringc text = getPromptSelectedText();
|
|
Environment->getOSOperator()->copyToClipboard(text.c_str());
|
|
return true;
|
|
} else if (m_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_HISTORY) {
|
|
irr::core::stringc text = getSelectedText();
|
|
Environment->getOSOperator()->copyToClipboard(text.c_str());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Ctrl-C pressed
|
|
// Copy text to clipboard
|
|
if (prompt.getCursorLength() <= 0)
|
|
return true;
|
|
std::wstring wselected = prompt.getSelection();
|
|
std::string selected = wide_to_utf8(wselected);
|
|
Environment->getOSOperator()->copyToClipboard(selected.c_str());
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_KEY_V && event.KeyInput.Control)
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_begin != m_mark_end) {
|
|
deletePromptSelection();
|
|
}
|
|
|
|
// Ctrl-V pressed
|
|
// paste text from clipboard
|
|
if (prompt.getCursorLength() > 0) {
|
|
// Delete selected section of text
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_DELETE,
|
|
ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
|
|
ChatPrompt::CURSOROP_SCOPE_SELECTION);
|
|
}
|
|
IOSOperator *os_operator = Environment->getOSOperator();
|
|
const c8 *text = os_operator->getTextFromClipboard();
|
|
if (!text)
|
|
return true;
|
|
prompt.input(utf8_to_wide(text));
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_KEY_X && event.KeyInput.Control)
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_begin != m_mark_end) {
|
|
irr::core::stringc text = getPromptSelectedText();
|
|
Environment->getOSOperator()->copyToClipboard(text.c_str());
|
|
deletePromptSelection();
|
|
return true;
|
|
}
|
|
|
|
// Ctrl-X pressed
|
|
// Cut text to clipboard
|
|
if (prompt.getCursorLength() <= 0)
|
|
return true;
|
|
std::wstring wselected = prompt.getSelection();
|
|
std::string selected = wide_to_utf8(wselected);
|
|
Environment->getOSOperator()->copyToClipboard(selected.c_str());
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_DELETE,
|
|
ChatPrompt::CURSOROP_DIR_LEFT, // Ignored
|
|
ChatPrompt::CURSOROP_SCOPE_SELECTION);
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_KEY_U && event.KeyInput.Control)
|
|
{
|
|
// Ctrl-U pressed
|
|
// kill line to left end
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_DELETE,
|
|
ChatPrompt::CURSOROP_DIR_LEFT,
|
|
ChatPrompt::CURSOROP_SCOPE_LINE);
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_KEY_K && event.KeyInput.Control)
|
|
{
|
|
// Ctrl-K pressed
|
|
// kill line to right end
|
|
prompt.cursorOperation(
|
|
ChatPrompt::CURSOROP_DELETE,
|
|
ChatPrompt::CURSOROP_DIR_RIGHT,
|
|
ChatPrompt::CURSOROP_SCOPE_LINE);
|
|
return true;
|
|
}
|
|
else if(event.KeyInput.Key == KEY_TAB)
|
|
{
|
|
// Tab or Shift-Tab pressed
|
|
// Nick completion
|
|
std::list<std::string> names = m_client->getConnectedPlayerNames();
|
|
bool backwards = event.KeyInput.Shift;
|
|
prompt.nickCompletion(names, backwards);
|
|
return true;
|
|
} else if (!iswcntrl(event.KeyInput.Char) && !event.KeyInput.Control) {
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_begin != m_mark_end) {
|
|
deletePromptSelection();
|
|
}
|
|
|
|
#if defined(__linux__) && (IRRLICHT_VERSION_MAJOR == 1 && IRRLICHT_VERSION_MINOR < 9)
|
|
wchar_t wc = L'_';
|
|
mbtowc( &wc, (char *) &event.KeyInput.Char, sizeof(event.KeyInput.Char) );
|
|
prompt.input(wc);
|
|
#else
|
|
prompt.input(event.KeyInput.Char);
|
|
#endif
|
|
return true;
|
|
}
|
|
}
|
|
#ifdef _IRR_COMPILE_WITH_SDL_DEVICE_
|
|
else if(event.EventType == EET_SDL_TEXT_EVENT)
|
|
{
|
|
if (event.SDLTextEvent.Type == ESDLET_TEXTINPUT)
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_begin != m_mark_end) {
|
|
deletePromptSelection();
|
|
}
|
|
|
|
std::wstring text = utf8_to_wide(event.SDLTextEvent.Text);
|
|
|
|
for (u32 i = 0; i < text.size(); i++)
|
|
prompt.input(text[i]);
|
|
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#endif
|
|
else if(event.EventType == EET_MOUSE_INPUT_EVENT)
|
|
{
|
|
if(event.MouseInput.Event == EMIE_MOUSE_WHEEL)
|
|
{
|
|
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) {
|
|
u32 row = m_chat_backend->getConsoleBuffer().getRows();
|
|
s32 prompt_y = row * m_fontsize.Y + m_height - m_desired_height;
|
|
|
|
if (event.MouseInput.Y >= prompt_y) {
|
|
m_prompt_marking = true;
|
|
if (event.MouseInput.Shift) {
|
|
if (m_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
m_mark_end.selection_type != ChatSelection::SELECTION_PROMPT) {
|
|
m_mark_begin = getCurrentPromptCursorPos();
|
|
m_mark_end = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
|
}
|
|
} else {
|
|
m_mark_begin = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
|
m_mark_end = m_mark_begin;
|
|
}
|
|
movePromptCursor(event.MouseInput.X, event.MouseInput.Y);
|
|
} else {
|
|
m_history_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_prompt_marking) {
|
|
m_mark_end = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
|
m_prompt_marking = false;
|
|
|
|
if (!event.MouseInput.Shift) {
|
|
if (m_mark_begin == m_mark_end) {
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
}
|
|
}
|
|
} else if (m_history_marking) {
|
|
m_mark_end = getCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
|
m_history_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_prompt_marking) {
|
|
m_mark_end = getPromptCursorPos(event.MouseInput.X, event.MouseInput.Y);
|
|
movePromptCursor(event.MouseInput.X, event.MouseInput.Y);
|
|
} else if (m_history_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_history_marking = false;
|
|
m_prompt_marking = false;
|
|
m_long_press = false;
|
|
|
|
u32 row = m_chat_backend->getConsoleBuffer().getRows();
|
|
s32 prompt_y = row * m_fontsize.Y + m_height - m_desired_height;
|
|
|
|
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 (event.TouchInput.Y >= prompt_y) {
|
|
|
|
m_cursor_press_pos = getPromptCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
|
|
|
if (real_mark_begin.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
real_mark_end.selection_type != ChatSelection::SELECTION_PROMPT ||
|
|
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_prompt_marking = true;
|
|
}
|
|
} else {
|
|
m_cursor_press_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
|
|
|
if (real_mark_begin.selection_type != ChatSelection::SELECTION_HISTORY ||
|
|
real_mark_end.selection_type != ChatSelection::SELECTION_HISTORY ||
|
|
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_history_marking = true;
|
|
}
|
|
}
|
|
|
|
} else if (event.TouchInput.Event == irr::ETIE_LEFT_UP) {
|
|
if (m_prompt_marking) {
|
|
ChatSelection cursor_pos = getPromptCursorPos(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_prompt_marking = false;
|
|
} else if (m_history_marking) {
|
|
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_history_marking = false;
|
|
}
|
|
|
|
m_cursor_press_pos.reset();
|
|
m_long_press = false;
|
|
|
|
} else if (event.TouchInput.Event == irr::ETIE_MOVED) {
|
|
ChatSelection cursor_pos = getCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
|
ChatSelection prompt_cursor_pos = getPromptCursorPos(event.TouchInput.X, event.TouchInput.Y);
|
|
|
|
if (!m_prompt_marking && !m_long_press &&
|
|
m_cursor_press_pos.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_cursor_press_pos != prompt_cursor_pos) {
|
|
m_mark_begin = m_cursor_press_pos;
|
|
m_mark_end = m_cursor_press_pos;
|
|
m_prompt_marking = true;
|
|
} else if (!m_history_marking && !m_long_press &&
|
|
m_cursor_press_pos.selection_type == ChatSelection::SELECTION_HISTORY &&
|
|
m_cursor_press_pos != cursor_pos) {
|
|
m_mark_begin = m_cursor_press_pos;
|
|
m_mark_end = m_cursor_press_pos;
|
|
m_history_marking = true;
|
|
}
|
|
|
|
if (m_prompt_marking) {
|
|
m_mark_end = prompt_cursor_pos;
|
|
} else if (m_history_marking) {
|
|
m_mark_end = cursor_pos;
|
|
}
|
|
|
|
} else if (event.TouchInput.Event == irr::ETIE_PRESSED_LONG) {
|
|
if (!m_history_marking && ! m_prompt_marking) {
|
|
m_long_press = true;
|
|
if (m_mark_begin != m_mark_end) {
|
|
irr::core::stringc text;
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT) {
|
|
text = getPromptSelectedText();
|
|
} else if (m_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_HISTORY) {
|
|
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();
|
|
}
|
|
}
|
|
|
|
return Parent ? Parent->OnEvent(event) : false;
|
|
}
|
|
|
|
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_middle.png"),
|
|
tsrc->getTexture("gui/scrollbar_up.png"),
|
|
tsrc->getTexture("gui/scrollbar_down.png"),
|
|
tsrc->getTexture("gui/scrollbar_slider_top.png"),
|
|
tsrc->getTexture("gui/scrollbar_slider_bottom.png"),
|
|
});
|
|
|
|
addChild(m_vscrollbar);
|
|
}
|
|
|
|
void GUIChatConsole::updateVScrollBar(bool force_update, bool move_bottom)
|
|
{
|
|
if (!m_vscrollbar)
|
|
return;
|
|
|
|
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
|
|
|
if (m_bottom_scroll_pos != buf.getBottomScrollPos() || force_update) {
|
|
bool is_bottom = m_vscrollbar->getPos() == m_bottom_scroll_pos;
|
|
m_bottom_scroll_pos = buf.getBottomScrollPos();
|
|
|
|
if (buf.getBottomScrollPos() > 0) {
|
|
buf.scrollAbsolute(m_bottom_scroll_pos);
|
|
m_vscrollbar->setMax(m_bottom_scroll_pos);
|
|
if (is_bottom || move_bottom)
|
|
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 (buf.getDelFormatted() > 0) {
|
|
bool is_bottom = m_vscrollbar->getPos() == m_bottom_scroll_pos;
|
|
|
|
if (!is_bottom && ! move_bottom) {
|
|
s32 pos = m_vscrollbar->getPos() - buf.getDelFormatted();
|
|
|
|
if (pos >= 0)
|
|
m_vscrollbar->setPos(pos);
|
|
}
|
|
|
|
m_mark_begin.scroll -= buf.getDelFormatted();
|
|
m_mark_end.scroll -= buf.getDelFormatted();
|
|
buf.resetDelFormatted();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void GUIChatConsole::onLinesModified()
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_HISTORY &&
|
|
m_mark_end.selection_type == ChatSelection::SELECTION_HISTORY) {
|
|
|
|
ChatBuffer& buf = m_chat_backend->getConsoleBuffer();
|
|
const ChatLine& first_line = buf.getLine(0);
|
|
int first_line_index = first_line.line_index;
|
|
|
|
if (m_mark_begin.line_index < first_line_index ||
|
|
m_mark_end.line_index < first_line_index) {
|
|
m_mark_begin.reset();
|
|
m_mark_end.reset();
|
|
m_cursor_press_pos.reset();
|
|
m_history_marking = false;
|
|
m_long_press = false;
|
|
}
|
|
}
|
|
|
|
updateVScrollBar(true);
|
|
}
|
|
|
|
void GUIChatConsole::onPromptModified()
|
|
{
|
|
if (m_mark_begin.selection_type == ChatSelection::SELECTION_PROMPT)
|
|
m_mark_begin.reset();
|
|
if (m_mark_end.selection_type == ChatSelection::SELECTION_PROMPT)
|
|
m_mark_end.reset();
|
|
if (m_cursor_press_pos.selection_type == ChatSelection::SELECTION_PROMPT)
|
|
m_cursor_press_pos.reset();
|
|
if (m_prompt_marking) {
|
|
m_prompt_marking = false;
|
|
m_long_press = false;
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|