Add battery bar colour expressions

This commit is contained in:
ajp_anton
2026-06-18 14:56:01 +00:00
parent 7144b130f7
commit e1dfe637f8
6 changed files with 329 additions and 361 deletions
@@ -7,40 +7,66 @@ import java.util.Locale;
import se.ajpanton.statusbartweak.shell.settings.BatteryBarStyle;
final class ClockColorResolver {
public final class ClockColorResolver {
private static final int CHANNEL_ALPHA = 0;
private static final int CHANNEL_RED = 1;
private static final int CHANNEL_GREEN = 2;
private static final int CHANNEL_BLUE = 3;
private static final int CHANNEL_COUNT = 4;
String resolveColorHex(
public String resolveColorHex(
Context context,
String expression,
int referenceColor,
int fallbackColor
) {
int resolved = resolveColor(context, expression, referenceColor, fallbackColor);
return colorHex(resolved);
}
public String resolveColorHex(
Context context,
String expression,
int referenceColor,
int fallbackColor,
Options options
) {
int resolved = resolveColor(context, expression, referenceColor, fallbackColor, options);
return colorHex(resolved);
}
private String colorHex(int resolved) {
if (alpha(resolved) >= 255) {
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
}
return String.format(Locale.ROOT, "#%08X", resolved);
}
int resolveColor(
public int resolveColor(
Context context,
String expression,
int referenceColor,
int fallbackColor
) {
return resolveColor(context, expression, referenceColor, fallbackColor, Options.clock());
}
public int resolveColor(
Context context,
String expression,
int referenceColor,
int fallbackColor,
Options options
) {
if (isEmpty(expression)) {
return fallbackColor;
}
Options resolvedOptions = options != null ? options : Options.clock();
VariantExpressions variants = splitVariants(expression);
Value bright = evaluateExpression(context, variants.brightExpression, null, fallbackColor);
Value bright = evaluateExpression(context, variants.brightExpression, null, fallbackColor, resolvedOptions);
Value dark = variants.darkExpression.isEmpty()
? bright
: evaluateExpression(context, variants.darkExpression, bright, fallbackColor);
: evaluateExpression(context, variants.darkExpression, bright, fallbackColor, resolvedOptions);
return toColor(isDark(referenceColor) ? dark : bright);
}
@@ -48,7 +74,8 @@ final class ClockColorResolver {
Context context,
String expression,
Value inherited,
int fallbackColor
int fallbackColor,
Options options
) {
ArrayList<String> tokens = tokenize(expression);
ArrayList<Value> stack = new ArrayList<>();
@@ -56,7 +83,7 @@ final class ClockColorResolver {
stack.add(inherited.copy());
}
for (String token : tokens) {
applyToken(context, stack, token, fallbackColor);
applyToken(context, stack, token, fallbackColor, options);
}
if (stack.isEmpty()) {
throw new IllegalArgumentException("Empty colour expression");
@@ -64,7 +91,13 @@ final class ClockColorResolver {
return top(stack).copy();
}
private void applyToken(Context context, ArrayList<Value> stack, String token, int fallbackColor) {
private void applyToken(
Context context,
ArrayList<Value> stack,
String token,
int fallbackColor,
Options options
) {
if (isEmpty(token)) {
return;
}
@@ -73,7 +106,7 @@ final class ClockColorResolver {
applyOperation(stack, operation);
return;
}
stack.add(parseOperand(context, token, fallbackColor));
stack.add(parseOperand(context, token, fallbackColor, options));
}
private Operation parseOperation(String token) {
@@ -244,12 +277,15 @@ final class ClockColorResolver {
stack.add(Value.vector(result));
}
private Value parseOperand(Context context, String token, int fallbackColor) {
private Value parseOperand(Context context, String token, int fallbackColor, Options options) {
String value = token != null ? token.trim() : "";
if (value.isEmpty()) {
throw new IllegalArgumentException("Empty colour token");
}
if ("#batterybar".equalsIgnoreCase(value)) {
if (options == null || !options.allowBatteryBarOperand) {
throw new IllegalArgumentException("#batterybar is not supported here");
}
return Value.vector(vectorForColor(BatteryBarStyle.resolveCurrentColor(context, fallbackColor, fallbackColor)));
}
if (value.charAt(0) == '#') {
@@ -258,6 +294,9 @@ final class ClockColorResolver {
if (value.charAt(0) == '[') {
return Value.vector(parseVector(value));
}
if (options != null && options.allowBareHexOperand && looksLikeBareHex(value)) {
return Value.vector(vectorForColor(parseHexColor("#" + value)));
}
try {
return Value.number(Double.parseDouble(value));
} catch (NumberFormatException e) {
@@ -265,6 +304,22 @@ final class ClockColorResolver {
}
}
private boolean looksLikeBareHex(String value) {
int length = value != null ? value.length() : 0;
if (length != 3 && length != 4 && length != 6 && length != 8) {
return false;
}
boolean hasHexLetter = false;
for (int i = 0; i < length; i++) {
char c = value.charAt(i);
if (!isHexDigit(c)) {
return false;
}
hasHexLetter |= (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}
return length != 3 || hasHexLetter;
}
private double[] parseVector(String token) {
String value = token != null ? token.trim() : "";
if (!value.startsWith("[") || !value.endsWith("]")) {
@@ -608,4 +663,22 @@ final class ClockColorResolver {
return Math.max(0d, Math.min(255d, value));
}
}
public static final class Options {
private final boolean allowBatteryBarOperand;
private final boolean allowBareHexOperand;
private Options(boolean allowBatteryBarOperand, boolean allowBareHexOperand) {
this.allowBatteryBarOperand = allowBatteryBarOperand;
this.allowBareHexOperand = allowBareHexOperand;
}
public static Options clock() {
return new Options(true, false);
}
public static Options batteryBar() {
return new Options(false, true);
}
}
}
@@ -1,394 +1,105 @@
package se.ajpanton.statusbartweak.shell.settings;
import android.graphics.Color;
import android.text.TextUtils;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import se.ajpanton.statusbartweak.runtime.features.clock.ClockColorResolver;
public final class ColourExpressionSupport {
private static final Pattern OPERATION_PATTERN =
Pattern.compile("([+\\-*/])\\s*(" + hexOperandPattern() + "|\\d+(?:\\.\\d+)?|\\.\\d+)");
private static final Pattern LEADING_HEX_PATTERN =
Pattern.compile("(?i)^#?(?:[0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{4}|[0-9a-f]{3})");
private static final ClockColorResolver RESOLVER = new ClockColorResolver();
private static final ClockColorResolver.Options OPTIONS = ClockColorResolver.Options.batteryBar();
private ColourExpressionSupport() {
}
public static String normaliseExpression(String raw, boolean assumeHashPrefix) {
if (raw == null) {
return "";
}
String value = raw.trim();
String value = normaliseSeparators(raw);
if (value.isEmpty()) {
return "";
}
int variantSplit = findVariantSplit(value);
String left = value;
String right = null;
if (variantSplit >= 0) {
left = value.substring(0, variantSplit).trim();
right = value.substring(variantSplit + 1).trim();
}
if (!isValidSegment(left, assumeHashPrefix, false, Color.WHITE)) {
try {
RESOLVER.resolveColor(null, value, Color.WHITE, Color.WHITE, OPTIONS);
RESOLVER.resolveColor(null, value, Color.BLACK, Color.WHITE, OPTIONS);
return value;
} catch (IllegalArgumentException ignored) {
return "";
}
if (right != null && !isValidSegment(right, assumeHashPrefix, true, Color.WHITE)) {
return "";
}
if (right == null) {
return left;
}
return left + "|" + right;
}
public static int resolveColor(String raw,
int referenceColor,
int fallbackColor,
boolean assumeHashPrefix) {
String value = normaliseExpression(raw, assumeHashPrefix);
public static int resolveColor(
String raw,
int referenceColor,
int fallbackColor,
boolean assumeHashPrefix
) {
String value = normaliseSeparators(raw);
if (value.isEmpty()) {
return fallbackColor;
}
int variantSplit = findVariantSplit(value);
if (variantSplit < 0) {
return resolveSegment(value, fallbackColor, assumeHashPrefix, false);
}
String left = value.substring(0, variantSplit).trim();
String right = value.substring(variantSplit + 1).trim();
int brightVariant = resolveSegment(left, fallbackColor, assumeHashPrefix, false);
if (!isDark(referenceColor)) {
return brightVariant;
}
return resolveSegment(right, brightVariant, assumeHashPrefix, true);
}
public static String resolveColorHex(String raw,
int referenceColor,
int fallbackColor,
boolean assumeHashPrefix) {
int resolved = resolveColor(raw, referenceColor, fallbackColor, assumeHashPrefix);
int alpha = Color.alpha(resolved);
if (alpha >= 255) {
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
}
return String.format(Locale.ROOT, "#%08X", resolved);
}
private static boolean isValidSegment(String segment,
boolean assumeHashPrefix,
boolean allowInheritedBase,
int baseColor) {
if (TextUtils.isEmpty(segment)) {
return false;
}
try {
resolveSegment(segment, baseColor, assumeHashPrefix, allowInheritedBase);
return true;
return RESOLVER.resolveColor(null, value, referenceColor, fallbackColor, OPTIONS);
} catch (IllegalArgumentException ignored) {
return false;
return fallbackColor;
}
}
private static int resolveSegment(String segment,
int baseColor,
boolean assumeHashPrefix,
boolean allowInheritedBase) {
String value = segment != null ? segment.trim() : "";
public static String resolveColorHex(
String raw,
int referenceColor,
int fallbackColor,
boolean assumeHashPrefix
) {
String value = normaliseSeparators(raw);
if (value.isEmpty()) {
throw new IllegalArgumentException("Empty colour segment");
}
int index = 0;
boolean invert = false;
if (value.regionMatches(true, 0, "inv", 0, 3)) {
invert = true;
index = 3;
}
double[] channels;
double alpha;
String remainder;
if (startsWithOperator(value, index)) {
if (!allowInheritedBase) {
throw new IllegalArgumentException("Missing starting colour");
}
channels = channelsFor(baseColor);
alpha = Color.alpha(baseColor);
remainder = value.substring(index).trim();
} else {
LeadingColour leading = parseLeadingColour(value, index, assumeHashPrefix);
channels = channelsFor(leading.color);
alpha = Color.alpha(leading.color);
remainder = value.substring(leading.endIndex).trim();
}
if (invert) {
invertChannels(channels);
}
alpha = applyOperations(channels, alpha, remainder);
return colorFromChannels(channels, alpha);
}
private static LeadingColour parseLeadingColour(String value,
int start,
boolean assumeHashPrefix) {
String remainder = value.substring(start).trim();
Matcher matcher = LEADING_HEX_PATTERN.matcher(remainder);
if (!matcher.find()) {
throw new IllegalArgumentException("Missing starting colour");
}
String token = matcher.group();
if (assumeHashPrefix && token.charAt(0) != '#') {
token = "#" + token;
}
int color = parseHexColor(token, assumeHashPrefix);
return new LeadingColour(color, start + matcher.end());
}
private static double applyOperations(double[] channels, double alpha, String remainder) {
int index = 0;
while (index < remainder.length()) {
while (index < remainder.length() && Character.isWhitespace(remainder.charAt(index))) {
index++;
}
if (index >= remainder.length()) {
break;
}
Matcher matcher = OPERATION_PATTERN.matcher(remainder);
matcher.region(index, remainder.length());
if (!matcher.lookingAt()) {
throw new IllegalArgumentException("Invalid colour operation");
}
char operator = matcher.group(1).charAt(0);
String operandText = matcher.group(2);
alpha = applyOperation(channels, alpha, operator, operandText);
index = matcher.end();
}
return alpha;
}
private static double applyOperation(double[] channels, double alpha, char operator, String operandText) {
Operand operand = parseOperand(operandText);
for (int i = 0; i < 3; i++) {
double value = channels[i];
double rhs = operand.rgb[i];
if (operand.isHex && (operator == '*' || operator == '/')) {
rhs = rhs / 255d;
}
switch (operator) {
case '+':
channels[i] = value + rhs;
break;
case '-':
channels[i] = value - rhs;
break;
case '*':
channels[i] = value * rhs;
break;
case '/':
channels[i] = rhs == 0d ? value : value / rhs;
break;
default:
throw new IllegalArgumentException("Unknown operator");
}
}
if (operand.hasAlpha) {
double rhs = operand.alpha;
if (operand.isHex && (operator == '*' || operator == '/')) {
rhs = rhs / 255d;
}
switch (operator) {
case '+':
alpha = alpha + rhs;
break;
case '-':
alpha = alpha - rhs;
break;
case '*':
alpha = alpha * rhs;
break;
case '/':
alpha = rhs == 0d ? alpha : alpha / rhs;
break;
default:
throw new IllegalArgumentException("Unknown operator");
}
}
return alpha;
}
private static Operand parseOperand(String operandText) {
String value = operandText != null ? operandText.trim() : "";
if (value.isEmpty()) {
throw new IllegalArgumentException("Missing operand");
}
if (value.charAt(0) == '#') {
return parseHexOperand(value);
}
double scalar;
try {
scalar = Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid operand", e);
}
return new Operand(new double[] { scalar, scalar, scalar }, 0d, false, false);
}
private static int parseHexColor(String raw, boolean assumeHashPrefix) {
String value = raw != null ? raw.trim() : "";
if (value.isEmpty()) {
throw new IllegalArgumentException("Empty colour");
}
if (assumeHashPrefix && value.charAt(0) != '#') {
value = "#" + value;
}
if (value.charAt(0) != '#') {
throw new IllegalArgumentException("Expected colour literal");
}
String hex = value.substring(1);
if (hex.length() == 3) {
hex = expandShortHex(hex);
} else if (hex.length() == 4) {
hex = expandShortHex(hex);
} else if (hex.length() != 6 && hex.length() != 8) {
throw new IllegalArgumentException("Unsupported colour length");
return hexFor(fallbackColor);
}
try {
return Color.parseColor("#" + hex);
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException("Invalid colour", e);
return RESOLVER.resolveColorHex(null, value, referenceColor, fallbackColor, OPTIONS);
} catch (IllegalArgumentException ignored) {
return hexFor(fallbackColor);
}
}
private static String expandShortHex(String hex) {
StringBuilder out = new StringBuilder(hex.length() * 2);
for (int i = 0; i < hex.length(); i++) {
char c = hex.charAt(i);
out.append(c).append(c);
}
return out.toString();
}
private static double[] channelsFor(int color) {
return new double[] {
Color.red(color),
Color.green(color),
Color.blue(color)
};
}
private static void invertChannels(double[] channels) {
for (int i = 0; i < 3; i++) {
channels[i] = 255d - channels[i];
}
}
private static int colorFromChannels(double[] channels, double alpha) {
int red = clampChannel(channels[0]);
int green = clampChannel(channels[1]);
int blue = clampChannel(channels[2]);
return Color.argb(clampChannel(alpha), red, green, blue);
}
private static Operand parseHexOperand(String raw) {
private static String normaliseSeparators(String raw) {
String value = raw != null ? raw.trim() : "";
if (value.isEmpty() || value.charAt(0) != '#') {
throw new IllegalArgumentException("Expected colour literal");
if (value.isEmpty()) {
return "";
}
String hex = value.substring(1);
if (hex.length() == 3 || hex.length() == 4) {
hex = expandShortHex(hex);
} else if (hex.length() != 6 && hex.length() != 8) {
throw new IllegalArgumentException("Unsupported colour length");
int split = findTopLevelPipe(value);
if (split < 0) {
return value;
}
boolean hasAlpha = hex.length() == 8;
int offset = hasAlpha ? 2 : 0;
double alpha = 0d;
if (hasAlpha) {
alpha = parseHexByte(hex.substring(0, 2));
}
double[] rgb = new double[] {
parseHexByte(hex.substring(offset, offset + 2)),
parseHexByte(hex.substring(offset + 2, offset + 4)),
parseHexByte(hex.substring(offset + 4, offset + 6))
};
return new Operand(rgb, alpha, hasAlpha, true);
return value.substring(0, split).trim() + " ; " + value.substring(split + 1).trim();
}
private static double parseHexByte(String value) {
return Integer.parseInt(value, 16);
}
private static int clampChannel(double value) {
if (Double.isNaN(value) || Double.isInfinite(value)) {
return 0;
}
if (value <= 0d) {
return 0;
}
if (value >= 255d) {
return 255;
}
return (int) Math.round(value);
}
private static boolean startsWithOperator(String value, int start) {
if (start < 0 || start >= value.length()) {
return false;
}
char c = value.charAt(start);
return c == '+' || c == '-' || c == '*' || c == '/';
}
private static int findVariantSplit(String value) {
if (TextUtils.isEmpty(value)) {
return -1;
}
boolean quoted = false;
private static int findTopLevelPipe(String value) {
boolean inVector = false;
int split = -1;
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '\'') {
quoted = !quoted;
if (inVector) {
if (c == ']') {
inVector = false;
}
continue;
}
if (!quoted && c == '|') {
return i;
if (c == '[') {
inVector = true;
continue;
}
if (c == '|') {
if (split >= 0) {
return -1;
}
split = i;
}
}
return -1;
return split;
}
private static boolean isDark(int color) {
double brightness = (Color.red(color) * 299d
+ Color.green(color) * 587d
+ Color.blue(color) * 114d) / 1000d;
return brightness < 128d;
}
private static String hexOperandPattern() {
return "#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{4}|[0-9a-fA-F]{3})";
}
private static final class LeadingColour {
final int color;
final int endIndex;
LeadingColour(int color, int endIndex) {
this.color = color;
this.endIndex = endIndex;
}
}
private static final class Operand {
final double[] rgb;
final double alpha;
final boolean hasAlpha;
final boolean isHex;
Operand(double[] rgb, double alpha, boolean hasAlpha, boolean isHex) {
this.rgb = rgb;
this.alpha = alpha;
this.hasAlpha = hasAlpha;
this.isHex = isHex;
private static String hexFor(int color) {
if (Color.alpha(color) >= 255) {
return String.format(java.util.Locale.ROOT, "#%06X", color & 0xFFFFFF);
}
return String.format(java.util.Locale.ROOT, "#%08X", color);
}
}
@@ -3,7 +3,11 @@ package se.ajpanton.statusbartweak.shell.ui;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.UnderlineSpan;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
@@ -32,6 +36,7 @@ import com.google.android.material.switchmaterial.SwitchMaterial;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import se.ajpanton.statusbartweak.R;
import se.ajpanton.statusbartweak.shell.settings.BatteryBarGeometry;
@@ -80,6 +85,8 @@ public final class BatteryBarFragment extends Fragment {
TextView maxLevelLabel = root.findViewById(R.id.battery_bar_max_level_label);
MaterialButton defaultDischargeButton = root.findViewById(R.id.battery_bar_default_discharge_button);
MaterialButton defaultChargeButton = root.findViewById(R.id.battery_bar_default_charge_button);
TextView colourHelpToggle = root.findViewById(R.id.battery_bar_colour_help_toggle);
TextView colourHelp = root.findViewById(R.id.battery_bar_colour_help);
MaterialButton addThresholdButton = root.findViewById(R.id.battery_bar_add_threshold_button);
LinearLayout thresholdsContainer = root.findViewById(R.id.battery_bar_thresholds_container);
View geometrySection = root.findViewById(R.id.battery_bar_geometry_section);
@@ -145,6 +152,11 @@ public final class BatteryBarFragment extends Fragment {
SbtSettings.KEY_BATTERY_BAR_DEFAULT_CHARGE_COLOR,
SbtDefaults.BATTERY_BAR_DEFAULT_CHARGE_COLOR_DEFAULT),
R.string.battery_bar_colour_same_as_default);
bindExpandableHelp(
colourHelpToggle,
colourHelp,
R.string.battery_bar_colour_help_show,
R.string.battery_bar_colour_help_hide);
enabled.setOnCheckedChangeListener((buttonView, isChecked) -> {
prefs.edit().putBoolean(SbtSettings.KEY_BATTERY_BAR_ENABLED, isChecked).apply();
@@ -270,15 +282,129 @@ public final class BatteryBarFragment extends Fragment {
return;
}
String normalised = BatteryBarStyle.normalizeColor(colour);
String value;
CharSequence value;
int backgroundColor = Color.TRANSPARENT;
boolean hasBackground = false;
if (BatteryBarStyle.isThresholdChargeDefault(colour)) {
value = getString(R.string.battery_bar_colour_same_as_default);
} else if (normalised.isEmpty()) {
value = getString(emptyLabelRes);
} else {
value = normalised;
value = displayColourValue(normalised);
backgroundColor = resolvePreviewColour(normalised);
hasBackground = true;
}
button.setText(getString(labelRes) + ": " + value);
SpannableStringBuilder label = new SpannableStringBuilder();
label.append(compactColourLabel(labelRes));
label.append(":\n");
label.append(value);
button.setText(label);
if (!hasBackground) {
button.setBackgroundTintList(null);
button.setTextColor(ContextCompat.getColor(requireContext(), R.color.sbt_on_surface));
} else {
button.setBackgroundTintList(ColorStateList.valueOf(backgroundColor));
button.setTextColor(contrastTextColor(backgroundColor));
}
}
private String compactColourLabel(int labelRes) {
String label = getString(labelRes).toLowerCase(Locale.ROOT);
if (label.contains("discharge")) {
return getString(R.string.battery_bar_button_label_discharge);
}
return getString(R.string.battery_bar_button_label_charge);
}
private CharSequence displayColourValue(String expression) {
String normalised = BatteryBarStyle.normalizeColor(expression);
if (normalised.isEmpty()) {
return "";
}
SpannableStringBuilder out = new SpannableStringBuilder();
appendDisplayHex(out, resolveExpressionHex(normalised, Color.WHITE));
if (hasTintVariant(normalised)) {
out.append(" ; ");
appendDisplayHex(out, resolveExpressionHex(normalised, Color.BLACK));
}
return out;
}
private void appendDisplayHex(SpannableStringBuilder out, String hex) {
int start = out.length();
out.append(hex);
if (hex != null && hex.length() == 9) {
out.setSpan(new UnderlineSpan(), start + 1, start + 3, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
private String resolveExpressionHex(String expression, int referenceColor) {
int fallback = Color.WHITE;
int resolved = se.ajpanton.statusbartweak.shell.settings.ColourExpressionSupport.resolveColor(
expression,
referenceColor,
fallback,
true);
if (Color.alpha(resolved) >= 255) {
return String.format(Locale.ROOT, "#%06X", resolved & 0xFFFFFF);
}
return String.format(Locale.ROOT, "#%08X", resolved);
}
private boolean hasTintVariant(String expression) {
if (expression == null) {
return false;
}
boolean inVector = false;
for (int i = 0; i < expression.length(); i++) {
char c = expression.charAt(i);
if (inVector) {
if (c == ']') {
inVector = false;
}
continue;
}
if (c == '[') {
inVector = true;
continue;
}
if (c == ';' || c == '|') {
return true;
}
}
return false;
}
private int resolvePreviewColour(String expression) {
return se.ajpanton.statusbartweak.shell.settings.ColourExpressionSupport.resolveColor(
expression,
ContextCompat.getColor(requireContext(), R.color.sbt_on_surface),
Color.WHITE,
true);
}
private int contrastTextColor(int backgroundColor) {
int alpha = Color.alpha(backgroundColor);
int red = compositeChannel(Color.red(backgroundColor), alpha, 255);
int green = compositeChannel(Color.green(backgroundColor), alpha, 255);
int blue = compositeChannel(Color.blue(backgroundColor), alpha, 255);
double luminance = (0.299d * red + 0.587d * green + 0.114d * blue) / 255d;
return luminance > 0.55d ? Color.BLACK : Color.WHITE;
}
private int compositeChannel(int source, int alpha, int destination) {
return Math.round((source * alpha + destination * (255 - alpha)) / 255f);
}
private void bindExpandableHelp(TextView toggle, TextView content, int showRes, int hideRes) {
if (toggle == null || content == null) {
return;
}
toggle.setOnClickListener(v -> {
boolean show = content.getVisibility() != View.VISIBLE;
content.setVisibility(show ? View.VISIBLE : View.GONE);
toggle.setText(show ? hideRes : showRes);
});
}
private void trimMappingStepButton(MaterialButton button) {
@@ -570,6 +570,25 @@
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/battery_bar_default_charge_colour" />
<TextView
android:id="@+id/battery_bar_colour_help_toggle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:text="@string/battery_bar_colour_help_show"
android:textAppearance="?attr/textAppearanceBody1"
android:textColor="@color/sbt_accent_dark"
android:textStyle="bold" />
<TextView
android:id="@+id/battery_bar_colour_help"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:visibility="gone"
android:text="@string/battery_bar_colour_help_text"
android:textAppearance="?attr/textAppearanceBody2" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
+7 -2
View File
@@ -165,11 +165,13 @@
<string name="battery_bar_colours_title">Default colours</string>
<string name="battery_bar_default_discharge_colour">Discharge colour</string>
<string name="battery_bar_default_charge_colour">Charge colour</string>
<string name="battery_bar_button_label_discharge">Discharge</string>
<string name="battery_bar_button_label_charge">Charge</string>
<string name="battery_bar_thresholds_title">Threshold colours</string>
<string name="battery_bar_thresholds_hint">Below a threshold, the first matching colour overrides the default.</string>
<string name="battery_bar_add_threshold">Add threshold</string>
<string name="battery_bar_colour_dialog_title">Set colour</string>
<string name="battery_bar_colour_dialog_hint">Examples: FA0, 80FA, or FFAA00</string>
<string name="battery_bar_colour_dialog_hint">Examples: #FA0, FA0, or #FA0 ; invRGB 0.5 *RGB</string>
<string name="battery_bar_threshold_percent_title">Threshold percent</string>
<string name="battery_bar_threshold_percent_hint">Applies when battery is at or below this percent.</string>
<string name="battery_bar_threshold_change_percent">Change threshold %</string>
@@ -179,7 +181,10 @@
<string name="battery_bar_threshold_remove">Remove threshold</string>
<string name="battery_bar_colour_auto">Auto</string>
<string name="battery_bar_colour_same_as_discharge">Same as discharge</string>
<string name="battery_bar_colour_same_as_default">Same as default</string>
<string name="battery_bar_colour_same_as_default">Default</string>
<string name="battery_bar_colour_help_show">Show colour syntax</string>
<string name="battery_bar_colour_help_hide">Hide colour syntax</string>
<string name="battery_bar_colour_help_text">Colour values:\n#FA0, #FFAA00, #80FFAA00, FA0, or [255, 255, 170, 0]\n\nPostfix math:\n#FA0 1.2 *RGB clip\n#FA0 invRGB\n\nOperators:\n+ - * / ^ inv min max sqrt clip dup\n\nChannels:\nAdd A, R, G, or B to limit vector operations.\nExamples: invRGB, *A, +RB\n\nTint variants:\nUse ; for a dark-tint expression.\nThe dark expression starts with the result from the bright expression.</string>
<string name="battery_bar_threshold_duplicate">A threshold at that percentage already exists.</string>
<string name="battery_bar_threshold_reordered">That threshold moved to a new position in the list.</string>
<string name="battery_bar_threshold_charge_note">Leave the field empty and press OK for \"Same as discharge\", or use the button below for \"Same as default\".</string>