MenuType API addition InventoryView Builders (#11816)

This commit is contained in:
Miles
2025-01-11 19:21:24 +00:00
committed by GitHub
parent 775002a357
commit c94922514a
27 changed files with 759 additions and 95 deletions

View File

@ -40,6 +40,7 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemCraftResult;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MenuType;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.ItemMeta;
@ -1925,7 +1926,10 @@ public final class Bukkit {
* @param title the title of the corresponding merchant inventory, displayed
* when the merchant inventory is viewed
* @return a new merchant
* @deprecated The title parameter is no-longer needed when used with
* {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}.
*/
@Deprecated(since = "1.21.4")
public static @NotNull Merchant createMerchant(net.kyori.adventure.text.@Nullable Component title) {
return server.createMerchant(title);
}
@ -1936,7 +1940,8 @@ public final class Bukkit {
* @param title the title of the corresponding merchant inventory, displayed
* when the merchant inventory is viewed
* @return a new merchant
* @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}
* @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}. The title parameter is
* no-longer needed when used with {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}
*/
@NotNull
@Deprecated // Paper
@ -1944,6 +1949,16 @@ public final class Bukkit {
return server.createMerchant(title);
}
/**
* Creates an empty merchant.
*
* @return a new merchant
*/
@NotNull
public static Merchant createMerchant() {
return server.createMerchant();
}
/**
* Gets the amount of consecutive neighbor updates before skipping
* additional ones.

View File

@ -43,6 +43,7 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemCraftResult;
import org.bukkit.inventory.ItemFactory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MenuType;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.Recipe;
import org.bukkit.inventory.meta.ItemMeta;
@ -1565,11 +1566,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* <br>
* {@link InventoryType#WORKBENCH} will not process crafting recipes if
* created with this method. Use
* {@link Player#openWorkbench(Location, boolean)} instead.
* {@link MenuType#CRAFTING} instead.
* <br>
* {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s
* for possible enchanting results. Use
* {@link Player#openEnchanting(Location, boolean)} instead.
* {@link MenuType#ENCHANTMENT} instead.
*
* @param owner the holder of the inventory, or null to indicate no holder
* @param type the type of inventory to create
@ -1592,11 +1593,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* <br>
* {@link InventoryType#WORKBENCH} will not process crafting recipes if
* created with this method. Use
* {@link Player#openWorkbench(Location, boolean)} instead.
* {@link MenuType#CRAFTING} instead.
* <br>
* {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s
* for possible enchanting results. Use
* {@link Player#openEnchanting(Location, boolean)} instead.
* {@link MenuType#ENCHANTMENT} instead.
*
* @param owner The holder of the inventory; can be null if there's no holder.
* @param type The type of inventory to create.
@ -1620,11 +1621,11 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* <br>
* {@link InventoryType#WORKBENCH} will not process crafting recipes if
* created with this method. Use
* {@link Player#openWorkbench(Location, boolean)} instead.
* {@link MenuType#CRAFTING} instead.
* <br>
* {@link InventoryType#ENCHANTING} will not process {@link ItemStack}s
* for possible enchanting results. Use
* {@link Player#openEnchanting(Location, boolean)} instead.
* {@link MenuType#ENCHANTMENT} instead.
*
* @param owner The holder of the inventory; can be null if there's no holder.
* @param type The type of inventory to create.
@ -1691,7 +1692,10 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* @param title the title of the corresponding merchant inventory, displayed
* when the merchant inventory is viewed
* @return a new merchant
* @deprecated The title parameter is no-longer needed when used with
* {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}.
*/
@Deprecated(since = "1.21.4")
@NotNull Merchant createMerchant(net.kyori.adventure.text.@Nullable Component title);
// Paper start
/**
@ -1700,7 +1704,8 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
* @param title the title of the corresponding merchant inventory, displayed
* when the merchant inventory is viewed
* @return a new merchant
* @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}
* @deprecated in favour of {@link #createMerchant(net.kyori.adventure.text.Component)}, The title parameter is
* no-longer needed when used with {@link MenuType#MERCHANT} and {@link MenuType.Typed#builder()}.
*/
@NotNull
@Deprecated // Paper
@ -1715,6 +1720,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi
*/
int getMaxChainedNeighborUpdates();
/**
* Creates an empty merchant.
*
* @return a new merchant
*/
@NotNull
Merchant createMerchant();
/**
* Gets user-specified limit for number of monsters that can spawn in a
* chunk.

View File

@ -13,6 +13,7 @@ import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MainHand;
import org.bukkit.inventory.MenuType;
import org.bukkit.inventory.Merchant;
import org.bukkit.inventory.PlayerInventory;
import org.bukkit.inventory.meta.FireworkMeta;
@ -126,7 +127,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#CRAFTING}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openWorkbench(@Nullable Location location, boolean force);
@ -140,7 +144,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* location, no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#ENCHANTMENT}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openEnchanting(@Nullable Location location, boolean force);
@ -166,8 +173,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* @param trader The merchant to trade with. Cannot be null.
* @param force whether to force the trade even if another player is trading
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method can be replaced by using {@link MenuType#MERCHANT}
* in conjunction with {@link #openInventory(InventoryView)}.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openMerchant(@NotNull Villager trader, boolean force);
@ -180,8 +189,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* @param merchant The merchant to trade with. Cannot be null.
* @param force whether to force the trade even if another player is trading
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method can be replaced by using {@link MenuType#MERCHANT}
* in conjunction with {@link #openInventory(InventoryView)}.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openMerchant(@NotNull Merchant merchant, boolean force);
@ -196,7 +207,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#ANVIL}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openAnvil(@Nullable Location location, boolean force);
@ -210,7 +224,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#CARTOGRAPHY_TABLE}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openCartographyTable(@Nullable Location location, boolean force);
@ -224,7 +241,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#GRINDSTONE}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openGrindstone(@Nullable Location location, boolean force);
@ -238,7 +258,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#LOOM}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openLoom(@Nullable Location location, boolean force);
@ -252,7 +275,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#SMITHING}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openSmithingTable(@Nullable Location location, boolean force);
@ -266,7 +292,10 @@ public interface HumanEntity extends LivingEntity, AnimalTamer, InventoryHolder
* no inventory will be opened and null will be returned.
* @return The newly opened inventory view, or null if it could not be
* opened.
* @deprecated This method should be replaced by {@link MenuType#STONECUTTER}
* see {@link MenuType.Typed#builder()} and its options for more information.
*/
@Deprecated(since = "1.21.4")
@Nullable
public InventoryView openStonecutter(@Nullable Location location, boolean force);
// Paper end

View File

@ -14,6 +14,9 @@ import org.bukkit.inventory.view.LecternView;
import org.bukkit.inventory.view.LoomView;
import org.bukkit.inventory.view.MerchantView;
import org.bukkit.inventory.view.StonecutterView;
import org.bukkit.inventory.view.builder.InventoryViewBuilder;
import org.bukkit.inventory.view.builder.LocationInventoryViewBuilder;
import org.bukkit.inventory.view.builder.MerchantInventoryViewBuilder;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
@ -27,104 +30,104 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe
/**
* A MenuType which represents a chest with 1 row.
*/
MenuType.Typed<InventoryView> GENERIC_9X1 = get("generic_9x1");
MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X1 = get("generic_9x1");
/**
* A MenuType which represents a chest with 2 rows.
*/
MenuType.Typed<InventoryView> GENERIC_9X2 = get("generic_9x2");
MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X2 = get("generic_9x2");
/**
* A MenuType which represents a chest with 3 rows.
*/
MenuType.Typed<InventoryView> GENERIC_9X3 = get("generic_9x3");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GENERIC_9X3 = get("generic_9x3");
/**
* A MenuType which represents a chest with 4 rows.
*/
MenuType.Typed<InventoryView> GENERIC_9X4 = get("generic_9x4");
MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X4 = get("generic_9x4");
/**
* A MenuType which represents a chest with 5 rows.
*/
MenuType.Typed<InventoryView> GENERIC_9X5 = get("generic_9x5");
MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> GENERIC_9X5 = get("generic_9x5");
/**
* A MenuType which represents a chest with 6 rows.
*/
MenuType.Typed<InventoryView> GENERIC_9X6 = get("generic_9x6");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GENERIC_9X6 = get("generic_9x6");
/**
* A MenuType which represents a dispenser/dropper like menu with 3 columns
* and 3 rows.
*/
MenuType.Typed<InventoryView> GENERIC_3X3 = get("generic_3x3");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GENERIC_3X3 = get("generic_3x3");
/**
* A MenuType which represents a crafter
*/
MenuType.Typed<CrafterView> CRAFTER_3X3 = get("crafter_3x3");
MenuType.Typed<CrafterView, LocationInventoryViewBuilder<CrafterView>> CRAFTER_3X3 = get("crafter_3x3");
/**
* A MenuType which represents an anvil.
*/
MenuType.Typed<AnvilView> ANVIL = get("anvil");
MenuType.Typed<AnvilView, LocationInventoryViewBuilder<AnvilView>> ANVIL = get("anvil");
/**
* A MenuType which represents a beacon.
*/
MenuType.Typed<BeaconView> BEACON = get("beacon");
MenuType.Typed<BeaconView, LocationInventoryViewBuilder<BeaconView>> BEACON = get("beacon");
/**
* A MenuType which represents a blast furnace.
*/
MenuType.Typed<FurnaceView> BLAST_FURNACE = get("blast_furnace");
MenuType.Typed<FurnaceView, LocationInventoryViewBuilder<FurnaceView>> BLAST_FURNACE = get("blast_furnace");
/**
* A MenuType which represents a brewing stand.
*/
MenuType.Typed<BrewingStandView> BREWING_STAND = get("brewing_stand");
MenuType.Typed<BrewingStandView, LocationInventoryViewBuilder<BrewingStandView>> BREWING_STAND = get("brewing_stand");
/**
* A MenuType which represents a crafting table.
*/
MenuType.Typed<InventoryView> CRAFTING = get("crafting");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> CRAFTING = get("crafting");
/**
* A MenuType which represents an enchantment table.
*/
MenuType.Typed<EnchantmentView> ENCHANTMENT = get("enchantment");
MenuType.Typed<EnchantmentView, LocationInventoryViewBuilder<EnchantmentView>> ENCHANTMENT = get("enchantment");
/**
* A MenuType which represents a furnace.
*/
MenuType.Typed<FurnaceView> FURNACE = get("furnace");
MenuType.Typed<FurnaceView, LocationInventoryViewBuilder<FurnaceView>> FURNACE = get("furnace");
/**
* A MenuType which represents a grindstone.
*/
MenuType.Typed<InventoryView> GRINDSTONE = get("grindstone");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> GRINDSTONE = get("grindstone");
/**
* A MenuType which represents a hopper.
*/
MenuType.Typed<InventoryView> HOPPER = get("hopper");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> HOPPER = get("hopper");
/**
* A MenuType which represents a lectern, a book like view.
*/
MenuType.Typed<LecternView> LECTERN = get("lectern");
MenuType.Typed<LecternView, LocationInventoryViewBuilder<LecternView>> LECTERN = get("lectern");
/**
* A MenuType which represents a loom.
*/
MenuType.Typed<LoomView> LOOM = get("loom");
MenuType.Typed<LoomView, LocationInventoryViewBuilder<LoomView>> LOOM = get("loom");
/**
* A MenuType which represents a merchant.
*/
MenuType.Typed<MerchantView> MERCHANT = get("merchant");
MenuType.Typed<MerchantView, MerchantInventoryViewBuilder<MerchantView>> MERCHANT = get("merchant");
/**
* A MenuType which represents a shulker box.
*/
MenuType.Typed<InventoryView> SHULKER_BOX = get("shulker_box");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> SHULKER_BOX = get("shulker_box");
/**
* A MenuType which represents a stonecutter.
*/
MenuType.Typed<InventoryView> SMITHING = get("smithing");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> SMITHING = get("smithing");
/**
* A MenuType which represents a smoker.
*/
MenuType.Typed<FurnaceView> SMOKER = get("smoker");
MenuType.Typed<FurnaceView, LocationInventoryViewBuilder<FurnaceView>> SMOKER = get("smoker");
/**
* A MenuType which represents a cartography table.
*/
MenuType.Typed<InventoryView> CARTOGRAPHY_TABLE = get("cartography_table");
MenuType.Typed<InventoryView, LocationInventoryViewBuilder<InventoryView>> CARTOGRAPHY_TABLE = get("cartography_table");
/**
* A MenuType which represents a stonecutter.
*/
MenuType.Typed<StonecutterView> STONECUTTER = get("stonecutter");
MenuType.Typed<StonecutterView, LocationInventoryViewBuilder<StonecutterView>> STONECUTTER = get("stonecutter");
/**
* Typed represents a subtype of {@link MenuType}s that have a known
@ -133,7 +136,7 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe
* @param <V> the generic type of {@link InventoryView} that represents the
* view type.
*/
interface Typed<V extends InventoryView> extends MenuType {
interface Typed<V extends InventoryView, B extends InventoryViewBuilder<V>> extends MenuType {
/**
* Creates a view of the specified menu type.
@ -166,6 +169,9 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe
@NotNull
V create(@NotNull HumanEntity player, @NotNull net.kyori.adventure.text.Component title);
// Paper end - adventure
@NotNull
B builder();
}
// Paper start - adventure
@ -191,7 +197,7 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe
* @return the typed MenuType.
*/
@NotNull
MenuType.Typed<InventoryView> typed();
MenuType.Typed<InventoryView, InventoryViewBuilder<InventoryView>> typed();
/**
* Yields this MenuType as a typed version of itself with a specific
@ -201,12 +207,14 @@ public interface MenuType extends Keyed, io.papermc.paper.world.flag.FeatureDepe
* {@link InventoryView} with.
* @param <V> the generic type of the InventoryView to get this MenuType
* with
* @param <B> the generic type of the InventoryViewBuilder to get this
* MenuType with
* @return the typed MenuType
* @throws IllegalArgumentException if the provided viewClass cannot be
* typed to this MenuType
*/
@NotNull
<V extends InventoryView> MenuType.Typed<V> typed(@NotNull final Class<V> viewClass) throws IllegalArgumentException;
<V extends InventoryView, B extends InventoryViewBuilder<V>> MenuType.Typed<V, B> typed(@NotNull final Class<V> viewClass) throws IllegalArgumentException;
/**
* Gets the {@link InventoryView} class of this MenuType.

View File

@ -0,0 +1,38 @@
package org.bukkit.inventory.view.builder;
import net.kyori.adventure.text.Component;
import org.bukkit.entity.HumanEntity;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.ApiStatus;
/**
* Generic Builder for InventoryView's with no special attributes or parameters
*
* @param <V> the type of InventoryView created from this builder
*/
@ApiStatus.Experimental
public interface InventoryViewBuilder<V extends InventoryView> {
/**
* Makes a copy of this builder
*
* @return a copy of this builder
*/
InventoryViewBuilder<V> copy();
/**
* Sets the title of the builder
*
* @param title the title
* @return this builder
*/
InventoryViewBuilder<V> title(final Component title);
/**
* Builds this builder into a InventoryView
*
* @param player the player to assign to the view
* @return the created InventoryView
*/
V build(final HumanEntity player);
}

View File

@ -0,0 +1,55 @@
package org.bukkit.inventory.view.builder;
import net.kyori.adventure.text.Component;
import org.bukkit.Location;
import org.bukkit.inventory.InventoryView;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* An InventoryViewBuilder that can be bound by location within the world
*
* @param <V> the type of InventoryView created from this builder
*/
@ApiStatus.Experimental
public interface LocationInventoryViewBuilder<V extends InventoryView> extends InventoryViewBuilder<V> {
@Override
LocationInventoryViewBuilder<V> copy();
@Override
LocationInventoryViewBuilder<V> title(final @NotNull Component title);
/**
* Determines whether or not the server should check if the player can reach
* the location.
* <p>
* Not providing a location but setting checkReachable to true will
* automatically close the view when opened.
* <p>
* If checkReachable is set to false and a location is set on the builder if
* the target block exists and this builder is the correct menu for that
* block, e.g. MenuType.GENERIC_9X3 builder and target block set to chest,
* if that block is destroyed the view would persist.
*
* @param checkReachable whether or not to check if the view is "reachable"
* @return this builder
*/
LocationInventoryViewBuilder<V> checkReachable(final boolean checkReachable);
/**
* Binds a location to this builder.
* <p>
* By binding a location in an unloaded chunk to this builder it is likely
* that the given chunk the location is will load. That means that when,
* building this view it may come with the costs associated with chunk
* loading.
* <p>
* Providing a location of a tile entity with a non matching menu comes with
* extra costs associated with ensuring that the correct view is created.
*
* @param location the location to bind to this view
* @return this builder
*/
LocationInventoryViewBuilder<V> location(final Location location);
}

View File

@ -0,0 +1,44 @@
package org.bukkit.inventory.view.builder;
import net.kyori.adventure.text.Component;
import org.bukkit.Server;
import org.bukkit.inventory.InventoryView;
import org.bukkit.inventory.Merchant;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
/**
* An InventoryViewBuilder for creating merchant views
*
* @param <V> the type of InventoryView created by this builder
*/
@ApiStatus.Experimental
public interface MerchantInventoryViewBuilder<V extends InventoryView> extends InventoryViewBuilder<V> {
@Override
MerchantInventoryViewBuilder<V> copy();
@Override
MerchantInventoryViewBuilder<V> title(final @NotNull Component title);
/**
* Adds a merchant to this builder
*
* @param merchant the merchant
* @return this builder
*/
MerchantInventoryViewBuilder<V> merchant(final Merchant merchant);
/**
* Determines whether or not the server should check if the player can reach
* the location.
* <p>
* Given checkReachable is provided and a virtual merchant is provided to
* the builder from {@link Server#createMerchant(net.kyori.adventure.text.Component)} this method will
* have no effect on the actual menu status.
*
* @param checkReachable whether or not to check if the view is "reachable"
* @return this builder
*/
MerchantInventoryViewBuilder<V> checkReachable(final boolean checkReachable);
}

View File

@ -0,0 +1,9 @@
/**
* A Package that contains builders for building InventoryViews.
*/
@NullMarked
@ApiStatus.Experimental
package org.bukkit.inventory.view.builder;
import org.jetbrains.annotations.ApiStatus;
import org.jspecify.annotations.NullMarked;