From 89e3bcd11b68c9e5456ecd7fc38a6435468725e9 Mon Sep 17 00:00:00 2001 From: Benau Date: Mon, 10 Jun 2019 00:43:31 +0800 Subject: [PATCH] Update stk edit box to be more i18n friendly --- data/skins/Coal.stkskin | 1 + data/skins/Forest.stkskin | 1 + data/skins/Ocean.stkskin | 1 + data/skins/Peach.stkskin | 1 + data/skins/Ruby.stkskin | 1 + lib/irrlicht/include/GlyphLayout.h | 8 + src/font/font_manager.cpp | 2 + src/font/font_with_face.cpp | 53 +- src/guiengine/widgets/CGUIEditBox.cpp | 1454 ++++++++------------- src/guiengine/widgets/CGUIEditBox.hpp | 57 +- src/guiengine/widgets/text_box_widget.cpp | 17 +- 11 files changed, 660 insertions(+), 936 deletions(-) diff --git a/data/skins/Coal.stkskin b/data/skins/Coal.stkskin index 2b15c04fd..7d97d1d4c 100644 --- a/data/skins/Coal.stkskin +++ b/data/skins/Coal.stkskin @@ -294,6 +294,7 @@ when the border that intersect at this corner are enabled. + diff --git a/data/skins/Forest.stkskin b/data/skins/Forest.stkskin index e1bb6fcdf..d375b8f14 100644 --- a/data/skins/Forest.stkskin +++ b/data/skins/Forest.stkskin @@ -294,6 +294,7 @@ when the border that intersect at this corner are enabled. + diff --git a/data/skins/Ocean.stkskin b/data/skins/Ocean.stkskin index afc904d0e..fc2e81765 100644 --- a/data/skins/Ocean.stkskin +++ b/data/skins/Ocean.stkskin @@ -293,6 +293,7 @@ when the border that intersect at this corner are enabled. + diff --git a/data/skins/Peach.stkskin b/data/skins/Peach.stkskin index 23f3d402e..e09672c1f 100644 --- a/data/skins/Peach.stkskin +++ b/data/skins/Peach.stkskin @@ -291,6 +291,7 @@ when the border that intersect at this corner are enabled. + diff --git a/data/skins/Ruby.stkskin b/data/skins/Ruby.stkskin index bb228b173..388cdddab 100644 --- a/data/skins/Ruby.stkskin +++ b/data/skins/Ruby.stkskin @@ -292,6 +292,7 @@ when the border that intersect at this corner are enabled. + diff --git a/lib/irrlicht/include/GlyphLayout.h b/lib/irrlicht/include/GlyphLayout.h index c47c541da..b6f6c0050 100644 --- a/lib/irrlicht/include/GlyphLayout.h +++ b/lib/irrlicht/include/GlyphLayout.h @@ -26,6 +26,13 @@ GLF_QUICK_DRAW = 8, /* This glyph is not created by libraqm, which get x_advance GLF_NEWLINE = 16 /* This glyph will start a newline. */ }; +enum GlyphLayoutDraw +{ +GLD_NONE = 0, /* Default flag. */ +GLD_MARKED = 1, /* This glyph will be drawn with background marked for marked text. */ +GLD_COMPOSING = 2 /* This glyph will be drawn with underline (for example composing text). */ +}; + //! GlyphLayout copied from libraqm. struct GlyphLayout { @@ -37,6 +44,7 @@ s32 y_offset; /* Above variable is same for raqm_glyph_t */ // If some characters share the same glyph std::vector cluster; +std::vector draw_flags; //! used to sorting back the visual order after line breaking u32 original_index; u16 flags; diff --git a/src/font/font_manager.cpp b/src/font/font_manager.cpp index b4340fb4b..89f32fe18 100644 --- a/src/font/font_manager.cpp +++ b/src/font/font_manager.cpp @@ -437,6 +437,8 @@ void FontManager::shape(const std::u32string& text, extra_cluster <= next_cluster - 1; extra_cluster++) cur_line[l].cluster.push_back(extra_cluster); } + cur_line[l].draw_flags.resize(cur_line[l].cluster.size(), + gui::GLD_NONE); } // Sort glyphs in visual order std::sort(cur_line.begin(), cur_line.end(), [] diff --git a/src/font/font_with_face.cpp b/src/font/font_with_face.cpp index 460d3d5d4..9bf259f22 100644 --- a/src/font/font_with_face.cpp +++ b/src/font/font_with_face.cpp @@ -574,6 +574,8 @@ void FontWithFace::render(const std::vector& gl, core::array indices(text_size); core::array> offsets(text_size); std::vector fallback(text_size); + core::array> gld_offsets; + gui::GlyphLayoutDraw df_used = gui::GLD_NONE; // Check if the line is RTL bool rtl = (gl[0].flags & gui::GLF_RTL_LINE) != 0; @@ -596,7 +598,7 @@ void FontWithFace::render(const std::vector& gl, if (line_changed) { line_changed = false; - rtl = (gl[i].flags & gui::GLF_RTL_LINE) != 0; + rtl = (glyph_layout.flags & gui::GLF_RTL_LINE) != 0; offset.X = float(position.UpperLeftCorner.X); if (hcenter) { @@ -680,8 +682,32 @@ void FontWithFace::render(const std::vector& gl, } else { - offset.X += - (int)(glyph_layout.x_advance * m_inverse_shaping) * scale; + int width = (int)(glyph_layout.x_advance * m_inverse_shaping); + if (char_collector == NULL) + { + float each_size = width * scale / + (float)glyph_layout.cluster.size(); + float start = offset.X; + for (unsigned df = 0; df < glyph_layout.draw_flags.size(); + df++) + { + if (glyph_layout.draw_flags[df] != gui::GLD_NONE) + { + if (df_used == gui::GLD_NONE) + { + if (glyph_layout.draw_flags[df] & gui::GLD_MARKED) + df_used = gui::GLD_MARKED; + else if (glyph_layout.draw_flags[df] & + gui::GLD_COMPOSING) + df_used = gui::GLD_COMPOSING; + } + gld_offsets.push_back({start, offset.Y}); + gld_offsets.push_back({start + each_size, offset.Y}); + } + start += each_size; + } + } + offset.X += width * scale; } } // for i < text_size @@ -825,6 +851,27 @@ void FontWithFace::render(const std::vector& gl, } } } + for (unsigned i = 0; i < gld_offsets.size(); i += 2) + { + if (df_used == gui::GLD_MARKED) + { + core::rect gld((s32)gld_offsets[i].X, (s32)gld_offsets[i].Y, + (s32)gld_offsets[i + 1].X, + (s32)(gld_offsets[i + 1].Y + m_font_max_height * scale)); + GL32_draw2DRectangle(GUIEngine::getSkin()->getColor( + "text_field::background_marked"), gld, clip); + } + else if (df_used == gui::GLD_COMPOSING) + { + float line1 = m_font_max_height * scale * 0.88f; + float line2 = m_font_max_height * scale * 0.92f; + core::rect gld((s32)gld_offsets[i].X, (s32)gld_offsets[i].Y + line1, + (s32)gld_offsets[i + 1].X, + (s32)(gld_offsets[i + 1].Y + line2)); + GL32_draw2DRectangle(GUIEngine::getSkin()->getColor( + "text::neutral"), gld, clip); + } + } #endif } // render diff --git a/src/guiengine/widgets/CGUIEditBox.cpp b/src/guiengine/widgets/CGUIEditBox.cpp index 5f2205746..5e2f9f18a 100644 --- a/src/guiengine/widgets/CGUIEditBox.cpp +++ b/src/guiengine/widgets/CGUIEditBox.cpp @@ -13,16 +13,18 @@ #include "Keycodes.h" #include "config/user_config.hpp" +#include "font/font_manager.hpp" #include "graphics/2dutils.hpp" #include "graphics/irr_driver.hpp" +#include "guiengine/engine.hpp" #include "guiengine/screen_keyboard.hpp" #include "utils/string_utils.hpp" -#include "utils/translation.hpp" #include "utils/time.hpp" #include "../../../lib/irrlicht/include/IrrCompileConfig.h" #include "../../../lib/irrlicht/source/Irrlicht/CIrrDeviceLinux.h" +#include #include #ifdef ANDROID @@ -36,41 +38,159 @@ double click/ctrl click: word select + drag to select whole words, triple click to select line optional? dragging selected text numerical - correct the mark position in RTL text, currently you can identify by highlight the text */ -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) -#define UTF16_IS_SURROGATE_LO(c) (((c) & 0xFC00) == 0xDC00) -#define UTF16_IS_SURROGATE_HI(c) (((c) & 0xFC00) == 0xD800) +#ifndef SERVER_ONLY +#include +// Copied from libraqm +namespace Grahem +{ +typedef enum +{ + RAQM_GRAPHEM_CR, + RAQM_GRAPHEM_LF, + RAQM_GRAPHEM_CONTROL, + RAQM_GRAPHEM_EXTEND, + RAQM_GRAPHEM_REGIONAL_INDICATOR, + RAQM_GRAPHEM_PREPEND, + RAQM_GRAPHEM_SPACING_MARK, + RAQM_GRAPHEM_HANGUL_SYLLABLE, + RAQM_GRAPHEM_OTHER +} _raqm_grapheme_t; + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category); + +static bool +_raqm_allowed_grapheme_boundary (hb_codepoint_t l_char, + hb_codepoint_t r_char) +{ + hb_unicode_general_category_t l_category; + hb_unicode_general_category_t r_category; + _raqm_grapheme_t l_grapheme, r_grapheme; + hb_unicode_funcs_t* unicode_funcs = hb_unicode_funcs_get_default (); + + l_category = hb_unicode_general_category (unicode_funcs, l_char); + r_category = hb_unicode_general_category (unicode_funcs, r_char); + l_grapheme = _raqm_get_grapheme_break (l_char, l_category); + r_grapheme = _raqm_get_grapheme_break (r_char, r_category); + + if (l_grapheme == RAQM_GRAPHEM_CR && r_grapheme == RAQM_GRAPHEM_LF) + return false; /*Do not break between a CR and LF GB3*/ + if (l_grapheme == RAQM_GRAPHEM_CONTROL || l_grapheme == RAQM_GRAPHEM_CR || + l_grapheme == RAQM_GRAPHEM_LF || r_grapheme == RAQM_GRAPHEM_CONTROL || + r_grapheme == RAQM_GRAPHEM_CR || r_grapheme == RAQM_GRAPHEM_LF) + return true; /*Break before and after CONTROL GB4, GB5*/ + if (r_grapheme == RAQM_GRAPHEM_HANGUL_SYLLABLE) + return false; /*Do not break Hangul syllable sequences. GB6, GB7, GB8*/ + if (l_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR && + r_grapheme == RAQM_GRAPHEM_REGIONAL_INDICATOR) + return false; /*Do not break between regional indicator symbols. GB8a*/ + if (r_grapheme == RAQM_GRAPHEM_EXTEND) + return false; /*Do not break before extending characters. GB9*/ + /*Do not break before SpacingMarks, or after Prepend characters.GB9a, GB9b*/ + if (l_grapheme == RAQM_GRAPHEM_PREPEND) + return false; + if (r_grapheme == RAQM_GRAPHEM_SPACING_MARK) + return false; + return true; /*Otherwise, break everywhere. GB1, GB2, GB10*/ +} + +static _raqm_grapheme_t +_raqm_get_grapheme_break (hb_codepoint_t ch, + hb_unicode_general_category_t category) +{ + _raqm_grapheme_t gb_type; + + gb_type = RAQM_GRAPHEM_OTHER; + switch ((int)category) + { + case HB_UNICODE_GENERAL_CATEGORY_FORMAT: + if (ch == 0x200C || ch == 0x200D) + gb_type = RAQM_GRAPHEM_EXTEND; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_CONTROL: + if (ch == 0x000D) + gb_type = RAQM_GRAPHEM_CR; + else if (ch == 0x000A) + gb_type = RAQM_GRAPHEM_LF; + else + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_SURROGATE: + case HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_PARAGRAPH_SEPARATOR: + case HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED: + if ((ch >= 0xFFF0 && ch <= 0xFFF8) || + (ch >= 0xE0000 && ch <= 0xE0FFF)) + gb_type = RAQM_GRAPHEM_CONTROL; + break; + + case HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_ENCLOSING_MARK: + case HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK: + if (ch != 0x102B || ch != 0x102C || ch != 0x1038 || + (ch <= 0x1062 && ch >= 0x1064) || (ch <= 0x1067 && ch >= 0x106D) || + ch != 0x1083 || (ch <= 0x1087 && ch >= 0x108C) || ch != 0x108F || + (ch <= 0x109A && ch >= 0x109C) || ch != 0x1A61 || ch != 0x1A63 || + ch != 0x1A64 || ch != 0xAA7B || ch != 0xAA70 || ch != 0x11720 || + ch != 0x11721) /**/ + gb_type = RAQM_GRAPHEM_SPACING_MARK; + + else if (ch == 0x09BE || ch == 0x09D7 || + ch == 0x0B3E || ch == 0x0B57 || ch == 0x0BBE || ch == 0x0BD7 || + ch == 0x0CC2 || ch == 0x0CD5 || ch == 0x0CD6 || + ch == 0x0D3E || ch == 0x0D57 || ch == 0x0DCF || ch == 0x0DDF || + ch == 0x1D165 || (ch >= 0x1D16E && ch <= 0x1D172)) + gb_type = RAQM_GRAPHEM_EXTEND; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_LETTER: + if (ch == 0x0E33 || ch == 0x0EB3) + gb_type = RAQM_GRAPHEM_SPACING_MARK; + break; + + case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: + if (ch >= 0x1F1E6 && ch <= 0x1F1FF) + gb_type = RAQM_GRAPHEM_REGIONAL_INDICATOR; + break; + + default: + gb_type = RAQM_GRAPHEM_OTHER; + break; + } + + return gb_type; +} + +}; +#endif + +#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) namespace irr { void updateICPos(void* hWnd, s32 x, s32 y, s32 height); } #endif -StkTime::TimeType getTime() -{ - return StkTime::getTimeSinceEpoch(); -} - //! constructor CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, IGUIElement* parent, s32 id, - const core::rect& rectangle, bool is_rtl) + const core::rect& rectangle) : IGUIEditBox(environment, parent, id, rectangle), MouseMarking(false), - Border(border), OverrideColorEnabled(false), MarkBegin(0), MarkEnd(0), + Border(border), OverrideColorEnabled(false), m_mark_begin(0), m_mark_end(0), OverrideColor(video::SColor(101,255,255,255)), OverrideFont(0), LastBreakFont(0), - Operator(0), BlinkStartTime(0), CursorPos(0), HScrollPos(0), VScrollPos(0), Max(0), - WordWrap(false), MultiLine(false), AutoScroll(true), PasswordBox(false), - PasswordChar(L'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), + Operator(0), m_force_show_cursor_time(0), m_cursor_pos(0), m_scroll_pos(0), m_cursor_distance(0), + m_max_chars(0), AutoScroll(true), PasswordBox(false), + PasswordChar(U'*'), HAlign(EGUIA_UPPERLEFT), VAlign(EGUIA_CENTER), CurrentTextRect(0,0,1,1), FrameRect(rectangle) { - //m_rtl = is_rtl; - m_rtl = false; - // FIXME quick hack to enable mark movement with keyboard and mouse for rtl language, - // don't know why it's disabled in the first place, because STK fail - // to input unicode characters before? m_from_android_edittext = false; m_composing_start = 0; m_composing_end = 0; @@ -81,6 +201,7 @@ CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border, #endif Text = text; + m_edit_text = StringUtils::wideToUtf32(text); #ifndef SERVER_ONLY if (Environment) @@ -104,8 +225,6 @@ CGUIEditBox::CGUIEditBox(const wchar_t* text, bool border, FrameRect.LowerRightCorner.Y -= skin->getSize(EGDS_TEXT_DISTANCE_Y)+1; } - breakText(); - calculateScrollPos(); } @@ -146,7 +265,6 @@ void CGUIEditBox::setOverrideFont(IGUIFont* font) if (OverrideFont) OverrideFont->grab(); - breakText(); } @@ -183,45 +301,10 @@ bool CGUIEditBox::isOverrideColorEnabled() const return OverrideColorEnabled; } -//! Enables or disables word wrap -void CGUIEditBox::setWordWrap(bool enable) -{ - WordWrap = enable; - breakText(); -} - void CGUIEditBox::updateAbsolutePosition() { - core::rect oldAbsoluteRect(AbsoluteRect); IGUIElement::updateAbsolutePosition(); - if ( oldAbsoluteRect != AbsoluteRect ) - { - breakText(); - } -} - - -//! Checks if word wrap is enabled -bool CGUIEditBox::isWordWrapEnabled() const -{ - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return WordWrap; -} - - -//! Enables or disables newlines. -void CGUIEditBox::setMultiLine(bool enable) -{ - MultiLine = enable; -} - - -//! Checks if multi line editing is enabled -bool CGUIEditBox::isMultiLineEnabled() const -{ - _IRR_IMPLEMENT_MANAGED_MARSHALLING_BUGFIX; - return MultiLine; } @@ -229,12 +312,7 @@ void CGUIEditBox::setPasswordBox(bool passwordBox, wchar_t passwordChar) { PasswordBox = passwordBox; if (PasswordBox) - { - PasswordChar = passwordChar; - setMultiLine(false); - setWordWrap(false); - BrokenText.clear(); - } + PasswordChar = (char32_t)passwordChar; } @@ -286,7 +364,8 @@ bool CGUIEditBox::OnEvent(const SEvent& event) } else if (event.GUIEvent.EventType == EGET_ELEMENT_FOCUSED) { - MarkBegin = MarkEnd = CursorPos = (u32)Text.size(); + m_mark_begin = m_mark_end = m_cursor_pos = getTextCount(); + updateCursorDistance(); #ifdef _IRR_COMPILE_WITH_X11_DEVICE_ if (irr_driver->getDevice()->getType() == irr::EIDT_X11) { @@ -306,7 +385,7 @@ bool CGUIEditBox::OnEvent(const SEvent& event) m_from_android_edittext = true; CIrrDeviceAndroid* dl = dynamic_cast( irr_driver->getDevice()); - dl->fromSTKEditBox(getID(), Text, MarkBegin, MarkEnd, m_type); + dl->fromSTKEditBox(getID(), Text, m_mark_begin, m_mark_end, m_type); } else #endif @@ -338,17 +417,43 @@ bool CGUIEditBox::OnEvent(const SEvent& event) } +void CGUIEditBox::correctCursor(s32& cursor_pos, bool left) +{ +#ifndef SERVER_ONLY + if (left) + { + if (cursor_pos >= (s32)m_edit_text.size()) + return; + while (cursor_pos != 0 && + !Grahem::_raqm_allowed_grapheme_boundary(m_edit_text[cursor_pos - 1], + m_edit_text[cursor_pos])) + cursor_pos--; + } + else + { + while (cursor_pos != 0 && cursor_pos != (s32)m_edit_text.size() && + !Grahem::_raqm_allowed_grapheme_boundary(m_edit_text[cursor_pos - 1], + m_edit_text[cursor_pos])) + cursor_pos++; + } +#endif +} + + bool CGUIEditBox::processKey(const SEvent& event) { +#ifdef SERVER_ONLY + return false; +#else if (!event.KeyInput.PressedDown) return false; - bool textChanged = false; - s32 newMarkBegin = MarkBegin; - s32 newMarkEnd = MarkEnd; + bool text_changed = false; + s32 new_mark_begin = m_mark_begin; + s32 new_mark_end = m_mark_end; + s32 new_cursor_pos = m_cursor_pos; // control shortcut handling - if (event.KeyInput.Control) { // german backlash '\' entered with control + '?' @@ -362,161 +467,138 @@ bool CGUIEditBox::processKey(const SEvent& event) { case IRR_KEY_A: // select all - newMarkBegin = 0; - newMarkEnd = Text.size(); + new_mark_begin = 0; + new_mark_end = (s32)m_edit_text.size(); + new_cursor_pos = new_mark_end; break; case IRR_KEY_C: // copy to clipboard - if (!PasswordBox && Operator && MarkBegin != MarkEnd) + if (!PasswordBox && Operator && m_mark_begin != m_mark_end) { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - core::stringw s; - s = Text.subString(realmbgn, realmend - realmbgn).c_str(); + std::u32string s = m_edit_text.substr(realmbgn, realmend - realmbgn); #ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - Operator->copyToClipboard(s.c_str()); + Operator->copyToClipboard(StringUtils::utf32ToWide(s).c_str()); #else - Operator->copyToClipboard(StringUtils::wideToUtf8(s).c_str()); + Operator->copyToClipboard(StringUtils::utf32ToUtf8(s).c_str()); #endif } break; case IRR_KEY_X: // cut to the clipboard - if (!PasswordBox && Operator && MarkBegin != MarkEnd) + if (!PasswordBox && Operator && m_mark_begin != m_mark_end) { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; // copy - core::stringw sc; - sc = Text.subString(realmbgn, realmend - realmbgn).c_str(); + std::u32string s = m_edit_text.substr(realmbgn, realmend - realmbgn); #ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - Operator->copyToClipboard(sc.c_str()); + Operator->copyToClipboard(StringUtils::utf32ToWide(s).c_str()); #else - Operator->copyToClipboard(StringUtils::wideToUtf8(sc).c_str()); + Operator->copyToClipboard(StringUtils::utf32ToUtf8(s).c_str()); #endif if (isEnabled()) { // delete - core::stringw s; - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; + std::u32string sub_str = m_edit_text.substr(0, realmbgn); + sub_str += m_edit_text.substr(realmend, m_edit_text.size() - realmend); + m_edit_text = sub_str; - CursorPos = realmbgn; - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; + new_mark_begin = 0; + new_mark_end = 0; + new_cursor_pos = realmbgn; + text_changed = true; } } break; case IRR_KEY_V: - if ( !isEnabled() ) + if (!isEnabled()) break; // paste from the clipboard if (Operator) { - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; // add new character #ifdef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - const wchar_t* p = Operator->getTextFromClipboard(); + const std::u32string clipboard = + StringUtils::wideToUtf32(Operator->getTextFromClipboard()); #else - const c8* p = Operator->getTextFromClipboard(); + const std::u32string clipboard = + StringUtils::utf8ToUtf32(Operator->getTextFromClipboard()); #endif - if (p) + if (!clipboard.empty()) { - if (MarkBegin == MarkEnd) + if (m_mark_begin == m_mark_end) { // insert text - core::stringw s = Text.subString(0, CursorPos); -#ifndef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - s.append(StringUtils::utf8ToWide(p)); -#else - s.append(p); -#endif - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); + std::u32string sub_str = m_edit_text.substr(0, m_cursor_pos); + sub_str += clipboard; + sub_str += m_edit_text.substr(m_cursor_pos, m_edit_text.size() - m_cursor_pos); - if (!Max || s.size()<=Max) // thx to Fish FH for fix + if (m_max_chars == 0 || sub_str.size() <= m_max_chars) // thx to Fish FH for fix { - Text = s; -#ifndef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - s = StringUtils::utf8ToWide(p); -#else - s = p; -#endif - CursorPos += s.size(); + m_edit_text = sub_str; + new_cursor_pos = m_cursor_pos + (s32)clipboard.size(); } } else { // replace text + std::u32string sub_str = m_edit_text.substr(0, realmbgn); + sub_str += clipboard; + sub_str += m_edit_text.substr(realmend, m_edit_text.size() - realmend); - core::stringw s = Text.subString(0, realmbgn); - -#ifndef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - s.append(StringUtils::utf8ToWide(p)); -#else - s.append(p); -#endif - s.append( Text.subString(realmend, Text.size()-realmend) ); - - if (!Max || s.size()<=Max) // thx to Fish FH for fix + if (m_max_chars == 0 || sub_str.size() <= m_max_chars) // thx to Fish FH for fix { - Text = s; -#ifndef _IRR_COMPILE_WITH_WINDOWS_DEVICE_ - s = StringUtils::utf8ToWide(p); -#else - s = p; -#endif - CursorPos = realmbgn + s.size(); + m_edit_text = sub_str; + new_cursor_pos = realmbgn + (s32)sub_str.size(); } } + new_mark_begin = 0; + new_mark_end = 0; + text_changed = true; } - - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; } break; case IRR_KEY_HOME: - if (!m_rtl) { // move/highlight to start of text if (event.KeyInput.Shift) { - newMarkEnd = CursorPos; - newMarkBegin = 0; - CursorPos = 0; + new_mark_end = m_cursor_pos; + new_mark_begin = 0; + new_cursor_pos = 0; } else { - CursorPos = 0; - newMarkBegin = 0; - newMarkEnd = 0; + new_cursor_pos = 0; + new_mark_begin = 0; + new_mark_end = 0; } } break; case IRR_KEY_END: - if (!m_rtl) { // move/highlight to end of text if (event.KeyInput.Shift) { - newMarkBegin = CursorPos; - newMarkEnd = Text.size(); - CursorPos = 0; + new_mark_begin = m_cursor_pos; + new_mark_end = (s32)m_edit_text.size(); + new_cursor_pos = 0; } else { - CursorPos = Text.size(); - newMarkBegin = 0; - newMarkEnd = 0; + new_mark_begin = 0; + new_mark_end = 0; + new_cursor_pos = (s32)m_edit_text.size(); } } break; @@ -531,281 +613,199 @@ bool CGUIEditBox::processKey(const SEvent& event) /* case IRR_KEY_Q: inputChar(L'\u05DC'); - textChanged = true; + text_changed = true; return true; case IRR_KEY_W: inputChar(L'\u05DB'); - textChanged = true; + text_changed = true; return true; case IRR_KEY_E: inputChar(L'\u05DA'); - textChanged = true; + text_changed = true; return true; case IRR_KEY_R: inputChar(L'\u05D9'); - textChanged = true; + text_changed = true; return true; case IRR_KEY_T: inputChar(L'\u05D8'); - textChanged = true; + text_changed = true; return true; case IRR_KEY_Y: inputChar(L'\u05D7'); - textChanged = true; + text_changed = true; return true; */ case IRR_KEY_END: - if (!m_rtl) { - s32 p = Text.size(); - if (WordWrap || MultiLine) - { - p = getLineFromPos(CursorPos); - p = BrokenTextPositions[p] + (s32)BrokenText[p].size(); - if (p > 0 && (Text[p-1] == L'\r' || Text[p-1] == L'\n' )) - p-=1; - } - + s32 p = getTextCount(); if (event.KeyInput.Shift) { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - - newMarkEnd = p; + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; + new_mark_end = p; } else { - newMarkBegin = 0; - newMarkEnd = 0; + new_mark_begin = 0; + new_mark_end = 0; } - CursorPos = p; - BlinkStartTime = getTime(); + new_cursor_pos = p; + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; } break; case IRR_KEY_HOME: - if (!m_rtl) { - s32 p = 0; - if (WordWrap || MultiLine) - { - p = getLineFromPos(CursorPos); - p = BrokenTextPositions[p]; - } - if (event.KeyInput.Shift) { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; - newMarkEnd = p; + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; + new_mark_end = p; } else { - newMarkBegin = 0; - newMarkEnd = 0; + new_mark_begin = 0; + new_mark_end = 0; } - CursorPos = p; - BlinkStartTime = getTime(); + new_cursor_pos = p; + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; } break; case IRR_KEY_RETURN: - if (MultiLine) - { - inputChar(L'\n'); - return true; - } - else { irr_driver->getDevice()->toggleOnScreenKeyboard(false); sendGuiEvent( EGET_EDITBOX_ENTER ); } break; case IRR_KEY_LEFT: - if (!m_rtl) { if (event.KeyInput.Shift) { - if (CursorPos > 0) + if (m_cursor_pos > 0) { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; - newMarkEnd = CursorPos-1; + new_mark_end = m_cursor_pos-1; + correctCursor(new_mark_end, true/*left*/); } } else { - newMarkBegin = 0; - newMarkEnd = 0; + new_mark_begin = 0; + new_mark_end = 0; } - if (CursorPos > 0) CursorPos--; - BlinkStartTime = getTime(); + if (m_cursor_pos > 0) + { + new_cursor_pos = m_cursor_pos - 1; + correctCursor(new_cursor_pos, true/*left*/); + } + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; } break; case IRR_KEY_RIGHT: - if (!m_rtl) { if (event.KeyInput.Shift) { - if (Text.size() > (u32)CursorPos) + if (m_edit_text.size() > (u32)m_cursor_pos) { - if (MarkBegin == MarkEnd) - newMarkBegin = CursorPos; + if (m_mark_begin == m_mark_end) + new_mark_begin = m_cursor_pos; - newMarkEnd = CursorPos+1; + new_mark_end = m_cursor_pos + 1; + correctCursor(new_mark_end, false/*left*/); } } else { - newMarkBegin = 0; - newMarkEnd = 0; + new_mark_begin = 0; + new_mark_end = 0; } - if (Text.size() > (u32)CursorPos) CursorPos++; - BlinkStartTime = getTime(); + if (m_edit_text.size() > (u32)m_cursor_pos) + { + new_cursor_pos = m_cursor_pos + 1; + correctCursor(new_cursor_pos, false/*left*/); + } + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; } break; case IRR_KEY_UP: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - s32 lineNo = getLineFromPos(CursorPos); - s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin > MarkEnd ? MarkBegin : MarkEnd); - if (lineNo > 0) - { - s32 cp = CursorPos - BrokenTextPositions[lineNo]; - if ((s32)BrokenText[lineNo-1].size() < cp) - CursorPos = BrokenTextPositions[lineNo-1] + (s32)BrokenText[lineNo-1].size()-1; - else - CursorPos = BrokenTextPositions[lineNo-1] + cp; - } - - if (event.KeyInput.Shift) - { - newMarkBegin = mb; - newMarkEnd = CursorPos; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - } - else - { - return false; - } - break; case IRR_KEY_DOWN: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - s32 lineNo = getLineFromPos(CursorPos); - s32 mb = (MarkBegin == MarkEnd) ? CursorPos : (MarkBegin < MarkEnd ? MarkBegin : MarkEnd); - if (lineNo < (s32)BrokenText.size()-1) - { - s32 cp = CursorPos - BrokenTextPositions[lineNo]; - if ((s32)BrokenText[lineNo+1].size() < cp) - CursorPos = BrokenTextPositions[lineNo+1] + BrokenText[lineNo+1].size()-1; - else - CursorPos = BrokenTextPositions[lineNo+1] + cp; - } - - if (event.KeyInput.Shift) - { - newMarkBegin = mb; - newMarkEnd = CursorPos; - } - else - { - newMarkBegin = 0; - newMarkEnd = 0; - } - - } - else { return false; } break; case IRR_KEY_BACK: - if ( !isEnabled() ) + if (!isEnabled()) break; - if (Text.size()) + if (!m_edit_text.empty()) { - core::stringw s; - - if (MarkBegin != MarkEnd) + std::u32string sub_str; + if (m_mark_begin != m_mark_end) { // delete marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; + std::u32string sub_str = m_edit_text.substr(0, realmbgn); + sub_str += m_edit_text.substr(realmend, m_edit_text.size() - realmend); + m_edit_text = sub_str; + new_cursor_pos = realmbgn; } else { // delete text behind cursor - if (CursorPos>0) - s = Text.subString(0, CursorPos-1); + if (m_cursor_pos > 0) + sub_str = m_edit_text.substr(0, m_cursor_pos - 1); else - s = L""; - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); - Text = s; - --CursorPos; + sub_str.clear(); + sub_str += m_edit_text.substr(m_cursor_pos, m_edit_text.size() - m_cursor_pos); + m_edit_text = sub_str; + new_cursor_pos = m_cursor_pos - 1; } - - if (CursorPos < 0) - CursorPos = 0; - BlinkStartTime = getTime(); - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; + new_mark_begin = 0; + new_mark_end = 0; + text_changed = true; } break; case IRR_KEY_DELETE: - if ( !isEnabled() ) + if (!isEnabled()) break; - if (Text.size() != 0) + if (!m_edit_text.empty()) { - core::stringw s; - - if (MarkBegin != MarkEnd) + std::u32string sub_str; + if (m_mark_begin != m_mark_end) { // delete marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - s = Text.subString(0, realmbgn); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - - CursorPos = realmbgn; + sub_str = m_edit_text.substr(0, realmbgn); + sub_str += m_edit_text.substr(realmend, m_edit_text.size() - realmend); + m_edit_text = sub_str; + new_cursor_pos = realmbgn; + text_changed = true; } - else + else if (m_cursor_pos != getTextCount()) { // delete text before cursor - s = Text.subString(0, CursorPos); - s.append( Text.subString(CursorPos+1, Text.size()-CursorPos-1) ); - Text = s; + sub_str = m_edit_text.substr(0, m_cursor_pos); + sub_str += m_edit_text.substr(m_cursor_pos + 1, m_edit_text.size() - m_cursor_pos - 1); + m_edit_text = sub_str; + text_changed = true; } - - if (CursorPos > (s32)Text.size()) - CursorPos = (s32)Text.size(); - - BlinkStartTime = getTime(); - newMarkBegin = 0; - newMarkEnd = 0; - textChanged = true; + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; + new_mark_begin = 0; + new_mark_end = 0; } break; @@ -844,58 +844,26 @@ bool CGUIEditBox::processKey(const SEvent& event) return true; } - // Set new text markers - setTextMarkers( newMarkBegin, newMarkEnd ); - - // break the text if it has changed - if (textChanged) + // Update glyph layouts, the next setTextMarks will update text to android + if (text_changed) { - breakText(); + updateGlyphLayouts(); sendGuiEvent(EGET_EDITBOX_CHANGED); } + // Set new text markers + setTextMarkers(new_mark_begin, new_mark_end); + + if (new_cursor_pos > getTextCount()) + new_cursor_pos = getTextCount(); + m_cursor_pos = new_cursor_pos; + if (m_cursor_pos < 0) + m_cursor_pos = 0; + calculateScrollPos(); - if (CursorPos > (s32)Text.size()) - CursorPos = Text.size(); - -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) - switch(event.KeyInput.Key) - { - // If cursor points the surrogate low, send IRR_KEY_LEFT event. - case IRR_KEY_UP: - case IRR_KEY_DOWN: - if (MultiLine || (WordWrap && BrokenText.size() > 1) ) - { - if (UTF16_IS_SURROGATE_LO(Text[CursorPos])) - { - SEvent leftEvent; - leftEvent = event; - leftEvent.KeyInput.Key = IRR_KEY_LEFT; - Environment->postEventFromUser(leftEvent); - } - } - break; - // If cursor points the surrogate low, send a same event. - case IRR_KEY_LEFT: - case IRR_KEY_RIGHT: - case IRR_KEY_DELETE: - if (UTF16_IS_SURROGATE_LO(Text[CursorPos])) - Environment->postEventFromUser(event); - break; - // If cursor points front of the surrogate high, send a same event. - case IRR_KEY_BACK: - if (CursorPos > 0) - { - if (UTF16_IS_SURROGATE_HI(Text[CursorPos-1])) - Environment->postEventFromUser(event); - } - break; - default: - break; - } -#endif return true; +#endif } @@ -909,6 +877,7 @@ bool CGUIEditBox::processIMEEvent(const SEvent& event) return true; case EIME_CHANGE_POS: { + updateCursorDistance(); core::position2di pos = calculateICPos(); IGUIFont* font = OverrideFont; @@ -917,7 +886,7 @@ bool CGUIEditBox::processIMEEvent(const SEvent& event) if (!OverrideFont) font = skin->getFont(); - irr::updateICPos(event.InputMethodEvent.Handle, pos.X,pos.Y, font->getDimension(L"|").Height); + irr::updateICPos(event.InputMethodEvent.Handle, pos.X,pos.Y, font->getHeightPerLine()); return true; } @@ -934,24 +903,9 @@ bool CGUIEditBox::processIMEEvent(const SEvent& event) core::position2di CGUIEditBox::calculateICPos() { core::position2di pos; - IGUIFont* font = OverrideFont; - IGUISkin* skin = Environment->getSkin(); - if (!OverrideFont) - font = skin->getFont(); - - //drop the text that clipping on the right side - if (WordWrap | MultiLine) - { - // todo : It looks like a heavy drinker. Strange!! - pos.X = CurrentTextRect.LowerRightCorner.X - font->getDimension(Text.subString(CursorPos, BrokenTextPositions[getLineFromPos(CursorPos)] + BrokenText[getLineFromPos(CursorPos)].size() - CursorPos).c_str()).Width; - pos.Y = CurrentTextRect.UpperLeftCorner.Y + font->getDimension(L"|").Height + (Border ? 3 : 0) - ((MultiLine | WordWrap) ? 3 : 0); - } - else - { - pos.X = CurrentTextRect.LowerRightCorner.X - font->getDimension(Text.subString(CursorPos, Text.size() - CursorPos).c_str()).Width; - pos.Y = AbsoluteRect.getCenter().Y + (Border ? 3 : 0); //bug? The text is always drawn in the height of the center. SetTextAlignment() doesn't influence. - } - + pos.X = CurrentTextRect.UpperLeftCorner.X + m_cursor_distance; + //bug? The text is always drawn in the height of the center. SetTextAlignment() doesn't influence. + pos.Y = AbsoluteRect.getCenter().Y + (Border ? 3 : 0); return pos; } @@ -976,11 +930,10 @@ void CGUIEditBox::draw() FrameRect = AbsoluteRect; // draw the border - if (Border) { EGUI_DEFAULT_COLOR col = EGDC_GRAY_EDITABLE; - if ( isEnabled() ) + if (isEnabled()) col = focus ? EGDC_FOCUSED_EDITABLE : EGDC_EDITABLE; skin->draw3DSunkenPane(this, skin->getColor(col), false, true, FrameRect, &AbsoluteClippingRect); @@ -993,196 +946,78 @@ void CGUIEditBox::draw() core::rect localClipRect = FrameRect; localClipRect.clipAgainst(AbsoluteClippingRect); - // draw the text - IGUIFont* font = OverrideFont; if (!OverrideFont) font = skin->getFont(); - s32 cursorLine = 0; - s32 charcursorpos = 0; + if (!font) + return; - if (font) + setTextRect(0); + // Save the override color information. + // Then, alter it if the edit box is disabled. + const bool prevOver = OverrideColorEnabled; + const video::SColor prevColor = OverrideColor; + + if (!isEnabled() && !OverrideColorEnabled) { - if (LastBreakFont != font) + OverrideColorEnabled = true; + OverrideColor = skin->getColor(EGDC_GRAY_TEXT); + } + + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; + const s32 realcbgn = m_composing_start < m_composing_end ? m_composing_start : m_composing_end; + const s32 realcend = m_composing_start < m_composing_end ? m_composing_end : m_composing_start; + + for (unsigned i = 0; i < m_glyph_layouts.size(); i++) + { + GlyphLayout& glyph = m_glyph_layouts[i]; + auto& cluster = glyph.cluster; + for (unsigned c = 0; c < glyph.cluster.size(); c++) { - breakText(); - } - - // calculate cursor pos - - core::stringw *txtLine = &Text; - s32 startPos = 0; - - core::stringw s, s2; - - // get mark position - const bool ml = (!PasswordBox && (WordWrap || MultiLine)); - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - const s32 hlineStart = ml ? getLineFromPos(realmbgn) : 0; - const s32 hlineCount = ml ? getLineFromPos(realmend) - hlineStart + 1 : 1; - const s32 lineCount = ml ? BrokenText.size() : 1; - - // Save the override color information. - // Then, alter it if the edit box is disabled. - const bool prevOver = OverrideColorEnabled; - const video::SColor prevColor = OverrideColor; - - if (Text.size()) - { - if (!isEnabled() && !OverrideColorEnabled) + if (realmbgn != realmend) { - OverrideColorEnabled = true; - OverrideColor = skin->getColor(EGDC_GRAY_TEXT); + if (cluster[c] >= realmbgn && cluster[c] < realmend) + glyph.draw_flags.at(c) = GLD_MARKED; } - - for (s32 i=0; i < lineCount; ++i) + else if (!PasswordBox && realcbgn != realcend) { - setTextRect(i); - - // clipping test - don't draw anything outside the visible area - core::rect c = localClipRect; - c.clipAgainst(CurrentTextRect); - if (!c.isValid()) - continue; - - // get current line - if (PasswordBox) - { - if (BrokenText.size() != 1) - { - BrokenText.clear(); - BrokenText.push_back(core::stringw()); - } - if (BrokenText[0].size() != Text.size()) - { - BrokenText[0] = Text; - for (u32 q = 0; q < Text.size(); ++q) - { - BrokenText[0] [q] = PasswordChar; - } - } - txtLine = &BrokenText[0]; - startPos = 0; - } - else - { - txtLine = ml ? &BrokenText[i] : &Text; - startPos = ml ? BrokenTextPositions[i] : 0; - } - - font->draw(translations->fribidize(txtLine->c_str()), CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), - false, true, &localClipRect); - // draw with fribidize no matter what language, because in fribidize function, - // it will return the input pointer if (this->isRTLLanguage()) from Translations::isRTLText - // is false - - // draw composing text underline - if (!PasswordBox && focus && m_composing_start != m_composing_end && i == hlineStart) - { - s = txtLine->subString(0, m_composing_start); - s32 underline_begin = font->getDimension(s.c_str()).Width; - core::rect underline = CurrentTextRect; - underline.UpperLeftCorner.X += underline_begin; - s32 end_length = m_composing_end - m_composing_start; - if (end_length > 0 && end_length != (s32)Text.size()) - { - s = txtLine->subString(m_composing_start, end_length); - s32 underline_end = font->getDimension(s.c_str()).Width; - underline.LowerRightCorner.X = underline.UpperLeftCorner.X + underline_end; - } - s32 height = underline.LowerRightCorner.Y - underline.UpperLeftCorner.Y; - underline.UpperLeftCorner.Y += s32(std::abs(height) * 0.9f); - underline.LowerRightCorner.Y -= s32(std::abs(height) * 0.08f); - GL32_draw2DRectangle(video::SColor(255, 0, 0, 0), underline); - } - - // draw mark and marked text - if (focus && MarkBegin != MarkEnd && i >= hlineStart && i < hlineStart + hlineCount) - { - - s32 mbegin = 0, mend = 0; - s32 lineStartPos = 0, lineEndPos = txtLine->size(); - - if (i == hlineStart) - { - // highlight start is on this line - s = txtLine->subString(0, realmbgn - startPos); - mbegin = font->getDimension(s.c_str()).Width; - - // deal with kerning - mbegin += font->getKerningWidth( - &((*txtLine)[realmbgn - startPos]), - realmbgn - startPos > 0 ? &((*txtLine)[realmbgn - startPos - 1]) : 0); - - lineStartPos = realmbgn - startPos; - } - if (i == hlineStart + hlineCount - 1) - { - // highlight end is on this line - s2 = txtLine->subString(0, realmend - startPos); - mend = font->getDimension(s2.c_str()).Width; - lineEndPos = (s32)s2.size(); - } - else - mend = font->getDimension(txtLine->c_str()).Width; - - CurrentTextRect.UpperLeftCorner.X += mbegin; - CurrentTextRect.LowerRightCorner.X = CurrentTextRect.UpperLeftCorner.X + mend - mbegin; - - // draw mark - skin->draw2DRectangle(this, skin->getColor(EGDC_HIGH_LIGHT), CurrentTextRect, &localClipRect); - - // draw marked text - s = txtLine->subString(lineStartPos, lineEndPos - lineStartPos); - - if (s.size()) - font->draw(s.c_str(), CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_HIGH_LIGHT_TEXT), - false, true, &localClipRect); - - } + if (cluster[c] >= realcbgn && cluster[c] < realcend) + glyph.draw_flags.at(c) = GLD_COMPOSING; } - - // Return the override color information to its previous settings. - OverrideColorEnabled = prevOver; - OverrideColor = prevColor; - } - - // draw cursor - - if (WordWrap || MultiLine) - { - cursorLine = getLineFromPos(CursorPos); - txtLine = &BrokenText[cursorLine]; - startPos = BrokenTextPositions[cursorLine]; - } - s = txtLine->subString(0,CursorPos-startPos); - charcursorpos = font->getDimension(s.c_str()).Width ; - // + font->getKerningWidth(L"_", CursorPos-startPos > 0 ? &((*txtLine)[CursorPos-startPos-1]) : 0); - - if (focus && (getTime() - BlinkStartTime) % 2 == 0 && !m_rtl) - { - //setTextRect(cursorLine); - //CurrentTextRect.UpperLeftCorner.X += charcursorpos; - - setTextRect(0); - - core::rect< s32 > caret_rect = CurrentTextRect; - caret_rect.UpperLeftCorner.X += charcursorpos - 1; - caret_rect.LowerRightCorner.X = caret_rect.UpperLeftCorner.X + 2; - GL32_draw2DRectangle( video::SColor(255,0,0,0), caret_rect ); - - /* - font->draw(L"_", CurrentTextRect, - OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), - false, true, &localClipRect); - */ + else + glyph.draw_flags.at(c) = GLD_NONE; } } + // draw the text layout + font->draw(m_glyph_layouts, CurrentTextRect, + OverrideColorEnabled ? OverrideColor : skin->getColor(EGDC_BUTTON_TEXT), + false, true, &localClipRect); + + // Reset draw flags + for (unsigned i = 0; i < m_glyph_layouts.size(); i++) + { + GlyphLayout& glyph = m_glyph_layouts[i]; + std::fill(glyph.draw_flags.begin(), glyph.draw_flags.end(), GLD_NONE); + } + + // draw cursor + uint64_t time_ms = StkTime::getMonoTimeMs(); + if (focus && + ((time_ms / 600) % 2 == 0 || m_force_show_cursor_time > time_ms)) + { + core::rect< s32 > caret_rect = CurrentTextRect; + caret_rect.UpperLeftCorner.X += m_cursor_distance - 1; + caret_rect.LowerRightCorner.X = caret_rect.UpperLeftCorner.X + 2; + GL32_draw2DRectangle(video::SColor(255, 0, 0, 0), caret_rect); + } + + // Return the override color information to its previous settings. + OverrideColorEnabled = prevOver; + OverrideColor = prevColor; + // draw children IGUIElement::draw(); #endif @@ -1192,10 +1027,10 @@ void CGUIEditBox::draw() //! Sets the new caption of this element. void CGUIEditBox::setText(const wchar_t* text) { - Text = text; - MarkBegin = MarkEnd = CursorPos = (u32)Text.size(); - HScrollPos = 0; - breakText(); + m_edit_text = StringUtils::wideToUtf32(text); + updateGlyphLayouts(); + m_mark_begin = m_mark_end = m_cursor_pos = getTextCount(); + m_scroll_pos = 0; #ifdef ANDROID if (GUIEngine::ScreenKeyboard::shouldUseScreenKeyboard() && irr_driver->getDevice()->hasOnScreenKeyboard() && @@ -1203,7 +1038,7 @@ void CGUIEditBox::setText(const wchar_t* text) { CIrrDeviceAndroid* dl = dynamic_cast( irr_driver->getDevice()); - dl->fromSTKEditBox(getID(), Text, MarkBegin, MarkEnd, m_type); + dl->fromSTKEditBox(getID(), Text, m_mark_begin, m_mark_end, m_type); } #endif } @@ -1230,19 +1065,19 @@ bool CGUIEditBox::isAutoScrollEnabled() const //! \return Returns the size in pixels of the text core::dimension2du CGUIEditBox::getTextDimension() { - core::rect ret; + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return core::dimension2du(0, 0); - setTextRect(0); - ret = CurrentTextRect; + IGUIFont* font = OverrideFont; + if (!OverrideFont) + font = skin->getFont(); - for (u32 i=1; i < BrokenText.size(); ++i) - { - setTextRect(i); - ret.addInternalPoint(CurrentTextRect.UpperLeftCorner); - ret.addInternalPoint(CurrentTextRect.LowerRightCorner); - } + if (!font) + return core::dimension2du(0, 0); - return core::dimension2du(ret.getSize()); + return gui::getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), font->getScale()); } @@ -1251,17 +1086,16 @@ core::dimension2du CGUIEditBox::getTextDimension() //! infinity. void CGUIEditBox::setMax(u32 max) { - Max = max; - - if (Text.size() > Max && Max != 0) - Text = Text.subString(0, Max); + m_max_chars = max; + if (m_max_chars != 0 && m_edit_text.size() > m_max_chars) + m_edit_text.substr(0, m_max_chars); } //! Returns maximum amount of characters, previously set by setMax(); u32 CGUIEditBox::getMax() const { - return Max; + return m_max_chars; } @@ -1270,20 +1104,15 @@ bool CGUIEditBox::processMouse(const SEvent& event) switch(event.MouseInput.Event) { case irr::EMIE_LMOUSE_LEFT_UP: - if (Environment->hasFocus(this) && !m_rtl) + if (Environment->hasFocus(this)) { - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) - if (UTF16_IS_SURROGATE_LO(Text[CursorPos])) - { - if (CursorPos > 0) - --CursorPos; - } -#endif + m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + s32 old_cursor = m_cursor_pos; + correctCursor(m_cursor_pos, old_cursor < m_mark_begin); + correctCursor(m_mark_begin, old_cursor > m_mark_begin); if (MouseMarking) - { - setTextMarkers( MarkBegin, CursorPos ); - } + setTextMarkers(m_mark_begin, m_cursor_pos); + MouseMarking = false; calculateScrollPos(); return true; @@ -1297,15 +1126,9 @@ bool CGUIEditBox::processMouse(const SEvent& event) { if (MouseMarking) { - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) - if (UTF16_IS_SURROGATE_LO(Text[CursorPos])) - { - if (CursorPos > 0) - --CursorPos; - } -#endif - setTextMarkers( MarkBegin, CursorPos ); + m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + correctCursor(m_cursor_pos, m_cursor_pos < m_mark_begin); + setTextMarkers(m_mark_begin, m_cursor_pos); calculateScrollPos(); return true; } @@ -1314,22 +1137,16 @@ bool CGUIEditBox::processMouse(const SEvent& event) case EMIE_LMOUSE_PRESSED_DOWN: if (!Environment->hasFocus(this)) { - BlinkStartTime = getTime(); + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; MouseMarking = true; - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) - if (UTF16_IS_SURROGATE_LO(Text[CursorPos])) - { - if (CursorPos > 0) - --CursorPos; - } -#endif - setTextMarkers(CursorPos, CursorPos ); + m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + correctCursor(m_cursor_pos, m_cursor_pos < m_mark_begin); + setTextMarkers(m_cursor_pos, m_cursor_pos); calculateScrollPos(); return true; } - else if (!m_rtl) + else { if (!AbsoluteClippingRect.isPointInside( core::position2d(event.MouseInput.X, event.MouseInput.Y))) @@ -1346,21 +1163,13 @@ bool CGUIEditBox::processMouse(const SEvent& event) } // move cursor - CursorPos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); - -#if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) - if (UTF16_IS_SURROGATE_LO(Text[CursorPos])) - { - if (CursorPos > 0) - --CursorPos; - } -#endif - s32 newMarkBegin = MarkBegin; + m_cursor_pos = getCursorPos(event.MouseInput.X, event.MouseInput.Y); + s32 new_mark_begin = m_mark_begin; if (!MouseMarking) - newMarkBegin = CursorPos; + new_mark_begin = m_cursor_pos; MouseMarking = true; - setTextMarkers( newMarkBegin, CursorPos); + setTextMarkers(new_mark_begin, m_cursor_pos); calculateScrollPos(); return true; @@ -1377,157 +1186,17 @@ s32 CGUIEditBox::getCursorPos(s32 x, s32 y) { IGUIFont* font = OverrideFont; IGUISkin* skin = Environment->getSkin(); + if (!skin) + return 0; if (!OverrideFont) font = skin->getFont(); - const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; - - core::stringw *txtLine=0; - s32 startPos=0; - x+=3; - - for (u32 i=0; i < lineCount; ++i) - { - setTextRect(i); - if (i == 0 && y < CurrentTextRect.UpperLeftCorner.Y) - y = CurrentTextRect.UpperLeftCorner.Y; - if (i == lineCount - 1 && y > CurrentTextRect.LowerRightCorner.Y ) - y = CurrentTextRect.LowerRightCorner.Y; - - // is it inside this region? - if (y >= CurrentTextRect.UpperLeftCorner.Y && y <= CurrentTextRect.LowerRightCorner.Y) - { - // we've found the clicked line - txtLine = (WordWrap || MultiLine) ? &BrokenText[i] : &Text; - startPos = (WordWrap || MultiLine) ? BrokenTextPositions[i] : 0; - break; - } - } - - if (x < CurrentTextRect.UpperLeftCorner.X) - x = CurrentTextRect.UpperLeftCorner.X; - - s32 idx = txtLine ? font->getCharacterFromPos(txtLine->c_str(), x - CurrentTextRect.UpperLeftCorner.X) : -1; - - // click was on or left of the line - if (idx != -1) - return idx + startPos; - - // click was off the right edge of the line, go to end. - return txtLine->size() + startPos; -} - - -//! Breaks the single text line. -void CGUIEditBox::breakText() -{ - IGUISkin* skin = Environment->getSkin(); - - if ((!WordWrap && !MultiLine) || !skin) - return; - - BrokenText.clear(); // need to reallocate :/ - BrokenTextPositions.set_used(0); - - IGUIFont* font = OverrideFont; - if (!OverrideFont) - font = skin->getFont(); - - if (!font) - return; - - LastBreakFont = font; - - core::stringw line; - core::stringw word; - core::stringw whitespace; - s32 lastLineStart = 0; - s32 size = Text.size(); - s32 length = 0; - s32 elWidth = RelativeRect.getWidth() - 6; - - for (s32 i=0; igetDimension(whitespace.c_str()).Width; - s32 worldlgth = font->getDimension(word.c_str()).Width; - - if (WordWrap && length + worldlgth + whitelgth > elWidth) - { - // break to next line - length = worldlgth; - BrokenText.push_back(line); - BrokenTextPositions.push_back(lastLineStart); - lastLineStart = i - (s32)word.size(); - line = word; - } - else - { - // add word to line - line += whitespace; - line += word; - length += whitelgth + worldlgth; - } - - word = L""; - whitespace = L""; - } - - whitespace += c; - - // compute line break - if (lineBreak) - { - line += whitespace; - line += word; - BrokenText.push_back(line); - BrokenTextPositions.push_back(lastLineStart); - lastLineStart = i+1; - line = L""; - word = L""; - whitespace = L""; - length = 0; - } - } - else - { - // yippee this is a word.. - word += c; - } - } - - line += whitespace; - line += word; - BrokenText.push_back(line); - BrokenTextPositions.push_back(lastLineStart); + x -= AbsoluteRect.UpperLeftCorner.X; + x += m_scroll_pos; + if (x < 0) + x = 0; + return getCurosrFromDimension(x, y, m_glyph_layouts, font->getHeightPerLine(), + font->getInverseShaping(), font->getScale()); } @@ -1545,18 +1214,8 @@ void CGUIEditBox::setTextRect(s32 line) return; // get text dimension - const u32 lineCount = (WordWrap || MultiLine) ? BrokenText.size() : 1; - if (WordWrap || MultiLine) - { - d = font->getDimension(BrokenText[line].c_str()); - } - else - { - d = font->getDimension(Text.c_str()); - d.Height = AbsoluteRect.getHeight(); - } - d.Height += font->getKerningHeight(); - + d = gui::getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), font->getScale()); // justification switch (HAlign) { @@ -1582,12 +1241,12 @@ void CGUIEditBox::setTextRect(s32 line) case EGUIA_CENTER: // align to v centre CurrentTextRect.UpperLeftCorner.Y = - (FrameRect.getHeight()/2) - (lineCount*d.Height)/2 + d.Height*line; + (FrameRect.getHeight()/2) - (d.Height)/2 + d.Height*line; break; case EGUIA_LOWERRIGHT: // align to bottom edge CurrentTextRect.UpperLeftCorner.Y = - FrameRect.getHeight() - lineCount*d.Height + d.Height*line; + FrameRect.getHeight() - d.Height + d.Height*line; break; default: // align to top edge @@ -1595,9 +1254,8 @@ void CGUIEditBox::setTextRect(s32 line) break; } - CurrentTextRect.UpperLeftCorner.X -= HScrollPos; - CurrentTextRect.LowerRightCorner.X -= HScrollPos; - CurrentTextRect.UpperLeftCorner.Y -= VScrollPos; + CurrentTextRect.UpperLeftCorner.X -= m_scroll_pos; + CurrentTextRect.LowerRightCorner.X -= m_scroll_pos; CurrentTextRect.LowerRightCorner.Y = CurrentTextRect.UpperLeftCorner.Y + d.Height; CurrentTextRect += FrameRect.UpperLeftCorner; @@ -1605,112 +1263,104 @@ void CGUIEditBox::setTextRect(s32 line) } -s32 CGUIEditBox::getLineFromPos(s32 pos) -{ - if (!WordWrap && !MultiLine) - return 0; - - s32 i=0; - while (i < (s32)BrokenTextPositions.size()) - { - if (BrokenTextPositions[i] > pos) - return i-1; - ++i; - } - return (s32)BrokenTextPositions.size() - 1; -} - - void CGUIEditBox::inputChar(wchar_t c) { if (!isEnabled()) return; - if (c != 0) + // Ignore unsupported characters + if (c < 32) + return; + + if ((u32)getTextCount() < m_max_chars || m_max_chars == 0) { - if (Text.size() < Max || Max == 0) + std::u32string sub_str; + + if (m_mark_begin != m_mark_end) { - core::stringw s; + // replace marked text + const s32 realmbgn = m_mark_begin < m_mark_end ? m_mark_begin : m_mark_end; + const s32 realmend = m_mark_begin < m_mark_end ? m_mark_end : m_mark_begin; - if (MarkBegin != MarkEnd) - { - // replace marked text - const s32 realmbgn = MarkBegin < MarkEnd ? MarkBegin : MarkEnd; - const s32 realmend = MarkBegin < MarkEnd ? MarkEnd : MarkBegin; - - s = Text.subString(0, realmbgn); - s.append(c); - s.append( Text.subString(realmend, Text.size()-realmend) ); - Text = s; - CursorPos = realmbgn+1; - } - else - { - // add new character - s = Text.subString(0, CursorPos); - s.append(c); - s.append( Text.subString(CursorPos, Text.size()-CursorPos) ); - Text = s; - ++CursorPos; - } - - BlinkStartTime = getTime(); - setTextMarkers(0, 0); + sub_str = m_edit_text.substr(0, realmbgn); + sub_str += c; + sub_str += m_edit_text.substr(realmend, m_edit_text.size() - realmend); + m_edit_text = sub_str; + m_cursor_pos = realmbgn + 1; } + else + { + // add new character + sub_str = m_edit_text.substr(0, m_cursor_pos); + sub_str += c; + sub_str += m_edit_text.substr(m_cursor_pos, m_edit_text.size() - m_cursor_pos); + m_edit_text = sub_str; + m_cursor_pos++; + } + + m_force_show_cursor_time = StkTime::getMonoTimeMs() + 200; + updateGlyphLayouts(); + setTextMarkers(0, 0); + sendGuiEvent(EGET_EDITBOX_CHANGED); + calculateScrollPos(); } - breakText(); - sendGuiEvent(EGET_EDITBOX_CHANGED); - calculateScrollPos(); } +void CGUIEditBox::updateCursorDistance() +{ + m_cursor_distance = 0; + // Update cursor position + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + if (!font) + return; + if (m_glyph_layouts.empty()) + return; + if (m_cursor_pos != 0) + { + m_cursor_distance = getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), + font->getScale(), m_cursor_pos - 1).Width; + } + else if ((m_glyph_layouts[0].flags & GLF_RTL_LINE) != 0) + { + // For rtl line the cursor in the begining is total width + m_cursor_distance = getGlyphLayoutsDimension(m_glyph_layouts, + font->getHeightPerLine(), font->getInverseShaping(), + font->getScale()).Width; + } +} void CGUIEditBox::calculateScrollPos() { #ifndef SERVER_ONLY - if (!AutoScroll) + // Update cursor position + updateCursorDistance(); + + IGUISkin* skin = Environment->getSkin(); + if (!skin) + return; + IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); + if (!font) return; - // calculate horizontal scroll position - s32 cursLine = getLineFromPos(CursorPos); - setTextRect(cursLine); + if (!AutoScroll) + return; + setTextRect(0); - // don't do horizontal scrolling when wordwrap is enabled. - if (!WordWrap) - { - // get cursor position - IGUISkin* skin = Environment->getSkin(); - if (!skin) - return; - IGUIFont* font = OverrideFont ? OverrideFont : skin->getFont(); - if (!font) - return; + s32 cStart = CurrentTextRect.UpperLeftCorner.X + m_scroll_pos + + m_cursor_distance; + // Reserver 2x font height at border to see the clipped text + s32 cEnd = cStart + GUIEngine::getFontHeight() * 2; - core::stringw *txtLine = MultiLine ? &BrokenText[cursLine] : &Text; - s32 cPos = MultiLine ? CursorPos - BrokenTextPositions[cursLine] : CursorPos; - - s32 cStart = CurrentTextRect.UpperLeftCorner.X + HScrollPos + - font->getDimension(txtLine->subString(0, cPos).c_str()).Width; - - s32 cEnd = cStart + font->getDimension(L"_ ").Width; - - if (FrameRect.LowerRightCorner.X < cEnd) - HScrollPos = cEnd - FrameRect.LowerRightCorner.X; - else if (FrameRect.UpperLeftCorner.X > cStart) - HScrollPos = cStart - FrameRect.UpperLeftCorner.X; - else - HScrollPos = 0; - - // todo: adjust scrollbar - } - - // vertical scroll position - if (FrameRect.LowerRightCorner.Y < CurrentTextRect.LowerRightCorner.Y + VScrollPos) - VScrollPos = CurrentTextRect.LowerRightCorner.Y - FrameRect.LowerRightCorner.Y + VScrollPos; - - else if (FrameRect.UpperLeftCorner.Y > CurrentTextRect.UpperLeftCorner.Y + VScrollPos) - VScrollPos = CurrentTextRect.UpperLeftCorner.Y - FrameRect.UpperLeftCorner.Y + VScrollPos; + if (FrameRect.LowerRightCorner.X < cEnd) + m_scroll_pos = cEnd - FrameRect.LowerRightCorner.X; + else if (FrameRect.UpperLeftCorner.X > cStart) + m_scroll_pos = cStart - FrameRect.UpperLeftCorner.X; else - VScrollPos = 0; + m_scroll_pos = 0; // todo: adjust scrollbar #if defined(_IRR_COMPILE_WITH_X11_DEVICE_) @@ -1733,10 +1383,10 @@ void CGUIEditBox::setTextMarkers(s32 begin, s32 end) if (GUIEngine::ScreenKeyboard::isActive()) return; - if ( begin != MarkBegin || end != MarkEnd ) + if (begin != m_mark_begin || end != m_mark_end) { - MarkBegin = begin; - MarkEnd = end; + m_mark_begin = begin; + m_mark_end = end; sendGuiEvent(EGET_EDITBOX_MARKING_CHANGED); #ifdef ANDROID if (GUIEngine::ScreenKeyboard::shouldUseScreenKeyboard() && @@ -1745,7 +1395,7 @@ void CGUIEditBox::setTextMarkers(s32 begin, s32 end) { CIrrDeviceAndroid* dl = dynamic_cast( irr_driver->getDevice()); - dl->fromSTKEditBox(getID(), Text, MarkBegin, MarkEnd, m_type); + dl->fromSTKEditBox(getID(), Text, m_mark_begin, m_mark_end, m_type); } #endif } @@ -1774,9 +1424,7 @@ void CGUIEditBox::serializeAttributes(io::IAttributes* out, io::SAttributeReadWr out->addBool ("OverrideColorEnabled",OverrideColorEnabled ); out->addColor ("OverrideColor", OverrideColor); // out->addFont("OverrideFont",OverrideFont); - out->addInt ("MaxChars", Max); - out->addBool ("WordWrap", WordWrap); - out->addBool ("MultiLine", MultiLine); + out->addInt ("MaxChars", m_max_chars); out->addBool ("AutoScroll", AutoScroll); out->addBool ("PasswordBox", PasswordBox); core::stringw ch = L" "; @@ -1797,8 +1445,6 @@ void CGUIEditBox::deserializeAttributes(io::IAttributes* in, io::SAttributeReadW setOverrideColor(in->getAttributeAsColor("OverrideColor")); enableOverrideColor(in->getAttributeAsBool("OverrideColorEnabled")); setMax(in->getAttributeAsInt("MaxChars")); - setWordWrap(in->getAttributeAsBool("WordWrap")); - setMultiLine(in->getAttributeAsBool("MultiLine")); setAutoScroll(in->getAttributeAsBool("AutoScroll")); core::stringw ch = in->getAttributeAsStringW("PasswordChar"); @@ -1821,31 +1467,31 @@ void CGUIEditBox::openScreenKeyboard() if (GUIEngine::ScreenKeyboard::getCurrent() != NULL) return; - + new GUIEngine::ScreenKeyboard(1.0f, 0.40f, this); } // Real copying is happening in text_box_widget.cpp with static function -void CGUIEditBox::fromAndroidEditText(const core::stringw& text, int start, +void CGUIEditBox::fromAndroidEditText(const std::u32string& text, int start, int end, int composing_start, int composing_end) { // When focus of this element is lost, this will be set to false again m_from_android_edittext = true; - Text = text; // Prevent invalid start or end - if ((unsigned)end > Text.size()) + if ((unsigned)end > text.size()) { - end = (int)Text.size(); + end = (int)text.size(); start = end; } - - CursorPos = end; + m_edit_text = text; + updateGlyphLayouts(); + m_cursor_pos = end; m_composing_start = 0; m_composing_end = 0; - MarkBegin = start; - MarkEnd = end; + m_mark_begin = start; + m_mark_end = end; if (composing_start != composing_end) { @@ -1857,3 +1503,25 @@ void CGUIEditBox::fromAndroidEditText(const core::stringw& text, int start, m_composing_end = composing_end; } } + +void CGUIEditBox::updateGlyphLayouts() +{ +#ifndef SERVER_ONLY + // Clear any unsupported characters + m_edit_text.erase(std::remove(m_edit_text.begin(), m_edit_text.end(), + U'\r'), m_edit_text.end()); + m_edit_text.erase(std::remove(m_edit_text.begin(), m_edit_text.end(), + U'\t'), m_edit_text.end()); + m_edit_text.erase(std::remove(m_edit_text.begin(), m_edit_text.end(), + U'\n'), m_edit_text.end()); + m_glyph_layouts.clear(); + if (PasswordBox) + { + font_manager->shape(std::u32string(m_edit_text.size(), PasswordChar), + m_glyph_layouts); + } + else + font_manager->shape(m_edit_text, m_glyph_layouts); + Text = StringUtils::utf32ToWide(m_edit_text); +#endif +} diff --git a/src/guiengine/widgets/CGUIEditBox.hpp b/src/guiengine/widgets/CGUIEditBox.hpp index d6c04d2db..e818a25c7 100644 --- a/src/guiengine/widgets/CGUIEditBox.hpp +++ b/src/guiengine/widgets/CGUIEditBox.hpp @@ -11,10 +11,14 @@ #include "IOSOperator.h" #include "utils/leak_check.hpp" -#include "utils/time.hpp" +#include "GlyphLayout.h" + +#include +#include using namespace irr; using namespace gui; + namespace GUIEngine { enum TextBoxType: int; @@ -28,7 +32,7 @@ namespace GUIEngine //! constructor CGUIEditBox(const wchar_t* text, bool border, IGUIEnvironment* environment, - IGUIElement* parent, s32 id, const core::rect& rectangle, bool is_rtl); + IGUIElement* parent, s32 id, const core::rect& rectangle); //! destructor virtual ~CGUIEditBox(); @@ -54,20 +58,20 @@ namespace GUIEngine virtual void setDrawBorder(bool border); //! Enables or disables word wrap for using the edit box as multiline text editor. - virtual void setWordWrap(bool enable); + virtual void setWordWrap(bool enable) {} //! Checks if word wrap is enabled //! \return true if word wrap is enabled, false otherwise - virtual bool isWordWrapEnabled() const; + virtual bool isWordWrapEnabled() const { return false; } //! Enables or disables newlines. /** \param enable: If set to true, the EGET_EDITBOX_ENTER event will not be fired, instead a newline character will be inserted. */ - virtual void setMultiLine(bool enable); + virtual void setMultiLine(bool enable) {} //! Checks if multi line editing is enabled //! \return true if mult-line is enabled, false otherwise - virtual bool isMultiLineEnabled() const; + virtual bool isMultiLineEnabled() const { return false; } //! Enables or disables automatic scrolling with cursor position //! \param enable: If set to true, the text will move around with the cursor position @@ -123,19 +127,15 @@ namespace GUIEngine virtual irr::gui::IGUIFont* getActiveFont() const { return NULL; } virtual void setDrawBackground(bool) { } - void fromAndroidEditText(const core::stringw& text, int start, int end, + void fromAndroidEditText(const std::u32string& text, int start, int end, int composing_start, int composing_end); void openScreenKeyboard(); - s32 getCursorPosInBox() const { return CursorPos; } - s32 getTextCount() const { return (s32)Text.size(); } + s32 getCursorPosInBox() const { return m_cursor_pos; } + s32 getTextCount() const { return (s32)m_edit_text.size(); } void setTextBoxType(GUIEngine::TextBoxType t) { m_type = t; } protected: - //! Breaks the single text line. - void breakText(); //! sets the area of the given line void setTextRect(s32 line); - //! returns the line number that the cursor is on - s32 getLineFromPos(s32 pos); //! adds a letter to the edit box void inputChar(wchar_t c); //! calculates the current scroll position @@ -144,7 +144,7 @@ namespace GUIEngine void sendGuiEvent(EGUI_EVENT_TYPE type); //! set text markers void setTextMarkers(s32 begin, s32 end); - + void updateCursorDistance(); bool processKey(const SEvent& event); bool processMouse(const SEvent& event); #if defined(_IRR_COMPILE_WITH_WINDOWS_DEVICE_) @@ -155,12 +155,13 @@ namespace GUIEngine core::position2di calculateICPos(); #endif s32 getCursorPos(s32 x, s32 y); + void updateGlyphLayouts(); bool MouseMarking; bool Border; bool OverrideColorEnabled; - s32 MarkBegin; - s32 MarkEnd; + s32 m_mark_begin; + s32 m_mark_end; GUIEngine::TextBoxType m_type; @@ -168,20 +169,16 @@ namespace GUIEngine gui::IGUIFont *OverrideFont, *LastBreakFont; IOSOperator* Operator; - StkTime::TimeType BlinkStartTime; - s32 CursorPos; - s32 HScrollPos, VScrollPos; // scroll position in characters - u32 Max; + uint64_t m_force_show_cursor_time; + s32 m_cursor_pos; + s32 m_scroll_pos; + s32 m_cursor_distance; + u32 m_max_chars; - bool m_rtl; - - bool WordWrap, MultiLine, AutoScroll, PasswordBox; - wchar_t PasswordChar; + bool AutoScroll, PasswordBox; + char32_t PasswordChar; EGUI_ALIGNMENT HAlign, VAlign; - core::array< core::stringw > BrokenText; - core::array< s32 > BrokenTextPositions; - core::rect CurrentTextRect, FrameRect; // temporary values s32 m_composing_start; @@ -190,6 +187,12 @@ namespace GUIEngine /* If true, this editbox will copy text and selection only from * android edittext, and process only mouse event. */ bool m_from_android_edittext; + + /* UTF32 string for shaping and editing to avoid wchar_t issue in + * windows */ + std::u32string m_edit_text; + std::vector m_glyph_layouts; + void correctCursor(s32& cursor_pos, bool left); }; diff --git a/src/guiengine/widgets/text_box_widget.cpp b/src/guiengine/widgets/text_box_widget.cpp index 342c13393..66ee17a84 100644 --- a/src/guiengine/widgets/text_box_widget.cpp +++ b/src/guiengine/widgets/text_box_widget.cpp @@ -22,7 +22,6 @@ #include "guiengine/widgets/CGUIEditBox.hpp" #include "utils/ptr_vector.hpp" #include "utils/translation.hpp" -#include "utils/utf8/unchecked.h" #include #include @@ -39,9 +38,8 @@ public: MyCGUIEditBox(const wchar_t* text, bool border, gui::IGUIEnvironment* environment, gui:: IGUIElement* parent, s32 id, const core::rect& rectangle) : - CGUIEditBox(text, border, environment, parent, id, rectangle, translations->isRTLLanguage()) + CGUIEditBox(text, border, environment, parent, id, rectangle) { - if (translations->isRTLLanguage()) setTextAlignment(irr::gui::EGUIA_LOWERRIGHT, irr::gui::EGUIA_CENTER); } void addListener(GUIEngine::ITextBoxWidgetListener* listener) @@ -68,8 +66,7 @@ public: if (m_listeners[n].onEnterPressed(Text)) { handled = true; - Text = L""; - CursorPos = MarkBegin = MarkEnd = 0; + setText(L""); } } return handled; @@ -271,14 +268,8 @@ ANDROID_EDITTEXT_CALLBACK(ANDROID_PACKAGE_CALLBACK_NAME) if (utf8_text == NULL) return; - // Use utf32 for emoji later - static_assert(sizeof(wchar_t) == sizeof(uint32_t), "Invalid wchar size"); - std::vector utf32line; - utf8::unchecked::utf8to32(utf8_text, utf8_text + strlen(utf8_text), - back_inserter(utf32line)); - utf32line.push_back(0); - - core::stringw to_editbox(&utf32line[0]); + // Android use 32bit wchar_t + std::u32string to_editbox = StringUtils::utf8ToUtf32(utf8_text); env->ReleaseStringUTFChars(text, utf8_text); GUIEngine::addGUIFunctionBeforeRendering([widget_id, to_editbox, start,