1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package androidx.constraintlayout.core.state; 18 19 import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_INTERPOLATOR_TYPE; 20 import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTIONSTEPS; 21 import static androidx.constraintlayout.core.motion.utils.TypedValues.MotionType.TYPE_QUANTIZE_MOTION_PHASE; 22 import static androidx.constraintlayout.core.widgets.ConstraintWidget.HORIZONTAL; 23 import static androidx.constraintlayout.core.widgets.ConstraintWidget.VERTICAL; 24 25 import androidx.annotation.RestrictTo; 26 import androidx.constraintlayout.core.motion.utils.TypedBundle; 27 import androidx.constraintlayout.core.motion.utils.TypedValues; 28 import androidx.constraintlayout.core.parser.CLArray; 29 import androidx.constraintlayout.core.parser.CLElement; 30 import androidx.constraintlayout.core.parser.CLKey; 31 import androidx.constraintlayout.core.parser.CLNumber; 32 import androidx.constraintlayout.core.parser.CLObject; 33 import androidx.constraintlayout.core.parser.CLParser; 34 import androidx.constraintlayout.core.parser.CLParsingException; 35 import androidx.constraintlayout.core.parser.CLString; 36 import androidx.constraintlayout.core.state.helpers.BarrierReference; 37 import androidx.constraintlayout.core.state.helpers.ChainReference; 38 import androidx.constraintlayout.core.state.helpers.FlowReference; 39 import androidx.constraintlayout.core.state.helpers.GridReference; 40 import androidx.constraintlayout.core.state.helpers.GuidelineReference; 41 import androidx.constraintlayout.core.widgets.ConstraintWidget; 42 import androidx.constraintlayout.core.widgets.Flow; 43 44 import org.jspecify.annotations.NonNull; 45 46 import java.util.ArrayList; 47 import java.util.HashMap; 48 49 public class ConstraintSetParser { 50 51 private static final boolean PARSER_DEBUG = false; 52 53 public static class DesignElement { 54 String mId; 55 String mType; 56 HashMap<String, String> mParams; 57 getId()58 public String getId() { 59 return mId; 60 } 61 getType()62 public String getType() { 63 return mType; 64 } 65 getParams()66 public HashMap<String, String> getParams() { 67 return mParams; 68 } 69 DesignElement(String id, String type, HashMap<String, String> params)70 DesignElement(String id, 71 String type, 72 HashMap<String, String> params) { 73 mId = id; 74 mType = type; 75 mParams = params; 76 } 77 } 78 79 /** 80 * Provide the storage for managing Variables in the system. 81 * When the json has a variable:{ } section this is used. 82 */ 83 public static class LayoutVariables { 84 HashMap<String, Integer> mMargins = new HashMap<>(); 85 HashMap<String, GeneratedValue> mGenerators = new HashMap<>(); 86 HashMap<String, ArrayList<String>> mArrayIds = new HashMap<>(); 87 put(String elementName, int element)88 void put(String elementName, int element) { 89 mMargins.put(elementName, element); 90 } 91 put(String elementName, float start, float incrementBy)92 void put(String elementName, float start, float incrementBy) { 93 if (mGenerators.containsKey(elementName)) { 94 if (mGenerators.get(elementName) instanceof OverrideValue) { 95 return; 96 } 97 } 98 mGenerators.put(elementName, new Generator(start, incrementBy)); 99 } 100 put(String elementName, float from, float to, float step, String prefix, String postfix)101 void put(String elementName, 102 float from, 103 float to, 104 float step, 105 String prefix, 106 String postfix) { 107 if (mGenerators.containsKey(elementName)) { 108 if (mGenerators.get(elementName) instanceof OverrideValue) { 109 return; 110 } 111 } 112 FiniteGenerator generator = 113 new FiniteGenerator(from, to, step, prefix, postfix); 114 mGenerators.put(elementName, generator); 115 mArrayIds.put(elementName, generator.array()); 116 117 } 118 119 /** 120 * insert an override variable 121 * 122 * @param elementName the name 123 * @param value the value a float 124 */ putOverride(String elementName, float value)125 public void putOverride(String elementName, float value) { 126 GeneratedValue generator = new OverrideValue(value); 127 mGenerators.put(elementName, generator); 128 } 129 get(Object elementName)130 float get(Object elementName) { 131 if (elementName instanceof CLString) { 132 String stringValue = ((CLString) elementName).content(); 133 if (mGenerators.containsKey(stringValue)) { 134 return mGenerators.get(stringValue).value(); 135 } 136 if (mMargins.containsKey(stringValue)) { 137 return mMargins.get(stringValue).floatValue(); 138 } 139 } else if (elementName instanceof CLNumber) { 140 return ((CLNumber) elementName).getFloat(); 141 } 142 return 0f; 143 } 144 getList(String elementName)145 ArrayList<String> getList(String elementName) { 146 if (mArrayIds.containsKey(elementName)) { 147 return mArrayIds.get(elementName); 148 } 149 return null; 150 } 151 put(String elementName, ArrayList<String> elements)152 void put(String elementName, ArrayList<String> elements) { 153 mArrayIds.put(elementName, elements); 154 } 155 156 } 157 158 interface GeneratedValue { value()159 float value(); 160 } 161 162 /** 163 * Generate a floating point value 164 */ 165 static class Generator implements GeneratedValue { 166 float mStart = 0; 167 float mIncrementBy = 0; 168 float mCurrent = 0; 169 boolean mStop = false; 170 Generator(float start, float incrementBy)171 Generator(float start, float incrementBy) { 172 mStart = start; 173 mIncrementBy = incrementBy; 174 mCurrent = start; 175 } 176 177 @Override value()178 public float value() { 179 if (!mStop) { 180 mCurrent += mIncrementBy; 181 } 182 return mCurrent; 183 } 184 } 185 186 /** 187 * Generate values like button1, button2 etc. 188 */ 189 static class FiniteGenerator implements GeneratedValue { 190 float mFrom = 0; 191 float mTo = 0; 192 float mStep = 0; 193 boolean mStop = false; 194 String mPrefix; 195 String mPostfix; 196 float mCurrent = 0; 197 float mInitial; 198 float mMax; 199 FiniteGenerator(float from, float to, float step, String prefix, String postfix)200 FiniteGenerator(float from, 201 float to, 202 float step, 203 String prefix, 204 String postfix) { 205 mFrom = from; 206 mTo = to; 207 mStep = step; 208 mPrefix = (prefix == null) ? "" : prefix; 209 mPostfix = (postfix == null) ? "" : postfix; 210 mMax = to; 211 mInitial = from; 212 } 213 214 @Override value()215 public float value() { 216 if (mCurrent >= mMax) { 217 mStop = true; 218 } 219 if (!mStop) { 220 mCurrent += mStep; 221 } 222 return mCurrent; 223 } 224 array()225 public ArrayList<String> array() { 226 ArrayList<String> array = new ArrayList<>(); 227 int value = (int) mInitial; 228 int maxInt = (int) mMax; 229 for (int i = value; i <= maxInt; i++) { 230 array.add(mPrefix + value + mPostfix); 231 value += (int) mStep; 232 } 233 return array; 234 235 } 236 237 } 238 239 static class OverrideValue implements GeneratedValue { 240 float mValue; 241 OverrideValue(float value)242 OverrideValue(float value) { 243 mValue = value; 244 } 245 246 @Override value()247 public float value() { 248 return mValue; 249 } 250 } 251 //==================== end store variables ========================= 252 //==================== MotionScene ========================= 253 254 public enum MotionLayoutDebugFlags { 255 NONE, 256 SHOW_ALL, 257 UNKNOWN 258 } 259 260 //==================== end Motion Scene ========================= 261 262 /** 263 * Parse and populate a transition 264 * 265 * @param content JSON string to parse 266 * @param transition The Transition to be populated 267 * @param state 268 */ parseJSON(String content, Transition transition, int state)269 public static void parseJSON(String content, Transition transition, int state) { 270 try { 271 CLObject json = CLParser.parse(content); 272 ArrayList<String> elements = json.names(); 273 if (elements == null) { 274 return; 275 } 276 for (String elementName : elements) { 277 CLElement base_element = json.get(elementName); 278 if (base_element instanceof CLObject) { 279 CLObject element = (CLObject) base_element; 280 CLObject customProperties = element.getObjectOrNull("custom"); 281 if (customProperties != null) { 282 ArrayList<String> properties = customProperties.names(); 283 for (String property : properties) { 284 CLElement value = customProperties.get(property); 285 if (value instanceof CLNumber) { 286 transition.addCustomFloat( 287 state, 288 elementName, 289 property, 290 value.getFloat() 291 ); 292 } else if (value instanceof CLString) { 293 long color = parseColorString(value.content()); 294 if (color != -1) { 295 transition.addCustomColor(state, 296 elementName, property, (int) color); 297 } 298 } 299 } 300 } 301 } 302 303 } 304 } catch (CLParsingException e) { 305 System.err.println("Error parsing JSON " + e); 306 } 307 } 308 309 /** 310 * Parse and build a motionScene 311 * 312 * this should be in a MotionScene / MotionSceneParser 313 */ parseMotionSceneJSON(CoreMotionScene scene, String content)314 public static void parseMotionSceneJSON(CoreMotionScene scene, String content) { 315 try { 316 CLObject json = CLParser.parse(content); 317 ArrayList<String> elements = json.names(); 318 if (elements == null) { 319 return; 320 } 321 for (String elementName : elements) { 322 CLElement element = json.get(elementName); 323 if (element instanceof CLObject) { 324 CLObject clObject = (CLObject) element; 325 switch (elementName) { 326 case "ConstraintSets": 327 parseConstraintSets(scene, clObject); 328 break; 329 case "Transitions": 330 parseTransitions(scene, clObject); 331 break; 332 case "Header": 333 parseHeader(scene, clObject); 334 break; 335 } 336 } 337 } 338 } catch (CLParsingException e) { 339 System.err.println("Error parsing JSON " + e); 340 } 341 } 342 343 /** 344 * Parse ConstraintSets and populate MotionScene 345 */ parseConstraintSets(CoreMotionScene scene, CLObject json)346 static void parseConstraintSets(CoreMotionScene scene, 347 CLObject json) throws CLParsingException { 348 ArrayList<String> constraintSetNames = json.names(); 349 if (constraintSetNames == null) { 350 return; 351 } 352 353 for (String csName : constraintSetNames) { 354 CLObject constraintSet = json.getObject(csName); 355 boolean added = false; 356 String ext = constraintSet.getStringOrNull("Extends"); 357 if (ext != null && !ext.isEmpty()) { 358 String base = scene.getConstraintSet(ext); 359 if (base == null) { 360 continue; 361 } 362 363 CLObject baseJson = CLParser.parse(base); 364 ArrayList<String> widgetsOverride = constraintSet.names(); 365 if (widgetsOverride == null) { 366 continue; 367 } 368 369 for (String widgetOverrideName : widgetsOverride) { 370 CLElement value = constraintSet.get(widgetOverrideName); 371 if (value instanceof CLObject) { 372 override(baseJson, widgetOverrideName, (CLObject) value); 373 } 374 } 375 376 scene.setConstraintSetContent(csName, baseJson.toJSON()); 377 added = true; 378 } 379 if (!added) { 380 scene.setConstraintSetContent(csName, constraintSet.toJSON()); 381 } 382 } 383 384 } 385 override(CLObject baseJson, String name, CLObject overrideValue)386 static void override(CLObject baseJson, 387 String name, CLObject overrideValue) throws CLParsingException { 388 if (!baseJson.has(name)) { 389 baseJson.put(name, overrideValue); 390 } else { 391 CLObject base = baseJson.getObject(name); 392 ArrayList<String> keys = overrideValue.names(); 393 for (String key : keys) { 394 if (!key.equals("clear")) { 395 base.put(key, overrideValue.get(key)); 396 continue; 397 } 398 CLArray toClear = overrideValue.getArray("clear"); 399 for (int i = 0; i < toClear.size(); i++) { 400 String clearedKey = toClear.getStringOrNull(i); 401 if (clearedKey == null) { 402 continue; 403 } 404 switch (clearedKey) { 405 case "dimensions": 406 base.remove("width"); 407 base.remove("height"); 408 break; 409 case "constraints": 410 base.remove("start"); 411 base.remove("end"); 412 base.remove("top"); 413 base.remove("bottom"); 414 base.remove("baseline"); 415 base.remove("center"); 416 base.remove("centerHorizontally"); 417 base.remove("centerVertically"); 418 break; 419 case "transforms": 420 base.remove("visibility"); 421 base.remove("alpha"); 422 base.remove("pivotX"); 423 base.remove("pivotY"); 424 base.remove("rotationX"); 425 base.remove("rotationY"); 426 base.remove("rotationZ"); 427 base.remove("scaleX"); 428 base.remove("scaleY"); 429 base.remove("translationX"); 430 base.remove("translationY"); 431 break; 432 default: 433 base.remove(clearedKey); 434 435 } 436 } 437 } 438 } 439 } 440 441 /** 442 * Parse the Transition 443 */ parseTransitions(CoreMotionScene scene, CLObject json)444 static void parseTransitions(CoreMotionScene scene, CLObject json) throws CLParsingException { 445 ArrayList<String> elements = json.names(); 446 if (elements == null) { 447 return; 448 } 449 for (String elementName : elements) { 450 scene.setTransitionContent(elementName, json.getObject(elementName).toJSON()); 451 } 452 } 453 454 /** 455 * Used to parse for "export" 456 */ parseHeader(CoreMotionScene scene, CLObject json)457 static void parseHeader(CoreMotionScene scene, CLObject json) { 458 String name = json.getStringOrNull("export"); 459 if (name != null) { 460 scene.setDebugName(name); 461 } 462 } 463 464 /** 465 * Top leve parsing of the json ConstraintSet supporting 466 * "Variables", "Helpers", "Generate", guidelines, and barriers 467 * 468 * @param content the JSON string 469 * @param state the state to populate 470 * @param layoutVariables the variables to override 471 */ parseJSON(String content, State state, LayoutVariables layoutVariables)472 public static void parseJSON(String content, State state, 473 LayoutVariables layoutVariables) throws CLParsingException { 474 try { 475 CLObject json = CLParser.parse(content); 476 populateState(json, state, layoutVariables); 477 } catch (CLParsingException e) { 478 System.err.println("Error parsing JSON " + e); 479 } 480 } 481 482 /** 483 * Populates the given {@link State} with the parameters from {@link CLObject}. Where the 484 * object represents a parsed JSONObject of a ConstraintSet. 485 * 486 * @param parsedJson CLObject of the parsed ConstraintSet 487 * @param state the state to populate 488 * @param layoutVariables the variables to override 489 * @throws CLParsingException when parsing fails 490 */ 491 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) populateState( @onNull CLObject parsedJson, @NonNull State state, @NonNull LayoutVariables layoutVariables )492 public static void populateState( 493 @NonNull CLObject parsedJson, 494 @NonNull State state, 495 @NonNull LayoutVariables layoutVariables 496 ) throws CLParsingException { 497 ArrayList<String> elements = parsedJson.names(); 498 if (elements == null) { 499 return; 500 } 501 for (String elementName : elements) { 502 CLElement element = parsedJson.get(elementName); 503 if (PARSER_DEBUG) { 504 System.out.println("[" + elementName + "] = " + element 505 + " > " + element.getContainer()); 506 } 507 switch (elementName) { 508 case "Variables": 509 if (element instanceof CLObject) { 510 parseVariables(state, layoutVariables, (CLObject) element); 511 } 512 break; 513 case "Helpers": 514 if (element instanceof CLArray) { 515 parseHelpers(state, layoutVariables, (CLArray) element); 516 } 517 break; 518 case "Generate": 519 if (element instanceof CLObject) { 520 parseGenerate(state, layoutVariables, (CLObject) element); 521 } 522 break; 523 default: 524 if (element instanceof CLObject) { 525 String type = lookForType((CLObject) element); 526 if (type != null) { 527 switch (type) { 528 case "hGuideline": 529 parseGuidelineParams( 530 ConstraintWidget.HORIZONTAL, 531 state, 532 elementName, 533 (CLObject) element 534 ); 535 break; 536 case "vGuideline": 537 parseGuidelineParams( 538 ConstraintWidget.VERTICAL, 539 state, 540 elementName, 541 (CLObject) element 542 ); 543 break; 544 case "barrier": 545 parseBarrier(state, elementName, (CLObject) element); 546 break; 547 case "vChain": 548 case "hChain": 549 parseChainType( 550 type, 551 state, 552 elementName, 553 layoutVariables, 554 (CLObject) element 555 ); 556 break; 557 case "vFlow": 558 case "hFlow": 559 parseFlowType( 560 type, 561 state, 562 elementName, 563 layoutVariables, 564 (CLObject) element 565 ); 566 break; 567 case "grid": 568 case "row": 569 case "column": 570 parseGridType( 571 type, 572 state, 573 elementName, 574 layoutVariables, 575 (CLObject) element 576 ); 577 break; 578 } 579 } else { 580 parseWidget(state, 581 layoutVariables, 582 elementName, 583 (CLObject) element); 584 } 585 } else if (element instanceof CLNumber) { 586 layoutVariables.put(elementName, element.getInt()); 587 } 588 } 589 } 590 } 591 parseVariables(State state, LayoutVariables layoutVariables, CLObject json)592 private static void parseVariables(State state, 593 LayoutVariables layoutVariables, 594 CLObject json) throws CLParsingException { 595 ArrayList<String> elements = json.names(); 596 if (elements == null) { 597 return; 598 } 599 for (String elementName : elements) { 600 CLElement element = json.get(elementName); 601 if (element instanceof CLNumber) { 602 layoutVariables.put(elementName, element.getInt()); 603 } else if (element instanceof CLObject) { 604 CLObject obj = (CLObject) element; 605 ArrayList<String> arrayIds; 606 if (obj.has("from") && obj.has("to")) { 607 float from = layoutVariables.get(obj.get("from")); 608 float to = layoutVariables.get(obj.get("to")); 609 String prefix = obj.getStringOrNull("prefix"); 610 String postfix = obj.getStringOrNull("postfix"); 611 layoutVariables.put(elementName, from, to, 1f, prefix, postfix); 612 } else if (obj.has("from") && obj.has("step")) { 613 float start = layoutVariables.get(obj.get("from")); 614 float increment = layoutVariables.get(obj.get("step")); 615 layoutVariables.put(elementName, start, increment); 616 617 } else if (obj.has("ids")) { 618 CLArray ids = obj.getArray("ids"); 619 arrayIds = new ArrayList<>(); 620 for (int i = 0; i < ids.size(); i++) { 621 arrayIds.add(ids.getString(i)); 622 } 623 layoutVariables.put(elementName, arrayIds); 624 } else if (obj.has("tag")) { 625 arrayIds = state.getIdsForTag(obj.getString("tag")); 626 layoutVariables.put(elementName, arrayIds); 627 } 628 } 629 } 630 } 631 632 /** 633 * parse the Design time elements. 634 * 635 * @param content the json 636 * @param list output the list of design elements 637 */ parseDesignElementsJSON( String content, ArrayList<DesignElement> list)638 public static void parseDesignElementsJSON( 639 String content, ArrayList<DesignElement> list) throws CLParsingException { 640 CLObject json = CLParser.parse(content); 641 ArrayList<String> elements = json.names(); 642 if (elements == null) { 643 return; 644 } 645 for (int i = 0; i < elements.size(); i++) { 646 String elementName = elements.get(i); 647 CLElement element = json.get(elementName); 648 if (PARSER_DEBUG) { 649 System.out.println("[" + element + "] " + element.getClass()); 650 } 651 switch (elementName) { 652 case "Design": 653 if (!(element instanceof CLObject)) { 654 return; 655 } 656 CLObject obj = (CLObject) element; 657 elements = obj.names(); 658 for (int j = 0; j < elements.size(); j++) { 659 String designElementName = elements.get(j); 660 CLObject designElement = 661 (CLObject) ((CLObject) element).get(designElementName); 662 System.out.printf("element found " + designElementName + ""); 663 String type = designElement.getStringOrNull("type"); 664 if (type != null) { 665 HashMap<String, String> parameters = new HashMap<String, String>(); 666 int size = designElement.size(); 667 for (int k = 0; k < size; k++) { 668 669 CLKey key = (CLKey) designElement.get(j); 670 String paramName = key.content(); 671 String paramValue = key.getValue().content(); 672 if (paramValue != null) { 673 parameters.put(paramName, paramValue); 674 } 675 } 676 list.add(new DesignElement(elementName, type, parameters)); 677 } 678 } 679 } 680 break; 681 } 682 } 683 parseHelpers(State state, LayoutVariables layoutVariables, CLArray element)684 static void parseHelpers(State state, 685 LayoutVariables layoutVariables, 686 CLArray element) throws CLParsingException { 687 for (int i = 0; i < element.size(); i++) { 688 CLElement helper = element.get(i); 689 if (helper instanceof CLArray) { 690 CLArray array = (CLArray) helper; 691 if (array.size() > 1) { 692 switch (array.getString(0)) { 693 case "hChain": 694 parseChain(ConstraintWidget.HORIZONTAL, state, layoutVariables, array); 695 break; 696 case "vChain": 697 parseChain(ConstraintWidget.VERTICAL, state, layoutVariables, array); 698 break; 699 case "hGuideline": 700 parseGuideline(ConstraintWidget.HORIZONTAL, state, array); 701 break; 702 case "vGuideline": 703 parseGuideline(ConstraintWidget.VERTICAL, state, array); 704 break; 705 } 706 } 707 } 708 } 709 } 710 parseGenerate(State state, LayoutVariables layoutVariables, CLObject json)711 static void parseGenerate(State state, 712 LayoutVariables layoutVariables, 713 CLObject json) throws CLParsingException { 714 ArrayList<String> elements = json.names(); 715 if (elements == null) { 716 return; 717 } 718 for (String elementName : elements) { 719 CLElement element = json.get(elementName); 720 ArrayList<String> arrayIds = layoutVariables.getList(elementName); 721 if (arrayIds != null && element instanceof CLObject) { 722 for (String id : arrayIds) { 723 parseWidget(state, layoutVariables, id, (CLObject) element); 724 } 725 } 726 } 727 } 728 parseChain(int orientation, State state, LayoutVariables margins, CLArray helper)729 static void parseChain(int orientation, State state, 730 LayoutVariables margins, CLArray helper) throws CLParsingException { 731 ChainReference chain = (orientation == ConstraintWidget.HORIZONTAL) 732 ? state.horizontalChain() : state.verticalChain(); 733 CLElement refs = helper.get(1); 734 if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) { 735 return; 736 } 737 for (int i = 0; i < ((CLArray) refs).size(); i++) { 738 chain.add(((CLArray) refs).getString(i)); 739 } 740 741 if (helper.size() > 2) { // we have additional parameters 742 CLElement params = helper.get(2); 743 if (!(params instanceof CLObject)) { 744 return; 745 } 746 CLObject obj = (CLObject) params; 747 ArrayList<String> constraints = obj.names(); 748 for (String constraintName : constraints) { 749 switch (constraintName) { 750 case "style": 751 CLElement styleObject = ((CLObject) params).get(constraintName); 752 String styleValue; 753 if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) { 754 styleValue = ((CLArray) styleObject).getString(0); 755 float biasValue = ((CLArray) styleObject).getFloat(1); 756 chain.bias(biasValue); 757 } else { 758 styleValue = styleObject.content(); 759 } 760 switch (styleValue) { 761 case "packed": 762 chain.style(State.Chain.PACKED); 763 break; 764 case "spread_inside": 765 chain.style(State.Chain.SPREAD_INSIDE); 766 break; 767 default: 768 chain.style(State.Chain.SPREAD); 769 break; 770 } 771 772 break; 773 default: 774 parseConstraint( 775 state, 776 margins, 777 (CLObject) params, 778 (ConstraintReference) chain, 779 constraintName 780 ); 781 break; 782 } 783 } 784 } 785 } 786 toPix(State state, float dp)787 private static float toPix(State state, float dp) { 788 return state.getDpToPixel().toPixels(dp); 789 } 790 791 /** 792 * Support parsing Chain in the following manner 793 * chainId : { 794 * type:'hChain' // or vChain 795 * contains: ['id1', 'id2', 'id3' ] 796 * contains: [['id', weight, marginL ,marginR], 'id2', 'id3' ] 797 * start: ['parent', 'start',0], 798 * end: ['parent', 'end',0], 799 * top: ['parent', 'top',0], 800 * bottom: ['parent', 'bottom',0], 801 * style: 'spread' 802 * } 803 804 * @throws CLParsingException 805 */ parseChainType(String orientation, State state, String chainName, LayoutVariables margins, CLObject object)806 private static void parseChainType(String orientation, 807 State state, 808 String chainName, 809 LayoutVariables margins, 810 CLObject object) throws CLParsingException { 811 812 ChainReference chain = (orientation.charAt(0) == 'h') 813 ? state.horizontalChain() : state.verticalChain(); 814 chain.setKey(chainName); 815 816 for (String params : object.names()) { 817 switch (params) { 818 case "contains": 819 CLElement refs = object.get(params); 820 if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) { 821 System.err.println( 822 chainName + " contains should be an array \"" + refs.content() 823 + "\""); 824 return; 825 } 826 for (int i = 0; i < ((CLArray) refs).size(); i++) { 827 CLElement chainElement = ((CLArray) refs).get(i); 828 if (chainElement instanceof CLArray) { 829 CLArray array = (CLArray) chainElement; 830 if (array.size() > 0) { 831 String id = array.get(0).content(); 832 float weight = Float.NaN; 833 float preMargin = Float.NaN; 834 float postMargin = Float.NaN; 835 float preGoneMargin = Float.NaN; 836 float postGoneMargin = Float.NaN; 837 switch (array.size()) { 838 case 2: // sets only the weight 839 weight = array.getFloat(1); 840 break; 841 case 3: // sets the pre and post margin to the 2 arg 842 weight = array.getFloat(1); 843 postMargin = preMargin = toPix(state, array.getFloat(2)); 844 break; 845 case 4: // sets the pre and post margin 846 weight = array.getFloat(1); 847 preMargin = toPix(state, array.getFloat(2)); 848 postMargin = toPix(state, array.getFloat(3)); 849 break; 850 case 6: // weight, preMargin, postMargin, preGoneMargin, 851 // postGoneMargin 852 weight = array.getFloat(1); 853 preMargin = toPix(state, array.getFloat(2)); 854 postMargin = toPix(state, array.getFloat(3)); 855 preGoneMargin = toPix(state, array.getFloat(4)); 856 postGoneMargin = toPix(state, array.getFloat(5)); 857 break; 858 } 859 chain.addChainElement(id, 860 weight, 861 preMargin, 862 postMargin, 863 preGoneMargin, 864 postGoneMargin); 865 } 866 } else { 867 chain.add(chainElement.content()); 868 } 869 } 870 break; 871 case "start": 872 case "end": 873 case "top": 874 case "bottom": 875 case "left": 876 case "right": 877 parseConstraint(state, margins, object, chain, params); 878 break; 879 case "style": 880 881 CLElement styleObject = object.get(params); 882 String styleValue; 883 if (styleObject instanceof CLArray && ((CLArray) styleObject).size() > 1) { 884 styleValue = ((CLArray) styleObject).getString(0); 885 float biasValue = ((CLArray) styleObject).getFloat(1); 886 chain.bias(biasValue); 887 } else { 888 styleValue = styleObject.content(); 889 } 890 switch (styleValue) { 891 case "packed": 892 chain.style(State.Chain.PACKED); 893 break; 894 case "spread_inside": 895 chain.style(State.Chain.SPREAD_INSIDE); 896 break; 897 default: 898 chain.style(State.Chain.SPREAD); 899 break; 900 } 901 902 break; 903 } 904 } 905 } 906 907 /** 908 * Support parsing Grid in the following manner 909 * chainId : { 910 * height: "parent", 911 * width: "parent", 912 * type: "Grid", 913 * vGap: 10, 914 * hGap: 10, 915 * orientation: 0, 916 * rows: 0, 917 * columns: 1, 918 * columnWeights: "", 919 * rowWeights: "", 920 * contains: ["btn1", "btn2", "btn3", "btn4"], 921 * top: ["parent", "top", 10], 922 * bottom: ["parent", "bottom", 20], 923 * right: ["parent", "right", 30], 924 * left: ["parent", "left", 40], 925 * } 926 * 927 * @param gridType type of the Grid helper could be "Grid"|"Row"|"Column" 928 * @param state ConstraintLayout State 929 * @param name the name of the Grid Helper 930 * @param layoutVariables layout margins 931 * @param element the element to be parsed 932 * @throws CLParsingException 933 */ parseGridType(String gridType, State state, String name, LayoutVariables layoutVariables, CLObject element)934 private static void parseGridType(String gridType, 935 State state, 936 String name, 937 LayoutVariables layoutVariables, 938 CLObject element) throws CLParsingException { 939 GridReference grid = state.getGrid(name, gridType); 940 941 for (String param : element.names()) { 942 switch (param) { 943 case "contains": 944 CLArray list = element.getArrayOrNull(param); 945 if (list != null) { 946 for (int j = 0; j < list.size(); j++) { 947 948 String elementNameReference = list.get(j).content(); 949 ConstraintReference elementReference = 950 state.constraints(elementNameReference); 951 grid.add(elementReference); 952 } 953 } 954 break; 955 case "orientation": 956 int orientation = element.get(param).getInt(); 957 grid.setOrientation(orientation); 958 break; 959 case "rows": 960 int rows = element.get(param).getInt(); 961 if (rows > 0) { 962 grid.setRowsSet(rows); 963 } 964 break; 965 case "columns": 966 int columns = element.get(param).getInt(); 967 if (columns > 0) { 968 grid.setColumnsSet(columns); 969 } 970 break; 971 case "hGap": 972 float hGap = element.get(param).getFloat(); 973 grid.setHorizontalGaps(toPix(state, hGap)); 974 break; 975 case "vGap": 976 float vGap = element.get(param).getFloat(); 977 grid.setVerticalGaps(toPix(state, vGap)); 978 break; 979 case "spans": 980 String spans = element.get(param).content(); 981 if (spans != null && spans.contains(":")) { 982 grid.setSpans(spans); 983 } 984 break; 985 case "skips": 986 String skips = element.get(param).content(); 987 if (skips != null && skips.contains(":")) { 988 grid.setSkips(skips); 989 } 990 break; 991 case "rowWeights": 992 String rowWeights = element.get(param).content(); 993 if (rowWeights != null && rowWeights.contains(",")) { 994 grid.setRowWeights(rowWeights); 995 } 996 break; 997 case "columnWeights": 998 String columnWeights = element.get(param).content(); 999 if (columnWeights != null && columnWeights.contains(",")) { 1000 grid.setColumnWeights(columnWeights); 1001 } 1002 break; 1003 case "padding": 1004 // Note that padding is currently not properly handled in GridCore 1005 CLElement paddingObject = element.get(param); 1006 float paddingStart = 0; 1007 float paddingTop = 0; 1008 float paddingEnd = 0; 1009 float paddingBottom = 0; 1010 if (paddingObject instanceof CLArray && ((CLArray) paddingObject).size() > 1) { 1011 paddingStart = ((CLArray) paddingObject).getInt(0); 1012 paddingEnd = paddingStart; 1013 paddingTop = ((CLArray) paddingObject).getInt(1); 1014 paddingBottom = paddingTop; 1015 if (((CLArray) paddingObject).size() > 2) { 1016 paddingEnd = ((CLArray) paddingObject).getInt(2); 1017 try { 1018 paddingBottom = ((CLArray) paddingObject).getInt(3); 1019 } catch (ArrayIndexOutOfBoundsException e) { 1020 paddingBottom = 0; 1021 } 1022 1023 } 1024 } else { 1025 paddingStart = paddingObject.getInt(); 1026 paddingTop = paddingStart; 1027 paddingEnd = paddingStart; 1028 paddingBottom = paddingStart; 1029 } 1030 grid.setPaddingStart(Math.round(toPix(state, paddingStart))); 1031 grid.setPaddingTop(Math.round(toPix(state, paddingTop))); 1032 grid.setPaddingEnd(Math.round(toPix(state, paddingEnd))); 1033 grid.setPaddingBottom(Math.round(toPix(state, paddingBottom))); 1034 break; 1035 case "flags": 1036 int flagValue = 0; 1037 String flags = ""; 1038 try { 1039 CLElement obj = element.get(param); 1040 if (obj instanceof CLNumber) { 1041 flagValue = obj.getInt(); 1042 } else { 1043 flags = obj.content(); 1044 } 1045 } catch (Exception ex) { 1046 System.err.println("Error parsing grid flags " + ex); 1047 } 1048 1049 if (flags != null && !flags.isEmpty()) { 1050 // In older APIs, the flags may still be defined as a String 1051 grid.setFlags(flags); 1052 } else { 1053 grid.setFlags(flagValue); 1054 } 1055 break; 1056 default: 1057 ConstraintReference reference = state.constraints(name); 1058 applyAttribute(state, layoutVariables, reference, element, param); 1059 } 1060 } 1061 } 1062 1063 /** 1064 * It's used to parse the Flow type of Helper with the following format: 1065 * flowID: { 1066 * type: 'hFlow'|'vFlow’ 1067 * wrap: 'chain'|'none'|'aligned', 1068 * contains: ['id1', 'id2', 'id3' ] | 1069 * [['id1', weight, preMargin , postMargin], 'id2', 'id3'], 1070 * vStyle: 'spread'|'spread_inside'|'packed' | ['first', 'middle', 'last'], 1071 * hStyle: 'spread'|'spread_inside'|'packed' | ['first', 'middle', 'last'], 1072 * vAlign: 'top'|'bottom'|'baseline'|'center', 1073 * hAlign: 'start'|'end'|'center', 1074 * vGap: 32, 1075 * hGap: 23, 1076 * padding: 32, 1077 * maxElement: 5, 1078 * vBias: 0.3 | [0.0, 0.5, 0.5], 1079 * hBias: 0.4 | [0.0, 0.5, 0.5], 1080 * start: ['parent', 'start', 0], 1081 * end: ['parent', 'end', 0], 1082 * top: ['parent', 'top', 0], 1083 * bottom: ['parent', 'bottom', 0], 1084 * } 1085 * 1086 * @param flowType orientation of the Flow Helper 1087 * @param state ConstraintLayout State 1088 * @param flowName the name of the Flow Helper 1089 * @param layoutVariables layout margins 1090 * @param element the element to be parsed 1091 * @throws CLParsingException 1092 */ parseFlowType(String flowType, State state, String flowName, LayoutVariables layoutVariables, CLObject element)1093 private static void parseFlowType(String flowType, 1094 State state, 1095 String flowName, 1096 LayoutVariables layoutVariables, 1097 CLObject element) throws CLParsingException { 1098 boolean isVertical = flowType.charAt(0) == 'v'; 1099 FlowReference flow = state.getFlow(flowName, isVertical); 1100 1101 for (String param : element.names()) { 1102 switch (param) { 1103 case "contains": 1104 CLElement refs = element.get(param); 1105 if (!(refs instanceof CLArray) || ((CLArray) refs).size() < 1) { 1106 System.err.println( 1107 flowName + " contains should be an array \"" + refs.content() 1108 + "\""); 1109 return; 1110 } 1111 for (int i = 0; i < ((CLArray) refs).size(); i++) { 1112 CLElement chainElement = ((CLArray) refs).get(i); 1113 if (chainElement instanceof CLArray) { 1114 CLArray array = (CLArray) chainElement; 1115 if (array.size() > 0) { 1116 String id = array.get(0).content(); 1117 float weight = Float.NaN; 1118 float preMargin = Float.NaN; 1119 float postMargin = Float.NaN; 1120 switch (array.size()) { 1121 case 2: // sets only the weight 1122 weight = array.getFloat(1); 1123 break; 1124 case 3: // sets the pre and post margin to the 2 arg 1125 weight = array.getFloat(1); 1126 postMargin = preMargin = toPix(state, array.getFloat(2)); 1127 break; 1128 case 4: // sets the pre and post margin 1129 weight = array.getFloat(1); 1130 preMargin = toPix(state, array.getFloat(2)); 1131 postMargin = toPix(state, array.getFloat(3)); 1132 break; 1133 } 1134 flow.addFlowElement(id, weight, preMargin, postMargin); 1135 } 1136 } else { 1137 flow.add(chainElement.content()); 1138 } 1139 } 1140 break; 1141 case "type": 1142 if (element.get(param).content().equals("hFlow")) { 1143 flow.setOrientation(HORIZONTAL); 1144 } else { 1145 flow.setOrientation(VERTICAL); 1146 } 1147 break; 1148 case "wrap": 1149 String wrapValue = element.get(param).content(); 1150 flow.setWrapMode(State.Wrap.getValueByString(wrapValue)); 1151 break; 1152 case "vGap": 1153 int vGapValue = element.get(param).getInt(); 1154 flow.setVerticalGap(vGapValue); 1155 break; 1156 case "hGap": 1157 int hGapValue = element.get(param).getInt(); 1158 flow.setHorizontalGap(hGapValue); 1159 break; 1160 case "maxElement": 1161 int maxElementValue = element.get(param).getInt(); 1162 flow.setMaxElementsWrap(maxElementValue); 1163 break; 1164 case "padding": 1165 CLElement paddingObject = element.get(param); 1166 float paddingLeft = 0; 1167 float paddingTop = 0; 1168 float paddingRight = 0; 1169 float paddingBottom = 0; 1170 if (paddingObject instanceof CLArray && ((CLArray) paddingObject).size() > 1) { 1171 paddingLeft = ((CLArray) paddingObject).getInt(0); 1172 paddingRight = paddingLeft; 1173 paddingTop = ((CLArray) paddingObject).getInt(1); 1174 paddingBottom = paddingTop; 1175 if (((CLArray) paddingObject).size() > 2) { 1176 paddingRight = ((CLArray) paddingObject).getInt(2); 1177 try { 1178 paddingBottom = ((CLArray) paddingObject).getInt(3); 1179 } catch (ArrayIndexOutOfBoundsException e) { 1180 paddingBottom = 0; 1181 } 1182 1183 } 1184 } else { 1185 paddingLeft = paddingObject.getInt(); 1186 paddingTop = paddingLeft; 1187 paddingRight = paddingLeft; 1188 paddingBottom = paddingLeft; 1189 } 1190 flow.setPaddingLeft(Math.round(toPix(state, paddingLeft))); 1191 flow.setPaddingTop(Math.round(toPix(state, paddingTop))); 1192 flow.setPaddingRight(Math.round(toPix(state, paddingRight))); 1193 flow.setPaddingBottom(Math.round(toPix(state, paddingBottom))); 1194 break; 1195 case "vAlign": 1196 String vAlignValue = element.get(param).content(); 1197 switch (vAlignValue) { 1198 case "top": 1199 flow.setVerticalAlign(Flow.VERTICAL_ALIGN_TOP); 1200 break; 1201 case "bottom": 1202 flow.setVerticalAlign(Flow.VERTICAL_ALIGN_BOTTOM); 1203 break; 1204 case "baseline": 1205 flow.setVerticalAlign(Flow.VERTICAL_ALIGN_BASELINE); 1206 break; 1207 default: 1208 flow.setVerticalAlign(Flow.VERTICAL_ALIGN_CENTER); 1209 break; 1210 } 1211 break; 1212 case "hAlign": 1213 String hAlignValue = element.get(param).content(); 1214 switch (hAlignValue) { 1215 case "start": 1216 flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_START); 1217 break; 1218 case "end": 1219 flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_END); 1220 break; 1221 default: 1222 flow.setHorizontalAlign(Flow.HORIZONTAL_ALIGN_CENTER); 1223 break; 1224 } 1225 break; 1226 case "vFlowBias": 1227 CLElement vBiasObject = element.get(param); 1228 Float vBiasValue = 0.5f; 1229 Float vFirstBiasValue = 0.5f; 1230 Float vLastBiasValue = 0.5f; 1231 if (vBiasObject instanceof CLArray && ((CLArray) vBiasObject).size() > 1) { 1232 vFirstBiasValue = ((CLArray) vBiasObject).getFloat(0); 1233 vBiasValue = ((CLArray) vBiasObject).getFloat(1); 1234 if (((CLArray) vBiasObject).size() > 2) { 1235 vLastBiasValue = ((CLArray) vBiasObject).getFloat(2); 1236 } 1237 } else { 1238 vBiasValue = vBiasObject.getFloat(); 1239 } 1240 try { 1241 flow.verticalBias(vBiasValue); 1242 if (vFirstBiasValue != 0.5f) { 1243 flow.setFirstVerticalBias(vFirstBiasValue); 1244 } 1245 if (vLastBiasValue != 0.5f) { 1246 flow.setLastVerticalBias(vLastBiasValue); 1247 } 1248 } catch(NumberFormatException e) { 1249 1250 } 1251 break; 1252 case "hFlowBias": 1253 CLElement hBiasObject = element.get(param); 1254 Float hBiasValue = 0.5f; 1255 Float hFirstBiasValue = 0.5f; 1256 Float hLastBiasValue = 0.5f; 1257 if (hBiasObject instanceof CLArray && ((CLArray) hBiasObject).size() > 1) { 1258 hFirstBiasValue = ((CLArray) hBiasObject).getFloat(0); 1259 hBiasValue = ((CLArray) hBiasObject).getFloat(1); 1260 if (((CLArray) hBiasObject).size() > 2) { 1261 hLastBiasValue = ((CLArray) hBiasObject).getFloat(2); 1262 } 1263 } else { 1264 hBiasValue = hBiasObject.getFloat(); 1265 } 1266 try { 1267 flow.horizontalBias(hBiasValue); 1268 if (hFirstBiasValue != 0.5f) { 1269 flow.setFirstHorizontalBias(hFirstBiasValue); 1270 } 1271 if (hLastBiasValue != 0.5f) { 1272 flow.setLastHorizontalBias(hLastBiasValue); 1273 } 1274 } catch(NumberFormatException e) { 1275 1276 } 1277 break; 1278 case "vStyle": 1279 CLElement vStyleObject = element.get(param); 1280 String vStyleValueStr = ""; 1281 String vFirstStyleValueStr = ""; 1282 String vLastStyleValueStr = ""; 1283 if (vStyleObject instanceof CLArray && ((CLArray) vStyleObject).size() > 1) { 1284 vFirstStyleValueStr = ((CLArray) vStyleObject).getString(0); 1285 vStyleValueStr = ((CLArray) vStyleObject).getString(1); 1286 if (((CLArray) vStyleObject).size() > 2) { 1287 vLastStyleValueStr = ((CLArray) vStyleObject).getString(2); 1288 } 1289 } else { 1290 vStyleValueStr = vStyleObject.content(); 1291 } 1292 1293 if (!vStyleValueStr.equals("")) { 1294 flow.setVerticalStyle(State.Chain.getValueByString(vStyleValueStr)); 1295 } 1296 if (!vFirstStyleValueStr.equals("")) { 1297 flow.setFirstVerticalStyle( 1298 State.Chain.getValueByString(vFirstStyleValueStr)); 1299 } 1300 if (!vLastStyleValueStr.equals("")) { 1301 flow.setLastVerticalStyle(State.Chain.getValueByString(vLastStyleValueStr)); 1302 } 1303 break; 1304 case "hStyle": 1305 CLElement hStyleObject = element.get(param); 1306 String hStyleValueStr = ""; 1307 String hFirstStyleValueStr = ""; 1308 String hLastStyleValueStr = ""; 1309 if (hStyleObject instanceof CLArray && ((CLArray) hStyleObject).size() > 1) { 1310 hFirstStyleValueStr = ((CLArray) hStyleObject).getString(0); 1311 hStyleValueStr = ((CLArray) hStyleObject).getString(1); 1312 if (((CLArray) hStyleObject).size() > 2) { 1313 hLastStyleValueStr = ((CLArray) hStyleObject).getString(2); 1314 } 1315 } else { 1316 hStyleValueStr = hStyleObject.content(); 1317 } 1318 1319 if (!hStyleValueStr.equals("")) { 1320 flow.setHorizontalStyle(State.Chain.getValueByString(hStyleValueStr)); 1321 } 1322 if (!hFirstStyleValueStr.equals("")) { 1323 flow.setFirstHorizontalStyle( 1324 State.Chain.getValueByString(hFirstStyleValueStr)); 1325 } 1326 if (!hLastStyleValueStr.equals("")) { 1327 flow.setLastHorizontalStyle( 1328 State.Chain.getValueByString(hLastStyleValueStr)); 1329 } 1330 break; 1331 default: 1332 // Get the underlying reference for the flow, apply the constraints 1333 // attributes to it 1334 ConstraintReference reference = state.constraints(flowName); 1335 applyAttribute(state, layoutVariables, reference, element, param); 1336 } 1337 } 1338 } 1339 parseGuideline(int orientation, State state, CLArray helper)1340 static void parseGuideline(int orientation, 1341 State state, CLArray helper) throws CLParsingException { 1342 CLElement params = helper.get(1); 1343 if (!(params instanceof CLObject)) { 1344 return; 1345 } 1346 String guidelineId = ((CLObject) params).getStringOrNull("id"); 1347 if (guidelineId == null) return; 1348 parseGuidelineParams(orientation, state, guidelineId, (CLObject) params); 1349 } 1350 parseGuidelineParams( int orientation, State state, String guidelineId, CLObject params )1351 static void parseGuidelineParams( 1352 int orientation, 1353 State state, 1354 String guidelineId, 1355 CLObject params 1356 ) throws CLParsingException { 1357 ArrayList<String> constraints = params.names(); 1358 if (constraints == null) return; 1359 ConstraintReference reference = state.constraints(guidelineId); 1360 1361 if (orientation == ConstraintWidget.HORIZONTAL) { 1362 state.horizontalGuideline(guidelineId); 1363 } else { 1364 state.verticalGuideline(guidelineId); 1365 } 1366 1367 // Layout direction may be ignored for Horizontal guidelines (placed along the Y axis), 1368 // since `start` & `end` represent the `top` and `bottom` distances respectively. 1369 boolean isLtr = !state.isRtl() || orientation == ConstraintWidget.HORIZONTAL; 1370 1371 GuidelineReference guidelineReference = (GuidelineReference) reference.getFacade(); 1372 1373 // Whether the guideline is based on percentage or distance 1374 boolean isPercent = false; 1375 1376 // Percent or distance value of the guideline 1377 float value = 0f; 1378 1379 // Indicates if the value is considered from the "start" position, 1380 // meaning "left" anchor for vertical guidelines and "top" anchor for 1381 // horizontal guidelines 1382 boolean fromStart = true; 1383 for (String constraintName : constraints) { 1384 switch (constraintName) { 1385 // left and right are here just to support LTR independent vertical guidelines 1386 case "left": 1387 value = toPix(state, params.getFloat(constraintName)); 1388 fromStart = true; 1389 break; 1390 case "right": 1391 value = toPix(state, params.getFloat(constraintName)); 1392 fromStart = false; 1393 break; 1394 case "start": 1395 value = toPix(state, params.getFloat(constraintName)); 1396 fromStart = isLtr; 1397 break; 1398 case "end": 1399 value = toPix(state, params.getFloat(constraintName)); 1400 fromStart = !isLtr; 1401 break; 1402 case "percent": 1403 isPercent = true; 1404 CLArray percentParams = params.getArrayOrNull(constraintName); 1405 if (percentParams == null) { 1406 fromStart = true; 1407 value = params.getFloat(constraintName); 1408 } else if (percentParams.size() > 1) { 1409 String origin = percentParams.getString(0); 1410 value = percentParams.getFloat(1); 1411 switch (origin) { 1412 case "left": 1413 fromStart = true; 1414 break; 1415 case "right": 1416 fromStart = false; 1417 break; 1418 case "start": 1419 fromStart = isLtr; 1420 break; 1421 case "end": 1422 fromStart = !isLtr; 1423 break; 1424 } 1425 } 1426 break; 1427 } 1428 } 1429 1430 // Populate the guideline based on the resolved properties 1431 if (isPercent) { 1432 if (fromStart) { 1433 guidelineReference.percent(value); 1434 } else { 1435 guidelineReference.percent(1f - value); 1436 } 1437 } else { 1438 if (fromStart) { 1439 guidelineReference.start(value); 1440 } else { 1441 guidelineReference.end(value); 1442 } 1443 } 1444 } 1445 parseBarrier( State state, String elementName, CLObject element )1446 static void parseBarrier( 1447 State state, 1448 String elementName, CLObject element 1449 ) throws CLParsingException { 1450 boolean isLtr = !state.isRtl(); 1451 BarrierReference reference = state.barrier(elementName, State.Direction.END); 1452 ArrayList<String> constraints = element.names(); 1453 if (constraints == null) { 1454 return; 1455 } 1456 for (String constraintName : constraints) { 1457 switch (constraintName) { 1458 case "direction": { 1459 switch (element.getString(constraintName)) { 1460 case "start": 1461 if (isLtr) { 1462 reference.setBarrierDirection(State.Direction.LEFT); 1463 } else { 1464 reference.setBarrierDirection(State.Direction.RIGHT); 1465 } 1466 break; 1467 case "end": 1468 if (isLtr) { 1469 reference.setBarrierDirection(State.Direction.RIGHT); 1470 } else { 1471 reference.setBarrierDirection(State.Direction.LEFT); 1472 } 1473 break; 1474 case "left": 1475 reference.setBarrierDirection(State.Direction.LEFT); 1476 break; 1477 case "right": 1478 reference.setBarrierDirection(State.Direction.RIGHT); 1479 break; 1480 case "top": 1481 reference.setBarrierDirection(State.Direction.TOP); 1482 break; 1483 case "bottom": 1484 reference.setBarrierDirection(State.Direction.BOTTOM); 1485 break; 1486 } 1487 } 1488 break; 1489 case "margin": 1490 float margin = element.getFloatOrNaN(constraintName); 1491 if (!Float.isNaN(margin)) { 1492 reference.margin(toPix(state, margin)); 1493 } 1494 break; 1495 case "contains": 1496 CLArray list = element.getArrayOrNull(constraintName); 1497 if (list != null) { 1498 for (int j = 0; j < list.size(); j++) { 1499 1500 String elementNameReference = list.get(j).content(); 1501 ConstraintReference elementReference = 1502 state.constraints(elementNameReference); 1503 if (PARSER_DEBUG) { 1504 System.out.println( 1505 "Add REFERENCE " 1506 + "($elementNameReference = $elementReference) " 1507 + "TO BARRIER " 1508 ); 1509 } 1510 reference.add(elementReference); 1511 } 1512 } 1513 break; 1514 } 1515 } 1516 } 1517 parseWidget( State state, LayoutVariables layoutVariables, String elementName, CLObject element )1518 static void parseWidget( 1519 State state, 1520 LayoutVariables layoutVariables, 1521 String elementName, 1522 CLObject element 1523 ) throws CLParsingException { 1524 ConstraintReference reference = state.constraints(elementName); 1525 parseWidget(state, layoutVariables, reference, element); 1526 } 1527 1528 /** 1529 * Set/apply attribute to a widget/helper reference 1530 * 1531 * @param state Constraint State 1532 * @param layoutVariables layout variables 1533 * @param reference widget/helper reference 1534 * @param element the parsed CLObject 1535 * @param attributeName Name of the attribute to be set/applied 1536 * @throws CLParsingException 1537 */ applyAttribute( State state, LayoutVariables layoutVariables, ConstraintReference reference, CLObject element, String attributeName)1538 static void applyAttribute( 1539 State state, 1540 LayoutVariables layoutVariables, 1541 ConstraintReference reference, 1542 CLObject element, 1543 String attributeName) throws CLParsingException { 1544 1545 float value; 1546 switch (attributeName) { 1547 case "width": 1548 reference.setWidth(parseDimension(element, 1549 attributeName, state, state.getDpToPixel())); 1550 break; 1551 case "height": 1552 reference.setHeight(parseDimension(element, 1553 attributeName, state, state.getDpToPixel())); 1554 break; 1555 case "center": 1556 String target = element.getString(attributeName); 1557 1558 ConstraintReference targetReference; 1559 if (target.equals("parent")) { 1560 targetReference = state.constraints(State.PARENT); 1561 } else { 1562 targetReference = state.constraints(target); 1563 } 1564 reference.startToStart(targetReference); 1565 reference.endToEnd(targetReference); 1566 reference.topToTop(targetReference); 1567 reference.bottomToBottom(targetReference); 1568 break; 1569 case "centerHorizontally": 1570 target = element.getString(attributeName); 1571 targetReference = target.equals("parent") 1572 ? state.constraints(State.PARENT) : state.constraints(target); 1573 1574 reference.startToStart(targetReference); 1575 reference.endToEnd(targetReference); 1576 break; 1577 case "centerVertically": 1578 target = element.getString(attributeName); 1579 targetReference = target.equals("parent") 1580 ? state.constraints(State.PARENT) : state.constraints(target); 1581 1582 reference.topToTop(targetReference); 1583 reference.bottomToBottom(targetReference); 1584 break; 1585 case "alpha": 1586 value = layoutVariables.get(element.get(attributeName)); 1587 reference.alpha(value); 1588 break; 1589 case "scaleX": 1590 value = layoutVariables.get(element.get(attributeName)); 1591 reference.scaleX(value); 1592 break; 1593 case "scaleY": 1594 value = layoutVariables.get(element.get(attributeName)); 1595 reference.scaleY(value); 1596 break; 1597 case "translationX": 1598 value = layoutVariables.get(element.get(attributeName)); 1599 reference.translationX(toPix(state, value)); 1600 break; 1601 case "translationY": 1602 value = layoutVariables.get(element.get(attributeName)); 1603 reference.translationY(toPix(state, value)); 1604 break; 1605 case "translationZ": 1606 value = layoutVariables.get(element.get(attributeName)); 1607 reference.translationZ(toPix(state, value)); 1608 break; 1609 case "pivotX": 1610 value = layoutVariables.get(element.get(attributeName)); 1611 reference.pivotX(value); 1612 break; 1613 case "pivotY": 1614 value = layoutVariables.get(element.get(attributeName)); 1615 reference.pivotY(value); 1616 break; 1617 case "rotationX": 1618 value = layoutVariables.get(element.get(attributeName)); 1619 reference.rotationX(value); 1620 break; 1621 case "rotationY": 1622 value = layoutVariables.get(element.get(attributeName)); 1623 reference.rotationY(value); 1624 break; 1625 case "rotationZ": 1626 value = layoutVariables.get(element.get(attributeName)); 1627 reference.rotationZ(value); 1628 break; 1629 case "visibility": 1630 switch (element.getString(attributeName)) { 1631 case "visible": 1632 reference.visibility(ConstraintWidget.VISIBLE); 1633 break; 1634 case "invisible": 1635 reference.visibility(ConstraintWidget.INVISIBLE); 1636 reference.alpha(0f); 1637 break; 1638 case "gone": 1639 reference.visibility(ConstraintWidget.GONE); 1640 break; 1641 } 1642 break; 1643 case "vBias": 1644 value = layoutVariables.get(element.get(attributeName)); 1645 reference.verticalBias(value); 1646 break; 1647 case "hRtlBias": 1648 // TODO: This is a temporary solution to support bias with start/end constraints, 1649 // where the bias needs to be reversed in RTL, we probably want a better or more 1650 // intuitive way to do this 1651 value = layoutVariables.get(element.get(attributeName)); 1652 if (state.isRtl()) { 1653 value = 1f - value; 1654 } 1655 reference.horizontalBias(value); 1656 break; 1657 case "hBias": 1658 value = layoutVariables.get(element.get(attributeName)); 1659 reference.horizontalBias(value); 1660 break; 1661 case "vWeight": 1662 value = layoutVariables.get(element.get(attributeName)); 1663 reference.setVerticalChainWeight(value); 1664 break; 1665 case "hWeight": 1666 value = layoutVariables.get(element.get(attributeName)); 1667 reference.setHorizontalChainWeight(value); 1668 break; 1669 case "custom": 1670 parseCustomProperties(element, reference, attributeName); 1671 break; 1672 case "motion": 1673 parseMotionProperties(element.get(attributeName), reference); 1674 break; 1675 default: 1676 parseConstraint(state, layoutVariables, element, reference, attributeName); 1677 } 1678 } 1679 parseWidget( State state, LayoutVariables layoutVariables, ConstraintReference reference, CLObject element )1680 static void parseWidget( 1681 State state, 1682 LayoutVariables layoutVariables, 1683 ConstraintReference reference, 1684 CLObject element 1685 ) throws CLParsingException { 1686 if (reference.getWidth() == null) { 1687 // Default to Wrap when the Dimension has not been assigned 1688 reference.setWidth(Dimension.createWrap()); 1689 } 1690 if (reference.getHeight() == null) { 1691 // Default to Wrap when the Dimension has not been assigned 1692 reference.setHeight(Dimension.createWrap()); 1693 } 1694 ArrayList<String> constraints = element.names(); 1695 if (constraints == null) { 1696 return; 1697 } 1698 for (String constraintName : constraints) { 1699 applyAttribute(state, layoutVariables, reference, element, constraintName); 1700 } 1701 } 1702 parseCustomProperties( CLObject element, ConstraintReference reference, String constraintName )1703 static void parseCustomProperties( 1704 CLObject element, 1705 ConstraintReference reference, 1706 String constraintName 1707 ) throws CLParsingException { 1708 CLObject json = element.getObjectOrNull(constraintName); 1709 if (json == null) { 1710 return; 1711 } 1712 ArrayList<String> properties = json.names(); 1713 if (properties == null) { 1714 return; 1715 } 1716 for (String property : properties) { 1717 CLElement value = json.get(property); 1718 if (value instanceof CLNumber) { 1719 reference.addCustomFloat(property, value.getFloat()); 1720 } else if (value instanceof CLString) { 1721 long it = parseColorString(value.content()); 1722 if (it != -1) { 1723 reference.addCustomColor(property, (int) it); 1724 } 1725 } 1726 } 1727 } 1728 indexOf(String val, String... types)1729 private static int indexOf(String val, String... types) { 1730 for (int i = 0; i < types.length; i++) { 1731 if (types[i].equals(val)) { 1732 return i; 1733 } 1734 } 1735 return -1; 1736 } 1737 1738 /** 1739 * parse the motion section of a constraint 1740 * <pre> 1741 * csetName: { 1742 * idToConstrain : { 1743 * motion: { 1744 * pathArc : 'startVertical' 1745 * relativeTo: 'id' 1746 * easing: 'curve' 1747 * stagger: '2' 1748 * quantize: steps or [steps, 'interpolator' phase ] 1749 * } 1750 * } 1751 * } 1752 * </pre> 1753 */ parseMotionProperties( CLElement element, ConstraintReference reference )1754 private static void parseMotionProperties( 1755 CLElement element, 1756 ConstraintReference reference 1757 ) throws CLParsingException { 1758 if (!(element instanceof CLObject)) { 1759 return; 1760 } 1761 CLObject obj = (CLObject) element; 1762 TypedBundle bundle = new TypedBundle(); 1763 ArrayList<String> constraints = obj.names(); 1764 if (constraints == null) { 1765 return; 1766 } 1767 for (String constraintName : constraints) { 1768 1769 switch (constraintName) { 1770 case "pathArc": 1771 String val = obj.getString(constraintName); 1772 int ord = indexOf(val, "none", "startVertical", "startHorizontal", "flip", 1773 "below", "above"); 1774 if (ord == -1) { 1775 System.err.println(obj.getLine() + " pathArc = '" + val + "'"); 1776 break; 1777 } 1778 bundle.add(TypedValues.MotionType.TYPE_PATHMOTION_ARC, ord); 1779 break; 1780 case "relativeTo": 1781 bundle.add(TypedValues.MotionType.TYPE_ANIMATE_RELATIVE_TO, 1782 obj.getString(constraintName)); 1783 break; 1784 case "easing": 1785 bundle.add(TypedValues.MotionType.TYPE_EASING, obj.getString(constraintName)); 1786 break; 1787 case "stagger": 1788 bundle.add(TypedValues.MotionType.TYPE_STAGGER, obj.getFloat(constraintName)); 1789 break; 1790 case "quantize": 1791 CLElement quant = obj.get(constraintName); 1792 if (quant instanceof CLArray) { 1793 CLArray array = (CLArray) quant; 1794 int len = array.size(); 1795 if (len > 0) { 1796 bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, array.getInt(0)); 1797 if (len > 1) { 1798 bundle.add(TYPE_QUANTIZE_INTERPOLATOR_TYPE, array.getString(1)); 1799 if (len > 2) { 1800 bundle.add(TYPE_QUANTIZE_MOTION_PHASE, array.getFloat(2)); 1801 } 1802 } 1803 } 1804 } else { 1805 bundle.add(TYPE_QUANTIZE_MOTIONSTEPS, obj.getInt(constraintName)); 1806 } 1807 break; 1808 } 1809 } 1810 reference.mMotionProperties = bundle; 1811 } 1812 parseConstraint( State state, LayoutVariables layoutVariables, CLObject element, ConstraintReference reference, String constraintName )1813 static void parseConstraint( 1814 State state, 1815 LayoutVariables layoutVariables, 1816 CLObject element, 1817 ConstraintReference reference, 1818 String constraintName 1819 ) throws CLParsingException { 1820 boolean isLtr = !state.isRtl(); 1821 CLArray constraint = element.getArrayOrNull(constraintName); 1822 if (constraint != null && constraint.size() > 1) { 1823 // params: target, anchor 1824 String target = constraint.getString(0); 1825 String anchor = constraint.getStringOrNull(1); 1826 float margin = 0f; 1827 float marginGone = 0f; 1828 if (constraint.size() > 2) { 1829 // params: target, anchor, margin 1830 CLElement arg2 = constraint.getOrNull(2); 1831 margin = layoutVariables.get(arg2); 1832 margin = toPix(state, margin); 1833 } 1834 if (constraint.size() > 3) { 1835 // params: target, anchor, margin, marginGone 1836 CLElement arg2 = constraint.getOrNull(3); 1837 marginGone = layoutVariables.get(arg2); 1838 marginGone = toPix(state, marginGone); 1839 } 1840 1841 ConstraintReference targetReference = target.equals("parent") 1842 ? state.constraints(State.PARENT) : 1843 state.constraints(target); 1844 1845 // For simplicity, we'll apply horizontal constraints separately 1846 boolean isHorizontalConstraint = false; 1847 boolean isHorOriginLeft = true; 1848 boolean isHorTargetLeft = true; 1849 1850 switch (constraintName) { 1851 case "circular": 1852 float angle = layoutVariables.get(constraint.get(1)); 1853 float distance = 0f; 1854 if (constraint.size() > 2) { 1855 CLElement distanceArg = constraint.getOrNull(2); 1856 distance = layoutVariables.get(distanceArg); 1857 distance = toPix(state, distance); 1858 } 1859 reference.circularConstraint(targetReference, angle, distance); 1860 break; 1861 case "top": 1862 switch (anchor) { 1863 case "top": 1864 reference.topToTop(targetReference); 1865 break; 1866 case "bottom": 1867 reference.topToBottom(targetReference); 1868 break; 1869 case "baseline": 1870 state.baselineNeededFor(targetReference.getKey()); 1871 reference.topToBaseline(targetReference); 1872 break; 1873 } 1874 break; 1875 case "bottom": 1876 switch (anchor) { 1877 case "top": 1878 reference.bottomToTop(targetReference); 1879 break; 1880 case "bottom": 1881 reference.bottomToBottom(targetReference); 1882 break; 1883 case "baseline": 1884 state.baselineNeededFor(targetReference.getKey()); 1885 reference.bottomToBaseline(targetReference); 1886 } 1887 break; 1888 case "baseline": 1889 switch (anchor) { 1890 case "baseline": 1891 state.baselineNeededFor(reference.getKey()); 1892 state.baselineNeededFor(targetReference.getKey()); 1893 reference.baselineToBaseline(targetReference); 1894 break; 1895 case "top": 1896 state.baselineNeededFor(reference.getKey()); 1897 reference.baselineToTop(targetReference); 1898 break; 1899 case "bottom": 1900 state.baselineNeededFor(reference.getKey()); 1901 reference.baselineToBottom(targetReference); 1902 break; 1903 } 1904 break; 1905 case "left": 1906 isHorizontalConstraint = true; 1907 isHorOriginLeft = true; 1908 break; 1909 case "right": 1910 isHorizontalConstraint = true; 1911 isHorOriginLeft = false; 1912 break; 1913 case "start": 1914 isHorizontalConstraint = true; 1915 isHorOriginLeft = isLtr; 1916 break; 1917 case "end": 1918 isHorizontalConstraint = true; 1919 isHorOriginLeft = !isLtr; 1920 break; 1921 } 1922 1923 if (isHorizontalConstraint) { 1924 // Resolve horizontal target anchor 1925 switch (anchor) { 1926 case "left": 1927 isHorTargetLeft = true; 1928 break; 1929 case "right": 1930 isHorTargetLeft = false; 1931 break; 1932 case "start": 1933 isHorTargetLeft = isLtr; 1934 break; 1935 case "end": 1936 isHorTargetLeft = !isLtr; 1937 break; 1938 } 1939 1940 // Resolved anchors, apply corresponding constraint 1941 if (isHorOriginLeft) { 1942 if (isHorTargetLeft) { 1943 reference.leftToLeft(targetReference); 1944 } else { 1945 reference.leftToRight(targetReference); 1946 } 1947 } else { 1948 if (isHorTargetLeft) { 1949 reference.rightToLeft(targetReference); 1950 } else { 1951 reference.rightToRight(targetReference); 1952 } 1953 } 1954 } 1955 1956 reference.margin(margin).marginGone(marginGone); 1957 } else { 1958 String target = element.getStringOrNull(constraintName); 1959 if (target != null) { 1960 ConstraintReference targetReference = target.equals("parent") 1961 ? state.constraints(State.PARENT) : 1962 state.constraints(target); 1963 1964 switch (constraintName) { 1965 case "start": 1966 if (isLtr) { 1967 reference.leftToLeft(targetReference); 1968 } else { 1969 reference.rightToRight(targetReference); 1970 } 1971 break; 1972 case "end": 1973 if (isLtr) { 1974 reference.rightToRight(targetReference); 1975 } else { 1976 reference.leftToLeft(targetReference); 1977 } 1978 break; 1979 case "top": 1980 reference.topToTop(targetReference); 1981 break; 1982 case "bottom": 1983 reference.bottomToBottom(targetReference); 1984 break; 1985 case "baseline": 1986 state.baselineNeededFor(reference.getKey()); 1987 state.baselineNeededFor(targetReference.getKey()); 1988 reference.baselineToBaseline(targetReference); 1989 break; 1990 } 1991 } 1992 } 1993 } 1994 parseDimensionMode(String dimensionString)1995 static Dimension parseDimensionMode(String dimensionString) { 1996 Dimension dimension = Dimension.createFixed(0); 1997 switch (dimensionString) { 1998 case "wrap": 1999 dimension = Dimension.createWrap(); 2000 break; 2001 case "preferWrap": 2002 dimension = Dimension.createSuggested(Dimension.WRAP_DIMENSION); 2003 break; 2004 case "spread": 2005 dimension = Dimension.createSuggested(Dimension.SPREAD_DIMENSION); 2006 break; 2007 case "parent": 2008 dimension = Dimension.createParent(); 2009 break; 2010 default: { 2011 if (dimensionString.endsWith("%")) { 2012 // parent percent 2013 String percentString = 2014 dimensionString.substring(0, dimensionString.indexOf('%')); 2015 float percentValue = Float.parseFloat(percentString) / 100f; 2016 dimension = Dimension.createPercent(0, percentValue).suggested(0); 2017 } else if (dimensionString.contains(":")) { 2018 dimension = Dimension.createRatio(dimensionString) 2019 .suggested(Dimension.SPREAD_DIMENSION); 2020 } 2021 } 2022 } 2023 return dimension; 2024 } 2025 parseDimension(CLObject element, String constraintName, State state, CorePixelDp dpToPixels)2026 static Dimension parseDimension(CLObject element, 2027 String constraintName, 2028 State state, 2029 CorePixelDp dpToPixels) throws CLParsingException { 2030 CLElement dimensionElement = element.get(constraintName); 2031 Dimension dimension = Dimension.createFixed(0); 2032 if (dimensionElement instanceof CLString) { 2033 dimension = parseDimensionMode(dimensionElement.content()); 2034 } else if (dimensionElement instanceof CLNumber) { 2035 dimension = Dimension.createFixed( 2036 state.convertDimension(dpToPixels.toPixels(element.getFloat(constraintName)))); 2037 2038 } else if (dimensionElement instanceof CLObject) { 2039 CLObject obj = (CLObject) dimensionElement; 2040 String mode = obj.getStringOrNull("value"); 2041 if (mode != null) { 2042 dimension = parseDimensionMode(mode); 2043 } 2044 2045 CLElement minEl = obj.getOrNull("min"); 2046 if (minEl != null) { 2047 if (minEl instanceof CLNumber) { 2048 float min = ((CLNumber) minEl).getFloat(); 2049 dimension.min(state.convertDimension(dpToPixels.toPixels(min))); 2050 } else if (minEl instanceof CLString) { 2051 dimension.min(Dimension.WRAP_DIMENSION); 2052 } 2053 } 2054 CLElement maxEl = obj.getOrNull("max"); 2055 if (maxEl != null) { 2056 if (maxEl instanceof CLNumber) { 2057 float max = ((CLNumber) maxEl).getFloat(); 2058 dimension.max(state.convertDimension(dpToPixels.toPixels(max))); 2059 } else if (maxEl instanceof CLString) { 2060 dimension.max(Dimension.WRAP_DIMENSION); 2061 } 2062 } 2063 } 2064 return dimension; 2065 } 2066 2067 /** 2068 * parse a color string 2069 * 2070 * @return -1 if it cannot parse unsigned long 2071 */ parseColorString(String value)2072 static long parseColorString(String value) { 2073 String str = value; 2074 if (str.startsWith("#")) { 2075 str = str.substring(1); 2076 if (str.length() == 6) { 2077 str = "FF" + str; 2078 } 2079 return Long.parseLong(str, 16); 2080 } else { 2081 return -1L; 2082 } 2083 } 2084 lookForType(CLObject element)2085 static String lookForType(CLObject element) throws CLParsingException { 2086 ArrayList<String> constraints = element.names(); 2087 for (String constraintName : constraints) { 2088 if (constraintName.equals("type")) { 2089 return element.getString("type"); 2090 } 2091 } 2092 return null; 2093 } 2094 }