|
| 1 | +registerLayout('test', class { |
| 2 | + static get inputProperties() { |
| 3 | + return [ '--padding', '--columns' ]; |
| 4 | + } |
| 5 | + static get childInputProperties() { |
| 6 | + return [ ]; |
| 7 | + } |
| 8 | + |
| 9 | + *intrinsicSizes() {} |
| 10 | + *layout(children, edges, constraints, styleMap) { |
| 11 | + const inlineSize = constraints.fixedInlineSize; |
| 12 | + |
| 13 | + const padding = parseInt(styleMap.get('--padding').toString()); |
| 14 | + const columnValue = styleMap.get('--columns').toString(); |
| 15 | + |
| 16 | + let columns = parseInt(columnValue); |
| 17 | + if (columnValue == 'auto' || !columns) { |
| 18 | + columns = Math.ceil(inlineSize / 350); // MAGIC. |
| 19 | + } |
| 20 | + |
| 21 | + const childInlineSize = (inlineSize - ((columns + 1) * padding)) / columns; |
| 22 | + const childFragments = yield children.map((child) => { |
| 23 | + return child.layoutNextFragment({fixedInlineSize: childInlineSize}); |
| 24 | + }); |
| 25 | + |
| 26 | + let autoBlockSize = 0; |
| 27 | + const columnOffsets = Array(columns).fill(0); |
| 28 | + for (let childFragment of childFragments) { |
| 29 | + const min = columnOffsets.reduce((acc, val, idx) => { |
| 30 | + if (!acc || val < acc.val) { |
| 31 | + return {idx, val}; |
| 32 | + } |
| 33 | + |
| 34 | + return acc; |
| 35 | + }, {val: +Infinity, idx: -1}); |
| 36 | + |
| 37 | + childFragment.inlineOffset = padding + (childInlineSize + padding) * min.idx; |
| 38 | + childFragment.blockOffset = padding + min.val; |
| 39 | + |
| 40 | + columnOffsets[min.idx] = childFragment.blockOffset + childFragment.blockSize; |
| 41 | + autoBlockSize = Math.max(autoBlockSize, columnOffsets[min.idx] + padding); |
| 42 | + } |
| 43 | + |
| 44 | + return {autoBlockSize, childFragments}; |
| 45 | + } |
| 46 | +}); |
| 47 | + |
| 48 | +registerLayout('simple-block', class { |
| 49 | + *intrinsicSizes() {} |
| 50 | + *layout(children, _, constraints) { |
| 51 | + const childFragments = yield children.map((child) => { |
| 52 | + return child.layoutNextFragment({fixedInlineSize: constraints.fixedInlineSize}); |
| 53 | + }); |
| 54 | + |
| 55 | + let autoBlockSize = 0; |
| 56 | + for (let childFragment of childFragments) { |
| 57 | + childFragment.blockOffset = autoBlockSize; |
| 58 | + autoBlockSize += childFragment.blockSize; |
| 59 | + } |
| 60 | + |
| 61 | + return {autoBlockSize, childFragments}; |
| 62 | + } |
| 63 | +}); |
| 64 | + |
| 65 | +function normalizeOperator(operator) { |
| 66 | + switch (operator) { |
| 67 | + case 'above': |
| 68 | + return 'above'; |
| 69 | + case 'below': |
| 70 | + return 'below'; |
| 71 | + case 'left-of': |
| 72 | + case 'leftOf': |
| 73 | + return 'leftOf'; |
| 74 | + case 'right-of': |
| 75 | + case 'rightOf': |
| 76 | + return 'rightOf'; |
| 77 | + case 'align-top': |
| 78 | + case 'alignTop': |
| 79 | + return 'alignTop'; |
| 80 | + case 'align-bottom': |
| 81 | + case 'alignBottom': |
| 82 | + return 'alignBottom'; |
| 83 | + case 'align-left': |
| 84 | + case 'alignLeft': |
| 85 | + return 'alignLeft'; |
| 86 | + case 'align-right': |
| 87 | + case 'alignRight': |
| 88 | + return 'alignRight'; |
| 89 | + case 'align-parent-top': |
| 90 | + case 'alignParentTop': |
| 91 | + return 'alignParentTop'; |
| 92 | + case 'align-parent-bottom': |
| 93 | + case 'alignParentBottom': |
| 94 | + return 'alignParentBottom'; |
| 95 | + case 'align-parent-left': |
| 96 | + case 'alignParentLeft': |
| 97 | + return 'alignParentLeft'; |
| 98 | + case 'align-parent-right': |
| 99 | + case 'alignParentRight': |
| 100 | + return 'alignParentRight'; |
| 101 | + case 'center-horizontal': |
| 102 | + case 'centerHorizontal': |
| 103 | + return 'centerHorizontal'; |
| 104 | + case 'center-vertical': |
| 105 | + case 'centerVertical': |
| 106 | + return 'centerVertical'; |
| 107 | + default: |
| 108 | + return null; |
| 109 | + } |
| 110 | +} |
| 111 | + |
| 112 | +function parseRelativeConstraints(str) { |
| 113 | + const constraintList = str.split(',').map(str => str.trim().split(' ').map(str2 => str2.trim())); |
| 114 | + const relativeConstraints = {}; |
| 115 | + |
| 116 | + for (let constraint of constraintList) { |
| 117 | + if (constraint.length === 3 || constraint.length === 2) { |
| 118 | + const [target, op, dest] = constraint; |
| 119 | + |
| 120 | + if (!relativeConstraints[target]) |
| 121 | + relativeConstraints[target] = {}; |
| 122 | + |
| 123 | + const operator = normalizeOperator(op); |
| 124 | + if (!operator) |
| 125 | + continue; |
| 126 | + |
| 127 | + relativeConstraints[target][normalizeOperator(op)] = dest || true; |
| 128 | + } |
| 129 | + } |
| 130 | + |
| 131 | + return relativeConstraints; |
| 132 | +} |
| 133 | + |
| 134 | +function sortChildren(relativeConstraints, childNames, mode) { |
| 135 | + const predecessorsByChild = {}; |
| 136 | + for (let childName of childNames) { |
| 137 | + predecessorsByChild[childName] = mode === 'vertical' ? |
| 138 | + getPredecessorsVertical(relativeConstraints, childName) : |
| 139 | + getPredecessorsHorizontal(relativeConstraints, childName); |
| 140 | + } |
| 141 | + |
| 142 | + let sortedSoFar = 0; |
| 143 | + let changed = true; |
| 144 | + |
| 145 | + while (changed) { |
| 146 | + changed = false; |
| 147 | + |
| 148 | + for (let i = sortedSoFar; i < childNames.length; i++) { |
| 149 | + const childName = childNames[i]; |
| 150 | + const predecessors = predecessorsByChild[childName]; |
| 151 | + if (predecessors.length === 0) { |
| 152 | + // Move element into sorted part of the list. |
| 153 | + if (i !== sortedSoFar) { |
| 154 | + const tmp = childNames[i]; |
| 155 | + childNames[i] = childNames[sortedSoFar]; |
| 156 | + childNames[sortedSoFar] = tmp; |
| 157 | + } |
| 158 | + |
| 159 | + sortedSoFar++; |
| 160 | + changed = true; |
| 161 | + |
| 162 | + // Remove this as a predecessor. |
| 163 | + for (let j = sortedSoFar; j < childNames.length; j++) { |
| 164 | + const l = predecessorsByChild[childNames[j]]; |
| 165 | + const idx = l.indexOf(childName); |
| 166 | + if (idx >= 0) |
| 167 | + l.splice(idx, 1); |
| 168 | + } |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + if (sortedSoFar < childNames.length) { |
| 174 | + throw Error("Cycle in dependency graph."); |
| 175 | + } |
| 176 | +} |
| 177 | + |
| 178 | +function getPredecessorsVertical(relativeConstraints, childName) { |
| 179 | + let predecessors = []; |
| 180 | + |
| 181 | + const c = relativeConstraints[childName] || {}; |
| 182 | + if (c.above) |
| 183 | + predecessors.push(c.above); |
| 184 | + |
| 185 | + if (c.below) |
| 186 | + predecessors.push(c.below); |
| 187 | + |
| 188 | + if (c.alignTop) |
| 189 | + predecessors.push(c.alignTop); |
| 190 | + |
| 191 | + if (c.alignBottom) |
| 192 | + predecessors.push(c.alignBottom); |
| 193 | + |
| 194 | + return predecessors; |
| 195 | +} |
| 196 | + |
| 197 | +function getPredecessorsHorizontal(relativeConstraints, childName) { |
| 198 | + let predecessors = []; |
| 199 | + |
| 200 | + const c = relativeConstraints[childName] || {}; |
| 201 | + if (c.leftOf) |
| 202 | + predecessors.push(c.leftOf); |
| 203 | + |
| 204 | + if (c.rightOf) |
| 205 | + predecessors.push(c.rightOf); |
| 206 | + |
| 207 | + if (c.alignLeft) |
| 208 | + predecessors.push(c.alignLeft); |
| 209 | + |
| 210 | + if (c.alignRight) |
| 211 | + predecessors.push(c.alignRight); |
| 212 | + |
| 213 | + return predecessors; |
| 214 | +} |
| 215 | + |
| 216 | +function applyHorizontalRules(relativeConstraints, childPositions, childName, inlineSize) { |
| 217 | + const position = childPositions[childName]; |
| 218 | + position.left = -1; |
| 219 | + position.right = -1; |
| 220 | + |
| 221 | + const c = relativeConstraints[childName] || {}; |
| 222 | + if (c.leftOf && childPositions[c.leftOf]) |
| 223 | + position.right = childPositions[c.leftOf].left; |
| 224 | + |
| 225 | + if (c.rightOf && childPositions[c.rightOf]) |
| 226 | + position.left = childPositions[c.rightOf].right; |
| 227 | + |
| 228 | + if (c.alignLeft && childPositions[c.alignLeft]) |
| 229 | + position.left = childPositions[c.alignLeft].left |
| 230 | + |
| 231 | + if (c.alignRight && childPositions[c.alignRight]) |
| 232 | + position.right = childPositions[c.alignRight].right; |
| 233 | + |
| 234 | + if (c.alignParentLeft) |
| 235 | + position.left = 0; |
| 236 | + |
| 237 | + if (c.alignParentRight) |
| 238 | + position.right = inlineSize; |
| 239 | +} |
| 240 | + |
| 241 | +function applyVerticalRules(relativeConstraints, childPositions, childName, blockSize) { |
| 242 | + const position = childPositions[childName]; |
| 243 | + position.top = -1; |
| 244 | + position.bottom = -1; |
| 245 | + |
| 246 | + const c = relativeConstraints[childName] || {}; |
| 247 | + if (c.above && childPositions[c.above]) |
| 248 | + position.bottom = childPositions[c.above].top; |
| 249 | + |
| 250 | + if (c.below && childPositions[c.below]) |
| 251 | + position.top = childPositions[c.below].bottom; |
| 252 | + |
| 253 | + if (c.alignTop && childPositions[c.alignTop]) |
| 254 | + position.top = childPositions[c.alignTop].top |
| 255 | + |
| 256 | + if (c.alignBottom && childPositions[c.alignBottom]) |
| 257 | + position.bottom = childPositions[c.alignBottom].bottom; |
| 258 | + |
| 259 | + if (c.alignParentTop) |
| 260 | + position.top = 0; |
| 261 | + |
| 262 | + if (c.alignParentBottom && blockSize !== null) |
| 263 | + position.bottom = blockSize; |
| 264 | +} |
| 265 | + |
| 266 | +function* measureChildHorizontal(child, position) { |
| 267 | + let childInlineSize = 0; |
| 268 | + if (position.left >= 0 && position.right >= 0) { |
| 269 | + childInlineSize = Math.max(0, position.right - position.left); |
| 270 | + } else { |
| 271 | + childInlineSize = (yield child.layoutNextFragment({})).inlineSize; |
| 272 | + } |
| 273 | + |
| 274 | + return childInlineSize; |
| 275 | +} |
| 276 | + |
| 277 | +function* measureChild(child, position) { |
| 278 | + const childConstraints = {}; |
| 279 | + |
| 280 | + if (position.top >= 0 && position.bottom >= 0) |
| 281 | + childConstraints.fixedBlockSize = Math.max(0, position.bottom - position.top); |
| 282 | + |
| 283 | + if (position.left >= 0 && position.right >= 0) |
| 284 | + childConstraints.fixedInlineSize = Math.max(0, position.right - position.left); |
| 285 | + |
| 286 | + return yield child.layoutNextFragment(childConstraints); |
| 287 | +} |
| 288 | + |
| 289 | +function positionChildHorizontal(position, constraint, inlineSize, childInlineSize) { |
| 290 | + if (position.left < 0 && position.right >= 0) { |
| 291 | + // Right fixed, left unspecified. |
| 292 | + position.left = position.right - childInlineSize; |
| 293 | + } else if (position.left >= 0 && position.right < 0) { |
| 294 | + position.right = position.left + childInlineSize; |
| 295 | + } else if (position.left < 0 && position.right < 0) { |
| 296 | + // Both unspecified. |
| 297 | + const c = constraint || {}; |
| 298 | + if (c.centerHorizontal) { |
| 299 | + position.left = (inlineSize - childInlineSize) / 2; |
| 300 | + position.right = position.left + childInlineSize; |
| 301 | + } else { |
| 302 | + position.left = 0; |
| 303 | + position.right = childInlineSize; |
| 304 | + } |
| 305 | + } |
| 306 | +} |
| 307 | + |
| 308 | +function positionChildVertical(position, cosntraint, blockSize, childBlockSize) { |
| 309 | + if (position.top < 0 && position.bottom >= 0) { |
| 310 | + position.top = position.bottom - childBlockSize; |
| 311 | + } else if (position.top >= 0 && position.bottom < 0) { |
| 312 | + position.bottom = position.top + childBlockSize; |
| 313 | + } else { |
| 314 | + // TODO implement once blockSize is not null. |
| 315 | + position.top = 0; |
| 316 | + position.bottom = childBlockSize; |
| 317 | + } |
| 318 | +} |
| 319 | + |
| 320 | +registerLayout('relative', class { |
| 321 | + static get inputProperties() { return [ '--relative-constraints', ]; } |
| 322 | + static get childInputProperties() { return [ '--relative-name', ]; } |
| 323 | + |
| 324 | + *intrinsicSizes() {} |
| 325 | + |
| 326 | + *layout(children, edges, constraints, styleMap) { |
| 327 | + const relativeConstraints = parseRelativeConstraints(styleMap.get('--relative-constraints').toString()); |
| 328 | + |
| 329 | + const childrenMap = children.reduce((map, child) => { |
| 330 | + const val = child.styleMap.get('--relative-name'); |
| 331 | + if (val && val.toString() !== '') { |
| 332 | + child.name = val.toString().trim(); |
| 333 | + map[child.name] = child; |
| 334 | + } |
| 335 | + return map; |
| 336 | + }, {}); |
| 337 | + |
| 338 | + const childPositions = Object.keys(childrenMap).reduce((map, key) => { |
| 339 | + map[key] = {}; |
| 340 | + return map; |
| 341 | + }, {}); |
| 342 | + |
| 343 | + const sortedChildNamesHorizontal = Object.keys(childrenMap); |
| 344 | + const sortedChildNamesVertical = Object.keys(childrenMap); |
| 345 | + sortChildren(relativeConstraints, sortedChildNamesHorizontal, 'horizontal'); |
| 346 | + sortChildren(relativeConstraints, sortedChildNamesVertical, 'vertical'); |
| 347 | + |
| 348 | + for (let childName of sortedChildNamesHorizontal) { |
| 349 | + applyHorizontalRules(relativeConstraints, childPositions, childName, constraints.fixedInlineSize); |
| 350 | + const childInlineSize = yield* measureChildHorizontal(childrenMap[childName], childPositions[childName]); |
| 351 | + positionChildHorizontal(childPositions[childName], relativeConstraints[childName], constraints.fixedInlineSize, childInlineSize); |
| 352 | + } |
| 353 | + |
| 354 | + const childFragmentMap = {}; |
| 355 | + for (let childName of sortedChildNamesVertical) { |
| 356 | + applyVerticalRules(relativeConstraints, childPositions, childName, null); |
| 357 | + const fragment = yield* measureChild(childrenMap[childName], childPositions[childName]); |
| 358 | + childFragmentMap[childName] = fragment; |
| 359 | + positionChildVertical(childPositions[childName], relativeConstraints[childName], null, fragment.blockSize); |
| 360 | + } |
| 361 | + |
| 362 | + let autoBlockSize = 0; |
| 363 | + const childFragments = []; |
| 364 | + for (let i = 0; i < children.length; i++) { |
| 365 | + const childName = children[i].name; |
| 366 | + const fragment = childName ? childFragmentMap[childName] : (yield children[i].layoutNextFragment({})); |
| 367 | + childFragments.push(fragment); |
| 368 | + |
| 369 | + if (childName) { |
| 370 | + const position = childPositions[childName]; |
| 371 | + fragment.inlineOffset = position.left; |
| 372 | + fragment.blockOffset = position.top; |
| 373 | + } |
| 374 | + |
| 375 | + autoBlockSize = Math.max(autoBlockSize, fragment.blockOffset + fragment.blockSize); |
| 376 | + } |
| 377 | + |
| 378 | + return {autoBlockSize, childFragments}; |
| 379 | + } |
| 380 | +}); |
0 commit comments