blob: 2c7af90017fd2c68a994db1c22505d422231a2f6 [file] [log] [blame]
/**********************************************************************
* Copyright (c) 2019 Ericsson
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License 2.0 which
* accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
**********************************************************************/
package org.eclipse.tracecompass.tmf.ui.model;
import java.util.Collections;
import java.util.Map;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.tracecompass.tmf.core.model.OutputElementStyle;
import org.eclipse.tracecompass.tmf.core.model.StyleProperties;
import org.eclipse.tracecompass.tmf.core.presentation.RGBAColor;
/**
* A manager for a map of output element styles. Styles have a key that is
* unique to this manager instance. Style inheritance is possible by having a
* style refer to a parent style key.
*
* @author Patrick Tasse
* @since 5.2
*/
public class StyleManager {
private final static StyleManager EMPTY = new StyleManager(Collections.emptyMap());
private final Map<String, OutputElementStyle> fStyleMap;
/**
* Constructor
*
* @param styleMap a style map
*/
public StyleManager(Map<String, OutputElementStyle> styleMap) {
fStyleMap = styleMap;
}
/**
* Get a manager that has an empty style map. It can be used to resolve
* element styles that have no parent key.
*
* @return an empty style manager
*/
public static StyleManager empty() {
return EMPTY;
}
/**
* Get the style property value for the specified element style. The style
* hierarchy is traversed until a value is found.
*
* @param elementStyle
* the style
* @param property
* the style property
* @return the style value, or null
*/
public @Nullable Object getStyle(OutputElementStyle elementStyle, String property) {
OutputElementStyle style = elementStyle;
while (style != null) {
Map<String, Object> styleValues = style.getStyleValues();
Object value = styleValues.get(property);
if (value != null) {
return value;
}
style = fStyleMap.get(style.getParentKey());
}
return null;
}
/**
* Get the style property factor value for the specified element style. The
* style hierarchy is traversed until a float value is found, and the
* returned float value will be multiplied by the first
* {@link StyleProperties#FACTOR} suffixed modifier style that was found
* along the way, if any.
*
* @param elementStyle
* the style
* @param property
* the style property
* @return the style float value, or null
*/
public @Nullable Float getFactorStyle(OutputElementStyle elementStyle, String property) {
Float factor = null;
OutputElementStyle style = elementStyle;
while (style != null) {
Map<String, Object> styleValues = style.getStyleValues();
if (factor == null) {
Object value = styleValues.get(property + StyleProperties.FACTOR);
if (value instanceof Float) {
factor = (Float) value;
}
}
Object value = styleValues.get(property);
if (value instanceof Float) {
return (factor == null) ? (Float) value : factor * (Float) value;
}
style = fStyleMap.get(style.getParentKey());
}
return null;
}
/**
* Get the style property color value for the specified element style. The
* style hierarchy is traversed until a color and opacity value is found,
* and the returned color value will be blended with the first
* {@link StyleProperties#BLEND} suffixed modifier style that was found
* along the way, if any.
*
* @param elementStyle
* the style
* @param property
* the style property
* @return the style value, or null
*/
public @Nullable RGBAColor getColorStyle(OutputElementStyle elementStyle, String property) {
String color = null;
Float opacity = null;
RGBAColor blend = null;
OutputElementStyle style = elementStyle;
while (style != null) {
Map<String, Object> styleValues = style.getStyleValues();
if (blend == null) {
Object value = styleValues.get(property + StyleProperties.BLEND);
if (value instanceof String) {
RGBAColor rgba = RGBAColor.fromString((String) value);
if (rgba != null) {
blend = rgba;
}
}
}
if (opacity == null) {
Object value = styleValues.get(StyleProperties.OPACITY);
if (value instanceof Float) {
opacity = (Float) value;
if (color != null) {
break;
}
}
}
if (color == null) {
Object value = styleValues.get(property);
if (value instanceof String) {
color = (String) value;
if (opacity != null) {
break;
}
}
}
style = fStyleMap.get(style.getParentKey());
}
int alpha = (opacity == null) ? 255 : (int) (opacity * 255);
RGBAColor rgba = (color == null) ? new RGBAColor(0, 0, 0, alpha) : RGBAColor.fromString(color + String.format("%02X", alpha)); //$NON-NLS-1$
return (rgba == null) ? null : (blend == null) ? rgba : blend(rgba, blend);
}
private static RGBAColor blend(RGBAColor rgba1, RGBAColor rgba2) {
/**
* If a color component 'c' with alpha 'a' is blended with color
* component 'd' with alpha 'b', the blended color and alpha are:
*
* <pre>
* color = (a*(1-b)*c + b*d) / (a + b - a*b)
* alpha = (a + b - a*b)
* </pre>
*/
float alpha1 = rgba1.getAlpha() / 255.0f;
float alpha2 = rgba2.getAlpha() / 255.0f;
float alpha = alpha1 + alpha2 - alpha1 * alpha2;
int r = blend(alpha1, rgba1.getRed(), alpha2, rgba2.getRed(), alpha);
int g = blend(alpha1, rgba1.getGreen(), alpha2, rgba2.getGreen(), alpha);
int b = blend(alpha1, rgba1.getBlue(), alpha2, rgba2.getBlue(), alpha);
return new RGBAColor(r, g, b, Math.round(alpha * 255.0f));
}
private static int blend(float alpha1, int color1, float alpha2, int color2, float alpha) {
return (int) ((alpha1 * (1.0f - alpha2) * color1 + alpha2 * color2) / alpha);
}
}