/// <summary> /// Updates the <see cref="VisualText"/> including text wrapping. /// </summary> /// <param name="text">The text.</param> /// <param name="textWidth">The available width.</param> /// <param name="font">The font.</param> private void UpdateVisualText(string text, float textWidth, SpriteFont font) { VisualText.Clear(); WrapText(textWidth, font); if (_lineStarts == null || _lineStarts.Count == 0) { // Single-line: Just add all text. The renderer has to clip the text. VisualText.Append(text); } else { // Multiline: Add line by line. for (int i = 0; i < _lineStarts.Count; i++) { int start = _lineStarts[i]; int end = (i + 1 < _lineStarts.Count) ? _lineStarts[i + 1] : -text.Length; VisualText.Append(text, Math.Abs(start), Math.Abs(end) - Math.Abs(start)); // A positive index indicates that a newline character needs to be added // for text wrapping. if (end > 0) { VisualText.Append('\n'); } } } }
protected override Vector2F OnMeasure(Vector2F availableSize) { // This control can only measure itself if it is in a screen because it needs a font. var screen = Screen; if (screen == null) { return(base.OnMeasure(availableSize)); } // Clear old renderer info. VisualClip = false; VisualText.Clear(); string text = Text; float width = Width; float height = Height; bool hasWidth = Numeric.IsPositiveFinite(width); bool hasHeight = Numeric.IsPositiveFinite(height); if (string.IsNullOrEmpty(text)) { // No text --> Abort. return(new Vector2F( hasWidth ? width : 0, hasHeight ? height : 0)); } // Limit constraint size by user-defined width and height. if (hasWidth && width < availableSize.X) { availableSize.X = width; } if (hasHeight && height < availableSize.Y) { availableSize.Y = height; } // Remove padding from constraint size. Vector4 padding = Padding; Vector2F contentSize = availableSize; if (Numeric.IsPositiveFinite(availableSize.X)) { contentSize.X -= padding.X + padding.Z; } if (Numeric.IsPositiveFinite(availableSize.Y)) { contentSize.Y -= padding.Y + padding.W; } // Measure text size. var font = screen.Renderer.GetFont(Font); Vector2F size = (Vector2F)font.MeasureString(text); if (size < contentSize) { // All text is visible. (VisualText is equal to Text.) VisualText.Append(text); return(new Vector2F( hasWidth ? width : size.X + padding.X + padding.Z, hasHeight ? height : size.Y + padding.Y + padding.W)); } // Get number of lines. int numberOfLines = 1; for (int i = 0; i < text.Length - 1; i++) { if (text[i] == '\n') { numberOfLines++; } } if (numberOfLines == 1 && size.Y > contentSize.Y) { // Not enough space for a single line height. --> Keep all text and use clipping. VisualText.Append(text); VisualClip = true; return(new Vector2F( hasWidth ? width : size.X + padding.X + padding.Z, hasHeight ? height : size.Y + padding.Y + padding.W)); } if (!WrapText) { // Not using word wrapping. // Compute desired size. Vector2F desiredSize = new Vector2F( hasWidth ? width : availableSize.X, hasHeight ? height : availableSize.Y); desiredSize.X = Math.Min(desiredSize.X, size.X + padding.X + padding.Z); desiredSize.Y = Math.Min(desiredSize.Y, size.Y + padding.Y + padding.W); if (numberOfLines > 1 || // 2 or more lines? !UseEllipsis || // No ellipsis needed? size.Y > contentSize.Y) // Single line is already to high? { // Just clip the text. VisualClip = true; VisualText.Append(text); } else { // 1 line that is too long and we have to insert an ellipsis. VisualText.Append(text); TrimText(VisualText, font, contentSize.X); } return(desiredSize); } // Get words. var words = SplitText(); // Note: We can compute line heights without font.MeasureString(). But we cannot compute // line widths without font.MeasureString() because we do not have kerning information. int lineHeight = font.LineSpacing; Debug.Assert(lineHeight <= contentSize.Y, "At least one line must fit into content"); float currentHeight = lineHeight; // Add words to string builder until space runs out. // In each loop iteration one line is built. int index = 0; int lineWordCount = 0; var line = new StringBuilder(); while (currentHeight < contentSize.Y && // Room for one more line? index < words.Count) // Words left? { if (index > 0) { VisualText.Append(line.ToString()); // Add line of last iteration. VisualText.Append("\n"); // If the next word is a newline, then we can skip it because we start a new line // anyways. if (words[index] == null) { index++; } } // Start with empty line. line.Remove(0, line.Length); lineWordCount = 0; // Build line. while (index < words.Count && // As long as we have words. words[index] != null) // And as long the next word is not a newline. { // Add spaces after first word. if (lineWordCount > 0) { line.Append(" "); } // Add next word. line.Append(words[index]); lineWordCount++; index++; float lineWidth = font.MeasureString(line).X; if (lineWidth > contentSize.X) { // Line is too long! Remove last word + blank. But keep at least one word per line. if (lineWordCount > 1) { index--; lineWordCount--; int wordLength = words[index].Length; line.Remove(line.Length - wordLength - 1, wordLength + 1); } else { VisualClip = true; // A single word is too long and must be clipped. } break; } } currentHeight += lineHeight; } // Nearly all visible text is in VisualText. // The last line is in "line" and was not yet added to VisualText. if (UseEllipsis) { if ((index < words.Count || // Not enough space to print all words. font.MeasureString(line).X > contentSize.X)) // The last line is too long and needs to be trimmed. { // Trim the last line and add an ellipsis. line.Append("�"); while (lineWordCount > 0 && font.MeasureString(line).X > contentSize.X) { index--; // We have to remove one word before the ellipsis. int wordLength = words[index].Length; line.Remove(line.Length - 1 - wordLength, wordLength); // Remove ' ' before ellipsis. int indexBeforeEllipsis = line.Length - 2; if (indexBeforeEllipsis > 0 && line[indexBeforeEllipsis] == ' ') { line.Remove(indexBeforeEllipsis, 1); } lineWordCount--; } } } // Add last line. VisualText.Append(line); size = (Vector2F)font.MeasureString(VisualText); return(new Vector2F( hasWidth ? width : size.X + padding.X + padding.Z, hasHeight ? height : size.Y + padding.Y + padding.W)); }