void CreateNewLabelLayoutWrapped() { // Use default UI font if none set if (font == null) { font = DaggerfallUI.DefaultFont; } // // Stage 1 - Encode glyphs and calculate final dimensions // // Start a new layout labelLayout = new LabelLayoutData(); List <GlyphLayoutData> glyphLayout = new List <GlyphLayoutData>(); // Set a local maxWidth that compensates for textScale int maxWidth = (int)(this.maxWidth / textScale); // First pass encodes ASCII and calculates final dimensions int width = 0; int greatestWidthFound = 0; int lastEndOfRowByte = 0; asciiBytes = Encoding.ASCII.GetBytes(text); List <byte[]> rows = new List <byte[]>(); List <int> rowWidth = new List <int>(); for (int i = 0; i < asciiBytes.Length; i++) { // Invalid ASCII bytes are cast to a space character if (!font.HasGlyph(asciiBytes[i])) { asciiBytes[i] = DaggerfallFont.SpaceASCII; } // Calculate total width DaggerfallFont.GlyphInfo glyph = font.GetGlyph(asciiBytes[i]); // If maxWidth is set, don't allow the label texture to exceed it if ((maxWidth <= 0) || ((width + glyph.width + font.GlyphSpacing) <= maxWidth)) { width += glyph.width + font.GlyphSpacing; } else { int rowLength; if (wrapWords) { int j; for (j = i; j >= lastEndOfRowByte; j--) { glyph = font.GetGlyph(asciiBytes[j]); if (j < i) // glyph i has not been added to width { width -= glyph.width + font.GlyphSpacing; if (width <= maxWidth && asciiBytes[j] == DaggerfallFont.SpaceASCII) { break; } } } if (j > lastEndOfRowByte) // space found in row at position that is not exceeding maxWidth for all summed glyph's widths before this position { i = j; // set new processing position (j is the space position, i will be increased on next loop iteration to point to next chat after the space char) rowLength = j - lastEndOfRowByte; // set length from row start to position before space } else { rowLength = i - lastEndOfRowByte; } // compute width of text-wrapped line width = 0; for (int k = lastEndOfRowByte; k < j; k++) { if (k < j - 1 || (k == j - 1 && asciiBytes[k] != DaggerfallFont.SpaceASCII)) // all expect last character if it is a space { glyph = font.GetGlyph(asciiBytes[k]); width += glyph.width + font.GlyphSpacing; } } } else { rowLength = i - lastEndOfRowByte; } // The row of glyphs exceeded maxWidth. Add it to the list of rows and start // counting width again with the remainder of the ASCII bytes. List <byte> content = new List <byte>(asciiBytes).GetRange(lastEndOfRowByte, rowLength); if (content[content.Count - 1] == DaggerfallFont.SpaceASCII) { content.RemoveAt(content.Count - 1); } byte[] trimmed = content.ToArray(); rows.Add(trimmed); rowWidth.Add(width); // update greatest width found so far if (greatestWidthFound < width) { greatestWidthFound = width; } // reset width for next line width = 0; lastEndOfRowByte = i + 1; // position after space for next line, note: i will be increased on next loop iteration anyway - so no need to increase i } } if (lastEndOfRowByte > 0) { asciiBytes = new List <byte>(asciiBytes).GetRange(lastEndOfRowByte, asciiBytes.Length - lastEndOfRowByte).ToArray(); } // also get width of last line width = 0; for (int i = 0; i < asciiBytes.Length; i++) { DaggerfallFont.GlyphInfo glyph = font.GetGlyph(asciiBytes[i]); width += glyph.width + font.GlyphSpacing; } // update greatest width found so far if (width <= maxWidth && greatestWidthFound < width) // width should always be <= maxWidth here { greatestWidthFound = width; } rows.Add(asciiBytes); rowWidth.Add(width); // Create virtual layout area totalWidth = maxWidth; totalHeight = (int)(rows.Count * font.GlyphHeight); numTextLines = rows.Count; // // Stage 2 - Add glyph to layout // // Second pass adds glyphs to label texture int xpos = 0; int ypos = totalHeight - font.GlyphHeight; //foreach (byte[] row in rows) for (int r = 0; r < rows.Count; r++) { byte[] row = rows[r]; float alignmentOffset; switch (horizontalTextAlignment) { default: case HorizontalTextAlignmentSetting.None: case HorizontalTextAlignmentSetting.Left: case HorizontalTextAlignmentSetting.Justify: alignmentOffset = 0.0f; break; case HorizontalTextAlignmentSetting.Center: alignmentOffset = (totalWidth - rowWidth[r]) * 0.5f; break; case HorizontalTextAlignmentSetting.Right: alignmentOffset = totalWidth - rowWidth[r]; break; } int numSpaces = 0; // needed to compute extra offset between words for HorizontalTextAlignmentSetting.Justify int extraSpaceToDistribute = 0; // needed to compute extra offset between words for HorizontalTextAlignmentSetting.Justify if (horizontalTextAlignment == HorizontalTextAlignmentSetting.Justify) { for (int i = 0; i < row.Length; i++) { if (row[i] == DaggerfallFont.SpaceASCII) { numSpaces++; } } extraSpaceToDistribute = maxWidth - rowWidth[r]; } xpos = (int)alignmentOffset; for (int i = 0; i < row.Length; i++) { DaggerfallFont.GlyphInfo glyph = font.GetGlyph(row[i]); if (xpos + glyph.width > totalWidth) { break; } if (row[i] == DaggerfallFont.SpaceASCII) { if (numSpaces > 1) { int currentPortionExtraSpaceToDistribute = (int)Mathf.Round((float)extraSpaceToDistribute / (float)numSpaces); xpos += currentPortionExtraSpaceToDistribute; extraSpaceToDistribute -= currentPortionExtraSpaceToDistribute; numSpaces--; } else if (numSpaces == 1) { xpos += extraSpaceToDistribute; } } GlyphLayoutData glyphPos = new GlyphLayoutData() { x = xpos, y = totalHeight - font.GlyphHeight - ypos, glyphRawAscii = row[i], glyphWidth = glyph.width, }; glyphLayout.Add(glyphPos); xpos += glyph.width + font.GlyphSpacing; } ypos -= font.GlyphHeight; } labelLayout.glyphLayout = glyphLayout.ToArray(); this.Size = new Vector2(totalWidth * textScale, totalHeight * textScale); }
private void DrawLabels(Rect chartPosition, ChartViewData data, int selectedFrame, ChartType chartType) { if (data.selectedLabels == null || Event.current.type != EventType.Repaint) { return; } // exit early if the selected frame is outside the domain of the chart var domain = data.GetDataDomain(); if ( selectedFrame < data.firstSelectableFrame || selectedFrame > data.chartDomainOffset + (int)(domain.y - domain.x) || domain.y - domain.x == 0f ) { return; } var selectedIndex = selectedFrame - data.chartDomainOffset; m_LabelOrder.Clear(); m_LabelOrder.AddRange(data.order); // get values of all series and cumulative value of all enabled stacks m_SelectedFrameValues.Clear(); var stacked = chartType == ChartType.StackedFill; var numLabels = 0; for (int s = 0; s < data.numSeries; ++s) { var chartData = data.hasOverlay ? data.overlays[s] : data.series[s]; var value = chartData.yValues[selectedIndex] * chartData.yScale; m_SelectedFrameValues.Add(value); if (data.series[s].enabled) { ++numLabels; } } if (numLabels == 0) { return; } // populate layout data array with default data m_LabelData.Clear(); var selectedFrameMidline = chartPosition.x + chartPosition.width * ((selectedIndex + 0.5f) / (domain.y - domain.x)); var maxLabelWidth = 0f; numLabels = 0; for (int s = 0; s < data.numSeries; ++s) { var labelData = new LabelLayoutData(); var chartData = data.series[s]; var value = m_SelectedFrameValues[s]; if (chartData.enabled && value >= labelRange.x && value <= labelRange.y) { var rangeAxis = chartData.rangeAxis; var rangeSize = rangeAxis.sqrMagnitude == 0f ? 1f : rangeAxis.y * chartData.yScale - rangeAxis.x; // convert stacked series to cumulative value of enabled series if (stacked) { var accumulatedValues = 0f; int currentChartIndex = m_LabelOrder.FindIndex(x => x == s); for (int i = currentChartIndex - 1; i >= 0; --i) { var otherSeriesIdx = data.order[i]; var otherChartData = data.hasOverlay ? data.overlays[otherSeriesIdx] : data.series[otherSeriesIdx]; bool enabled = data.series[otherSeriesIdx].enabled; if (enabled) { accumulatedValues += otherChartData.yValues[selectedIndex] * otherChartData.yScale; } } // labels for stacked series will be in the middle of their stacks value = accumulatedValues + (0.5f * value); } // default position is left aligned to midline var position = new Vector2( // offset by half a point so there is a 1-point gap down the midline if labels are on both sides selectedFrameMidline + 0.5f, chartPosition.y + chartPosition.height * (1.0f - ((value * chartData.yScale - rangeAxis.x) / rangeSize)) ); var size = Styles.whiteLabel.CalcSize(EditorGUIUtility.TempContent(data.selectedLabels[s])); position.y -= 0.5f * size.y; position.y = Mathf.Clamp(position.y, chartPosition.yMin, chartPosition.yMax - size.y); labelData.position = new Rect(position, size); labelData.desiredYPosition = labelData.position.center.y; ++numLabels; } m_LabelData.Add(labelData); maxLabelWidth = Mathf.Max(maxLabelWidth, labelData.position.width); } if (numLabels == 0) { return; } // line charts order labels based on series values if (!stacked) { m_LabelOrder.Sort(SortLineLabelIndices); } // right align labels to the selected frame midline if approaching right border if (selectedFrameMidline > chartPosition.x + chartPosition.width - maxLabelWidth) { for (int s = 0; s < data.numSeries; ++s) { var label = m_LabelData[s]; label.position.x -= label.position.width; m_LabelData[s] = label; } } // alternate right/left alignment if in the middle else if (selectedFrameMidline > chartPosition.x + maxLabelWidth) { var processed = 0; for (int s = 0; s < data.numSeries; ++s) { var labelIndex = m_LabelOrder[s]; if (m_LabelData[labelIndex].position.size.sqrMagnitude == 0f) { continue; } if ((processed & 1) == 0) { var label = m_LabelData[labelIndex]; // ensure there is a 1-point gap down the midline label.position.x -= label.position.width + 1f; m_LabelData[labelIndex] = label; } ++processed; } } // separate overlapping labels for (int it = 0; it < k_LabelLayoutMaxIterations; ++it) { m_MostOverlappingLabels.Clear(); // work on the biggest cluster of overlapping rects for (int s1 = 0; s1 < data.numSeries; ++s1) { m_OverlappingLabels.Clear(); m_OverlappingLabels.Add(s1); if (m_LabelData[s1].position.size.sqrMagnitude == 0f) { continue; } for (int s2 = 0; s2 < data.numSeries; ++s2) { if (m_LabelData[s2].position.size.sqrMagnitude == 0f) { continue; } if (s1 != s2 && m_LabelData[s1].position.Overlaps(m_LabelData[s2].position)) { m_OverlappingLabels.Add(s2); } } if (m_OverlappingLabels.Count > m_MostOverlappingLabels.Count) { m_MostOverlappingLabels.Clear(); m_MostOverlappingLabels.AddRange(m_OverlappingLabels); } } // finish if there are no more overlapping rects if (m_MostOverlappingLabels.Count == 1) { break; } float totalHeight; var geometricCenter = GetGeometricCenter(m_MostOverlappingLabels, m_LabelData, out totalHeight); // account for other rects that will overlap after expanding var foundOverlaps = true; while (foundOverlaps) { foundOverlaps = false; var minY = geometricCenter - 0.5f * totalHeight; var maxY = geometricCenter + 0.5f * totalHeight; for (int s = 0; s < data.numSeries; ++s) { if (m_MostOverlappingLabels.Contains(s)) { continue; } var testRect = m_LabelData[s].position; if (testRect.size.sqrMagnitude == 0f) { continue; } var x = testRect.xMax < selectedFrameMidline ? testRect.xMax : testRect.xMin; if ( testRect.Contains(new Vector2(x, minY)) || testRect.Contains(new Vector2(x, maxY)) ) { m_MostOverlappingLabels.Add(s); foundOverlaps = true; } } GetGeometricCenter(m_MostOverlappingLabels, m_LabelData, out totalHeight); // keep labels inside chart rect if (geometricCenter - 0.5f * totalHeight < chartPosition.yMin) { geometricCenter = chartPosition.yMin + 0.5f * totalHeight; } else if (geometricCenter + 0.5f * totalHeight > chartPosition.yMax) { geometricCenter = chartPosition.yMax - 0.5f * totalHeight; } } // separate overlapping rects and distribute them away from their geometric center m_MostOverlappingLabels.Sort(SortOverlappingRectIndices); var heightAllotted = 0f; for (int i = 0; i < m_MostOverlappingLabels.Count; ++i) { var labelIndex = m_MostOverlappingLabels[i]; var label = m_LabelData[labelIndex]; label.position.y = geometricCenter - totalHeight * 0.5f + heightAllotted; m_LabelData[labelIndex] = label; heightAllotted += label.position.height; } } // draw the labels var oldContentColor = GUI.contentColor; for (int s = 0; s < data.numSeries; ++s) { var labelIndex = m_LabelOrder[s]; if (m_LabelData[labelIndex].position.size.sqrMagnitude == 0f) { continue; } GUI.contentColor = Color.Lerp(data.series[labelIndex].color, Color.white, Styles.labelLerpToWhiteAmount); var layoutData = m_LabelData[labelIndex]; EditorGUI.DoDropShadowLabel( layoutData.position, EditorGUIUtility.TempContent(data.selectedLabels[labelIndex]), Styles.whiteLabel, Styles.labelDropShadowOpacity ); } GUI.contentColor = oldContentColor; }
void CreateNewLabelLayoutSingleLine() { // // Stage 1 - Encode glyphs and calculate final dimensions // // Use default UI font if none set if (font == null) { font = DaggerfallUI.DefaultFont; } // Start a new layout labelLayout = new LabelLayoutData(); List <GlyphLayoutData> glyphLayout = new List <GlyphLayoutData>(); // Set a local maxWidth that compensates for textScale int maxWidth = (int)(this.maxWidth / textScale); // First pass encodes ASCII and calculates final dimensions int width = 0; asciiBytes = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding("ISO-8859-1"), Encoding.Default.GetBytes(text)); for (int i = startCharacterIndex; i < asciiBytes.Length; i++) { // Invalid ASCII bytes are cast to a space character if (!font.HasGlyph(asciiBytes[i])) { asciiBytes[i] = DaggerfallFont.SpaceASCII; } // Calculate total width DaggerfallFont.GlyphInfo glyph = font.GetGlyph(asciiBytes[i]); width += glyph.width + font.GlyphSpacing; } // Trim width if (maxWidth > 0 && width > maxWidth) { width = maxWidth; } // Create virtual layout area totalWidth = width; totalHeight = (int)(font.GlyphHeight); numTextLines = 1; labelLayout.width = totalWidth; labelLayout.height = totalHeight; // // Stage 2 - Add glyph to layout // // Determine horizontal alignment offset float alignmentOffset; switch (horizontalTextAlignment) { default: case HorizontalTextAlignmentSetting.None: case HorizontalTextAlignmentSetting.Left: case HorizontalTextAlignmentSetting.Justify: alignmentOffset = 0.0f; break; case HorizontalTextAlignmentSetting.Center: alignmentOffset = (totalWidth - width) * 0.5f; break; case HorizontalTextAlignmentSetting.Right: alignmentOffset = totalWidth - width; break; } // Second pass adds glyphs to layout int xpos = (int)alignmentOffset; for (int i = startCharacterIndex; i < asciiBytes.Length; i++) { DaggerfallFont.GlyphInfo glyph = font.GetGlyph(asciiBytes[i]); if (xpos + glyph.width >= totalWidth) { break; } GlyphLayoutData glyphPos = new GlyphLayoutData() { x = xpos, y = 0, glyphRawAscii = asciiBytes[i], glyphWidth = glyph.width, }; glyphLayout.Add(glyphPos); xpos += glyph.width + font.GlyphSpacing; } labelLayout.glyphLayout = glyphLayout.ToArray(); this.Size = new Vector2(totalWidth * textScale, totalHeight * textScale); }