Exemple #1
0
        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);
        }
Exemple #2
0
        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;
        }
Exemple #3
0
        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);
        }