internal BoxLayoutGroup() { horizontal = null; layoutPriority = 1; parameters = new BoxLayoutParams(); vertical = null; }
/// <summary> /// Calculates the size of the box layout container. /// </summary> /// <param name="obj">The container to lay out.</param> /// <param name="args">The parameters to use for layout.</param> /// <param name="direction">The direction which is being calculated.</param> /// <returns>The minimum and preferred box layout size.</returns> private static BoxLayoutResults Calc(GameObject obj, BoxLayoutParams args, PanelDirection direction) { var transform = obj.AddOrGet <RectTransform>(); int n = transform.childCount; var result = new BoxLayoutResults(direction, n); var components = ListPool <Component, BoxLayoutGroup> .Allocate(); for (int i = 0; i < n; i++) { var child = transform.GetChild(i)?.gameObject; if (child != null && child.activeInHierarchy) { // Only on active game objects components.Clear(); child.GetComponents(components); var hc = PUIUtils.CalcSizes(child, direction, components); if (!hc.ignore) { if (args.Direction == direction) { result.Accum(hc, args.Spacing); } else { result.Expand(hc); } result.children.Add(hc); } } } components.Recycle(); return(result); }
/// <summary> /// Without adding a BoxLayoutGroup component to the specified object, lays it out /// based on its current child and layout element sizes, then updates its preferred /// and minimum sizes based on the results. The component will be laid out at a fixed /// size equal to its preferred size. /// /// UI elements should be active before any layouts are added, especially if they are /// to be frozen. /// </summary> /// <param name="obj">The object to lay out immediately.</param> /// <param name="parameters">The layout parameters to use.</param> /// <param name="size">The minimum component size.</param> /// <returns>obj for call chaining</returns> public static GameObject LayoutNow(GameObject obj, BoxLayoutParams parameters = null, Vector2 size = default) { if (obj == null) { throw new ArgumentNullException("obj"); } var args = parameters ?? new BoxLayoutParams(); var margin = args.Margin ?? new RectOffset(); // Calculate var horizontal = Calc(obj, args, PanelDirection.Horizontal); var vertical = Calc(obj, args, PanelDirection.Vertical); // Update or create fixed layout element var layoutElement = obj.AddOrGet <LayoutElement>(); float hmin = horizontal.total.preferred + margin.left + margin.right, vmin = vertical.total.preferred + margin.top + margin.bottom, hsize = Math.Max(size.x, hmin), vsize = Math.Max(size.y, vmin); layoutElement.minWidth = hsize; layoutElement.preferredWidth = hsize; layoutElement.flexibleWidth = 0.0f; layoutElement.minHeight = vsize; layoutElement.preferredHeight = vsize; layoutElement.flexibleHeight = 0.0f; // Size the object now var rt = obj.rectTransform(); rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, hsize); rt.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, vsize); // Allocate DoLayout(args, horizontal, hsize); DoLayout(args, vertical, vsize); return(obj); }
/// <summary> /// Lays out components in the box layout container parallel to the layout axis. /// </summary> /// <param name="required">The calculated minimum and preferred sizes.</param> /// <param name="args">The parameters to use for layout.</param> /// <param name="status">The current status of layout.</param> private static void DoLayoutLinear(BoxLayoutResults required, BoxLayoutParams args, BoxLayoutStatus status) { var total = required.total; var components = ListPool <ILayoutController, BoxLayoutGroup> .Allocate(); var direction = args.Direction; // Determine flex size ratio float size = status.size, prefRatio = 0.0f, minSize = total.min, prefSize = total.preferred, excess = Math.Max(0.0f, size - prefSize), flexTotal = total. flexible, offset = status.offset, spacing = args.Spacing; if (size > minSize && prefSize > minSize) { // Do not divide by 0 prefRatio = Math.Min(1.0f, (size - minSize) / (prefSize - minSize)); } if (excess > 0.0f && flexTotal == 0.0f) { // If no components can be expanded, offset all offset += PUIUtils.GetOffset(args.Alignment, status.direction, excess); } foreach (var child in required.children) { var obj = child.source; // Active objects only if (obj != null && obj.activeInHierarchy) { float compSize = child.min; if (prefRatio > 0.0f) { compSize += (child.preferred - child.min) * prefRatio; } if (excess > 0.0f && flexTotal > 0.0f) { compSize += excess * child.flexible / flexTotal; } // Place and size component obj.AddOrGet <RectTransform>().SetInsetAndSizeFromParentEdge(status.edge, offset, compSize); offset += compSize + ((compSize > 0.0f) ? spacing : 0.0f); // Invoke SetLayout on dependents components.Clear(); obj.GetComponents(components); foreach (var component in components) { if (direction == PanelDirection.Horizontal) { component.SetLayoutHorizontal(); } else // if (direction == PanelDirection.Vertical) { component.SetLayoutVertical(); } } } } components.Recycle(); }
public static GameObject LayoutNow(GameObject obj, BoxLayoutParams parameters = null, Vector2 size = default) { if (obj == null) { throw new ArgumentNullException("obj"); } var layout = obj.AddOrGet <BoxLayoutGroup>(); layout.Params = parameters; layout.LockLayout(); obj.SetMinUISize(size); return(obj); }
/// <summary> /// Gets the offset required for a component in its box. /// </summary> /// <param name="args">The parameters to use for layout.</param> /// <param name="direction">The direction of layout.</param> /// <param name="delta">The remaining space.</param> /// <returns>The offset from the edge.</returns> private static float GetOffset(BoxLayoutParams args, PanelDirection direction, float delta) { float offset = 0.0f; // Based on alignment, offset component if (direction == PanelDirection.Horizontal) { switch (args.Alignment) { case TextAnchor.LowerCenter: case TextAnchor.MiddleCenter: case TextAnchor.UpperCenter: offset = delta * 0.5f; break; case TextAnchor.LowerRight: case TextAnchor.MiddleRight: case TextAnchor.UpperRight: offset = delta; break; default: break; } } else { switch (args.Alignment) { case TextAnchor.MiddleLeft: case TextAnchor.MiddleCenter: case TextAnchor.MiddleRight: offset = delta * 0.5f; break; case TextAnchor.LowerLeft: case TextAnchor.LowerCenter: case TextAnchor.LowerRight: offset = delta; break; default: break; } } return(offset); }
/// <summary> /// Lays out components in the box layout container against the layout axis. /// </summary> /// <param name="required">The calculated minimum and preferred sizes.</param> /// <param name="args">The parameters to use for layout.</param> /// <param name="status">The current status of layout.</param> private static void DoLayoutPerp(LayoutResults required, BoxLayoutParams args, LayoutStatus status) { var components = ListPool <ILayoutController, BoxLayoutGroup> .Allocate(); var direction = args.Direction; float size = status.size; foreach (var child in required.children) { var obj = child.source; // Active objects only if (obj != null && obj.activeInHierarchy) { float compSize = size; if (child.flexible <= 0.0f) { // Does not expand to all compSize = Math.Min(compSize, child.preferred); } float offset = (size > compSize) ? GetOffset(args, status.direction, size - compSize) : 0.0f; // Place and size component obj.AddOrGet <RectTransform>().SetInsetAndSizeFromParentEdge(status.edge, offset + status.offset, compSize); // Invoke SetLayout on dependents components.Clear(); obj.GetComponents(components); foreach (var component in components) { if (!PUIUtils.IgnoreLayout(component)) { if (direction == PanelDirection.Horizontal) { component.SetLayoutVertical(); } else // if (direction == PanelDirection.Vertical) { component.SetLayoutHorizontal(); } } } } } components.Recycle(); }
/// <summary> /// Lays out components in the box layout container. /// </summary> /// <param name="args">The parameters to use for layout.</param> /// <param name="required">The calculated minimum and preferred sizes.</param> /// <param name="size">The total available size in this dimension.</param> private static void DoLayout(BoxLayoutParams args, LayoutResults required, float size) { if (required == null) { throw new ArgumentNullException("required"); } var direction = required.direction; var status = new LayoutStatus(direction, args.Margin ?? new RectOffset(), size); if (args.Direction == direction) { DoLayoutLinear(required, args, status); } else { DoLayoutPerp(required, args, status); } }
public override GameObject Build() { var label = PUIElements.CreateUI(null, Name); // Background if (BackColor.a > 0) { label.AddComponent <Image>().color = BackColor; } // Add foreground image if (Sprite != null) { ImageChildHelper(label, this); } // Add text if (!string.IsNullOrEmpty(Text)) { TextChildHelper(label, TextStyle ?? PUITuning.Fonts.UILightStyle, Text); } // Add tooltip if (!string.IsNullOrEmpty(ToolTip)) { label.AddComponent <ToolTip>().toolTip = ToolTip; } label.SetActive(true); // Icon and text are side by side var lp = new BoxLayoutParams() { Spacing = IconSpacing, Direction = PanelDirection.Horizontal, Margin = Margin, Alignment = TextAlignment }; if (DynamicSize) { label.AddComponent <BoxLayoutGroup>().Params = lp; } else { BoxLayoutGroup.LayoutNow(label, lp); } label.SetFlexUISize(FlexSize); InvokeRealize(label); return(label); }
public GameObject Build() { var panel = PUIElements.CreateUI(Name); if (BackColor.a > 0.0f) { panel.AddComponent <Image>().color = BackColor; } panel.layer = LayerMask.NameToLayer("UI"); // Add children foreach (var child in children) { PUIElements.SetParent(child.Build(), panel); } // Add layout component var args = new BoxLayoutParams() { Direction = Direction, Alignment = Alignment, Spacing = Spacing, Margin = Margin }; // Gotta love freezable layouts if (DynamicSize) { var lg = panel.AddComponent <BoxLayoutGroup>(); lg.Params = args; lg.flexibleWidth = FlexSize.x; lg.flexibleHeight = FlexSize.y; } else { BoxLayoutGroup.LayoutNow(panel, args).SetFlexUISize(FlexSize); } OnRealize?.Invoke(panel); return(panel); }
public GameObject Build() { var textField = PUIElements.CreateUI(Name); // Background var style = TextStyle ?? PUITuning.Fonts.TextLightStyle; textField.AddComponent <Image>().color = style.textColor; // Text box with rectangular clipping area var textArea = PUIElements.CreateUI("Text Area", true, false); textArea.AddComponent <Image>().color = BackColor; PUIElements.SetParent(textArea, textField); var mask = textArea.AddComponent <RectMask2D>(); // Scrollable text var textBox = PUIElements.CreateUI("Text"); PUIElements.SetParent(textBox, textArea); // Text to display var textDisplay = textBox.AddComponent <TextMeshProUGUI>(); textDisplay.alignment = TextAlignment; textDisplay.autoSizeTextContainer = false; textDisplay.enabled = true; textDisplay.color = style.textColor; textDisplay.font = style.sdfFont; textDisplay.fontSize = style.fontSize; textDisplay.fontStyle = style.style; textDisplay.maxVisibleLines = 1; // Text field itself var onChange = OnTextChanged; textField.SetActive(false); var textEntry = textField.AddComponent <TMP_InputField>(); textEntry.textComponent = textDisplay; textEntry.textViewport = textArea.rectTransform(); textField.SetActive(true); textEntry.text = Text ?? ""; textDisplay.text = Text ?? ""; ConfigureTextEntry(textEntry).onDeselect.AddListener((text) => { onChange?.Invoke(textField, (text ?? "").TrimEnd()); }); // Add tooltip if (!string.IsNullOrEmpty(ToolTip)) { textField.AddComponent <ToolTip>().toolTip = ToolTip; } mask.enabled = true; // Lay out - TMP_InputField does not support auto layout so we have to do a hack var minSize = new Vector2(MinWidth, LayoutUtility.GetPreferredHeight(textBox. rectTransform())); textArea.SetMinUISize(minSize).SetFlexUISize(Vector2.one); var lp = new BoxLayoutParams() { Direction = PanelDirection.Horizontal, Alignment = TextAnchor.MiddleLeft, Margin = new RectOffset(1, 1, 1, 1) }; if (DynamicSize) { var layout = textField.AddComponent <BoxLayoutGroup>(); layout.Params = lp; layout.flexibleWidth = FlexSize.x; layout.flexibleHeight = FlexSize.y; textField.SetMinUISize(minSize); } else { BoxLayoutGroup.LayoutNow(textField, lp, new Vector2(MinWidth, 0.0f)). SetFlexUISize(FlexSize); } OnRealize?.Invoke(textField); return(textField); }
public override GameObject Build() { var button = PUIElements.CreateUI(null, Name); // Background var kImage = button.AddComponent <KImage>(); var trueColor = Color ?? PUITuning.Colors.ButtonPinkStyle; kImage.colorStyleSetting = trueColor; kImage.color = trueColor.inactiveColor; kImage.sprite = PUITuning.Images.ButtonBorder; kImage.type = Image.Type.Sliced; // Set on click event var kButton = button.AddComponent <KButton>(); var evt = OnClick; if (evt != null) { kButton.onClick += () => { evt?.Invoke(button); } } ; kButton.additionalKImages = new KImage[0]; kButton.soundPlayer = PUITuning.ButtonSounds; kButton.bgImage = kImage; // Add foreground image since the background already has one if (Sprite != null) { kButton.fgImage = ImageChildHelper(button, this); } // Set colors kButton.colorStyleSetting = trueColor; // Add text if (!string.IsNullOrEmpty(Text)) { TextChildHelper(button, TextStyle ?? PUITuning.Fonts.UILightStyle, Text); } // Add tooltip if (!string.IsNullOrEmpty(ToolTip)) { button.AddComponent <ToolTip>().toolTip = ToolTip; } button.SetActive(true); // Icon and text are side by side var lp = new BoxLayoutParams() { Spacing = IconSpacing, Direction = PanelDirection.Horizontal, Margin = Margin, Alignment = TextAlignment }; if (DynamicSize) { button.AddComponent <BoxLayoutGroup>().Params = lp; } else { BoxLayoutGroup.LayoutNow(button, lp); } button.SetFlexUISize(FlexSize); InvokeRealize(button); return(button); }
public override GameObject Build() { var checkbox = PUIElements.CreateUI(null, Name); // Background var trueColor = CheckColor ?? PUITuning.Colors.ComponentLightStyle; // Checkbox background var checkBack = PUIElements.CreateUI(checkbox, "CheckBox"); checkBack.AddComponent <Image>().color = BackColor; // Checkbox border var checkBorder = PUIElements.CreateUI(checkBack, "CheckBorder"); var borderImg = checkBorder.AddComponent <Image>(); borderImg.sprite = PUITuning.Images.CheckBorder; borderImg.color = trueColor.activeColor; borderImg.type = Image.Type.Sliced; // Checkbox foreground var imageChild = PUIElements.CreateUI(checkBorder, "CheckMark", true, PUIAnchoring. Center, PUIAnchoring.Center); var img = imageChild.AddComponent <Image>(); img.sprite = PUITuning.Images.Checked; img.preserveAspect = true; // Determine the checkbox size var actualSize = CheckSize; if (actualSize.x <= 0.0f || actualSize.y <= 0.0f) { var rt = imageChild.rectTransform(); actualSize.x = LayoutUtility.GetPreferredWidth(rt); actualSize.y = LayoutUtility.GetPreferredHeight(rt); } imageChild.SetUISize(CheckSize, false); // Add foreground image since the background already has one if (Sprite != null) { ImageChildHelper(checkbox, this); } // Add text if (!string.IsNullOrEmpty(Text)) { TextChildHelper(checkbox, TextStyle ?? PUITuning.Fonts.UILightStyle, Text); } // Add tooltip if (!string.IsNullOrEmpty(ToolTip)) { checkbox.AddComponent <ToolTip>().toolTip = ToolTip; } // Toggle var mToggle = checkbox.AddComponent <MultiToggle>(); var evt = OnChecked; if (evt != null) { mToggle.onClick += () => { evt?.Invoke(checkbox, mToggle.CurrentState); } } ; mToggle.play_sound_on_click = true; mToggle.play_sound_on_release = false; mToggle.states = GenerateStates(trueColor); mToggle.toggle_image = img; mToggle.ChangeState(InitialState); checkbox.SetActive(true); // Lay out the checkbox using anchors only checkBack.SetUISize(new Vector2(CheckSize.x + 2.0f * CHECKBOX_MARGIN, CheckSize.y + 2.0f * CHECKBOX_MARGIN), true); imageChild.SetUISize(CheckSize); // Icon and text are side by side var lp = new BoxLayoutParams() { Margin = Margin, Spacing = Math.Max(IconSpacing, 0), Alignment = TextAnchor. MiddleLeft }; if (DynamicSize) { checkbox.AddComponent <BoxLayoutGroup>().Params = lp; } else { BoxLayoutGroup.LayoutNow(checkbox, lp); } checkbox.SetFlexUISize(FlexSize); InvokeRealize(checkbox); return(checkbox); }
public override GameObject Build() { var checkbox = PUIElements.CreateUI(Name); // Background var trueColor = CheckColor ?? PUITuning.Colors.CheckboxWhiteStyle; // Checkbox background var checkBack = PUIElements.CreateUI("CheckBox"); checkBack.AddComponent <Image>().color = BackColor; PUIElements.SetParent(checkBack, checkbox); // Checkbox border var checkBorder = PUIElements.CreateUI("CheckBorder"); var borderImg = checkBorder.AddComponent <Image>(); borderImg.sprite = PUITuning.Images.CheckBorder; borderImg.color = trueColor.activeColor; borderImg.type = Image.Type.Sliced; PUIElements.SetParent(checkBorder, checkBack); // Checkbox foreground var imageChild = PUIElements.CreateUI("CheckMark"); var img = imageChild.AddComponent <Image>(); PUIElements.SetParent(imageChild, checkBorder); img.sprite = PUITuning.Images.Checked; img.preserveAspect = true; // Limit size if needed if (CheckSize.x > 0.0f && CheckSize.y > 0.0f) { PUIElements.SetSizeImmediate(imageChild, CheckSize); } else { PUIElements.AddSizeFitter(imageChild, false); } // Add foreground image since the background already has one if (Sprite != null) { ImageChildHelper(checkbox, Sprite, SpriteTransform, SpriteSize); } // Add text if (!string.IsNullOrEmpty(Text)) { TextChildHelper(checkbox, TextStyle ?? PUITuning.Fonts.UILightStyle, Text); } // Add tooltip if (!string.IsNullOrEmpty(ToolTip)) { checkbox.AddComponent <ToolTip>().toolTip = ToolTip; } // Toggle var kButton = checkbox.AddComponent <MultiToggle>(); var evt = OnChecked; if (evt != null) { kButton.onClick += () => { evt?.Invoke(checkbox, kButton.CurrentState); } } ; kButton.play_sound_on_click = true; kButton.play_sound_on_release = false; kButton.states = GenerateStates(trueColor, borderImg); kButton.toggle_image = img; kButton.ChangeState(InitialState); checkbox.SetActive(true); BoxLayoutGroup.LayoutNow(checkBorder, new BoxLayoutParams() { Margin = CHECKBOX_MARGIN }); BoxLayoutGroup.LayoutNow(checkBack); // Icon and text are side by side var lp = new BoxLayoutParams() { Margin = Margin, Spacing = Math.Max(IconSpacing, 0), Alignment = TextAnchor. MiddleLeft }; if (DynamicSize) { checkbox.AddComponent <BoxLayoutGroup>().Params = lp; } else { BoxLayoutGroup.LayoutNow(checkbox, lp); } PUIElements.AddSizeFitter(checkbox, DynamicSize).SetFlexUISize(FlexSize); InvokeRealize(checkbox); return(checkbox); }