Skip to content

Commit

Permalink
Merge pull request #842 from KyoriPowered/feature/virtual-component-h…
Browse files Browse the repository at this point in the history
…olders

feat(api): virtual components
  • Loading branch information
zml2008 authored Dec 18, 2024
2 parents bec02ef + 9d2e952 commit a8b1dac
Show file tree
Hide file tree
Showing 18 changed files with 1,141 additions and 601 deletions.
73 changes: 73 additions & 0 deletions api/src/main/java/net/kyori/adventure/text/Component.java
Original file line number Diff line number Diff line change
Expand Up @@ -1259,6 +1259,79 @@ public interface Component extends ComponentBuilderApplicable, ComponentLike, Ex
return text(String.valueOf(value), color, decorations);
}

/*
* --------------------------
* ---- VirtualComponent ----
* --------------------------
*/

/**
* Creates a virtual component.
*
* @param <C> the context type
* @param contextType the context type
* @param renderer the renderer
* @return a virtual component
* @since 4.18.0
*/
@Contract(value = "_, _ -> new", pure = true)
static <C> @NotNull VirtualComponent virtual(final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer) {
requireNonNull(contextType, "context type");
requireNonNull(renderer, "renderer");
return VirtualComponentImpl.createVirtual(contextType, renderer);
}

/**
* Creates a virtual component with a value.
*
* @param <C> the context type
* @param contextType the context type
* @param renderer the renderer
* @param style the style
* @return a virtual component
* @since 4.18.0
*/
@Contract(value = "_, _, _ -> new", pure = true)
static <C> @NotNull VirtualComponent virtual(final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer, final @NotNull Style style) {
requireNonNull(contextType, "context type");
requireNonNull(renderer, "renderer");
return VirtualComponentImpl.createVirtual(contextType, renderer, Collections.emptyList(), style);
}

/**
* Creates a virtual component with a value.
*
* @param <C> the context type
* @param contextType the context type
* @param renderer the renderer
* @param style the style elements
* @return a virtual component
* @since 4.18.0
*/
@Contract(value = "_, _, _ -> new", pure = true)
static <C> @NotNull VirtualComponent virtual(final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer, final @NotNull StyleBuilderApplicable... style) {
requireNonNull(contextType, "context type");
requireNonNull(renderer, "renderer");
return VirtualComponentImpl.createVirtual(contextType, renderer, Collections.emptyList(), Style.style(style));
}

/**
* Creates a virtual component with a value.
*
* @param <C> the context type
* @param contextType the context type
* @param renderer the renderer
* @param style the style elements
* @return a virtual component
* @since 4.18.0
*/
@Contract(value = "_, _, _ -> new", pure = true)
static <C> @NotNull VirtualComponent virtual(final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer, final @NotNull Iterable<StyleBuilderApplicable> style) {
requireNonNull(contextType, "context type");
requireNonNull(renderer, "renderer");
return VirtualComponentImpl.createVirtual(contextType, renderer, Collections.emptyList(), Style.style(style));
}

