From 418308bc7ce4e9924d6280f784222ba45172eea4 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Wed, 6 Nov 2019 12:34:26 +0100 Subject: Characters can have more than one representation in LLFontFreetype * By default all viewer text will use B/W glyphs * Added temporary use_color attribute to LLTextBase for testing --- indra/llui/llfolderviewitem.cpp | 6 +++--- indra/llui/lltextbase.cpp | 11 ++++++++--- indra/llui/lltextbase.h | 3 +++ indra/llui/llview.cpp | 2 +- 4 files changed, 15 insertions(+), 7 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfolderviewitem.cpp b/indra/llui/llfolderviewitem.cpp index 0510e472c5..0c1c3c40ec 100644 --- a/indra/llui/llfolderviewitem.cpp +++ b/indra/llui/llfolderviewitem.cpp @@ -826,7 +826,7 @@ void LLFolderViewItem::drawLabel(const LLFontGL * font, const F32 x, const F32 y // font->renderUTF8(mLabel, 0, x, y, color, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, TRUE); + S32_MAX, getRect().getWidth() - (S32) x - mLabelPaddingRight, &right_x, /*use_ellipses*/TRUE, /*use_color*/FALSE); } void LLFolderViewItem::draw() @@ -905,7 +905,7 @@ void LLFolderViewItem::draw() { font->renderUTF8( mLabelSuffix, 0, right_x, y, isFadeItem() ? color : (LLColor4)sSuffixColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, &right_x, FALSE ); + S32_MAX, S32_MAX, &right_x, /*use_ellipses*/FALSE, /*use_color*/FALSE ); } //--------------------------------------------------------------------------------// @@ -917,7 +917,7 @@ void LLFolderViewItem::draw() F32 yy = (F32)getRect().getHeight() - font->getLineHeight() - (F32)mTextPad - (F32)TOP_PAD; font->renderUTF8( combined_string, mViewModelItem->getFilterStringOffset(), match_string_left, yy, sFilterTextColor, LLFontGL::LEFT, LLFontGL::BOTTOM, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - filter_string_length, S32_MAX, &right_x, FALSE ); + filter_string_length, S32_MAX, &right_x, /*use_ellipses*/FALSE, /*use_color*/FALSE ); } //Gilbert Linden 9-20-2012: Although this should be legal, removing it because it causes the mLabelSuffix rendering to diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index a23741b6dd..64b3a0ddcc 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -163,6 +163,7 @@ LLTextBase::Params::Params() wrap("wrap"), trusted_content("trusted_content", true), use_ellipses("use_ellipses", false), + use_color("use_color", false), parse_urls("parse_urls", false), force_urls_external("force_urls_external", false), parse_highlights("parse_highlights", false) @@ -217,6 +218,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mPlainText ( p.plain_text ), mWordWrap(p.wrap), mUseEllipses( p.use_ellipses ), + mUseColor(p.use_color), mParseHTML(p.parse_urls), mForceUrlsExternal(p.force_urls_external), mParseHighlights(p.parse_highlights), @@ -3198,7 +3200,8 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele mStyle->getShadowType(), length, &right_x, - mEditor.getUseEllipses()); + mEditor.getUseEllipses(), + mEditor.getUseColor()); } rect.mLeft = right_x; @@ -3217,7 +3220,8 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele LLFontGL::NO_SHADOW, length, &right_x, - mEditor.getUseEllipses()); + mEditor.getUseEllipses(), + mEditor.getUseColor()); } rect.mLeft = right_x; if( selection_end < seg_end ) @@ -3234,7 +3238,8 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele mStyle->getShadowType(), length, &right_x, - mEditor.getUseEllipses()); + mEditor.getUseEllipses(), + mEditor.getUseColor()); } return right_x; } diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 9831c35858..6f1e178e36 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -314,6 +314,7 @@ public: plain_text, wrap, use_ellipses, + use_color, parse_urls, force_urls_external, parse_highlights, @@ -389,6 +390,7 @@ public: // used by LLTextSegment layout code bool getWordWrap() { return mWordWrap; } bool getUseEllipses() { return mUseEllipses; } + bool getUseColor() { return mUseColor; } bool truncate(); // returns true of truncation occurred bool isContentTrusted() {return mTrustedContent;} @@ -681,6 +683,7 @@ protected: bool mParseHighlights; // highlight user-defined keywords bool mWordWrap; bool mUseEllipses; + bool mUseColor; bool mTrackEnd; // if true, keeps scroll position at end of document during resize bool mReadOnly; bool mBGVisible; // render background? diff --git a/indra/llui/llview.cpp b/indra/llui/llview.cpp index 89ad8138d8..40537e6c4a 100644 --- a/indra/llui/llview.cpp +++ b/indra/llui/llview.cpp @@ -1282,7 +1282,7 @@ void LLView::drawDebugRect() debug_rect.getWidth(), debug_rect.getHeight()); LLFontGL::getFontSansSerifSmall()->renderUTF8(debug_text, 0, (F32)x, (F32)y, border_color, LLFontGL::HCENTER, LLFontGL::BASELINE, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, - S32_MAX, S32_MAX, NULL, FALSE); + S32_MAX, S32_MAX, NULL, /*use_ellipses*/FALSE, /*use_color*/FALSE); } } LLUI::popMatrix(); -- cgit v1.2.3 From b44ade68e6eea656dc0e31738f9603caffe4d659 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Thu, 7 Nov 2019 20:33:53 +0100 Subject: FIXED Calling LLTextBase::insertStringNoUndo() with more than one segment results in overlapping segment ranges Text is only inserted into the view model *after* the segments are added so if seg1_pos_start is the current EOF: -> 1st segment: getSegIterContaining(seg1_pos_start) returns the last segment and insertSegment() ends up properly adjusting its start/end position -> 2nd segment: getSegIterContaining(seg2_pos_start) returns mSegments.end() since its position is beyond the available and insertSegment() leaves the last 2 segments with overlapping ranges After the fix: -> if index runs past the end of all segments then mSegments.end() is returned (no change) -> if index is a position past the length of text but claimed by a segment then that segment is returned (change) -> if index specifies a position in the middle of the document unclaimed by any segment then the first segment after that position is returned (no change) (this does break the assertion that segment->mStart <= index <= segment->mEnd?) --- indra/llui/lltextbase.cpp | 4 ---- 1 file changed, 4 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 64b3a0ddcc..ecceb289f0 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -1889,8 +1889,6 @@ LLTextBase::segment_set_t::iterator LLTextBase::getSegIterContaining(S32 index) text_len = mLabel.getWString().length(); } - if (index > text_len) { return mSegments.end(); } - // when there are no segments, we return the end iterator, which must be checked by caller if (mSegments.size() <= 1) { return mSegments.begin(); } @@ -1914,8 +1912,6 @@ LLTextBase::segment_set_t::const_iterator LLTextBase::getSegIterContaining(S32 i text_len = mLabel.getWString().length(); } - if (index > text_len) { return mSegments.end(); } - // when there are no segments, we return the end iterator, which must be checked by caller if (mSegments.size() <= 1) { return mSegments.begin(); } -- cgit v1.2.3 From d58b530e805e2b3c943b1ff446ac84a10c500b32 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Thu, 7 Nov 2019 20:48:20 +0100 Subject: Add text_valign to LLTextBase to specify the vertical alignment within a single document line The existing font_valign property is used as to position the entire document so it's impossible to top align a text editor with each line's text centered within that line's extents --- indra/llui/lltextbase.cpp | 10 ++++++---- indra/llui/lltextbase.h | 7 +++++-- 2 files changed, 11 insertions(+), 6 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index ecceb289f0..cc44f46706 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -160,6 +160,7 @@ LLTextBase::Params::Params() line_spacing("line_spacing"), max_text_length("max_length", 255), font_shadow("font_shadow"), + text_valign("text_valign"), wrap("wrap"), trusted_content("trusted_content", true), use_ellipses("use_ellipses", false), @@ -205,6 +206,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mVPad(p.v_pad), mHAlign(p.font_halign), mVAlign(p.font_valign), + mTextVAlign(p.text_valign.isProvided() ? p.text_valign : p.font_valign), mLineSpacingMult(p.line_spacing.multiple), mLineSpacingPixels(p.line_spacing.pixels), mClip(p.clip), @@ -515,7 +517,7 @@ void LLTextBase::drawCursor() fontp = segmentp->getStyle()->getFont(); fontp->render(text, mCursorPos, cursor_rect, LLColor4(1.f - text_color.mV[VRED], 1.f - text_color.mV[VGREEN], 1.f - text_color.mV[VBLUE], alpha), - LLFontGL::LEFT, mVAlign, + LLFontGL::LEFT, mTextVAlign, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, 1); @@ -3191,7 +3193,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele font->render(text, start, rect, color, - LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::LEFT, mEditor.mTextVAlign, LLFontGL::NORMAL, mStyle->getShadowType(), length, @@ -3211,7 +3213,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele font->render(text, start, rect, mStyle->getSelectedColor().get(), - LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::LEFT, mEditor.mTextVAlign, LLFontGL::NORMAL, LLFontGL::NO_SHADOW, length, @@ -3229,7 +3231,7 @@ F32 LLNormalTextSegment::drawClippedSegment(S32 seg_start, S32 seg_end, S32 sele font->render(text, start, rect, color, - LLFontGL::LEFT, mEditor.mVAlign, + LLFontGL::LEFT, mEditor.mTextVAlign, LLFontGL::NORMAL, mStyle->getShadowType(), length, diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 6f1e178e36..99c243a346 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -333,6 +333,8 @@ public: Optional font_shadow; + Optional text_valign; + Params(); }; @@ -673,8 +675,9 @@ protected: // configuration S32 mHPad; // padding on left of text S32 mVPad; // padding above text - LLFontGL::HAlign mHAlign; - LLFontGL::VAlign mVAlign; + LLFontGL::HAlign mHAlign; // horizontal alignment of the document in its entirety + LLFontGL::VAlign mVAlign; // vertical alignment of the document in its entirety + LLFontGL::VAlign mTextVAlign; // vertical alignment of a text segment within a single line of text F32 mLineSpacingMult; // multiple of line height used as space for a single line of text (e.g. 1.5 to get 50% padding) S32 mLineSpacingPixels; // padding between lines bool mBorderVisible; -- cgit v1.2.3 From bab70f6f152952ce755188d29bea1cfd8821a4be Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Mon, 29 Aug 2022 00:00:31 +0200 Subject: Review + resolve minor issues --- indra/llui/lltextbase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 9005d70b2e..0447e7070c 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -208,7 +208,7 @@ LLTextBase::LLTextBase(const LLTextBase::Params &p) mVPad(p.v_pad), mHAlign(p.font_halign), mVAlign(p.font_valign), - mTextVAlign(p.text_valign.isProvided() ? p.text_valign : p.font_valign), + mTextVAlign(p.text_valign.isProvided() ? p.text_valign.getValue() : p.font_valign.getValue()), mLineSpacingMult(p.line_spacing.multiple), mLineSpacingPixels(p.line_spacing.pixels), mClip(p.clip), -- cgit v1.2.3 From 063fe5953ada75177c1668f8b805cd9b79724581 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Wed, 2 Nov 2022 14:25:27 +0100 Subject: Create a separate segment for emoji characters so that we can display them in a slightly larger font size than the surrounding text --- indra/llui/lltextbase.cpp | 49 ++++++++++++++++++++++++++++++++++++++++++++--- indra/llui/lltextbase.h | 12 ++++++++++++ 2 files changed, 58 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 0447e7070c..2a6e6901e4 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -2302,6 +2302,36 @@ void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const s insertStringNoUndo(getLength(), widget_wide_text, &segments); } +void LLTextBase::createTextWithEmojiSegment(const LLWString& text, S32 segment_start, LLStyleConstSP style, segment_vec_t& segments) +{ + LLStyleSP emoji_style; + + S32 text_start = 0, text_kitty = 0, text_len = text.size(); + for (; text_kitty < text_len; text_kitty++) + { + if (LLStringOps::isEmoji(text[text_kitty])) + { + if (text_kitty > text_start) + { + segments.push_back(new LLNormalTextSegment(style, segment_start + text_start, segment_start + text_kitty, *this)); + } + + if (!emoji_style) + { + emoji_style = new LLStyle(*style); + emoji_style->setFont(LLFontGL::getFontEmoji()); + } + segments.push_back(new LLEmojiTextSegment(emoji_style, segment_start + text_kitty, segment_start + text_kitty + 1, *this)); + text_start = text_kitty + 1; + } + } + + if (text_start < text_len) + { + segments.push_back(new LLNormalTextSegment(style, segment_start + text_start, segment_start + text_len, *this)); + } +} + void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) { // Save old state @@ -2334,6 +2364,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig S32 cur_length = getLength(); LLStyleConstSP sp(new LLStyle(highlight_params)); LLTextSegmentPtr segmentp; + segment_vec_t segments; if (underline_on_hover_only || mSkipLinkUnderline) { highlight_params.font.style("NORMAL"); @@ -2342,9 +2373,8 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig } else { - segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); + createTextWithEmojiSegment(wide_text, cur_length, sp, segments); } - segment_vec_t segments; segments.push_back(segmentp); insertStringNoUndo(cur_length, wide_text, &segments); } @@ -2367,7 +2397,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig } else { - segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); + createTextWithEmojiSegment(wide_text, segment_start, sp, segments); } insertStringNoUndo(getLength(), wide_text, &segments); @@ -3514,6 +3544,19 @@ const S32 LLLabelTextSegment::getLength() const return mEditor.getWlabel().length(); } +// +// LLEmojiTextSegment +// +LLEmojiTextSegment::LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor) + : LLNormalTextSegment(style, start, end, editor) +{ +} + +LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible) + : LLNormalTextSegment(color, start, end, editor, is_visible) +{ +} + // // LLOnHoverChangeableTextSegment // diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 590e7c9dbb..fc999c4cca 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -178,6 +178,17 @@ protected: /*virtual*/ const S32 getLength() const; }; +// Text segment that represents a single emoji character that has a different style (=font size) than the rest of +// the document it belongs to +class LLEmojiTextSegment : public LLNormalTextSegment +{ +public: + LLEmojiTextSegment(LLStyleConstSP style, S32 start, S32 end, LLTextBase& editor); + LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE); + + bool canEdit() const override { return false; } +}; + // Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment) class LLOnHoverChangeableTextSegment : public LLNormalTextSegment { @@ -629,6 +640,7 @@ protected: void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); + void createTextWithEmojiSegment(const LLWString& wide_text, S32 segment_start, LLStyleConstSP style, segment_vec_t& segments); S32 normalizeUri(std::string& uri); protected: -- cgit v1.2.3 From 8694f055b64eb7ce13897e49afe73c4d3295a29a Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:09:30 +0200 Subject: Add emoji helper support to LLTextEditor --- indra/llui/lltexteditor.cpp | 40 ++++++++++++++++++++++++++++++++++++++++ indra/llui/lltexteditor.h | 5 +++++ 2 files changed, 45 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index b1f8b00cab..889940cf9a 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -43,6 +43,7 @@ #include "llmath.h" #include "llclipboard.h" +#include "llemojihelper.h" #include "llscrollbar.h" #include "llstl.h" #include "llstring.h" @@ -238,6 +239,7 @@ LLTextEditor::Params::Params() default_color("default_color"), commit_on_focus_lost("commit_on_focus_lost", false), show_context_menu("show_context_menu"), + show_emoji_helper("show_emoji_helper"), enable_tooltip_paste("enable_tooltip_paste") { addSynonym(prevalidate_callback, "text_type"); @@ -259,6 +261,7 @@ LLTextEditor::LLTextEditor(const LLTextEditor::Params& p) : mPrevalidateFunc(p.prevalidate_callback()), mContextMenu(NULL), mShowContextMenu(p.show_context_menu), + mShowEmojiHelper(p.show_emoji_helper), mEnableTooltipPaste(p.enable_tooltip_paste), mPassDelete(FALSE), mKeepSelectionOnReturn(false) @@ -501,6 +504,15 @@ void LLTextEditor::getSegmentsInRange(LLTextEditor::segment_vec_t& segments_out, } } +void LLTextEditor::setShowEmojiHelper(bool show) { + if (!mShowEmojiHelper) + { + LLEmojiHelper::instance().hideHelper(this); + } + + mShowEmojiHelper = show; +} + BOOL LLTextEditor::selectionContainsLineBreaks() { if (hasSelection()) @@ -930,6 +942,12 @@ BOOL LLTextEditor::handleDoubleClick(S32 x, S32 y, MASK mask) S32 LLTextEditor::execute( TextCmd* cmd ) { + if (!mReadOnly && mShowEmojiHelper) + { + // Any change to our contents should always hide the helper + LLEmojiHelper::instance().hideHelper(this); + } + S32 delta = 0; if( cmd->execute(this, &delta) ) { @@ -1124,6 +1142,17 @@ void LLTextEditor::addChar(llwchar wc) setCursorPos(mCursorPos + addChar( mCursorPos, wc )); + if (!mReadOnly && mShowEmojiHelper) + { + LLWString wtext(getWText()); S32 shortCodePos; + if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos)) + { + const LLRect cursorRect = getLocalRectFromDocIndex(mCursorPos); + const LLWString shortCode = wtext.substr(shortCodePos, mCursorPos); + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, wstring_to_utf8str(shortCode)); + } + } + if (!mReadOnly && mAutoreplaceCallback != NULL) { // autoreplace the text, if necessary @@ -1774,6 +1803,11 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) } else { + if (!mReadOnly && mShowEmojiHelper && LLEmojiHelper::instance().handleKey(this, key, mask)) + { + return TRUE; + } + if (mEnableTooltipPaste && LLToolTipMgr::instance().toolTipVisible() && KEY_TAB == key) @@ -1815,6 +1849,12 @@ BOOL LLTextEditor::handleKeyHere(KEY key, MASK mask ) { resetCursorBlink(); needsScroll(); + + if (mShowEmojiHelper) + { + // Dismiss the helper whenever we handled a key that it didn't + LLEmojiHelper::instance().hideHelper(this); + } } return handled; diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 26702b2412..04910b6f68 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -60,6 +60,7 @@ public: ignore_tab, commit_on_focus_lost, show_context_menu, + show_emoji_helper, enable_tooltip_paste, auto_indent; @@ -201,6 +202,9 @@ public: void setShowContextMenu(bool show) { mShowContextMenu = show; } bool getShowContextMenu() const { return mShowContextMenu; } + void setShowEmojiHelper(bool show); + bool getShowEmojiHelper() const { return mShowEmojiHelper; } + void setPassDelete(BOOL b) { mPassDelete = b; } protected: @@ -317,6 +321,7 @@ private: BOOL mAllowEmbeddedItems; bool mShowContextMenu; + bool mShowEmojiHelper; bool mEnableTooltipPaste; bool mPassDelete; bool mKeepSelectionOnReturn; // disabling of removing selected text after pressing of Enter -- cgit v1.2.3 From ec23b4bc633b853223d8442f60e769d44a25fe2d Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:06:41 +0200 Subject: Add the basic emoji dictionary class (responsible for loading them from disk and providing helpful lookup functions) --- indra/llui/CMakeLists.txt | 2 + indra/llui/llemojidictionary.cpp | 177 +++++++++++++++++++++++++++++++++++++++ indra/llui/llemojidictionary.h | 71 ++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 indra/llui/llemojidictionary.cpp create mode 100644 indra/llui/llemojidictionary.h (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index f781ff4110..4e007e429a 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -53,6 +53,7 @@ set(llui_SOURCE_FILES lldockcontrol.cpp lldraghandle.cpp lleditmenuhandler.cpp + llemojidictionary.cpp llf32uictrl.cpp llfiltereditor.cpp llflashtimer.cpp @@ -163,6 +164,7 @@ set(llui_HEADER_FILES lldockablefloater.h lldockcontrol.h lleditmenuhandler.h + llemojidictionary.h llf32uictrl.h llfiltereditor.h llflashtimer.h diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp new file mode 100644 index 0000000000..e149832a8b --- /dev/null +++ b/indra/llui/llemojidictionary.cpp @@ -0,0 +1,177 @@ +/** +* @file llemojidictionary.cpp +* @brief Implementation of LLEmojiDictionary +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library 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; +* version 2.1 of the License only. +* +* This library 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 library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "lldir.h" +#include "llemojidictionary.h" +#include "llsdserialize.h" + +#include + +// ============================================================================ +// Constants +// + +constexpr char SKINNED_EMOJI_FILENAME[] = "emoji_characters.xml"; + +// ============================================================================ +// Helper functions +// + +template +std::list llsd_array_to_list(const LLSD& sd, std::function mutator = {}); + +template<> +std::list llsd_array_to_list(const LLSD& sd, std::function mutator) +{ + std::list result; + for (LLSD::array_const_iterator it = sd.beginArray(), end = sd.endArray(); it != end; ++it) + { + const LLSD& entry = *it; + if (!entry.isString()) + continue; + + result.push_back(entry.asStringRef()); + if (mutator) + { + mutator(result.back()); + } + } + return result; +} + +LLEmojiDescriptor::LLEmojiDescriptor(const LLSD& descriptor_sd) +{ + Name = descriptor_sd["Name"].asStringRef(); + + const LLWString emoji_string = utf8str_to_wstring(descriptor_sd["Character"].asString()); + Character = (1 == emoji_string.size()) ? emoji_string[0] : L'\0'; // We don't currently support character composition + + auto toLower = [](std::string& str) { LLStringUtil::toLower(str); }; + ShortCodes = llsd_array_to_list(descriptor_sd["ShortCodes"], toLower); + Categories = llsd_array_to_list(descriptor_sd["Categories"], toLower); + + if (Name.empty()) + { + Name = ShortCodes.front(); + } +} + +bool LLEmojiDescriptor::isValid() const +{ + return + Character && + !ShortCodes.empty() && + !Categories.empty(); +} + +// ============================================================================ +// LLEmojiDictionary class +// + +LLEmojiDictionary::LLEmojiDictionary() +{ +} + +// static +void LLEmojiDictionary::initClass() +{ + LLEmojiDictionary* pThis = &LLEmojiDictionary::initParamSingleton(); + + LLSD data; + + const std::string filename = gDirUtilp->findSkinnedFilenames(LLDir::XUI, SKINNED_EMOJI_FILENAME, LLDir::CURRENT_SKIN).front(); + llifstream file(filename.c_str()); + if (file.is_open()) + { + LL_DEBUGS() << "Loading emoji characters file at " << filename << LL_ENDL; + LLSDSerialize::fromXML(data, file); + } + + if (data.isUndefined()) + { + LL_WARNS() << "Emoji file characters missing or ill-formed" << LL_ENDL; + return; + } + + for (LLSD::array_const_iterator descriptor_it = data.beginArray(), descriptor_end = data.endArray(); descriptor_it != descriptor_end; ++descriptor_it) + { + LLEmojiDescriptor descriptor(*descriptor_it); + if (!descriptor.isValid()) + { + LL_WARNS() << "Skipping invalid emoji descriptor " << descriptor.Character << LL_ENDL; + continue; + } + pThis->addEmoji(std::move(descriptor)); + } +} + +LLWString LLEmojiDictionary::findMatchingEmojis(std::string needle) +{ + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + LLStringUtil::toLower(needle); + const auto kitty_needle = boost::make_iterator_range((boost::starts_with(needle, ":")) ? needle.begin() + 1 : needle.begin(), needle.end()); + + LLWString wstr; + for (const auto& descr : mEmojis) + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, kitty_needle)) + { + wstr.push_back(descr.Character); + continue; + } + } + + for (const auto& category : descr.Categories) + { + if (boost::icontains(category, kitty_needle)) + { + wstr.push_back(descr.Character); + continue; + } + } + } + + return wstr; +} + +std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) +{ + const auto it = mEmoji2Descr.find(ch); + return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; +} + +void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) +{ + mEmojis.push_back(descr); + const LLEmojiDescriptor& back = mEmojis.back(); + mEmoji2Descr.insert(std::make_pair(descr.Character, &back)); +} + +// ============================================================================ diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h new file mode 100644 index 0000000000..87ea4a5aef --- /dev/null +++ b/indra/llui/llemojidictionary.h @@ -0,0 +1,71 @@ +/** +* @file llemojidictionary.h +* @brief Header file for LLEmojiDictionary +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library 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; +* version 2.1 of the License only. +* +* This library 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 library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#pragma once + +#include "lldictionary.h" +#include "llinitdestroyclass.h" +#include "llsingleton.h" + +// ============================================================================ +// LLEmojiDescriptor class +// + +struct LLEmojiDescriptor +{ + LLEmojiDescriptor(const LLSD& descriptor_sd); + + bool isValid() const; + + std::string Name; + llwchar Character; + std::list ShortCodes; + std::list Categories; +}; + +// ============================================================================ +// LLEmojiDictionary class +// + +class LLEmojiDictionary : public LLParamSingleton, public LLInitClass +{ + LLSINGLETON(LLEmojiDictionary); + ~LLEmojiDictionary() override {}; + +public: + static void initClass(); + LLWString findMatchingEmojis(std::string needle); + std::string getNameFromEmoji(llwchar ch); + +private: + void addEmoji(LLEmojiDescriptor&& descr); + +private: + std::list mEmojis; + std::map mEmoji2Descr; +}; + +// ============================================================================ -- cgit v1.2.3 From dad25bcb1e17e3dc384a9fb35d21b669bc3bbacd Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:17:02 +0200 Subject: Add the emoji helper class which can be used by text-input controls to provide emoji picker support --- indra/llui/CMakeLists.txt | 2 + indra/llui/llemojihelper.cpp | 142 +++++++++++++++++++++++++++++++++++++++++++ indra/llui/llemojihelper.h | 64 +++++++++++++++++++ 3 files changed, 208 insertions(+) create mode 100644 indra/llui/llemojihelper.cpp create mode 100644 indra/llui/llemojihelper.h (limited to 'indra/llui') diff --git a/indra/llui/CMakeLists.txt b/indra/llui/CMakeLists.txt index 4e007e429a..68019734ab 100644 --- a/indra/llui/CMakeLists.txt +++ b/indra/llui/CMakeLists.txt @@ -54,6 +54,7 @@ set(llui_SOURCE_FILES lldraghandle.cpp lleditmenuhandler.cpp llemojidictionary.cpp + llemojihelper.cpp llf32uictrl.cpp llfiltereditor.cpp llflashtimer.cpp @@ -165,6 +166,7 @@ set(llui_HEADER_FILES lldockcontrol.h lleditmenuhandler.h llemojidictionary.h + llemojihelper.h llf32uictrl.h llfiltereditor.h llflashtimer.h diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp new file mode 100644 index 0000000000..d4c31ee986 --- /dev/null +++ b/indra/llui/llemojihelper.cpp @@ -0,0 +1,142 @@ +/** +* @file llemojihelper.h +* @brief Header file for LLEmojiHelper +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library 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; +* version 2.1 of the License only. +* +* This library 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 library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#include "linden_common.h" + +#include "llemojidictionary.h" +#include "llemojihelper.h" +#include "llfloater.h" +#include "llfloaterreg.h" +#include "lluictrl.h" + +// ============================================================================ +// Constants +// + +constexpr char DEFAULT_EMOJI_HELPER_FLOATER[] = "emoji_complete"; +constexpr S32 HELPER_FLOATER_OFFSET_X = 20; +constexpr S32 HELPER_FLOATER_OFFSET_Y = 0; + +// ============================================================================ +// LLEmojiHelper +// + +std::string LLEmojiHelper::getToolTip(llwchar ch) const +{ + return LLEmojiDictionary::instance().getNameFromEmoji(ch); +} + +bool LLEmojiHelper::isActive(const LLUICtrl* ctrl_p) const +{ + return mHostHandle.get() == ctrl_p; +} + +// static +bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S32* pShortCodePos) +{ + S32 shortCodePos = cursorPos; + + while (shortCodePos > 1 && + (LLStringOps::isAlnum(wtext[shortCodePos - 1]) || wtext[shortCodePos - 1] == L'-' || wtext[shortCodePos - 1] == L'_') ) + { + shortCodePos--; + } + + bool isShortCode = (L':' == wtext[shortCodePos - 1]) && (cursorPos - shortCodePos >= 2); + if (pShortCodePos) + *pShortCodePos = (isShortCode) ? shortCodePos - 1 : -1; + return isShortCode; +} + +void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function cb) +{ + if (mHelperHandle.isDead()) + { + LLFloater* pHelperFloater = LLFloaterReg::getInstance(DEFAULT_EMOJI_HELPER_FLOATER); + mHelperHandle = pHelperFloater->getHandle(); + mHelperCommitConn = pHelperFloater->setCommitCallback(std::bind([&](const LLSD& sdValue) { onCommitEmoji(utf8str_to_wstring(sdValue.asStringRef())); }, std::placeholders::_2)); + } + setHostCtrl(hostctrl_p); + mEmojiCommitCb = cb; + + S32 floater_x, floater_y; + if (!hostctrl_p->localPointToOtherView(local_x, local_y, &floater_x, &floater_y, gFloaterView)) + { + LL_ERRS() << "Cannot show emoji helper for non-floater controls." << LL_ENDL; + return; + } + + LLFloater* pHelperFloater = mHelperHandle.get(); + LLRect rct = pHelperFloater->getRect(); + rct.setLeftTopAndSize(floater_x - HELPER_FLOATER_OFFSET_X, floater_y - HELPER_FLOATER_OFFSET_Y + rct.getHeight(), rct.getWidth(), rct.getHeight()); + pHelperFloater->setRect(rct); + pHelperFloater->openFloater(LLSD().with("hint", short_code)); +} + +void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p) +{ + setHostCtrl(nullptr); +} + +bool LLEmojiHelper::handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask) +{ + if (mHelperHandle.isDead() || !isActive(ctrl_p)) + { + return false; + } + + return mHelperHandle.get()->handleKey(key, mask, true); +} + +void LLEmojiHelper::onCommitEmoji(const LLWString& wstr) +{ + if (!mHostHandle.isDead() && mEmojiCommitCb) + { + mEmojiCommitCb(wstr); + } +} + +void LLEmojiHelper::setHostCtrl(LLUICtrl* hostctrl_p) +{ + const LLUICtrl* pCurHostCtrl = mHostHandle.get(); + if (pCurHostCtrl != hostctrl_p) + { + mHostCtrlFocusLostConn.disconnect(); + mHostHandle.markDead(); + mEmojiCommitCb = {}; + + if (!mHelperHandle.isDead()) + { + mHelperHandle.get()->closeFloater(); + } + + if (hostctrl_p) + { + mHostHandle = hostctrl_p->getHandle(); + mHostCtrlFocusLostConn = hostctrl_p->setFocusLostCallback(std::bind([&]() { hideHelper(getHostCtrl()); })); + } + } +} diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h new file mode 100644 index 0000000000..7ed042fc5f --- /dev/null +++ b/indra/llui/llemojihelper.h @@ -0,0 +1,64 @@ +/** +* @file llemojihelper.h +* @brief Header file for LLEmojiHelper +* +* $LicenseInfo:firstyear=2014&license=viewerlgpl$ +* Second Life Viewer Source Code +* Copyright (C) 2014, Linden Research, Inc. +* +* This library 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; +* version 2.1 of the License only. +* +* This library 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 library; if not, write to the Free Software +* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +* +* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA +* $/LicenseInfo$ +*/ + +#pragma once + +#include "llhandle.h" +#include "llsingleton.h" + +#include + +class LLFloater; +class LLUICtrl; + +class LLEmojiHelper : public LLSingleton +{ + LLSINGLETON(LLEmojiHelper) {} + ~LLEmojiHelper() override {} + +public: + // General + std::string getToolTip(llwchar ch) const; + bool isActive(const LLUICtrl* ctrl_p) const; + static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr); + void showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function commit_cb); + void hideHelper(const LLUICtrl* ctrl_p); + + // Eventing + bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask); + void onCommitEmoji(const LLWString& wstr); + +protected: + LLUICtrl* getHostCtrl() const { return mHostHandle.get(); } + void setHostCtrl(LLUICtrl* hostctrl_p); + +private: + LLHandle mHostHandle; + LLHandle mHelperHandle; + boost::signals2::connection mHostCtrlFocusLostConn; + boost::signals2::connection mHelperCommitConn; + std::function mEmojiCommitCb; +}; -- cgit v1.2.3 From 3acb4caa0fb9d381be6cfbe1693ace389d90a16c Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:18:22 +0200 Subject: Add emoji helper support to LLTextEditor --- indra/llui/lltexteditor.cpp | 21 ++++++++++++++++++--- indra/llui/lltexteditor.h | 2 ++ 2 files changed, 20 insertions(+), 3 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 889940cf9a..168c260c7d 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -676,6 +676,21 @@ void LLTextEditor::selectByCursorPosition(S32 prev_cursor_pos, S32 next_cursor_p endSelection(); } +void LLTextEditor::handleEmojiCommit(const LLWString& wstr) +{ + LLWString wtext(getWText()); S32 shortCodePos; + if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos)) + { + remove(shortCodePos, mCursorPos - shortCodePos, true); + + auto styleParams = LLStyle::Params(); + styleParams.font = LLFontGL::getFontEmoji(); + insert(shortCodePos, wstr, false, new LLEmojiTextSegment(new LLStyle(styleParams), shortCodePos, shortCodePos + wstr.size(), *this)); + + setCursorPos(shortCodePos + 1); + } +} + BOOL LLTextEditor::handleMouseDown(S32 x, S32 y, MASK mask) { BOOL handled = FALSE; @@ -1147,9 +1162,9 @@ void LLTextEditor::addChar(llwchar wc) LLWString wtext(getWText()); S32 shortCodePos; if (LLEmojiHelper::isCursorInEmojiCode(wtext, mCursorPos, &shortCodePos)) { - const LLRect cursorRect = getLocalRectFromDocIndex(mCursorPos); - const LLWString shortCode = wtext.substr(shortCodePos, mCursorPos); - LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, wstring_to_utf8str(shortCode)); + const LLRect cursorRect = getLocalRectFromDocIndex(mCursorPos - 1); + const LLWString shortCode = wtext.substr(shortCodePos, mCursorPos - shortCodePos); + LLEmojiHelper::instance().showHelper(this, cursorRect.mLeft, cursorRect.mTop, wstring_to_utf8str(shortCode), std::bind(&LLTextEditor::handleEmojiCommit, this, std::placeholders::_1)); } } diff --git a/indra/llui/lltexteditor.h b/indra/llui/lltexteditor.h index 04910b6f68..4c8175a286 100644 --- a/indra/llui/lltexteditor.h +++ b/indra/llui/lltexteditor.h @@ -92,6 +92,8 @@ public: static S32 spacesPerTab(); + void handleEmojiCommit(const LLWString& wstr); + // mousehandler overrides virtual BOOL handleMouseDown(S32 x, S32 y, MASK mask); virtual BOOL handleMouseUp(S32 x, S32 y, MASK mask); -- cgit v1.2.3 From f6f52d327be5f03265d66a95df6fc0716f91ca07 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:35:44 +0200 Subject: Provide a way for a floater to remain the topmost floater, even when focus is given to a different floater --- indra/llui/llfloater.cpp | 24 +++++++++++++++++++++++- indra/llui/llfloater.h | 4 ++++ 2 files changed, 27 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 0e42922543..04f6b11b7c 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -3039,7 +3039,29 @@ void LLFloaterView::syncFloaterTabOrder() LLFloater* floaterp = dynamic_cast(*child_it); if (gFocusMgr.childHasKeyboardFocus(floaterp)) { - bringToFront(floaterp, FALSE); + if (mFrontChild != floaterp) + { + // Grab a list of the top floaters that want to stay on top of the focused floater + std::list listTop; + if (mFrontChild && !mFrontChild->canFocusStealFrontmost()) + { + for (LLView* childfloaterp : *getChildList()) + { + if (static_cast(childfloaterp)->canFocusStealFrontmost()) + break; + listTop.push_back(childfloaterp); + } + } + + bringToFront(floaterp, FALSE); + + // Restore top floaters + for (LLView* childp :listTop) + { + sendChildToFront(childp); + } + } + break; } } diff --git a/indra/llui/llfloater.h b/indra/llui/llfloater.h index 2672d600c6..1d4aff31eb 100644 --- a/indra/llui/llfloater.h +++ b/indra/llui/llfloater.h @@ -313,6 +313,9 @@ public: /*virtual*/ void setVisible(BOOL visible); // do not override /*virtual*/ void onVisibilityChange ( BOOL new_visibility ); // do not override + bool canFocusStealFrontmost() const { return mFocusStealsFrontmost; } + void setFocusStealsFrontmost(bool wants_frontmost) { mFocusStealsFrontmost = wants_frontmost; } + void setFrontmost(BOOL take_focus = TRUE, BOOL restore = TRUE); virtual void setVisibleAndFrontmost(BOOL take_focus=TRUE, const LLSD& key = LLSD()); @@ -478,6 +481,7 @@ private: BOOL mCanTearOff; BOOL mCanMinimize; BOOL mCanClose; + bool mFocusStealsFrontmost = true; // FALSE if we don't want the currently focused floater to cover this floater without user interaction BOOL mDragOnLeft; BOOL mResizable; -- cgit v1.2.3 From 8d08f417dc1f5b2681774f000951993e0f0cf79f Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 16:37:10 +0200 Subject: Show tooltip when hovering over an emoji text segment (currently will show its shortcode) --- indra/llui/lltextbase.cpp | 14 ++++++++++++++ indra/llui/lltextbase.h | 1 + 2 files changed, 15 insertions(+) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 2a6e6901e4..693dcb7b8d 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -3557,6 +3557,20 @@ LLEmojiTextSegment::LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end { } +BOOL LLEmojiTextSegment::handleToolTip(S32 x, S32 y, MASK mask) +{ + if (mTooltip.empty()) + { + LLWString emoji = getWText().substr(getStart(), getEnd() - getStart()); + if (!emoji.empty()) + { + mTooltip = LLEmojiHelper::instance().getToolTip(emoji[0]); + } + } + + return LLNormalTextSegment::handleToolTip(x, y, mask); +} + // // LLOnHoverChangeableTextSegment // diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index fc999c4cca..31e9f16110 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -187,6 +187,7 @@ public: LLEmojiTextSegment(const LLColor4& color, S32 start, S32 end, LLTextBase& editor, BOOL is_visible = TRUE); bool canEdit() const override { return false; } + BOOL handleToolTip(S32 x, S32 y, MASK mask); }; // Text segment that changes it's style depending of mouse pointer position ( is it inside or outside segment) -- cgit v1.2.3 From 58cdcd5dd23778c6fad9fa0da31b670ceff8eeeb Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 17:47:51 +0200 Subject: Handle return and escape in the mini emoji helper --- indra/llui/llemojihelper.cpp | 5 +++++ indra/llui/llemojihelper.h | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'indra/llui') diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index d4c31ee986..54c801ab7b 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -98,6 +98,11 @@ void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, c void LLEmojiHelper::hideHelper(const LLUICtrl* ctrl_p) { + if (ctrl_p && !isActive(ctrl_p)) + { + return; + } + setHostCtrl(nullptr); } diff --git a/indra/llui/llemojihelper.h b/indra/llui/llemojihelper.h index 7ed042fc5f..63f5c640c9 100644 --- a/indra/llui/llemojihelper.h +++ b/indra/llui/llemojihelper.h @@ -45,7 +45,7 @@ public: bool isActive(const LLUICtrl* ctrl_p) const; static bool isCursorInEmojiCode(const LLWString& wtext, S32 cursor_pos, S32* short_code_pos_p = nullptr); void showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function commit_cb); - void hideHelper(const LLUICtrl* ctrl_p); + void hideHelper(const LLUICtrl* ctrl_p = nullptr); // Eventing bool handleKey(const LLUICtrl* ctrl_p, KEY key, MASK mask); -- cgit v1.2.3 From fb1553d8cf704eeb222de7ebd520a81fa242b317 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 17:55:13 +0200 Subject: Cannot use the :+1 emoji shortcode --- indra/llui/llemojihelper.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index 54c801ab7b..551f0331e7 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -59,8 +59,18 @@ bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S { S32 shortCodePos = cursorPos; - while (shortCodePos > 1 && - (LLStringOps::isAlnum(wtext[shortCodePos - 1]) || wtext[shortCodePos - 1] == L'-' || wtext[shortCodePos - 1] == L'_') ) + auto isPartOfShortcode = [](llwchar ch) { + switch (ch) + { + case L'-': + case L'_': + case L'+': + return true; + default: + return LLStringOps::isAlnum(ch); + } + }; + while (shortCodePos > 1 && isPartOfShortcode(wtext[shortCodePos - 1])) { shortCodePos--; } -- cgit v1.2.3 From d1dbefc09b77990563d22aaf08d0c5708cc6cbbe Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Sun, 23 Oct 2022 19:05:19 +0200 Subject: Refactor emoji matching --- indra/llui/llemojidictionary.cpp | 70 ++++++++++++++++++++++++---------------- indra/llui/llemojidictionary.h | 2 +- 2 files changed, 43 insertions(+), 29 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index e149832a8b..eefa4047a2 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -31,6 +31,8 @@ #include "llsdserialize.h" #include +#include +#include // ============================================================================ // Constants @@ -89,6 +91,41 @@ bool LLEmojiDescriptor::isValid() const !Categories.empty(); } +struct emoji_filter_base +{ + emoji_filter_base(const std::string& needle) + { + // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category + mNeedle = (boost::starts_with(needle, ":")) ? needle.substr(1) : needle; + LLStringUtil::toLower(mNeedle); + } + +protected: + std::string mNeedle; +}; + +struct emoji_filter_shortcode_or_category_contains : public emoji_filter_base +{ + emoji_filter_shortcode_or_category_contains(const std::string& needle) : emoji_filter_base(needle) {} + + bool operator()(const LLEmojiDescriptor& descr) const + { + for (const auto& short_code : descr.ShortCodes) + { + if (boost::icontains(short_code, mNeedle)) + return true; + } + + for (const auto& category : descr.Categories) + { + if (boost::icontains(category, mNeedle)) + return true; + } + + return false; + } +}; + // ============================================================================ // LLEmojiDictionary class // @@ -130,35 +167,12 @@ void LLEmojiDictionary::initClass() } } -LLWString LLEmojiDictionary::findMatchingEmojis(std::string needle) +LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const { - // Search without the colon (if present) so the user can type ':food' and see all emojis in the 'Food' category - LLStringUtil::toLower(needle); - const auto kitty_needle = boost::make_iterator_range((boost::starts_with(needle, ":")) ? needle.begin() + 1 : needle.begin(), needle.end()); - - LLWString wstr; - for (const auto& descr : mEmojis) - { - for (const auto& short_code : descr.ShortCodes) - { - if (boost::icontains(short_code, kitty_needle)) - { - wstr.push_back(descr.Character); - continue; - } - } - - for (const auto& category : descr.Categories) - { - if (boost::icontains(category, kitty_needle)) - { - wstr.push_back(descr.Character); - continue; - } - } - } - - return wstr; + LLWString result; + boost::transform(mEmojis | boost::adaptors::filtered(emoji_filter_shortcode_or_category_contains(needle)), + std::back_inserter(result), [](const auto& descr) { return descr.Character; }); + return result; } std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h index 87ea4a5aef..3fa55cd417 100644 --- a/indra/llui/llemojidictionary.h +++ b/indra/llui/llemojidictionary.h @@ -57,7 +57,7 @@ class LLEmojiDictionary : public LLParamSingleton, public LLI public: static void initClass(); - LLWString findMatchingEmojis(std::string needle); + LLWString findMatchingEmojis(const std::string& needle) const; std::string getNameFromEmoji(llwchar ch); private: -- cgit v1.2.3 From c40d3351d511b19f4468f7467be38499bf16588f Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Wed, 2 Nov 2022 19:05:24 +0100 Subject: Commit immediately if the user already typed a full shortcode --- indra/llui/llemojidictionary.cpp | 17 +++++++++++++---- indra/llui/llemojidictionary.h | 6 ++++-- indra/llui/llemojihelper.cpp | 8 ++++++++ indra/llui/lltextbase.cpp | 1 + 4 files changed, 26 insertions(+), 6 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index eefa4047a2..c31638b0bf 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -175,17 +175,26 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const return result; } -std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) +const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const +{ + const auto it = mShortCode2Descr.find(short_code); + return (mShortCode2Descr.end() != it) ? &it->second : nullptr; +} + +std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const { const auto it = mEmoji2Descr.find(ch); - return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; + return (mEmoji2Descr.end() != it) ? it->second.Name : LLStringUtil::null; } void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) { mEmojis.push_back(descr); - const LLEmojiDescriptor& back = mEmojis.back(); - mEmoji2Descr.insert(std::make_pair(descr.Character, &back)); + mEmoji2Descr.insert(std::make_pair(descr.Character, mEmojis.back())); + for (const std::string& shortCode : descr.ShortCodes) + { + mShortCode2Descr.insert(std::make_pair(shortCode, mEmojis.back())); + } } // ============================================================================ diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h index 3fa55cd417..0cde663719 100644 --- a/indra/llui/llemojidictionary.h +++ b/indra/llui/llemojidictionary.h @@ -58,14 +58,16 @@ class LLEmojiDictionary : public LLParamSingleton, public LLI public: static void initClass(); LLWString findMatchingEmojis(const std::string& needle) const; - std::string getNameFromEmoji(llwchar ch); + const LLEmojiDescriptor* getDescriptorFromShortCode(const std::string& short_code) const; + std::string getNameFromEmoji(llwchar ch) const; private: void addEmoji(LLEmojiDescriptor&& descr); private: std::list mEmojis; - std::map mEmoji2Descr; + std::map mEmoji2Descr; + std::map mShortCode2Descr; }; // ============================================================================ diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index 551f0331e7..32471e59a8 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -83,6 +83,14 @@ bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S void LLEmojiHelper::showHelper(LLUICtrl* hostctrl_p, S32 local_x, S32 local_y, const std::string& short_code, std::function cb) { + // Commit immediately if the user already typed a full shortcode + if (const auto* emojiDescrp = LLEmojiDictionary::instance().getDescriptorFromShortCode(short_code)) + { + cb(LLWString(1, emojiDescrp->Character)); + hideHelper(); + return; + } + if (mHelperHandle.isDead()) { LLFloater* pHelperFloater = LLFloaterReg::getInstance(DEFAULT_EMOJI_HELPER_FLOATER); diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index 693dcb7b8d..b88c7ced40 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -29,6 +29,7 @@ #include "lltextbase.h" +#include "llemojihelper.h" #include "lllocalcliprect.h" #include "llmenugl.h" #include "llscrollcontainer.h" -- cgit v1.2.3 From 81dd143d0d901e8e5234cff01fbda246e4621628 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 8 Nov 2022 00:16:58 +0100 Subject: [FIXED] Various minor issues - Typing :+1: doesn't replace the short code with the thumbs-up emoji - Moving the mouse over the emoji complete panel highlights the wrong emoji when mScrollPos > 0 - Emoji complete panel is missing attributes - Crash when attempting to show the tooltip for an emoji text segment - Emoji autocomplete panel can sometimes show empty (type ':cat', select the heart eyed one, Ctrl-Z and then type 2 which should show the emoji for :cat2 but shows an empty square instead) --- indra/llui/llemojidictionary.cpp | 8 ++++---- indra/llui/llemojidictionary.h | 4 ++-- indra/llui/llemojihelper.cpp | 3 ++- 3 files changed, 8 insertions(+), 7 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llemojidictionary.cpp b/indra/llui/llemojidictionary.cpp index c31638b0bf..b70a9b2e7a 100644 --- a/indra/llui/llemojidictionary.cpp +++ b/indra/llui/llemojidictionary.cpp @@ -178,22 +178,22 @@ LLWString LLEmojiDictionary::findMatchingEmojis(const std::string& needle) const const LLEmojiDescriptor* LLEmojiDictionary::getDescriptorFromShortCode(const std::string& short_code) const { const auto it = mShortCode2Descr.find(short_code); - return (mShortCode2Descr.end() != it) ? &it->second : nullptr; + return (mShortCode2Descr.end() != it) ? it->second : nullptr; } std::string LLEmojiDictionary::getNameFromEmoji(llwchar ch) const { const auto it = mEmoji2Descr.find(ch); - return (mEmoji2Descr.end() != it) ? it->second.Name : LLStringUtil::null; + return (mEmoji2Descr.end() != it) ? it->second->Name : LLStringUtil::null; } void LLEmojiDictionary::addEmoji(LLEmojiDescriptor&& descr) { mEmojis.push_back(descr); - mEmoji2Descr.insert(std::make_pair(descr.Character, mEmojis.back())); + mEmoji2Descr.insert(std::make_pair(descr.Character, &mEmojis.back())); for (const std::string& shortCode : descr.ShortCodes) { - mShortCode2Descr.insert(std::make_pair(shortCode, mEmojis.back())); + mShortCode2Descr.insert(std::make_pair(shortCode, &mEmojis.back())); } } diff --git a/indra/llui/llemojidictionary.h b/indra/llui/llemojidictionary.h index 0cde663719..46a61f1cd7 100644 --- a/indra/llui/llemojidictionary.h +++ b/indra/llui/llemojidictionary.h @@ -66,8 +66,8 @@ private: private: std::list mEmojis; - std::map mEmoji2Descr; - std::map mShortCode2Descr; + std::map mEmoji2Descr; + std::map mShortCode2Descr; }; // ============================================================================ diff --git a/indra/llui/llemojihelper.cpp b/indra/llui/llemojihelper.cpp index 32471e59a8..1e4c19a183 100644 --- a/indra/llui/llemojihelper.cpp +++ b/indra/llui/llemojihelper.cpp @@ -57,7 +57,8 @@ bool LLEmojiHelper::isActive(const LLUICtrl* ctrl_p) const // static bool LLEmojiHelper::isCursorInEmojiCode(const LLWString& wtext, S32 cursorPos, S32* pShortCodePos) { - S32 shortCodePos = cursorPos; + // If the cursor is currently on a colon start the check one character further back + S32 shortCodePos = (cursorPos == 0 || L':' != wtext[cursorPos - 1]) ? cursorPos : cursorPos - 1; auto isPartOfShortcode = [](llwchar ch) { switch (ch) -- cgit v1.2.3 From 17a3924a28770e6910022ad8f627bb8e2b667730 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Tue, 8 Nov 2022 01:10:09 +0100 Subject: Add proper mouse down handler to the emoji complete panel -> the previous commit didn't properly set mFrontChild after restoring the topmost floaters -> additionally we don't want mouse clicks in "can't steal focus from frontmost" floaters to set focus to them --- indra/llui/llfloater.cpp | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/llfloater.cpp b/indra/llui/llfloater.cpp index 04f6b11b7c..6f341bc0cd 100644 --- a/indra/llui/llfloater.cpp +++ b/indra/llui/llfloater.cpp @@ -2486,7 +2486,7 @@ void LLFloaterView::bringToFront(LLFloater* child, BOOL give_focus, BOOL restore if (mFrontChild == child) { - if (give_focus && !gFocusMgr.childHasKeyboardFocus(child)) + if (give_focus && child->canFocusStealFrontmost() && !gFocusMgr.childHasKeyboardFocus(child)) { child->setFocus(TRUE); } @@ -3042,24 +3042,29 @@ void LLFloaterView::syncFloaterTabOrder() if (mFrontChild != floaterp) { // Grab a list of the top floaters that want to stay on top of the focused floater - std::list listTop; + std::list listTop; if (mFrontChild && !mFrontChild->canFocusStealFrontmost()) { - for (LLView* childfloaterp : *getChildList()) + for (LLView* childp : *getChildList()) { - if (static_cast(childfloaterp)->canFocusStealFrontmost()) + LLFloater* child_floaterp = static_cast(childp); + if (child_floaterp->canFocusStealFrontmost()) break; - listTop.push_back(childfloaterp); + listTop.push_back(child_floaterp); } } bringToFront(floaterp, FALSE); // Restore top floaters - for (LLView* childp :listTop) - { - sendChildToFront(childp); - } + if (!listTop.empty()) + { + for (LLView* childp : listTop) + { + sendChildToFront(childp); + } + mFrontChild = listTop.back(); + } } break; -- cgit v1.2.3 From 62d62abe9c35066bb7bb29155046311245854563 Mon Sep 17 00:00:00 2001 From: Kitty Barnett Date: Wed, 9 Nov 2022 00:08:43 +0100 Subject: [FIXED] Using the Windows emoji picker or pasting text containing emojis doesn't create emoji segments (=emoji size is same size as the text size) -> Partial revert of 063fe59 --- indra/llui/lltextbase.cpp | 55 +++++++++++++++++++---------------------------- indra/llui/lltextbase.h | 1 - 2 files changed, 22 insertions(+), 34 deletions(-) (limited to 'indra/llui') diff --git a/indra/llui/lltextbase.cpp b/indra/llui/lltextbase.cpp index b88c7ced40..28165aa1ef 100644 --- a/indra/llui/lltextbase.cpp +++ b/indra/llui/lltextbase.cpp @@ -834,6 +834,25 @@ S32 LLTextBase::insertStringNoUndo(S32 pos, const LLWString &wstr, LLTextBase::s } } + // Insert special segments where necessary (insertSegment takes care of splitting normal text segments around them for us) + { + LLStyleSP emoji_style; + for (S32 text_kitty = 0, text_len = wstr.size(); text_kitty < text_len; text_kitty++) + { + if (LLStringOps::isEmoji(wstr[text_kitty])) + { + if (!emoji_style) + { + emoji_style = new LLStyle(getStyleParams()); + emoji_style->setFont(LLFontGL::getFontEmoji()); + } + + S32 new_seg_start = pos + text_kitty; + insertSegment(new LLEmojiTextSegment(emoji_style, new_seg_start, new_seg_start + 1, *this)); + } + } + } + getViewModel()->getEditableDisplay().insert(pos, wstr); if ( truncate() ) @@ -2303,36 +2322,6 @@ void LLTextBase::appendWidget(const LLInlineViewSegment::Params& params, const s insertStringNoUndo(getLength(), widget_wide_text, &segments); } -void LLTextBase::createTextWithEmojiSegment(const LLWString& text, S32 segment_start, LLStyleConstSP style, segment_vec_t& segments) -{ - LLStyleSP emoji_style; - - S32 text_start = 0, text_kitty = 0, text_len = text.size(); - for (; text_kitty < text_len; text_kitty++) - { - if (LLStringOps::isEmoji(text[text_kitty])) - { - if (text_kitty > text_start) - { - segments.push_back(new LLNormalTextSegment(style, segment_start + text_start, segment_start + text_kitty, *this)); - } - - if (!emoji_style) - { - emoji_style = new LLStyle(*style); - emoji_style->setFont(LLFontGL::getFontEmoji()); - } - segments.push_back(new LLEmojiTextSegment(emoji_style, segment_start + text_kitty, segment_start + text_kitty + 1, *this)); - text_start = text_kitty + 1; - } - } - - if (text_start < text_len) - { - segments.push_back(new LLNormalTextSegment(style, segment_start + text_start, segment_start + text_len, *this)); - } -} - void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only) { // Save old state @@ -2365,7 +2354,6 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig S32 cur_length = getLength(); LLStyleConstSP sp(new LLStyle(highlight_params)); LLTextSegmentPtr segmentp; - segment_vec_t segments; if (underline_on_hover_only || mSkipLinkUnderline) { highlight_params.font.style("NORMAL"); @@ -2374,8 +2362,9 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig } else { - createTextWithEmojiSegment(wide_text, cur_length, sp, segments); + segmentp = new LLNormalTextSegment(sp, cur_length, cur_length + wide_text.size(), *this); } + segment_vec_t segments; segments.push_back(segmentp); insertStringNoUndo(cur_length, wide_text, &segments); } @@ -2398,7 +2387,7 @@ void LLTextBase::appendAndHighlightTextImpl(const std::string &new_text, S32 hig } else { - createTextWithEmojiSegment(wide_text, segment_start, sp, segments); + segments.push_back(new LLNormalTextSegment(sp, segment_start, segment_end, *this )); } insertStringNoUndo(getLength(), wide_text, &segments); diff --git a/indra/llui/lltextbase.h b/indra/llui/lltextbase.h index 31e9f16110..a047db25b2 100644 --- a/indra/llui/lltextbase.h +++ b/indra/llui/lltextbase.h @@ -641,7 +641,6 @@ protected: void appendTextImpl(const std::string &new_text, const LLStyle::Params& input_params = LLStyle::Params()); void appendAndHighlightTextImpl(const std::string &new_text, S32 highlight_part, const LLStyle::Params& style_params, bool underline_on_hover_only = false); - void createTextWithEmojiSegment(const LLWString& wide_text, S32 segment_start, LLStyleConstSP style, segment_vec_t& segments); S32 normalizeUri(std::string& uri); protected: -- cgit v1.2.3