/// <summary> /// Returns positions of all ticks of the provided level. The ticks will be within the range provided to /// <see cref="SetRange"/>. /// </summary> /// <param name="level">Level for which to retrieve the positions. Must not be larger than /// <see cref="NumLevels"/> - 1.</param> /// <returns>Positions of all ticks of the provided level.</returns> internal float[] GetTicks(int level) { if (level < 0 || level >= numLevels) { return(new float[0]); } float step = validSteps[maxLevel + level]; // Round up and down so we get one extra tick on either side (outside of value range) // (Useful when rendering text above the ticks, so the text doesn't just pop-in when the tick appears, instead // it is slowly clipped-in.) int startTick = MathEx.CeilToInt(valueRangeStart / step); int endTick = MathEx.FloorToInt(valueRangeEnd / step); int numTicks = endTick - startTick + 1; float[] ticks = new float[numTicks]; for (int i = startTick; i <= endTick; i++) { ticks[i - startTick] = i * step; } return(ticks); }
/// <summary> /// Refreshes the contents of the content area. Must be called at least once after construction. /// </summary> /// <param name="viewType">Determines how to display the resource tiles.</param> /// <param name="entriesToDisplay">Project library entries to display.</param> /// <param name="bounds">Bounds within which to lay out the content entries.</param> public void Refresh(ProjectViewType viewType, LibraryEntry[] entriesToDisplay, Rect2I bounds) { if (mainPanel != null) { mainPanel.Destroy(); } entries.Clear(); entryLookup.Clear(); mainPanel = parent.Layout.AddPanel(); GUIPanel contentPanel = mainPanel.AddPanel(1); overlay = mainPanel.AddPanel(0); underlay = mainPanel.AddPanel(2); deepUnderlay = mainPanel.AddPanel(3); renameOverlay = mainPanel.AddPanel(-1); main = contentPanel.AddLayoutY(); List <ResourceToDisplay> resourcesToDisplay = new List <ResourceToDisplay>(); foreach (var entry in entriesToDisplay) { if (entry.Type == LibraryEntryType.Directory) { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single)); } else { FileEntry fileEntry = (FileEntry)entry; ResourceMeta[] metas = fileEntry.ResourceMetas; if (metas.Length > 0) { if (metas.Length == 1) { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single)); } else { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.MultiFirst)); for (int i = 1; i < metas.Length - 1; i++) { string path = Path.Combine(entry.Path, metas[i].SubresourceName); resourcesToDisplay.Add(new ResourceToDisplay(path, LibraryGUIEntryType.MultiElement)); } string lastPath = Path.Combine(entry.Path, metas[metas.Length - 1].SubresourceName); resourcesToDisplay.Add(new ResourceToDisplay(lastPath, LibraryGUIEntryType.MultiLast)); } } } } if (viewType == ProjectViewType.List16) { tileSize = 16; gridLayout = false; elementsPerRow = 1; horzElementSpacing = 0; int elemWidth = bounds.width; int elemHeight = tileSize; main.AddSpace(TOP_MARGIN); for (int i = 0; i < resourcesToDisplay.Count; i++) { ResourceToDisplay entry = resourcesToDisplay[i]; LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, main, entry.path, i, elemWidth, elemHeight, entry.type); entries.Add(guiEntry); entryLookup[guiEntry.path] = guiEntry; if (i != resourcesToDisplay.Count - 1) { main.AddSpace(LIST_ENTRY_SPACING); } } main.AddFlexibleSpace(); } else { int elemWidth = 0; int elemHeight = 0; int vertElemSpacing = 0; switch (viewType) { case ProjectViewType.Grid64: tileSize = 64; elemWidth = tileSize; elemHeight = tileSize + 36; horzElementSpacing = 10; vertElemSpacing = 12; break; case ProjectViewType.Grid48: tileSize = 48; elemWidth = tileSize; elemHeight = tileSize + 36; horzElementSpacing = 8; vertElemSpacing = 10; break; case ProjectViewType.Grid32: tileSize = 32; elemWidth = tileSize + 16; elemHeight = tileSize + 48; horzElementSpacing = 6; vertElemSpacing = 10; break; } gridLayout = true; int availableWidth = bounds.width; elementsPerRow = MathEx.FloorToInt((availableWidth - horzElementSpacing) / (float)(elemWidth + horzElementSpacing)); elementsPerRow = Math.Max(elementsPerRow, 1); int numRows = MathEx.CeilToInt(resourcesToDisplay.Count / (float)elementsPerRow); int neededHeight = numRows * elemHeight + TOP_MARGIN; if (numRows > 0) { neededHeight += (numRows - 1) * vertElemSpacing; } bool requiresScrollbar = neededHeight > bounds.height; if (requiresScrollbar) { availableWidth -= parent.ScrollBarWidth; elementsPerRow = MathEx.FloorToInt((availableWidth - horzElementSpacing) / (float)(elemWidth + horzElementSpacing)); elementsPerRow = Math.Max(elementsPerRow, 1); } int extraRowSpace = availableWidth - (elementsPerRow * (elemWidth + horzElementSpacing) + horzElementSpacing); main.AddSpace(TOP_MARGIN); GUILayoutX rowLayout = main.AddLayoutX(); rowLayout.AddSpace(horzElementSpacing); int elemsInRow = 0; for (int i = 0; i < resourcesToDisplay.Count; i++) { if (elemsInRow == elementsPerRow && elemsInRow > 0) { main.AddSpace(vertElemSpacing); rowLayout.AddSpace(extraRowSpace); rowLayout = main.AddLayoutX(); rowLayout.AddSpace(horzElementSpacing); elemsInRow = 0; } ResourceToDisplay entry = resourcesToDisplay[i]; LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, rowLayout, entry.path, i, elemWidth, elemHeight, entry.type); entries.Add(guiEntry); entryLookup[guiEntry.path] = guiEntry; rowLayout.AddSpace(horzElementSpacing); elemsInRow++; } int extraElements = elementsPerRow - elemsInRow; rowLayout.AddSpace((elemWidth + horzElementSpacing) * extraElements + extraRowSpace); main.AddFlexibleSpace(); } for (int i = 0; i < entries.Count; i++) { LibraryGUIEntry guiEntry = entries[i]; guiEntry.Initialize(); } }
/// <summary> /// Draws the curve using the provided color. /// </summary> /// <param name="curve">Curve to draw within the currently set range. </param> /// <param name="color">Color to draw the curve with.</param> private void DrawCurve(EdAnimationCurve curve, Color color) { float range = GetRange(true); float lengthPerPixel = range / drawableWidth; KeyFrame[] keyframes = curve.KeyFrames; if (keyframes.Length <= 0) { return; } // Draw start line { float curveStart = keyframes[0].time; float curveValue = curve.Evaluate(curveStart, false); Vector2I end = CurveToPixelSpace(new Vector2(curveStart, curveValue)); Vector2I start = new Vector2I(-GUIGraphTime.PADDING, end.y); if (start.x < end.x) { canvas.DrawLine(start, end, COLOR_MID_GRAY); } } List <Vector2I> linePoints = new List <Vector2I>(); // Draw in between keyframes float startVisibleTime = rangeOffset; float endVisibleTime = startVisibleTime + range; for (int i = 0; i < keyframes.Length - 1; i++) { float start = keyframes[i].time; float end = keyframes[i + 1].time; if (end < startVisibleTime || start > endVisibleTime) { continue; } bool isStep = keyframes[i].outTangent == float.PositiveInfinity || keyframes[i + 1].inTangent == float.PositiveInfinity; // If step tangent, draw the required lines without sampling, as the sampling will miss the step if (isStep) { float startValue = curve.Evaluate(start, false); float endValue = curve.Evaluate(end, false); linePoints.Add(CurveToPixelSpace(new Vector2(start, startValue))); linePoints.Add(CurveToPixelSpace(new Vector2(end, startValue))); linePoints.Add(CurveToPixelSpace(new Vector2(end, endValue))); } else // Draw normally { float splitIncrement = LINE_SPLIT_WIDTH * lengthPerPixel; float startValue = keyframes[i].value; float endValue = keyframes[i + 1].value; Vector2I startPixel = new Vector2I(); startPixel.x = (int)(start / lengthPerPixel); startPixel.y = (int)(startValue / lengthPerPixel); Vector2I endPixel = new Vector2I(); endPixel.x = (int)(end / lengthPerPixel); endPixel.y = (int)(endValue / lengthPerPixel); int distance = Vector2I.Distance(startPixel, endPixel); int numSplits; if (distance > 0) { float fNumSplits = distance / splitIncrement; numSplits = MathEx.CeilToInt(fNumSplits); splitIncrement = distance / (float)numSplits; } else { numSplits = 1; splitIncrement = 0.0f; } for (int j = 0; j < numSplits; j++) { float t = Math.Min(start + j * splitIncrement, end); float value = curve.Evaluate(t, false); linePoints.Add(CurveToPixelSpace(new Vector2(t, value))); } } } canvas.DrawPolyLine(linePoints.ToArray(), color); // Draw end line { float curveEnd = keyframes[keyframes.Length - 1].time; float curveValue = curve.Evaluate(curveEnd, false); Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue)); Vector2I end = new Vector2I(width, start.y); if (start.x < end.x) { canvas.DrawLine(start, end, COLOR_MID_GRAY); } } }
/// <inheritdoc/> public override void Update() { int numVisibleEntries = MathEx.CeilToInt(height / (float)entryHeight) + 1; numVisibleEntries = MathEx.Min(numVisibleEntries, entries.Count); while (visibleEntries.Count < numVisibleEntries) { TEntry newEntry = new TEntry(); newEntry.Initialize(this); newEntry.panel.SetHeight(entryHeight); visibleEntries.Add(newEntry); contentsDirty = true; } while (numVisibleEntries < visibleEntries.Count) { int lastIdx = visibleEntries.Count - 1; visibleEntries[lastIdx].Destroy(); visibleEntries.RemoveAt(lastIdx); contentsDirty = true; } if (scrollPct != scrollArea.VerticalScroll) { scrollPct = scrollArea.VerticalScroll; contentsDirty = true; } if (contentsDirty) { int newHeight = entries.Count * entryHeight; if (scrollToLatest) { if (totalHeight > height && scrollPct < 1.0f) { scrollToLatest = false; } } else { if (totalHeight <= height || scrollPct >= 1.0f) { scrollToLatest = true; } } totalHeight = newHeight; int maxScrollOffset = MathEx.Max(0, totalHeight - height - 1); float newScrollPct; if (!scrollToLatest) { // Calculate the new scroll pct (which will be active after we change the top/bottom padding element // sizes). If we use the existing scroll pct instead then the elements will lag one frame behind, which // can be very noticeable on quickly updating lists. newScrollPct = (scrollPct * scrollArea.Layout.Bounds.height) / totalHeight; } else { newScrollPct = 1.0f; } int startPos = MathEx.FloorToInt(newScrollPct * maxScrollOffset); int startIndex = MathEx.FloorToInt(startPos / (float)entryHeight); // Check if we're at the list bottom and the extra element is out of bounds if ((startIndex + visibleEntries.Count) > entries.Count) { startIndex--; // Keep the extra element at the top always } topPadding.SetHeight(startIndex * entryHeight); for (int i = 0; i < visibleEntries.Count; i++) { visibleEntries[i].UpdateContents(startIndex + i, entries[startIndex + i]); } int bottomPosition = MathEx.Min(totalHeight, (startIndex + visibleEntries.Count) * entryHeight); bottomPadding.SetHeight(totalHeight - bottomPosition); if (scrollToLatest) { if (newHeight <= height) { scrollArea.VerticalScroll = 0.0f; } else { scrollArea.VerticalScroll = 1.0f; } } contentsDirty = false; } }
/// <summary> /// Draws the curve using the provided color. /// </summary> /// <param name="curve">Curve to draw within the currently set range. </param> /// <param name="color">Color to draw the curve with.</param> private void DrawCurve(EdAnimationCurve curve, Color color) { float range = GetRange(); float lengthPerPixel = range / drawableWidth; KeyFrame[] keyframes = curve.KeyFrames; if (keyframes.Length <= 0) { return; } // Draw start line { float curveStart = MathEx.Clamp(keyframes[0].time, 0.0f, range); float curveValue = curve.Evaluate(0.0f, false); Vector2I start = CurveToPixelSpace(new Vector2(0.0f, curveValue)); start.x -= GUIGraphTime.PADDING; Vector2I end = CurveToPixelSpace(new Vector2(curveStart, curveValue)); canvas.DrawLine(start, end, COLOR_MID_GRAY); } List <Vector2I> linePoints = new List <Vector2I>(); // Draw in between keyframes for (int i = 0; i < keyframes.Length - 1; i++) { float start = MathEx.Clamp(keyframes[i].time, 0.0f, range); float end = MathEx.Clamp(keyframes[i + 1].time, 0.0f, range); bool isStep = keyframes[i].outTangent == float.PositiveInfinity || keyframes[i + 1].inTangent == float.PositiveInfinity; // If step tangent, draw the required lines without sampling, as the sampling will miss the step if (isStep) { float startValue = curve.Evaluate(start, false); float endValue = curve.Evaluate(end, false); linePoints.Add(CurveToPixelSpace(new Vector2(start, startValue))); linePoints.Add(CurveToPixelSpace(new Vector2(end, startValue))); linePoints.Add(CurveToPixelSpace(new Vector2(end, endValue))); } else // Draw normally { float timeIncrement = LINE_SPLIT_WIDTH * lengthPerPixel; int startPixel = (int)(start / lengthPerPixel); int endPixel = (int)(end / lengthPerPixel); int numSplits; if (startPixel != endPixel) { float fNumSplits = (end - start) / timeIncrement; numSplits = MathEx.FloorToInt(fNumSplits); float remainder = fNumSplits - numSplits; float lengthRounded = (end - start) * (numSplits / fNumSplits); timeIncrement = lengthRounded / numSplits; numSplits += MathEx.CeilToInt(remainder) + 1; } else { numSplits = 1; timeIncrement = 0.0f; } for (int j = 0; j < numSplits; j++) { float t = Math.Min(start + j * timeIncrement, end); float value = curve.Evaluate(t, false); linePoints.Add(CurveToPixelSpace(new Vector2(t, value))); } } } canvas.DrawPolyLine(linePoints.ToArray(), color); // Draw end line { float curveEnd = MathEx.Clamp(keyframes[keyframes.Length - 1].time, 0.0f, range); float curveValue = curve.Evaluate(range, false); Vector2I start = CurveToPixelSpace(new Vector2(curveEnd, curveValue)); Vector2I end = new Vector2I(width, start.y); canvas.DrawLine(start, end, COLOR_MID_GRAY); } }
/// <summary> /// Refreshes the contents of the content area. Must be called at least once after construction. /// </summary> /// <param name="viewType">Determines how to display the resource tiles.</param> /// <param name="entriesToDisplay">Project library entries to display.</param> /// <param name="bounds">Bounds within which to lay out the content entries.</param> public void Refresh(ProjectViewType viewType, LibraryEntry[] entriesToDisplay, Rect2I bounds) { if (mainPanel != null) { mainPanel.Destroy(); } entries.Clear(); entryLookup.Clear(); mainPanel = parent.Layout.AddPanel(); GUIPanel contentPanel = mainPanel.AddPanel(1); overlay = mainPanel.AddPanel(0); underlay = mainPanel.AddPanel(2); deepUnderlay = mainPanel.AddPanel(3); renameOverlay = mainPanel.AddPanel(-1); main = contentPanel.AddLayoutY(); List <ResourceToDisplay> resourcesToDisplay = new List <ResourceToDisplay>(); foreach (var entry in entriesToDisplay) { if (entry.Type == LibraryEntryType.Directory) { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single)); } else { FileEntry fileEntry = (FileEntry)entry; ResourceMeta[] metas = fileEntry.ResourceMetas; if (metas.Length > 0) { if (metas.Length == 1) { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.Single)); } else { resourcesToDisplay.Add(new ResourceToDisplay(entry.Path, LibraryGUIEntryType.MultiFirst)); for (int i = 1; i < metas.Length - 1; i++) { string path = Path.Combine(entry.Path, metas[i].SubresourceName); resourcesToDisplay.Add(new ResourceToDisplay(path, LibraryGUIEntryType.MultiElement)); } string lastPath = Path.Combine(entry.Path, metas[metas.Length - 1].SubresourceName); resourcesToDisplay.Add(new ResourceToDisplay(lastPath, LibraryGUIEntryType.MultiLast)); } } } } int minHorzElemSpacing = 0; if (viewType == ProjectViewType.List16) { tileSize = 16; gridLayout = false; elementsPerRow = 1; minHorzElemSpacing = 0; int elemWidth = bounds.width; int elemHeight = tileSize; main.AddSpace(TOP_MARGIN); for (int i = 0; i < resourcesToDisplay.Count; i++) { ResourceToDisplay entry = resourcesToDisplay[i]; LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, main, entry.path, i, elemWidth, elemHeight, 0, entry.type); entries.Add(guiEntry); entryLookup[guiEntry.path] = guiEntry; if (i != resourcesToDisplay.Count - 1) { main.AddSpace(LIST_ENTRY_SPACING); } } main.AddFlexibleSpace(); } else { int elemWidth = 0; int elemHeight = 0; int vertElemSpacing = 0; switch (viewType) { case ProjectViewType.Grid64: tileSize = 64; elemWidth = tileSize; elemHeight = tileSize + 36; minHorzElemSpacing = 10; vertElemSpacing = 12; break; case ProjectViewType.Grid48: tileSize = 48; elemWidth = tileSize; elemHeight = tileSize + 36; minHorzElemSpacing = 8; vertElemSpacing = 10; break; case ProjectViewType.Grid32: tileSize = 32; elemWidth = tileSize + 16; elemHeight = tileSize + 48; minHorzElemSpacing = 6; vertElemSpacing = 10; break; } gridLayout = true; int availableWidth = bounds.width; elementsPerRow = MathEx.FloorToInt((availableWidth - minHorzElemSpacing) / (float)(elemWidth + minHorzElemSpacing)); int numRows = MathEx.CeilToInt(resourcesToDisplay.Count / (float)Math.Max(elementsPerRow, 1)); int neededHeight = numRows * elemHeight + TOP_MARGIN; if (numRows > 0) { neededHeight += (numRows - 1) * vertElemSpacing; } bool requiresScrollbar = neededHeight > bounds.height; if (requiresScrollbar) { availableWidth -= parent.ScrollBarWidth; elementsPerRow = MathEx.FloorToInt((availableWidth - minHorzElemSpacing) / (float)(elemWidth + minHorzElemSpacing)); } int extraRowSpace = availableWidth - minHorzElemSpacing * 2 - elementsPerRow * elemWidth; paddingLeft = minHorzElemSpacing; paddingRight = minHorzElemSpacing; float horzSpacing = 0.0f; // Distribute the spacing to the padding, as there are not in-between spaces to apply it to if (extraRowSpace > 0 && elementsPerRow < 2) { int extraPadding = extraRowSpace / 2; paddingLeft += extraPadding; paddingRight += (extraRowSpace - extraPadding); extraRowSpace = 0; } else { horzSpacing = extraRowSpace / (float)(elementsPerRow - 1); } elementsPerRow = Math.Max(elementsPerRow, 1); main.AddSpace(TOP_MARGIN); GUILayoutX rowLayout = main.AddLayoutX(); rowLayout.AddSpace(paddingLeft); float spacingCounter = 0.0f; int elemsInRow = 0; for (int i = 0; i < resourcesToDisplay.Count; i++) { if (elemsInRow == elementsPerRow && elemsInRow > 0) { main.AddSpace(vertElemSpacing); rowLayout = main.AddLayoutX(); rowLayout.AddSpace(paddingLeft); elemsInRow = 0; spacingCounter = 0.0f; } ResourceToDisplay entry = resourcesToDisplay[i]; elemsInRow++; if (elemsInRow != elementsPerRow) { spacingCounter += horzSpacing; } int spacing = (int)spacingCounter; spacingCounter -= spacing; LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, rowLayout, entry.path, i, elemWidth, elemHeight, spacing, entry.type); entries.Add(guiEntry); entryLookup[guiEntry.path] = guiEntry; if (elemsInRow == elementsPerRow) { rowLayout.AddSpace(paddingRight); } rowLayout.AddSpace(spacing); } int extraElements = elementsPerRow - elemsInRow; int extraSpacing = 0; if (extraElements > 1) { spacingCounter += (extraElements - 1) * horzSpacing; extraSpacing += (int)spacingCounter; } if (extraElements > 0) { rowLayout.AddSpace(elemWidth * extraElements + extraSpacing + paddingRight); } main.AddFlexibleSpace(); } // Fix bounds as that makes GUI updates faster underlay.Bounds = main.Bounds; overlay.Bounds = main.Bounds; deepUnderlay.Bounds = main.Bounds; renameOverlay.Bounds = main.Bounds; for (int i = 0; i < entries.Count; i++) { LibraryGUIEntry guiEntry = entries[i]; guiEntry.Initialize(); } }
/// <summary> /// Refreshes the contents of the content area. Must be called at least once after construction. /// </summary> /// <param name="viewType">Determines how to display the resource tiles.</param> /// <param name="entriesToDisplay">Project library entries to display.</param> public void Refresh(ProjectViewType viewType, LibraryEntry[] entriesToDisplay) { if (mainPanel != null) { mainPanel.Destroy(); } entries = new LibraryGUIEntry[entriesToDisplay.Length]; entryLookup.Clear(); mainPanel = parent.Layout.AddPanel(); GUIPanel contentPanel = mainPanel.AddPanel(1); overlay = mainPanel.AddPanel(0); underlay = mainPanel.AddPanel(2); renameOverlay = mainPanel.AddPanel(-1); main = contentPanel.AddLayoutY(); if (viewType == ProjectViewType.List16) { tileSize = 16; gridLayout = false; elementsPerRow = 1; } else { switch (viewType) { case ProjectViewType.Grid64: tileSize = 64; break; case ProjectViewType.Grid48: tileSize = 48; break; case ProjectViewType.Grid32: tileSize = 32; break; } gridLayout = true; Rect2I scrollBounds = parent.Bounds; int availableWidth = scrollBounds.width; int elemSize = tileSize + GRID_ENTRY_SPACING; elementsPerRow = (availableWidth - GRID_ENTRY_SPACING * 2) / elemSize; elementsPerRow = Math.Max(elementsPerRow, 1); int numRows = MathEx.CeilToInt(entriesToDisplay.Length / (float)elementsPerRow); int neededHeight = numRows * (elemSize); bool requiresScrollbar = neededHeight > scrollBounds.height; if (requiresScrollbar) { availableWidth -= parent.ScrollBarWidth; elementsPerRow = (availableWidth - GRID_ENTRY_SPACING * 2) / elemSize; } if (elementsPerRow > 0) { labelWidth = (availableWidth - (elementsPerRow + 1) * MIN_HORZ_SPACING) / elementsPerRow; } else { labelWidth = 0; } } if (viewType == ProjectViewType.List16) { for (int i = 0; i < entriesToDisplay.Length; i++) { LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, main, entriesToDisplay[i], i, labelWidth); entries[i] = guiEntry; entryLookup[guiEntry.path] = guiEntry; if (i != entriesToDisplay.Length - 1) { main.AddSpace(LIST_ENTRY_SPACING); } } main.AddFlexibleSpace(); } else { main.AddSpace(GRID_ENTRY_SPACING / 2); GUILayoutX rowLayout = main.AddLayoutX(); main.AddSpace(GRID_ENTRY_SPACING); rowLayout.AddFlexibleSpace(); int elemsInRow = 0; for (int i = 0; i < entriesToDisplay.Length; i++) { if (elemsInRow == elementsPerRow && elemsInRow > 0) { rowLayout = main.AddLayoutX(); main.AddSpace(GRID_ENTRY_SPACING); rowLayout.AddFlexibleSpace(); elemsInRow = 0; } LibraryGUIEntry guiEntry = new LibraryGUIEntry(this, rowLayout, entriesToDisplay[i], i, labelWidth); entries[i] = guiEntry; entryLookup[guiEntry.path] = guiEntry; rowLayout.AddFlexibleSpace(); elemsInRow++; } int extraElements = elementsPerRow - elemsInRow; for (int i = 0; i < extraElements; i++) { rowLayout.AddSpace(labelWidth); rowLayout.AddFlexibleSpace(); } main.AddFlexibleSpace(); } for (int i = 0; i < entries.Length; i++) { LibraryGUIEntry guiEntry = entries[i]; guiEntry.Initialize(); } }