public override void RenderInteractiveElements(float deltaTime) { Render2DTexture( richtTextTexture.TextureId, (int)Bounds.renderX, (int)Bounds.renderY, (int)richtTextTexture.Width, (int)richtTextTexture.Height, zPos, RenderColor ); bool found = false; int relx = (int)(api.Input.MouseX - Bounds.absX); int rely = (int)(api.Input.MouseY - Bounds.absY); MouseOverCursor = null; for (int i = 0; i < Components.Length; i++) { RichTextComponentBase comp = Components[i]; comp.RenderColor = RenderColor; comp.RenderInteractiveElements(deltaTime, Bounds.renderX, Bounds.renderY); for (int j = 0; !found && j < comp.BoundsPerLine.Length; j++) { LineRectangled rec = comp.BoundsPerLine[j]; if (rec.PointInside(relx, rely)) { MouseOverCursor = comp.MouseOverCursor; found = true; } } } }
private void ConstrainTextFlowPath(List <TextFlowPath> flowPath, double posY, RichTextComponentBase comp) { Rectangled rect = comp.BoundsPerLine[0]; EnumFloat elementFloat = comp.Float; double x1 = elementFloat == EnumFloat.Left ? rect.Width + comp.PaddingRight : 0; double x2 = elementFloat == EnumFloat.Right ? Bounds.InnerWidth - rect.Width - comp.PaddingLeft : Bounds.InnerWidth; double remainingHeight = rect.Height; for (int i = 0; i < flowPath.Count; i++) { TextFlowPath tfp = flowPath[i]; if (tfp.Y2 <= posY) { continue; // we already passed this one } double hereX1 = Math.Max(x1, tfp.X1); double hereX2 = Math.Min(x2, tfp.X2); // Current bounds are taller, let's make a split and insert ours if (tfp.Y2 > posY + rect.Height) { // Already more contrained, don't touch if (x1 <= tfp.X1 && x2 >= tfp.X2) { continue; } if (i == 0) { // We're at the begining, so don't need a "before" element flowPath[i] = new TextFlowPath(hereX1, posY, hereX2, posY + rect.Height); flowPath.Insert(i + 1, new TextFlowPath(tfp.X1, posY + rect.Height, tfp.X2, tfp.Y2)); } else { flowPath[i] = new TextFlowPath(tfp.X1, tfp.Y1, tfp.X2, posY); flowPath.Insert(i + 1, new TextFlowPath(tfp.X1, posY + rect.Height, tfp.X2, tfp.Y2)); flowPath.Insert(i, new TextFlowPath(hereX1, posY, hereX2, posY + rect.Height)); } remainingHeight = 0; break; } else // Current bounds are shorter, let's update it { flowPath[i].X1 = hereX1; flowPath[i].X2 = hereX2; remainingHeight -= tfp.Y2 - posY; } } if (remainingHeight > 0) { flowPath.Add(new TextFlowPath(x1, posY, x2, posY + remainingHeight)); } }
public void CalcHeightAndPositions() { Bounds.CalcWorldBounds(); if (DebugLogging) { api.Logger.VerboseDebug("GuiElementRichtext: before bounds: {0}/{1} w/h = {2},{3}", Bounds.absX, Bounds.absY, Bounds.OuterWidth, Bounds.OuterHeight); } double posX = 0; double posY = 0; List <int> currentLine = new List <int>(); List <TextFlowPath> flowPathList = new List <TextFlowPath>(); flowPathList.Add(new TextFlowPath(Bounds.InnerWidth)); double lineHeight = 0; double ascentHeight = 0; RichTextComponentBase comp = null; for (int i = 0; i < Components.Length; i++) { comp = Components[i]; bool didLineBreak = comp.CalcBounds(flowPathList.ToArray(), lineHeight, posX, posY); if (DebugLogging) { api.Logger.VerboseDebug("GuiElementRichtext, add comp {0}, posY={1}, lineHeight={2}", i, posY, lineHeight); api.Logger.VerboseDebug("GuiElementRichtext, Comp bounds 0 w/h: {0}/{1}", comp.BoundsPerLine[0].Width, comp.BoundsPerLine[0].Height); } posX += scaled(comp.PaddingLeft); if (comp.Float == EnumFloat.None) { posX = 0; posY += Math.Max(lineHeight, comp.BoundsPerLine[0].Height) + (didLineBreak ? GuiElement.scaled(comp.UnscaledMarginTop) : 0); posY = Math.Ceiling(posY); currentLine.Clear(); lineHeight = 0; ascentHeight = 0; continue; } if (didLineBreak) { lineHeight = Math.Ceiling(Math.Max(lineHeight, comp.BoundsPerLine[0].Height)); ascentHeight = Math.Ceiling(Math.Max(ascentHeight, comp.BoundsPerLine[0].AscentOrHeight)); // All previous elements in this line might need to have their Y pos adjusted due to a larger element in the line foreach (int index in currentLine) { RichTextComponentBase lineComp = Components[index]; Rectangled lastLineBounds = lineComp.BoundsPerLine[lineComp.BoundsPerLine.Length - 1]; if (lineComp.VerticalAlign == EnumVerticalAlign.Bottom) { lastLineBounds.Y = Math.Ceiling(lastLineBounds.Y + ascentHeight - lineComp.BoundsPerLine[lineComp.BoundsPerLine.Length - 1].AscentOrHeight); } if (lineComp.VerticalAlign == EnumVerticalAlign.Middle) { lastLineBounds.Y = Math.Ceiling(lastLineBounds.Y + ascentHeight - lineComp.BoundsPerLine[lineComp.BoundsPerLine.Length - 1].AscentOrHeight / 2); } } // The current element that was still on the same line as well // Offset all lines by the gained y-offset on the first line if (comp.VerticalAlign == EnumVerticalAlign.Bottom) { foreach (var val in comp.BoundsPerLine) { val.Y = Math.Ceiling(val.Y + ascentHeight - comp.BoundsPerLine[0].AscentOrHeight); } } if (comp.VerticalAlign == EnumVerticalAlign.Middle) { foreach (var val in comp.BoundsPerLine) { val.Y = Math.Ceiling(val.Y + ascentHeight - comp.BoundsPerLine[0].AscentOrHeight / 2); } } currentLine.Clear(); currentLine.Add(i); posY += lineHeight; for (int k = 1; k < comp.BoundsPerLine.Length - 1; k++) { posY += comp.BoundsPerLine[k].Height; } posY += scaled(comp.UnscaledMarginTop); posY = Math.Ceiling(posY); posX = comp.BoundsPerLine[comp.BoundsPerLine.Length - 1].Width; // + GuiElement.scaled(comp.PaddingLeft); - this adds too much padding when there is a line break inside a rich text compoment and afterwards there comes a link if (comp.BoundsPerLine[comp.BoundsPerLine.Length - 1].Width > 0) { lineHeight = comp.BoundsPerLine[comp.BoundsPerLine.Length - 1].Height; ascentHeight = comp.BoundsPerLine[comp.BoundsPerLine.Length - 1].AscentOrHeight; } else { lineHeight = 0; ascentHeight = 0; } } else { if (comp.Float == EnumFloat.Inline && comp.BoundsPerLine.Length > 0) { posX += comp.BoundsPerLine[0].Width; lineHeight = Math.Max(comp.BoundsPerLine[0].Height, lineHeight); ascentHeight = Math.Max(comp.BoundsPerLine[0].AscentOrHeight, ascentHeight); currentLine.Add(i); } } if (comp.Float != EnumFloat.Inline) { ConstrainTextFlowPath(flowPathList, posY, comp); } } if (DebugLogging) { api.Logger.VerboseDebug("GuiElementRichtext: after loop. posY = {0}", posY); } if (comp != null && posX > 0 && comp.BoundsPerLine.Length > 0) { posY += lineHeight; } Bounds.fixedHeight = (posY + 1) / RuntimeEnv.GUIScale; double maxHeight = 0; foreach (int index in currentLine) { RichTextComponentBase lineComp = Components[index]; Rectangled lastLineBounds = lineComp.BoundsPerLine[lineComp.BoundsPerLine.Length - 1]; maxHeight = Math.Max(maxHeight, lastLineBounds.Height); } foreach (int index in currentLine) { RichTextComponentBase lineComp = Components[index]; Rectangled lastLineBounds = lineComp.BoundsPerLine[lineComp.BoundsPerLine.Length - 1]; if (lineComp.VerticalAlign == EnumVerticalAlign.Bottom) { lastLineBounds.Y = Math.Ceiling(lastLineBounds.Y + ascentHeight - lineComp.BoundsPerLine[lineComp.BoundsPerLine.Length - 1].AscentOrHeight); } if (lineComp.VerticalAlign == EnumVerticalAlign.Middle) { lastLineBounds.Y = (maxHeight - lastLineBounds.Height) / 2f; } } this.flowPath = flowPathList.ToArray(); if (DebugLogging) { api.Logger.VerboseDebug("GuiElementRichtext: after bounds: {0}/{1} w/h = {2},{3}", Bounds.absX, Bounds.absY, Bounds.OuterWidth, Bounds.OuterHeight); api.Logger.VerboseDebug("GuiElementRichtext: posY = {0}", posY); api.Logger.VerboseDebug("GuiElementRichtext: framewidth/height: {0}/{1}", api.Render.FrameWidth, api.Render.FrameHeight); } }