Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(text-minimessage): Correctly handle phase and multi-colour gradients #896

Merged
merged 2 commits into from
Apr 9, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.ArgumentQueue;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.util.ShadyPines;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;

/**
* A transformation that applies a colour gradient.
Expand All @@ -51,12 +51,11 @@ final class GradientTag extends AbstractColorChangingTag {
static final TagResolver RESOLVER = TagResolver.resolver(GRADIENT, GradientTag::create);

private int index = 0;
private int colorIndex = 0;

private float factorStep = 0;
private double multiplier = 1;

private final TextColor[] colors;
private float phase;
private final boolean negativePhase;
private @Range(from = -1, to = 1) double phase;
zml2008 marked this conversation as resolved.
Show resolved Hide resolved

static Tag create(final ArgumentQueue args, final Context ctx) {
float phase = 0;
Expand All @@ -81,7 +80,7 @@ static Tag create(final ArgumentQueue args, final Context ctx) {
textColors.add(parsedColor);
}

if (textColors.size() < 2) {
if (textColors.size() == 1) {
throw ctx.newException("Invalid gradient, not enough colors. Gradients must have at least two colors.", args);
}
} else {
Expand All @@ -92,57 +91,45 @@ static Tag create(final ArgumentQueue args, final Context ctx) {
}

private GradientTag(final float phase, final List<TextColor> colors) {
if (phase < 0) {
this.negativePhase = true;
this.phase = 1 + phase;
Collections.reverse(colors);
} else {
this.negativePhase = false;
this.phase = phase;
}

if (colors.isEmpty()) {
this.colors = new TextColor[]{TextColor.color(0xffffff), TextColor.color(0x000000)};
} else {
this.colors = colors.toArray(new TextColor[0]);
}

if (phase < 0) {
this.phase = 1 + phase; // [-1, 0) -> [0, 1)
Collections.reverse(Arrays.asList(this.colors));
} else {
this.phase = phase;
}
}

@Override
protected void init() {
int sectorLength = this.size() / (this.colors.length - 1);
if (sectorLength < 1) {
sectorLength = 1;
}
this.factorStep = 1.0f / (sectorLength + this.index);
this.phase = this.phase * sectorLength;
// Set a scaling factor for character indices, so that the colours in a gradient are evenly spread across the original text
// make it so the max character index maps to the maximum colour
this.multiplier = this.size() == 1 ? 0 : (double) (this.colors.length - 1) / (this.size() - 1);
this.phase *= this.colors.length - 1;
this.index = 0;
}

@Override
protected void advanceColor() {
// color switch needed?
this.index++;
if (this.factorStep * this.index > 1) {
this.colorIndex++;
this.index = 0;
}
}

@Override
protected TextColor color() {
float factor = this.factorStep * (this.index + this.phase);
// loop around if needed
if (factor > 1) {
factor = 1 - (factor - 1);
}
// from [0, this.colors.length - 1], select the position in the gradient
// we will wrap around in order to preserve an even cycle as would be seen with non-zero phases
final double position = ((this.index * this.multiplier) + this.phase);
final int lowUnclamped = (int) Math.floor(position);

if (this.negativePhase && this.colors.length % 2 != 0) {
// flip the gradient segment for to allow for looping phase -1 through 1
return TextColor.lerp(factor, this.colors[this.colorIndex + 1], this.colors[this.colorIndex]);
} else {
return TextColor.lerp(factor, this.colors[this.colorIndex], this.colors[this.colorIndex + 1]);
}
final int high = (int) Math.ceil(position) % this.colors.length;
final int low = lowUnclamped % this.colors.length;

return TextColor.lerp((float) position - lowUnclamped, this.colors[low], this.colors[high]);
}

@Override
Expand All @@ -159,14 +146,13 @@ public boolean equals(final @Nullable Object other) {
if (other == null || this.getClass() != other.getClass()) return false;
final GradientTag that = (GradientTag) other;
return this.index == that.index
&& this.colorIndex == that.colorIndex
&& ShadyPines.equals(that.factorStep, this.factorStep)
&& this.phase == that.phase && Arrays.equals(this.colors, that.colors);
&& this.phase == that.phase
&& Arrays.equals(this.colors, that.colors);
}

@Override
public int hashCode() {
int result = Objects.hash(this.index, this.colorIndex, this.factorStep, this.phase);
int result = Objects.hash(this.index, this.phase);
result = 31 * result + Arrays.hashCode(this.colors);
return result;
}
Expand Down
Loading