diff --git a/src/console_gui.cpp b/src/console_gui.cpp index 530cb8bfd0..1e1fd069b3 100644 --- a/src/console_gui.cpp +++ b/src/console_gui.cpp @@ -351,12 +351,21 @@ struct IConsoleWindow : Window int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0); Point p1 = GetCharPosInString(_iconsole_cmdline.buf, from, FS_NORMAL); - Point p2 = from != to ? GetCharPosInString(_iconsole_cmdline.buf, from, FS_NORMAL) : p1; + Point p2 = from != to ? GetCharPosInString(_iconsole_cmdline.buf, from) : p1; Rect r = {this->line_offset + delta + p1.x, this->height - this->line_height, this->line_offset + delta + p2.x, this->height}; return r; } + virtual const char *GetTextCharacterAtPosition(const Point &pt) const + { + int delta = min(this->width - this->line_offset - _iconsole_cmdline.pixels - ICON_RIGHT_BORDERWIDTH, 0); + + if (!IsInsideMM(pt.y, this->height - this->line_height, this->height)) return NULL; + + return GetCharAtPosition(_iconsole_cmdline.buf, pt.x - delta); + } + virtual void OnMouseWheel(int wheel) { this->Scroll(-wheel); diff --git a/src/gfx.cpp b/src/gfx.cpp index 0e72e23d75..4fed391947 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -721,6 +721,21 @@ Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsiz return layout.GetCharPosition(ch); } +/** + * Get the character from a string that is drawn at a specific position. + * @param str String to test. + * @param x Position relative to the start of the string. + * @param start_fontsize Font size to start the text with. + * @return Pointer to the character at the position or NULL if there is no character at the position. + */ +const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize) +{ + if (x < 0) return NULL; + + Layouter layout(str, INT32_MAX, TC_FROMSTRING, start_fontsize); + return layout.GetCharAtPosition(x); +} + /** * Draw single character horizontally centered around (x,y) * @param c Character (glyph) to draw diff --git a/src/gfx_func.h b/src/gfx_func.h index 5dee846b23..f720672f88 100644 --- a/src/gfx_func.h +++ b/src/gfx_func.h @@ -129,6 +129,7 @@ Dimension GetStringMultiLineBoundingBox(StringID str, const Dimension &suggestio Dimension GetStringMultiLineBoundingBox(const char *str, const Dimension &suggestion); void LoadStringWidthTable(bool monospace = false); Point GetCharPosInString(const char *str, const char *ch, FontSize start_fontsize = FS_NORMAL); +const char *GetCharAtPosition(const char *str, int x, FontSize start_fontsize = FS_NORMAL); void DrawDirtyBlocks(); void SetDirtyBlocks(int left, int top, int right, int bottom); diff --git a/src/gfx_layout.cpp b/src/gfx_layout.cpp index 165edee3b6..4d1d382d6c 100644 --- a/src/gfx_layout.cpp +++ b/src/gfx_layout.cpp @@ -565,6 +565,50 @@ Point Layouter::GetCharPosition(const char *ch) const return p; } +/** + * Get the character that is at a position. + * @param x Position in the string. + * @return Pointer to the character at the position or NULL if no character is at the position. + */ +const char *Layouter::GetCharAtPosition(int x) const +{ + const ParagraphLayout::Line *line = *this->Begin();; + + for (int run_index = 0; run_index < line->countRuns(); run_index++) { + const ParagraphLayout::VisualRun *run = line->getVisualRun(run_index); + + for (int i = 0; i < run->getGlyphCount(); i++) { + /* Not a valid glyph (empty). */ + if (run->getGlyphs()[i] == 0xFFFF) continue; + + int begin_x = run->getPositions()[i * 2]; + int end_x = run->getPositions()[i * 2 + 2]; + + if (IsInsideMM(x, begin_x, end_x)) { + /* Found our glyph, now convert to UTF-8 string index. */ + size_t index = run->getGlyphToCharMap()[i]; + + size_t cur_idx = 0; + for (const char *str = this->string; *str != '\0'; ) { + if (cur_idx == index) return str; + + WChar c; + size_t len = Utf8Decode(&c, str); +#ifdef WITH_ICU + /* ICU uses UTF-16 internally which means we need to account for surrogate pairs. */ + cur_idx += len < 4 ? 1 : 2; +#else + cur_idx++; +#endif + str += len; + } + } + } + } + + return NULL; +} + /** * Get a static font instance. */ diff --git a/src/gfx_layout.h b/src/gfx_layout.h index 27c1e9c082..b4f9afdc65 100644 --- a/src/gfx_layout.h +++ b/src/gfx_layout.h @@ -212,6 +212,7 @@ public: Layouter(const char *str, int maxw = INT32_MAX, TextColour colour = TC_FROMSTRING, FontSize fontsize = FS_NORMAL); Dimension GetBounds(); Point GetCharPosition(const char *ch) const; + const char *GetCharAtPosition(int x) const; static void ResetFontCache(FontSize size); static void ResetLineCache(); diff --git a/src/misc_gui.cpp b/src/misc_gui.cpp index b309693bfd..d771f92eeb 100644 --- a/src/misc_gui.cpp +++ b/src/misc_gui.cpp @@ -854,6 +854,39 @@ Rect QueryString::GetBoundingRect(const Window *w, int wid, const char *from, co return r; } +/** + * Get the character that is rendered at a position. + * @param w Window the edit box is in. + * @param wid Widget index. + * @param pt Position to test. + * @return Pointer to the character at the position or NULL if no character is at the position. + */ +const char *QueryString::GetCharAtPosition(const Window *w, int wid, const Point &pt) const +{ + const NWidgetLeaf *wi = w->GetWidget(wid); + + assert((wi->type & WWT_MASK) == WWT_EDITBOX); + + bool rtl = _current_text_dir == TD_RTL; + Dimension sprite_size = GetSpriteSize(rtl ? SPR_IMG_DELETE_RIGHT : SPR_IMG_DELETE_LEFT); + int clearbtn_width = sprite_size.width + WD_IMGBTN_LEFT + WD_IMGBTN_RIGHT; + + int left = wi->pos_x + (rtl ? clearbtn_width : 0); + int right = wi->pos_x + (rtl ? wi->current_x : wi->current_x - clearbtn_width) - 1; + + int top = wi->pos_y + WD_FRAMERECT_TOP; + int bottom = wi->pos_y + wi->current_y - 1 - WD_FRAMERECT_BOTTOM; + + if (!IsInsideMM(pt.y, top, bottom)) return NULL; + + /* Clamp caret position to be inside our current width. */ + const Textbuf *tb = &this->text; + int delta = min(0, (right - left) - tb->pixels - 10); + if (tb->caretxoffs + delta < 0) delta = -tb->caretxoffs; + + return ::GetCharAtPosition(tb->buf, pt.x - delta - left); +} + void QueryString::ClickEditBox(Window *w, Point pt, int wid, int click_count, bool focus_changed) { const NWidgetLeaf *wi = w->GetWidget(wid); diff --git a/src/querystring_gui.h b/src/querystring_gui.h index 5c0bdbabef..a1f3896dd1 100644 --- a/src/querystring_gui.h +++ b/src/querystring_gui.h @@ -56,6 +56,7 @@ public: Point GetCaretPosition(const Window *w, int wid) const; Rect GetBoundingRect(const Window *w, int wid, const char *from, const char *to) const; + const char *GetCharAtPosition(const Window *w, int wid, const Point &pt) const; /** * Get the current text. diff --git a/src/video/cocoa/cocoa_v.mm b/src/video/cocoa/cocoa_v.mm index e4897ac507..69af4786db 100644 --- a/src/video/cocoa/cocoa_v.mm +++ b/src/video/cocoa/cocoa_v.mm @@ -909,7 +909,16 @@ static const char *Utf8AdvanceByUtf16Units(const char *str, NSUInteger count) /** Get the character that is rendered at the given point. */ - (NSUInteger)characterIndexForPoint:(NSPoint)thePoint { - return NSNotFound; + if (!EditBoxInGlobalFocus()) return NSNotFound; + + NSPoint view_pt = [ self convertPoint:[ [ self window ] convertScreenToBase:thePoint ] fromView:nil ]; + + Point pt = { view_pt.x, [ self frame ].size.height - view_pt.y }; + + const char *ch = _focused_window->GetTextCharacterAtPosition(pt); + if (ch == NULL) return NSNotFound; + + return CountUtf16Units(_focused_window->GetFocusedText(), ch); } /** Get the bounding rect for the given range. */ diff --git a/src/window.cpp b/src/window.cpp index 3496c56335..df7d6ef3d5 100644 --- a/src/window.cpp +++ b/src/window.cpp @@ -393,6 +393,19 @@ QueryString *Window::GetQueryString(uint widnum) return r; } +/** + * Get the character that is rendered at a position by the focused edit box. + * @param pt The position to test. + * @return Pointer to the character at the position or NULL if no character is at the position. + */ +/* virtual */ const char *Window::GetTextCharacterAtPosition(const Point &pt) const +{ + if (this->nested_focus != NULL && this->nested_focus->type == WWT_EDITBOX) { + return this->GetQueryString(this->nested_focus->index)->GetCharAtPosition(this, this->nested_focus->index, pt); + } + + return NULL; +} /** * Set the window that has the focus diff --git a/src/window_gui.h b/src/window_gui.h index da5c992ede..f17eb11d89 100644 --- a/src/window_gui.h +++ b/src/window_gui.h @@ -350,6 +350,7 @@ public: virtual const char *GetMarkedText(size_t *length) const; virtual Point GetCaretPosition() const; virtual Rect GetTextBoundingRect(const char *from, const char *to) const; + virtual const char *GetTextCharacterAtPosition(const Point &pt) const; void InitNested(WindowNumber number = 0); void CreateNestedTree(bool fill_nested = true);