/*******************************************************************************
* Copyright 2012-present Pixate, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package com.pixate.freestyle.styling;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import android.graphics.Color;
import android.util.DisplayMetrics;
import com.pixate.freestyle.cg.math.PXDimension;
import com.pixate.freestyle.cg.paints.PXGradient;
import com.pixate.freestyle.cg.paints.PXPaint;
import com.pixate.freestyle.cg.paints.PXPaintGroup;
import com.pixate.freestyle.cg.paints.PXSolidPaint;
import com.pixate.freestyle.styling.adapters.PXStyleAdapter;
import com.pixate.freestyle.styling.cache.PXStyleInfo;
import com.pixate.freestyle.styling.selectors.PXTypeSelector;
import com.pixate.freestyle.styling.stylers.PXStyler;
import com.pixate.freestyle.styling.virtualStyleables.PXVirtualStyleable;
import com.pixate.freestyle.util.CollectionUtil;
import com.pixate.freestyle.util.PXColorUtil;
import com.pixate.freestyle.util.PXLog;
import com.pixate.freestyle.util.StringUtil;
/**
* Pixate styling utilities.
*/
public class PXStyleUtils {
public static final Pattern PATTERN_WHITESPACE_PLUS = Pattern.compile("\\s+");
public static final Pattern PATTERN_WHITESPACE = Pattern.compile("\\s");
private static final String TAG = PXStyleUtils.class.getSimpleName();
/**
* A utility class to hold children information
*/
public static class PXStyleableChildrenInfo {
public int childrenCount;
public int childrenIndex;
public int childrenOfTypeCount;
public int childrenOfTypeIndex;
}
/**
* Returns a float dimension value
*
* @param value Can be a {@link Number} or a {@link PXDimension}
* @param metrics
* @return A float dimension value.
*/
public static float getFloatFromValue(Object value, DisplayMetrics metrics) {
float result = 0;
if (value != null) {
if (value instanceof Number) {
result = ((Number) value).floatValue();
} else if (value instanceof PXDimension) {
PXDimension dimension = (PXDimension) value;
PXDimension points = dimension.points(metrics);
result = points.getNumber();
}
}
return result;
}
/**
* Returns a {@link PXPaint} from a value.
*
* @param value Can be a {@link Number}, {@link PXGradient}, {@link Object}
* [] or {@link String}.
* @return A {@link PXPaint}
*/
public static PXPaint getPaintFromValue(Object value) {
PXPaint result = PXSolidPaint.createPaintWithColor(Color.BLACK);
if (value != null) {
if (value instanceof Number) {
result = PXSolidPaint.createPaintWithColor(((Integer) value).intValue());
} else if (value instanceof PXGradient) {
result = (PXGradient) value;
} else if (value instanceof String) {
result = PXSolidPaint.createPaintWithColor(PXColorUtil.getColorFromSVGName(value
.toString()));
} else if (value instanceof Object[]) {
Object[] paints = (Object[]) value;
PXPaintGroup paintGroup = new PXPaintGroup();
for (Object paint : paints) {
paintGroup.addPaint(getPaintFromValue(paint));
}
result = paintGroup;
}
}
return result;
}
public static void updateStyle(Object styleable) {
/* @formatter:off
// TODO - Handle similar Android instances
if (Pixate.configuration.cacheStyles &&
( styleable instanceof TableRow] || [styleable isKindOfClass:[UICollectionViewCell class]]))
{
// grab styleable's style hash
NSString *styleKey = styleable.styleKey;
PXStyleTreeInfo *cache = [PXCacheManager styleTreeInfoForKey:styleKey];
// cache this items style info if we haven't seen it before
if (cache == nil)
{
// collect style info
cache = [[PXStyleTreeInfo alloc] initWithStyleable:styleable];
// save for later
[PXCacheManager setStyleTreeInfo:cache forKey:styleKey];
}
// apply style info to the styleable and its descendants
[cache applyStylesToStyleable:styleable];
}
else
{
*/
// @formatter:on
PXStyleInfo styleInfo = PXStyleInfo.getStyleInfo(styleable);
if (styleInfo != null) {
styleInfo.applyTo(styleable);
}
// }
}
/**
* Updates the styles for a given styleable.
*
* @param styleable
* @param recurse Indicate whether to recursively style the styleable
* children. Note that direct virtual children will still be
* styled.
*/
public static void updateStyles(Object styleable, boolean recurse) {
if (styleable != null) {
updateStyle(styleable);
List<Object> children = PXStyleAdapter.getStyleAdapter(styleable).getElementChildren(
styleable);
if (children != null) {
if (recurse) {
for (Object child : children) {
updateStyles(child, recurse);
}
} else {
// in a non-recursive mode, we still want to style the virtual
// children.
for (Object child : children) {
if (child instanceof PXVirtualStyleable) {
updateStyles(child, recurse);
}
}
}
}
}
}
public static String getDescriptionForStyleable(Object styleable) {
List<String> parts = new ArrayList<String>(7);
// open text
parts.add("{ ");
// add class name and pointer address
parts.add("Class=");
parts.add(styleable.toString());
parts.add(", Hash=" + styleable.hashCode());
// add selector
parts.add(", Selector=");
parts.add(getSelector(styleable));
// close text
parts.add(" }");
return CollectionUtil.toString(parts, StringUtil.EMPTY);
}
public static String getSelector(Object styleable) {
List<String> parts = new ArrayList<String>();
PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable);
// add element name
parts.add(styleAdapter.getElementName(styleable));
// add id
String styleId = styleAdapter.getStyleId(styleable);
if (!StringUtil.isEmpty(styleId)) {
parts.add("#" + styleId);
}
// add classes
String styleClass = styleAdapter.getStyleClass(styleable);
if (!StringUtil.isEmpty(styleClass)) {
String[] classes = PATTERN_WHITESPACE_PLUS.split(styleClass);
Arrays.sort(classes);
for (String className : classes) {
parts.add("." + className);
}
}
return CollectionUtil.toString(parts, StringUtil.EMPTY);
}
public static List<PXStyler> getStylers(Object styleable) {
return PXStyleAdapter.getStyleAdapter(styleable).getStylers();
}
public static List<PXRuleSet> getMatchingRuleSets(Object styleable) {
// find matching rule sets, regardless of any supported or specified
// pseudo-classes
PXStylesheet stylesheet = PXStylesheet.getCurrentApplicationStylesheet();
List<PXRuleSet> ruleSets = null;
if (stylesheet != null) {
ruleSets = stylesheet.getRuleSetsMatchingStyleable(styleable);
} else if (PXLog.isLogging()) {
PXLog.w(TAG,
"Stylesheet was not found. Make sure you have your style CSS in the assets.");
}
// TODO: add matching sets from user stylesheet and view stylesheet
return ruleSets;
}
public static PXStyleableChildrenInfo getChildrenInfoForStyleable(Object styleable) {
PXStyleableChildrenInfo result = new PXStyleableChildrenInfo();
// init
result.childrenCount = 0;
result.childrenOfTypeCount = 0;
result.childrenIndex = -1;
result.childrenOfTypeIndex = -1;
PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable);
Object parent = styleAdapter.getParent(styleable);
int indexInParent = styleAdapter.getIndexInParent(styleable);
if (indexInParent > -1) {
result.childrenIndex = result.childrenOfTypeIndex = indexInParent;
int childCount = (parent != null) ? styleAdapter.getChildCount(parent) : styleAdapter
.getSiblingsCount(styleable);
result.childrenCount = result.childrenOfTypeCount = childCount;
} else {
PXStyleAdapter parentAdapter = PXStyleAdapter.getStyleAdapter(parent);
List<Object> children = parentAdapter.getElementChildren(parent);
String styleableElementName = styleAdapter.getElementName(styleable);
if (!CollectionUtil.isEmpty(children)) {
for (Object child : children) {
PXStyleAdapter childAdapter = PXStyleAdapter.getStyleAdapter(child);
String elementName = childAdapter.getElementName(child);
if (elementName != null && !elementName.startsWith("#")) {
result.childrenCount++;
// test for element existence after adding to make index
// 1-based
if (child == styleable) {
result.childrenIndex = result.childrenCount;
}
if (elementName.equals(styleableElementName)) {
result.childrenOfTypeCount++;
// test for element existence after adding to make
// index 1-based
if (child == styleable) {
result.childrenOfTypeIndex = result.childrenOfTypeCount;
}
}
}
}
}
}
return result;
}
public static List<PXRuleSet> filterRuleSets(List<PXRuleSet> ruleSets, Object styleable,
String stateName) {
// if we have a state name, then filter the specified rulesets to thos
// only referencing this state
List<PXRuleSet> ruleSetsForState = new ArrayList<PXRuleSet>();
// process each rule set
for (PXRuleSet ruleSet : ruleSets) {
// grab the target type selector (the selector itself or a
// combinator's RHS)
PXTypeSelector selector = ruleSet.getTargetTypeSelector();
// assume we will not be adding this rule set into our results
boolean add = false;
if (!selector.hasPseudoClasses()) {
// the selector didn't specify a pseudo-class so assume the
// default psuedo-class was specified
PXStyleAdapter styleAdapter = PXStyleAdapter.getStyleAdapter(styleable);
String defaultPseudoClass = styleAdapter.getDefaultPseudoClass(styleable);
if (defaultPseudoClass != null) {
// add rule set if default pseudo-class matches
// stateName
add = defaultPseudoClass.equals(stateName);
} else {
// add rule set if the styleable doesn't have a default
// pseudo-class and the specified state name
// was nil
add = (stateName == null);
}
} else {
// add if the styleable has the state name in its lists of
// supported pseudo-classes
add = selector.hasPseudoClass(stateName);
}
if (add) {
ruleSetsForState.add(ruleSet);
}
}
return ruleSetsForState;
}
public List<PXRuleSet> filterRuleSets(List<PXRuleSet> ruleSets, String pseudoElement) {
List<PXRuleSet> ruleSetsForPseudoElement = null;
// if we have a pseudo-element name, then filter the specified rulesets
// to those only referencing this pseudo-element
if (!StringUtil.isEmpty(pseudoElement)) {
// process each rule set
for (PXRuleSet ruleSet : ruleSets) {
// grab the target type selector (the selector itself or a
// combinator's RHS)
PXTypeSelector selector = ruleSet.getTargetTypeSelector();
if (pseudoElement.equals(selector.getPseudoElement())) {
if (ruleSetsForPseudoElement == null) {
ruleSetsForPseudoElement = new ArrayList<PXRuleSet>();
}
ruleSetsForPseudoElement.add(ruleSet);
}
}
}
return ruleSetsForPseudoElement;
}
}