/*
* -------------------------------
* ---- TranslatableComponent ----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ static Component compact(final @NotNull Component self, final @Nullable Style pa
}

// if there is only one child, check if self a useless empty component
if (childrenSize == 1 && optimized instanceof TextComponent) {
if (childrenSize == 1 && isText(optimized)) {
final TextComponent textComponent = (TextComponent) optimized;

if (textComponent.content().isEmpty()) {
Expand All @@ -87,7 +87,7 @@ static Component compact(final @NotNull Component self, final @Nullable Style pa
child = compact(child, childParentStyle);

// ignore useless empty children (regardless of its style)
if (child.children().isEmpty() && child instanceof TextComponent) {
if (child.children().isEmpty() && isText(child)) {
final TextComponent textComponent = (TextComponent) child;

if (textComponent.content().isEmpty()) {
Expand All @@ -99,12 +99,12 @@ static Component compact(final @NotNull Component self, final @Nullable Style pa
}

// try to merge children into this parent component
if (optimized instanceof TextComponent) {
if (isText(optimized)) {
while (!childrenToAppend.isEmpty()) {
final Component child = childrenToAppend.get(0);
final Style childStyle = child.style().merge(childParentStyle, Style.Merge.Strategy.IF_ABSENT_ON_TARGET);

if (child instanceof TextComponent && Objects.equals(childStyle, childParentStyle)) {
if (isText(child) && Objects.equals(childStyle, childParentStyle)) {
// merge child components into the parent if they are a text component with the same effective style
// in context of their parent style
optimized = joinText((TextComponent) optimized, (TextComponent) child);
Expand All @@ -125,7 +125,7 @@ static Component compact(final @NotNull Component self, final @Nullable Style pa
final Component child = childrenToAppend.get(i);
final Component neighbor = childrenToAppend.get(i + 1);

if (child.children().isEmpty() && child instanceof TextComponent && neighbor instanceof TextComponent) {
if (child.children().isEmpty() && isText(child) && isText(neighbor)) {
// calculate the children's styles in context of their parent style
final Style childStyle = child.style().merge(childParentStyle, Style.Merge.Strategy.IF_ABSENT_ON_TARGET);
final Style neighborStyle = neighbor.style().merge(childParentStyle, Style.Merge.Strategy.IF_ABSENT_ON_TARGET);
Expand Down Expand Up @@ -162,7 +162,7 @@ static Component compact(final @NotNull Component self, final @Nullable Style pa
* @return true if the provided component is blank, false otherwise
*/
private static boolean isBlank(final Component component) {
if (component instanceof TextComponent) {
if (isText(component)) {
final TextComponent textComponent = (TextComponent) component;

final String content = textComponent.content();
Expand Down Expand Up @@ -215,4 +215,8 @@ private static boolean isBlank(final Component component) {
private static TextComponent joinText(final TextComponent one, final TextComponent two) {
return TextComponentImpl.create(two.children(), one.style(), one.content() + two.content());
}

private static boolean isText(final Component component) {
return component instanceof TextComponent && !(component instanceof VirtualComponent);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

import static java.util.Objects.requireNonNull;

final class TextComponentImpl extends AbstractComponent implements TextComponent {
class TextComponentImpl extends AbstractComponent implements TextComponent {
private static final boolean WARN_WHEN_LEGACY_FORMATTING_DETECTED = Boolean.TRUE.equals(AdventureProperties.TEXT_WARN_WHEN_LEGACY_FORMATTING_DETECTED.value());
@VisibleForTesting
static final char SECTION_CHAR = '§';
Expand All @@ -56,6 +56,10 @@ static TextComponent create(final @NotNull List<? extends ComponentLike> childre
);
}

TextComponent create0(final @NotNull List<? extends ComponentLike> children, final @NotNull Style style, final @NotNull String content) {
return create(children, style, content);
}

private static @NotNull TextComponent createDirect(final @NotNull String content) {
return new TextComponentImpl(Collections.emptyList(), Style.empty(), content);
}
Expand Down Expand Up @@ -90,17 +94,17 @@ static TextComponent create(final @NotNull List<? extends ComponentLike> childre
@Override
public @NotNull TextComponent content(final @NotNull String content) {
if (Objects.equals(this.content, content)) return this;
return create(this.children, this.style, content);
return this.create0(this.children, this.style, content);
}

@Override
public @NotNull TextComponent children(final @NotNull List<? extends ComponentLike> children) {
return create(children, this.style, this.content);
return this.create0(children, this.style, this.content);
}

@Override
public @NotNull TextComponent style(final @NotNull Style style) {
return create(this.children, style, this.content);
return this.create0(this.children, style, this.content);
}

@Override
Expand Down Expand Up @@ -129,7 +133,7 @@ public String toString() {
return new BuilderImpl(this);
}

static final class BuilderImpl extends AbstractComponentBuilder<TextComponent, Builder> implements TextComponent.Builder {
static class BuilderImpl extends AbstractComponentBuilder<TextComponent, Builder> implements TextComponent.Builder {
/*
* We default to an empty string to avoid needing to manually set the
* content of a newly-created builder when we only want to append other
Expand Down
51 changes: 51 additions & 0 deletions api/src/main/java/net/kyori/adventure/text/VirtualComponent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2024 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;

import org.jetbrains.annotations.NotNull;

/**
* A virtual component.
*
* <p>This component type is transient, and not guaranteed to survive during any sort of transformations or serialization.</p>
*
* @since 4.18.0
*/
public interface VirtualComponent extends TextComponent {
/**
* Gets the renderer context type.
*
* @return the renderer context type
* @since 4.18.0
*/
@NotNull Class<?> contextType();

/**
* Gets the renderer.
*
* @return the renderer
* @since 4.18.0
*/
@NotNull VirtualComponentRenderer<?> renderer();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2024 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;

import java.util.Collections;
import java.util.List;
import net.kyori.adventure.text.format.Style;
import org.jetbrains.annotations.NotNull;

final class VirtualComponentImpl<C> extends TextComponentImpl implements VirtualComponent {
static <C> VirtualComponent createVirtual(final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer) {
return createVirtual(contextType, renderer, Collections.emptyList(), Style.empty());
}

static <C> VirtualComponent createVirtual(final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer, final List<? extends ComponentLike> children, final Style style) {
final List<Component> filteredChildren = ComponentLike.asComponents(children, IS_NOT_EMPTY);

return new VirtualComponentImpl<>(filteredChildren, style, "", contextType, renderer);
}

private final Class<C> contextType;
private final VirtualComponentRenderer<C> renderer;

private VirtualComponentImpl(final @NotNull List<Component> children, final @NotNull Style style, final @NotNull String content, final @NotNull Class<C> contextType, final @NotNull VirtualComponentRenderer<C> renderer) {
super(children, style, content);
this.contextType = contextType;
this.renderer = renderer;
}

@Override
VirtualComponent create0(final @NotNull List<? extends ComponentLike> children, final @NotNull Style style, final @NotNull String content) {
return new VirtualComponentImpl<>(ComponentLike.asComponents(children, IS_NOT_EMPTY), style, content, this.contextType, this.renderer);
}

@Override
public @NotNull Class<C> contextType() {
return this.contextType;
}

@Override
public @NotNull VirtualComponentRenderer<C> renderer() {
return this.renderer;
}

@Override
public @NotNull String content() {
return this.renderer.fallbackString();
}

@Override
public @NotNull Builder toBuilder() {
return new BuilderImpl<>(this);
}

static final class BuilderImpl<C> extends TextComponentImpl.BuilderImpl {
private final Class<C> contextType;
private final VirtualComponentRenderer<C> renderer;

BuilderImpl(final VirtualComponentImpl<C> other) {
super(other);
this.contextType = other.contextType();
this.renderer = other.renderer();
}

@Override
public @NotNull TextComponent build() {
return createVirtual(this.contextType, this.renderer, this.children, this.buildStyle());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* This file is part of adventure, licensed under the MIT License.
*
* Copyright (c) 2017-2024 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;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.UnknownNullability;

/**
* A holder for a value.
*
* @param <C> the context type
* @since 4.18.0
*/
public interface VirtualComponentRenderer<C> {
/**
* Gets the value by rendering using {@code context}.
*
* @param context the context
* @return the rendered value
* @since 4.18.0
*/
@UnknownNullability ComponentLike apply(final @NotNull C context);

/**
* Get a fallback value for when this component has been serialized without being rendered.
*
* <p>By default, this will be an empty string.</p>
*
* @return the fallback string
* @since 4.18.0
*/
default @NotNull String fallbackString() {
return "";
}
}
Loading

0 comments on commit a8b1dac

Please sign in to comment.