/** * create line of contents -> sort all content by base line. */ private HandlePoint SortByLayoutLine(List <VirtualGameObject> layoutLine, HandlePoint handlePoint) { // find tallest content in layoutLine. var targetHeightObjArray = layoutLine.OrderByDescending(c => c.vRectTransform.vSizeDelta.y + c.padding.PadHeight()).ToArray(); if (0 < targetHeightObjArray.Length) { var tallestContent = targetHeightObjArray[0]; // get tallest padded height. this will be this layoutLine's bottom line. var paddedHighestHeightInLine = tallestContent.vRectTransform.vSizeDelta.y + tallestContent.padding.PadHeight(); // other child content will be moved. var skipFirst = true; foreach (var childInLine in targetHeightObjArray) { if (skipFirst) { skipFirst = false; continue; } var childPaddedHeight = childInLine.vRectTransform.vSizeDelta.y + childInLine.padding.PadHeight(); var heightDiff = paddedHighestHeightInLine - childPaddedHeight; childInLine.vRectTransform.vAnchoredPosition += new Vector2(0, heightDiff); // Debug.LogError("childInLine:" + childInLine.tag + " childInLine.rectTransform.anchoredPosition:" + childInLine.rectTransform.anchoredPosition + " under tag:" + this.tag + " heightDiff:" + heightDiff); } // set next line head. handlePoint.nextLeftHandle = 0; handlePoint.nextTopHandle = tallestContent.vRectTransform.vAnchoredPosition.y + tallestContent.vRectTransform.vSizeDelta.y + tallestContent.padding.PadHeight(); // Debug.LogError("SortByLayoutLine handlePoint.nextTopHandle:" + handlePoint.nextTopHandle); // Debug.LogError("SortByLayoutLine rectTransform.anchoredPosition:" + rectTransform.anchoredPosition + " of tag:" + tag + " handlePoint:" + handlePoint.nextTopHandle); } return(handlePoint); }
/** * return generated game object. */ public GameObject MaterializeRoot(string viewName, Vector2 viewPort, Tokenizer.OnLayoutDelegate onLayoutDel, Tokenizer.OnMaterializeDelegate onMaterializeDel) { var rootHandlePoint = new HandlePoint(0, 0, viewPort.x, viewPort.y); // 事前計算、ここでコンテンツの一覧を返すようにすればいいかな。要素単位で。 Layout(this, rootHandlePoint, onLayoutDel, t => {}); this._gameObject = new GameObject(viewName + Tag.ROOT.ToString()); this.rootInstance = this._gameObject.AddComponent <InformationRootMonoBehaviour>(); var rectTrans = this._gameObject.AddComponent <RectTransform>(); rectTrans.anchorMin = Vector2.up; rectTrans.anchorMax = Vector2.up; rectTrans.pivot = Vector2.up; rectTrans.position = Vector2.zero; rectTrans.sizeDelta = viewPort; // 範囲指定してGOを充てる、ということがしたい。 Materialize(this, onMaterializeDel); return(this._gameObject); }
private void LayoutChildlen(List <VirtualGameObject> childlen, HandlePoint handlePoint, Tokenizer.OnLayoutDelegate onLayoutDel) { // Debug.LogWarning("LayoutChildlen、子供にいくに従って、親要素の起点から幅と高さの制限をつける必要がある。"); // Debug.LogError("handlePoint.nextLeftHandle:" + handlePoint.nextLeftHandle); var childHandlePoint = new HandlePoint(0, 0, handlePoint.viewWidth, handlePoint.viewHeight); // layout -> resize -> padding of childlen. var layoutLine = new List <VirtualGameObject>(); var i = 0; while (true) { if (childlen.Count <= i) { break; } var child = childlen[i]; // consume br as linefeed. if (child.tag == Tag.BR) { // Debug.LogError("brが発生するので、handlePointのyは変わってるはず:" + handlePoint.nextTopHandle); childHandlePoint = SortByLayoutLine(layoutLine, childHandlePoint); // Debug.LogError("brが発生したので、handlePointのyは変わってるはず:" + handlePoint.nextTopHandle); // forget current line. layoutLine.Clear(); // set next line. childHandlePoint.nextLeftHandle = 0; i++; continue; } // Debug.LogWarning("hr、これ下の方でまとめて処理できるかも。"); // consume hr 1/2 as horizontal rule. if (child.tag == Tag.HR) { childHandlePoint = SortByLayoutLine(layoutLine, childHandlePoint); // forget current line. layoutLine.Clear(); } var sortLayoutLineBeforeLining = false; var sortLayoutLineAfterLining = false; /* * insert content to childlen list. * create new content from one long content by length overflow. */ Action <List <VirtualGameObject> > insertAct = insertNewVGameObject => { childlen.InsertRange(i + 1, insertNewVGameObject); // this line is ended at this content. need layout. sortLayoutLineAfterLining = true; }; // set position and calculate size. childHandlePoint = child.Layout(this, childHandlePoint, onLayoutDel, insertAct); // consume hr 2/2 as horizontal rule. if (child.tag == Tag.HR) { // set next line. childHandlePoint.nextLeftHandle = 0; i++; continue; } // root content does not request sorting child contents. if (this.tag == Tag.ROOT) { i++; continue; } /* * the insertAct(t) is raised or not raised. */ // if child is content and that width is 0, this is because, there is not enough width in this line. // line is ended. if (child.tag == Tag._CONTENT && child.vRectTransform.vSizeDelta.x == 0) { sortLayoutLineAfterLining = true; } /* * nested bq. */ if (this.tag == Tag.BLOCKQUOTE) { // nested bq. if (child.tag == Tag.BLOCKQUOTE) { sortLayoutLineBeforeLining = true; } } /* * nested list's child list should be located to new line. */ if (this.tag == Tag.LI) { // nested list. if (child.tag == Tag.OL || child.tag == Tag.UL) { sortLayoutLineBeforeLining = true; } } // list's child should be ordered vertically. if (this.tag == Tag.OL || this.tag == Tag.UL) { sortLayoutLineAfterLining = true; } // check width overflow. // if next left handle is overed, sort as lined contents. if (childHandlePoint.viewWidth < childHandlePoint.nextLeftHandle) { sortLayoutLineBeforeLining = true; } // table { if (child.tag == Tag.THEAD) // table head is single line. { sortLayoutLineAfterLining = true; } else if (child.tag == Tag.TR) // table row. { sortLayoutLineAfterLining = true; } } /* * sort current lined contents as 1 line of contents. * before adding current content. */ if (sortLayoutLineBeforeLining) { childHandlePoint = SortByLayoutLine(layoutLine, childHandlePoint); // forget current line. layoutLine.Clear(); // move current child content to next line head. child.vRectTransform.vAnchoredPosition = new Vector2(childHandlePoint.nextLeftHandle + child.padding.left, childHandlePoint.nextTopHandle + child.padding.top); // Debug.LogError("child.rectTransform.anchoredPosition:" + child.rectTransform.anchoredPosition); // set next handle. childHandlePoint.nextLeftHandle = childHandlePoint.nextLeftHandle + child.padding.left + child.vRectTransform.vSizeDelta.x + child.padding.right; } // content width is smaller than viewpoint width. layoutLine.Add(child); /* * sort current lined contents as 1 line of contents. * after adding current content. * * this line is ended by this content. */ if (sortLayoutLineAfterLining) { childHandlePoint = SortByLayoutLine(layoutLine, childHandlePoint); // forget current line. layoutLine.Clear(); // set next content's head position. childHandlePoint.nextLeftHandle = 0; } i++; } // if layoutLine content is exist, re-layout all in 1 line. if (0 < layoutLine.Count) { childHandlePoint = SortByLayoutLine(layoutLine, childHandlePoint); } }
/** * layout contents. * * set position and size of content. */ private HandlePoint Layout(VirtualGameObject parent, HandlePoint handlePoint, Tokenizer.OnLayoutDelegate onLayoutDel, Action <List <VirtualGameObject> > insert) { switch (this.tag) { case Tag.ROOT: { // do nothing. break; } default: { LayoutTagContent(handlePoint.nextLeftHandle, handlePoint.nextTopHandle, handlePoint.viewWidth, handlePoint.viewHeight, insert); break; } } // parent layout is done. will be resized by child, then padding. var childlen = this.transform.GetChildlen(); if (0 < childlen.Count) { LayoutChildlen(childlen, handlePoint, onLayoutDel); /* * set parent = this content's size to wrapping all childlen. */ var rightBottomPoint = Vector2.zero; // fit most large bottom-right point. largest point of width and y. foreach (var child in childlen) { var paddedRightBottomPoint = child.PaddedRightBottomPoint(); if (rightBottomPoint.x < paddedRightBottomPoint.x) { rightBottomPoint.x = paddedRightBottomPoint.x; } if (rightBottomPoint.y < paddedRightBottomPoint.y) { rightBottomPoint.y = paddedRightBottomPoint.y; } } // fit size to wrap all child contents. vRectTransform.vSizeDelta = rightBottomPoint; // calculate table's contents. if (this.tag == Tag.TABLE) { /* * all contents size calculation inside this table is done. * count up row, * find longest content, * and adjust left point of contents. */ var tableLayoutRecord = new TableLayoutRecord(); // countup rows. foreach (var tableChild in this.transform.GetChildlen()) { CollectTableContentRowCountRecursively(tableChild, tableLayoutRecord); } // find longest content. foreach (var tableChild in this.transform.GetChildlen()) { CollectTableContentRowMaxWidthsRecursively(tableChild, tableLayoutRecord); } // resize & reset position of this table contents by calculated record. foreach (var tableChild in this.transform.GetChildlen()) { SetupTableContentPositionRecursively(tableChild, tableLayoutRecord); } } } /* * set padding if need. * default padding is 0. */ onLayoutDel(this.tag, this.depth, this.padding, this.keyValueStore); /* * adopt padding to this content. */ { // translate anchor position of content.(child follows parent.) vRectTransform.vAnchoredPosition += padding.LeftTopPoint(); handlePoint.nextLeftHandle += padding.PadWidth(); handlePoint.nextTopHandle += padding.PadHeight(); // Debug.LogWarning("実験した方が良さそう"); } // Debug.LogError("rectTransform.anchoredPosition:" + rectTransform.anchoredPosition); /* * set next left-top point by parent tag kind. */ switch (parent.tag) { default: { // 回り込みを実現する。んだけど、これはどちらかというと多数派で、デフォルトっぽい。 // next content is planned to layout to the next of this content. handlePoint.nextLeftHandle = this.vRectTransform.vAnchoredPosition.x + this.vRectTransform.vSizeDelta.x + this.padding.PadWidth(); // right edge with padding // Debug.LogError("handlePoint.nextLeftHandle:" + handlePoint.nextLeftHandle); break; } // Rootコンテンツにぶらさがっている項目は、全てCRLFがかかる。 case Tag.ROOT: { // CRLF handlePoint.nextLeftHandle = 0; handlePoint.nextTopHandle += this.vRectTransform.vSizeDelta.y + this.padding.PadHeight(); // Debug.LogError("親がRootなので、改行する。handlePoint.nextTopHandle:" + handlePoint.nextTopHandle + " of tag:" + tag + " rectTransform.anchoredPosition:" + this.rectTransform.anchoredPosition); break; } } return(handlePoint); }
/** * layout contents. * * set position and size of content. */ private HandlePoint Layout(VirtualGameObject parent, HandlePoint handlePoint, Tokenizer.OnLayoutDelegate onLayoutDel, Action <List <VirtualGameObject> > insert) { switch (this.tag) { case Tag.ROOT: { // do nothing. break; } default: { // Debug.LogError("before layout rectTransform.anchoredPosition:" + rectTransform.anchoredPosition + " of tag:" + tag + " handlePoint:" + handlePoint.nextTopHandle); LayoutTagContent(handlePoint.nextLeftHandle, handlePoint.nextTopHandle, handlePoint.viewWidth, handlePoint.viewHeight, insert); // Debug.LogError("after layout rectTransform.anchoredPosition:" + rectTransform.anchoredPosition + " of tag:" + tag + " handlePoint:" + handlePoint.nextTopHandle); break; } } // parent layout is done. will be resized by child, then padding. // calculate table's column count. if (this.tag == Tag.TABLE) { // ハンドラで、n x m のテーブルであることが通知できる。 // 別の話、N文字目に改行があったことが記録として残せるので、要素にidを振ることができる。 // n x mがわかったら、それぞれの幅をどうしたいかを通知できるはず。 // 指定したら、その幅を採用する。レイアウトも溢れも。ということはできそう。 // ッツー感じか。n単位でwidthを返せばいいので、nを受け取ってn x widthを返すのでよさげ。 var maxPoints = new MaxPoints(); // pre-layout table contents. foreach (var tableChild in this.transform.GetChildlen()) { DoLayoutTagContentRecursively(tableChild, handlePoint.nextLeftHandle, handlePoint.nextTopHandle, handlePoint.viewWidth, handlePoint.viewHeight, maxPoints); } } var childlen = this.transform.GetChildlen(); if (0 < childlen.Count) { LayoutChildlen(childlen, handlePoint, onLayoutDel); /* * set parent = this content's size to wrapping all childlen. */ var rightBottomPoint = Vector2.zero; // fit most large bottom-right point. largest point of width and y. foreach (var child in childlen) { var paddedRightBottomPoint = child.PaddedRightBottomPoint(); if (rightBottomPoint.x < paddedRightBottomPoint.x) { rightBottomPoint.x = paddedRightBottomPoint.x; } if (rightBottomPoint.y < paddedRightBottomPoint.y) { rightBottomPoint.y = paddedRightBottomPoint.y; } } // fit size to wrap all child contents. rectTransform.sizeDelta = rightBottomPoint; // Debug.LogError("set wrap rectTransform.sizeDelta:" + rectTransform.sizeDelta + " of tag:" + tag); // Debug.LogError("after wrap rectTransform.anchoredPosition:" + rectTransform.anchoredPosition + " of tag:" + tag + " handlePoint:" + handlePoint.nextTopHandle); // layout and padding and orientation of child tags are done. } /* * set padding if need. * default padding is 0. */ onLayoutDel(this.tag, this.depth, this.padding, this.keyValueStore); /* * adopt padding to this content. */ { // translate anchor position of content.(child follows parent.) rectTransform.anchoredPosition += padding.LeftTopPoint(); handlePoint.nextLeftHandle += padding.PadWidth(); handlePoint.nextTopHandle += padding.PadHeight(); // Debug.LogWarning("実験した方が良さそう"); } // Debug.LogError("rectTransform.anchoredPosition:" + rectTransform.anchoredPosition); /* * set next left-top point by parent tag kind. */ switch (parent.tag) { default: { // 回り込みを実現する。んだけど、これはどちらかというと多数派で、デフォルトっぽい。 // next content is planned to layout to the next of this content. handlePoint.nextLeftHandle = this.rectTransform.anchoredPosition.x + this.rectTransform.sizeDelta.x + this.padding.PadWidth(); // right edge with padding // Debug.LogError("handlePoint.nextLeftHandle:" + handlePoint.nextLeftHandle); break; } // Rootコンテンツにぶらさがっている項目は、全てCRLFがかかる。 case Tag.ROOT: { // CRLF handlePoint.nextLeftHandle = 0; handlePoint.nextTopHandle += this.rectTransform.sizeDelta.y + this.padding.PadHeight(); // Debug.LogError("親がRootなので、改行する。handlePoint.nextTopHandle:" + handlePoint.nextTopHandle + " of tag:" + tag + " rectTransform.anchoredPosition:" + this.rectTransform.anchoredPosition); break; } } return(handlePoint); }