1: <?php
2:
3: namespace Charcoal\Ui\Layout;
4:
5: use \InvalidArgumentException;
6:
7: /**
8: * Provides an implementation of {@see \Charcoal\Ui\Layout\LayoutInterface}.
9: */
10: trait LayoutTrait
11: {
12: /**
13: * @var integer $position
14: */
15: private $position = 0;
16:
17: /**
18: * @var array $structure
19: */
20: private $structure = [];
21:
22: /**
23: * @param integer $position The layout cell position.
24: * @throws InvalidArgumentException If the position argument is not a number.
25: * @return LayoutInterface Chainable
26: */
27: public function setPosition($position)
28: {
29: if (!is_numeric($position)) {
30: throw new InvalidArgumentException(
31: 'Position must be an integer.'
32: );
33: }
34: $this->position = (int)$position;
35: return $this;
36: }
37:
38: /**
39: * @return integer
40: */
41: public function position()
42: {
43: return $this->position;
44: }
45:
46: /**
47: * Prepare the layouts configuration in a simpler, ready, data structure.
48: *
49: * This function goes through the layout options to expand loops into extra layout data...
50: *
51: * @param array $layouts The original layout data, typically from configuration.
52: * @return array Computed layouts, ready for looping
53: */
54: public function setStructure(array $layouts)
55: {
56: $computedLayouts = [];
57: foreach ($layouts as $l) {
58: $loop = isset($l['loop']) ? (int)$l['loop'] : 1;
59: unset($l['loop']);
60: for ($i=0; $i<$loop; $i++) {
61: $computedLayouts[] = $l;
62: }
63: }
64:
65: $this->structure = $computedLayouts;
66:
67: return $this;
68: }
69:
70: /**
71: * @return array
72: */
73: public function structure()
74: {
75: return $this->structure;
76: }
77:
78: /**
79: * Get the total number of rows
80: *
81: * @return integer
82: */
83: public function numRows()
84: {
85: $structure = $this->structure();
86: return count($structure);
87: }
88:
89: /**
90: * Get the row index at a certain position
91: *
92: * @param integer $position Optional. Forced position.
93: * @return integer|null
94: */
95: public function rowIndex($position = null)
96: {
97: if ($position === null) {
98: $position = $this->position();
99: }
100:
101: $i = 0;
102: $p = 0;
103: foreach ($this->structure as $row_ident => $row) {
104: $numCells = count($row['columns']);
105: $p += $numCells;
106: if ($p > $position) {
107: return $i;
108: }
109: $i++;
110: }
111: return null;
112: }
113:
114: /**
115: * Get the row information
116: *
117: * If no `$position` is specified, then the current position will be used.
118: *
119: * @param integer $position Optional. Forced position.
120: * @return array|null
121: */
122: public function rowData($position = null)
123: {
124: if ($position === null) {
125: $position = $this->position();
126: }
127:
128: $rowIndex = $this->rowIndex($position);
129: if (isset($this->structure[$rowIndex])) {
130: return $this->structure[$rowIndex];
131: } else {
132: return null;
133: }
134: }
135:
136: /**
137: * Get the number of columns (the colspan) of the row at a certain position
138: *
139: * @param integer $position Optional. Forced position.
140: * @return integer|null
141: */
142: public function rowNumColumns($position = null)
143: {
144: if ($position === null) {
145: $position = $this->position();
146: }
147:
148: $row = $this->rowData($position);
149: if ($row === null) {
150: return null;
151: } else {
152: return array_sum($row['columns']);
153: }
154: }
155:
156: /**
157: * Get the number of cells at current position
158: *
159: * This can be different than the number of columns, in case
160: *
161: * @param integer $position Optional. Forced position.
162: * @return integer
163: */
164: public function rowNumCells($position = null)
165: {
166: if ($position === null) {
167: $position = $this->position();
168: }
169:
170: // Get the data ta position
171: $row = $this->rowData($position);
172: $numCells = isset($row['columns']) ? count($row['columns']) : null;
173: return $numCells;
174: }
175:
176: /**
177: * Get the cell index (position) of the first cell of current row
178: *
179: * @param integer $position Optional. Forced position.
180: * @return integer
181: */
182: public function rowFirstCellIndex($position = null)
183: {
184: if ($position === null) {
185: $position = $this->position();
186: }
187:
188: $structure = $this->structure();
189: if (empty($structure)) {
190: return null;
191: }
192: $firstList = [];
193: $i = 0;
194: $p = 0;
195: foreach ($structure as $row) {
196: $firstList[$i] = $p;
197: if ($p > $position) {
198: // Previous p
199: return $firstList[($i-1)];
200: }
201:
202: $numCells = isset($row['columns']) ? count($row['columns']) : 0;
203:
204: $p += $numCells;
205:
206: $i++;
207: }
208: return $firstList[($i-1)];
209: }
210:
211: /**
212: * Get the cell index in the current row
213: *
214: * @param integer $position Optional. Forced position.
215: * @return integer
216: */
217: public function cellRowIndex($position = null)
218: {
219: if ($position === null) {
220: $position = $this->position();
221: }
222: $first = $this->rowFirstCellIndex($position);
223:
224: return ($position - $first);
225: }
226:
227: /**
228: * Get the total number of cells, in all rows
229: *
230: * @return integer
231: */
232: public function numCellsTotal()
233: {
234: $numCells = 0;
235: foreach ($this->structure as $row) {
236: $rowCols = isset($row['columns']) ? count($row['columns']) : 0;
237: $numCells += $rowCols;
238: }
239: return $numCells;
240: }
241:
242: /**
243: * Get the span number (in # of columns) of the current cell
244: *
245: * @param integer $position Optional. Forced position.
246: * @return integer|null
247: */
248: public function cellSpan($position = null)
249: {
250: $row = $this->rowData($position);
251: $cellIndex = $this->cellRowIndex($position);
252:
253: // Cellspan (defaults to 1)
254: return isset($row['columns'][$cellIndex]) ? (int)$row['columns'][$cellIndex] : null;
255: }
256:
257: /**
258: * Get the span number as a part of 12 (for bootrap-style grids)
259: *
260: * @param integer $position Optional. Forced position.
261: * @return integer
262: */
263: public function cellSpanBy12($position = null)
264: {
265: $numColumns = $this->rowNumColumns($position);
266: if (!$numColumns) {
267: return null;
268: }
269: return ($this->cellSpan($position)*(12/$numColumns));
270: }
271:
272: /**
273: * Get wether or not the current cell starts a row (is the first one on the row)
274: *
275: * @param integer $position Optional. Forced position.
276: * @return boolean
277: */
278: public function cellStartsRow($position = null)
279: {
280: if ($position === null) {
281: $position = $this->position();
282: }
283:
284: return ($this->cellRowIndex($position) === 0);
285: }
286:
287: /**
288: * Get wether or not the current cell ends a row (is the last one on the row)
289: *
290: * @param integer $position Optional. Forced position.
291: * @return boolean
292: */
293: public function cellEndsRow($position = null)
294: {
295: if ($position === null) {
296: $position = $this->position();
297: }
298:
299: $cellNum = $this->cellRowIndex($position);
300: $numCells = $this->rowNumCells($position);
301:
302: return ($cellNum >= ($numCells-1));
303: }
304:
305: /**
306: * @return string
307: */
308: public function start()
309: {
310: return '';
311: }
312:
313: /**
314: * @return string
315: */
316: public function end()
317: {
318: $this->position++;
319: return '';
320: }
321: }
322: