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

adds progress and repeat tags #768

Open
wants to merge 6 commits into
base: main/4
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
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 @@ -30,7 +30,9 @@
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Locale;
import java.util.function.Supplier;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.standard.ProgressTag;
import org.jetbrains.annotations.NotNull;

/**
Expand Down Expand Up @@ -102,7 +104,7 @@ private Formatter() {
/**
* Creates a replacement that inserts a choice formatted text. The component will be formatted by the provided ChoiceFormat.
*
* <p>This tag expectes a format as attribute. Refer to {@link ChoiceFormat} for usable patterns.</p>
* <p>This tag expects a format as attribute. Refer to {@link ChoiceFormat} for usable patterns.</p>
*
* <p>This replacement is auto-closing, so its style will not influence the style of following components.</p>
*
Expand All @@ -118,4 +120,22 @@ private Formatter() {
return Tag.inserting(context.deserialize(choiceFormat.format(number)));
});
}

/**
* Creates a replacement that inserts a progress bar. The component will be formatted by the provided fill and remaining colors and length
*
* <p>This tag expects a text to use to denote progress and can accept length, filled color and empty color. The text has to be provided as first argument.
* In case the fill and empty colors and length are not provided, they will default to {@link ProgressTag#DEFAULT_FILLED_COLOR}, {@link ProgressTag#DEFAULT_REMAINING_COLOR} and {@value ProgressTag#DEFAULT_LENGTH} respectively.</p>
*
* <p>This replacement is auto-closing, so its style will not influence the style of following components.</p>
*
* @param key the key
* @param progressSupplier the supplier for the progress value
* @return the placeholder
* @since 4.13.0
*/
public static @NotNull TagResolver progress(final @NotNull String key, final @NotNull Supplier<Double> progressSupplier) {
return TagResolver.resolver(key, (args, ctx) -> ProgressTag.createProgressProvided(args, ctx, progressSupplier));
}

AV3RG marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2022 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.text.minimessage.tag.standard;

import java.util.Objects;
import java.util.OptionalDouble;
import java.util.OptionalInt;
import java.util.function.Supplier;
import java.util.stream.Stream;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.ParsingException;
import net.kyori.adventure.text.minimessage.tag.Inserting;
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.examination.Examinable;
import net.kyori.examination.ExaminableProperty;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
* Progress tag.
*
* @since 4.13.0
*/
@ApiStatus.Internal
public final class ProgressTag implements Inserting, Examinable {
AV3RG marked this conversation as resolved.
Show resolved Hide resolved

AV3RG marked this conversation as resolved.
Show resolved Hide resolved
private static final String PROGRESS = "progress";
private static final int DEFAULT_LENGTH = 10;
private static final NamedTextColor DEFAULT_FILLED_COLOR = NamedTextColor.GREEN;
private static final NamedTextColor DEFAULT_REMAINING_COLOR = NamedTextColor.RED;
static final TagResolver RESOLVER = TagResolver.resolver(PROGRESS, ProgressTag::create);
private final String text;
private final int totalLength;
private final TextColor fillColor;
private final TextColor remainingColor;

private final Supplier<Double> progressSupplier;

private ProgressTag(final String text, final int totalLength, final TextColor fillColor, final TextColor remainingColor, final Supplier<Double> progressSupplier) {
this.text = text;
this.totalLength = totalLength;
this.fillColor = fillColor;
this.remainingColor = remainingColor;
this.progressSupplier = progressSupplier;
}

static Tag create(final ArgumentQueue args, final Context ctx) throws ParsingException {

AV3RG marked this conversation as resolved.
Show resolved Hide resolved
final OptionalDouble optionalProgress = args.popOr("Invalid progress! Progress must be a double in the range [0d, 1.0d] (inclusive).").asDouble();
if (!optionalProgress.isPresent()) {
throw ctx.newException("Progress must be a double in the range [0d, 1.0d] (inclusive).");
}
final double progress = optionalProgress.getAsDouble();
if (progress < 0 || progress > 1) {
throw ctx.newException("Progress is out of range (%s). Must be in the range [0d, 1.0d] (inclusive).");
}
final Supplier<Double> progressSupplier = () -> progress;

return createProgressProvided(args, ctx, progressSupplier);
}

/**
* Creates a progress tag that uses progress from the supplier to calculate the progress when the value is requested.
*
* @param args the arguments
* @param ctx the context
* @param progressSupplier the progress supplier
*
* @return progress tag
* @throws ParsingException if length is not an integer greater than or equal to 1 or if the colors are invalid
* @since 4.13.0
*/
@ApiStatus.Internal
public static Tag createProgressProvided(final ArgumentQueue args, final Context ctx, final Supplier<Double> progressSupplier) throws ParsingException {
final String text = args.popOr("No text found to use for progress bar.").value();

final int length;
if (args.hasNext()) {
final OptionalInt optionalLength = args.pop().asInt();
if (!optionalLength.isPresent()) {
throw ctx.newException("Length must be an integer.");
}
length = optionalLength.getAsInt();
if (length < 1) {
throw ctx.newException("Progress bar length must be at least 1.");
}
} else {
length = DEFAULT_LENGTH;
}

final TextColor fillColor;
if (args.hasNext()) {
fillColor = ColorTagResolver.resolveColor(args.pop().value(), ctx);
} else {
fillColor = DEFAULT_FILLED_COLOR;
}

final TextColor remainingColor;
if (args.hasNext()) {
remainingColor = ColorTagResolver.resolveColor(args.pop().value(), ctx);
} else {
remainingColor = DEFAULT_REMAINING_COLOR;
}
return new ProgressTag(text, length, fillColor, remainingColor, progressSupplier);
}

@Override
public @NotNull Component value() {
final Component filledComponent = Component.text(this.text, this.fillColor);
final Component remainingComponent = Component.text(this.text, this.remainingColor);
final TextComponent.Builder barBuilder = Component.text();
final double progress = this.progressSupplier.get();
if (progress < 0 || progress > 1) {
throw new IllegalStateException("Progress must be a double in the range [0d, 1.0d] (inclusive).");
}
final int fillLength = (int) (this.totalLength * progress);
for (int i = 0; i < fillLength; i++) {
barBuilder.append(filledComponent);
}
for (int i = 0; i < this.totalLength - fillLength; i++) {
barBuilder.append(remainingComponent);
}
return barBuilder.build();
}

@Override
public boolean allowsChildren() {
return false;
}

@Override
public @NotNull Stream<? extends ExaminableProperty> examinableProperties() {
return Stream.of(
ExaminableProperty.of("text", this.text),
ExaminableProperty.of("totalLength", this.totalLength),
ExaminableProperty.of("fillColor", this.fillColor),
ExaminableProperty.of("remainingColor", this.remainingColor),
ExaminableProperty.of("progressSupplier", this.progressSupplier)
);
}

@Override
public boolean equals(final @Nullable Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
final ProgressTag that = (ProgressTag) other;
return this.text.equals(that.text)
&& this.totalLength == that.totalLength
&& this.fillColor.equals(that.fillColor)
&& this.remainingColor.equals(that.remainingColor)
&& this.progressSupplier.equals(that.progressSupplier);
}

@Override
public int hashCode() {
return Objects.hash(this.text, this.totalLength, this.fillColor, this.remainingColor, this.progressSupplier);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2022 KyoriPowered
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.kyori.adventure.text.minimessage.tag.standard;

import java.util.OptionalInt;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.Context;
import net.kyori.adventure.text.minimessage.ParsingException;
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;

/**
* Repeat tag.
*
* @since 4.11.0
*/
final class RepeatTag {

AV3RG marked this conversation as resolved.
Show resolved Hide resolved
private static final String REPEAT = "repeat";

static final TagResolver RESOLVER = TagResolver.resolver(REPEAT, RepeatTag::create);

static Tag create(final ArgumentQueue args, final Context ctx) throws ParsingException {
final OptionalInt optionalCount = args.popOr("No repeat count found.").asInt();
if (!optionalCount.isPresent()) {
throw ctx.newException("Repeat count must be an integer.");
}
final int count = optionalCount.getAsInt();
if (count < 0) {
throw ctx.newException("Repeat count must be non-negative.");
}
final String text = args.popOr("No text found to repeat.").value();
final Component toRepeat = ctx.deserialize(text);
Component bar = Component.empty();
for (int i = 0; i < count; i++) {
bar = bar.append(toRepeat);
}
return Tag.selfClosingInserting(bar);
}

private RepeatTag() {
}

AV3RG marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ private StandardTags() {
ResetTag.RESOLVER,
NewlineTag.RESOLVER,
TransitionTag.RESOLVER,
SelectorTag.RESOLVER
SelectorTag.RESOLVER,
ProgressTag.RESOLVER,
RepeatTag.RESOLVER
AV3RG marked this conversation as resolved.
Show resolved Hide resolved
)
.build();

Expand Down Expand Up @@ -214,7 +216,7 @@ public static TagResolver transition() {
public static @NotNull TagResolver newline() {
return NewlineTag.RESOLVER;
}

AV3RG marked this conversation as resolved.
Show resolved Hide resolved
/**
* Get a resolver for the {@value SelectorTag#SELECTOR} tag.
*
Expand All @@ -227,6 +229,26 @@ public static TagResolver transition() {
return SelectorTag.RESOLVER;
}

/**
* Get a resolver for the {@value ProgressTag#PROGRESS} tag.
*
* @return a resolver for the {@value ProgressTag#PROGRESS} tag
* @since 4.11.0
AV3RG marked this conversation as resolved.
Show resolved Hide resolved
*/
public static @NotNull TagResolver progress() {
return ProgressTag.RESOLVER;
}

/**
* Get a resolver for the {@value RepeatTag#REPEAT} tag.
*
* @return a resolver for the {@value RepeatTag#REPEAT} tag
* @since 4.11.0
AV3RG marked this conversation as resolved.
Show resolved Hide resolved
*/
public static @NotNull TagResolver repeat() {
return RepeatTag.RESOLVER;
}

/**
* Get a resolver that handles all default standard tags.
*
Expand Down
Loading