Something needs to be done with the button. #13
Открыто
открыта вчера AdamCalculator · комментариев: 22
AdamCalculator
AdamCalculator прокомментировал(а) вчера
image

Hello, my mod DynamicPack adds its own button in the same place as your mod. We need to do something about this

There is an idea to make a library, something like resourcepack-button-lib
she will be embeded in our mods and she will use the mixin to add a button, and our mods will only tell her to add a button. If the library needs to add more than 1 button, it adds its only button, when pressed, it opens a menu with a choice of which mod menu to open.

 image.png
208 KiB
AdamCalculator
AdamCalculator прокомментировал(а) вчера
Автор
upd: my buttons
image

 image.png
69 KiB
AdamCalculator
AdamCalculator прокомментировал(а) вчера
Автор
This should be convenient and also accessible to other modders who may need to add their own button here

AdamCalculator
AdamCalculator прокомментировал(а) вчера
Автор
I might make a prototype of such a library, I hope you don’t mind and understand the need for a compromise

AdamCalculator
AdamCalculator прокомментировал(а) вчера
Автор
(sorry english not my native lang)

Johannes Frohnmeyer
Johannes прокомментировал(а) 1 час назад
Владелец
I have experimented a bit and came up with the API below, which I would implement in a standalone LibJF module. Do you have any thoughts about it?

/**
 * Represents an additional widget inserted on the right hand side of a resource pack entry (in the resource pack or data pack screen)
 */
public interface ResourcePackEntryWidget {
    /**
     * Lists all known widgets from right to left. Immutable.
     */
    List<ResourcePackEntryWidget> WIDGETS = FabricLoader.getInstance()
            .getEntrypointContainers("libjf:resource_pack_entry_widget", ResourcePackEntryWidget.class)
            .stream()
            .sorted(Comparator.comparing(entrypoint -> entrypoint.getProvider().getMetadata().getId()))
            .map(EntrypointContainer::getEntrypoint)
            .toList();

    /**
     * Checks whether the widget should be rendered for a given pack.
     *
     * @param pack the pack to render the widget for
     * @param selectable whether the pack is selectable
     * @return whether the widget is visible
     */
    default boolean isVisible(ResourcePackOrganizer.Pack pack, boolean selectable) {
        return true;
    }

    /**
     * Gets the width of this widget.
     *
     * @param pack the pack to render the widget for
     * @return the width of the widget
     */
    default int getWidth(ResourcePackOrganizer.Pack pack) {
        return 20;
    }

    /**
     * Gets the height of this widget.
     *
     * @param pack the pack to render the widget for
     * @param rowHeight the height of the row containing the widget
     * @return the height of the widget
     */
    default int getHeight(ResourcePackOrganizer.Pack pack, int rowHeight) {
        return 20;
    }

    /**
     * Gets the Y position of this widget relative to the top of the row.
     *
     * @param pack the pack to render the widget for
     * @param rowHeight the height of the row containing the widget
     * @return the relative y position of the widget
     */
    default int getY(ResourcePackOrganizer.Pack pack, int rowHeight) {
        return (rowHeight - getHeight(pack, rowHeight)) / 2;
    }

    /**
     * Gets the X margin of the widget.
     * Two widgets will be separated by the maximum of their X margins.
     * Also, the rightmost widget will be separated from the edge of the entry by its margin.
     *
     * @param pack the pack to render the widget for
     * @return the X margin of the widget
     */
    default int getXMargin(ResourcePackOrganizer.Pack pack) {
        return 10;
    }

    /**
     * Renders the widget.
     *
     * @param pack the pack to render the widget for
     * @param context the context to draw to
     * @param x the absolute x coordinate at which to draw
     * @param y the absolute y coordinate at which to draw
     * @param hovered whether the widget is being hovered by the cursor
     */
    void render(ResourcePackOrganizer.Pack pack, DrawContext context, int x, int y, boolean hovered);

    /**
     * Executed when a widget is clicked.
     *
     * @param pack the pack for which the widget was clicked
     */
    void onClick(ResourcePackOrganizer.Pack pack);
}
AdamCalculator
AdamCalculator прокомментировал(а) 1 час назад
Автор
image haha, it’s clear that you and I are doing the same work in parallel
 image.png
146 KiB
AdamCalculator
AdamCalculator прокомментировал(а) 1 час назад
Автор
https://github.com/AdamCalculator/rp-button-lib

look at this please, it seems like I took our interests into account, made it quite abstract and also convenient for porting to different versions of the game.

AdamCalculator
AdamCalculator прокомментировал(а) 1 час назад
Автор
image
Small documentation:
RPButtonLib.getInstance().addInjector(injector_here)

Injector is a:
image

 image.png
62 KiB
 image.png
130 KiB
AdamCalculator
AdamCalculator прокомментировал(а) 1 час назад
Автор
image
ResourcePackArg

 image.png
46 KiB
AdamCalculator
AdamCalculator прокомментировал(а) 1 час назад
Автор
Are you satisfied with everything? maybe something can be changed? I will be glad to any criticism

Johannes Frohnmeyer
Johannes прокомментировал(а) 1 час назад
Владелец
To be honest, I disagree with a number of design choices made in that library:

I don't think a dedicated screen for what will likely never be more than two concurrent buttons makes sense
I don't like the "caller-driven" design of registering actions to a singleton and prefer the declarative approach I have taken
I dislike the lack of separation between API and implementation
I think the library is overly complex for what it is
I disagree with the naming of API elements (though this is clearly personal bias)
In comparison, the implementation of my approach simply amounts to the following, leaving the library with just two classes, one for API and one for implementation:

@Mixin(PackListWidget.ResourcePackEntry.class)
public abstract class ResourcePackEntryMixin {
    @Shadow protected abstract boolean isSelectable();
    @Shadow @Final private ResourcePackOrganizer.Pack pack;
    @Unique int libjf$selected = -1;

    @Inject(at = @At("TAIL"), method = "render(Lnet/minecraft/client/gui/DrawContext;IIIIIIIZF)V")
    private void render(DrawContext context, int index, int y, int x, int entryWidth, int entryHeight, int mouseX, int mouseY, boolean hovered, float tickDelta, CallbackInfo info) {
        int prevMargin = 0;
        int deltaX = 2;
        boolean selectable = isSelectable();
        libjf$selected = -1;
        for (int i = 0; i < ResourcePackEntryWidget.WIDGETS.size(); i++) {
            ResourcePackEntryWidget widget = ResourcePackEntryWidget.WIDGETS.get(i);
            if (!widget.isVisible(pack, selectable)) continue;
            deltaX += Math.max(prevMargin, widget.getXMargin(pack));
            int width = widget.getWidth(pack);
            int height = widget.getHeight(pack, entryHeight);
            int entryX = x + entryWidth - deltaX - width;
            int entryY = y + widget.getY(pack, entryHeight);
            boolean widgetHovered = mouseX <= entryX + width && mouseX >= entryX && mouseY <= entryY + height && mouseY >= entryY;
            widget.render(pack, context, entryX, entryY, widgetHovered, tickDelta);
            if (widgetHovered) libjf$selected = i;
            prevMargin = widget.getXMargin(pack);
        }
    }

    @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/AlwaysSelectedEntryListWidget$Entry;mouseClicked(DDI)Z"), method = "mouseClicked(DDI)Z", cancellable = true)
    public void mouseClicked(double mouseX, double mouseY, int button, CallbackInfoReturnable<Boolean> info) {
        // Inject before super call
        if (libjf$selected != -1) {
            info.setReturnValue(true);
            ResourcePackEntryWidget.WIDGETS.get(libjf$selected).onClick(pack);
        }
    }
}
AdamCalculator
AdamCalculator прокомментировал(а) 1 час назад
Автор
image
When pressed, if there are several mods, this screen will open with the choice of the desired mod. If you make several buttons directly on the resource pack, the buttons will cover all the text.

image
 image.png
74 KiB
 image.png
172 KiB
AdamCalculator
AdamCalculator прокомментировал(а) 50 минут назад
Автор
I don't think a dedicated screen for what will likely never be more than two concurrent buttons makes sense

I think we should make a library not only to resolve our conflict, but also for future people who will need a button here

I don't like the "caller-driven" design of registering actions to a singleton and prefer the declarative approach I have taken

Sorry, I didn't quite understand, can I have an example of your approach?

I dislike the lack of separation between API and implementation

in my opinion, it's not worth it here at all, what are the advantages if it is a library

Johannes Frohnmeyer
Johannes прокомментировал(а) 45 минут назад
Владелец
I think we should make a library not only to resolve our conflict, but also for future people who will need a button here

I agree, but still don't think this is a particularly common thing. If demand arises, unfolding could be retrofitted, though.

Sorry, I didn't quite understand, can I have an example of your approach?

The code used for respackopts would amount to

    "libjf:resource_pack_entry_widget": ["io.gitlab.jfronny.respackopts.RespackoptsPackWidget"],
public class RespackoptsPackWidget implements ResourcePackEntryWidget {
    @Override
    public boolean isVisible(ResourcePackOrganizer.Pack pack, boolean selectable) {
        return MetaCache.getKeyByDisplayName(pack.getDisplayName().getString()) != null;
    }

    @Override
    public void render(ResourcePackOrganizer.Pack pack, DrawContext context, int x, int y, boolean hovered, float tickDelta) {
        RenderSystem.setShaderColor(1, 1, 1, 1f);
        RenderSystem.disableDepthTest();
        context.drawTexture(Identifier.of("modmenu", "textures/gui/configure_button.png"), x, y, 0, hovered ? 20 : 0, 20, 20, 32, 64);
        RenderSystem.enableDepthTest();
    }

    @Override
    public void onClick(ResourcePackOrganizer.Pack pack) {
        CacheKey dataLocation = MetaCache.getKeyByDisplayName(pack.getDisplayName().getString());
        if (dataLocation != null) {
            MinecraftClient c = MinecraftClient.getInstance();
            String id = MetaCache.getId(dataLocation);
            ConfigInstance ci = DSL.create(id).config(builder -> MetaCache.getBranch(dataLocation).buildConfig(builder, id, dataLocation.dataLocation()));
            ConfigScreenFactory.Built<?> screen = ConfigScreenFactory.getInstance().create(ci, c.currentScreen);
            c.setScreen(screen.get());
        }
    }
}
in my opinion, it's not worth it here at all, what are the advantages if it is a library

I think a clean separation is especially important in libraries, since it allows users to understand how they are supposed to use it and what is intended to be used by them.

AdamCalculator
AdamCalculator прокомментировал(а) 39 минут назад
Автор
another of my complaints, I, and I think other people don't really want to include your library (libjf) in their mod. It would be easier with a separate library.

Johannes Frohnmeyer
Johannes прокомментировал(а) 39 минут назад
Владелец
LibJF is modular and this would go into a standalone module (which could naturally be included on its own).

👀
1
AdamCalculator
AdamCalculator прокомментировал(а) 38 минут назад
Автор
the requirements for this to be a separate independent small library that can be supplied immediately with the mod, I consider it mandatory

AdamCalculator
AdamCalculator прокомментировал(а) 36 минут назад
Автор
LibJF is modular.

it's not even that, but the fact that if we both disappear, it will be easier and more logical to fork a separate library for those who need it when we can't update it.

AdamCalculator
AdamCalculator прокомментировал(а) 33 минуты назад
Автор
My mod uses button here:
https://github.com/AdamCalculator/DynamicPack/blob/1.21/common/src/main/java/com/adamcalculator/dynamicpack/client/PackMixinHelper.java

It's just that in my fashion, a button is not just a button, it has animations and states, take this into account when designing a library. And also make it so that I can make it embedded in my mod please

Johannes Frohnmeyer
Johannes прокомментировал(а) 30 минут назад
Владелец
ResourcePackEntryWidget delegates rendering and on-click behavior entirely to user code, so that should not be a problem.

AdamCalculator
AdamCalculator прокомментировал(а) 27 минут назад
Автор
ResourcePackEntryWidget delegates rendering and on-click behavior entirely to user code, so that should not be a problem.

it's just that my button overlaps the text anyway, a few buttons will completely cover the text...

Johannes Frohnmeyer
Johannes прокомментировал(а) 11 минут назад
Владелец
I'll take a look at unfolding later, but for now, I have pushed my prototype as a snapshot.
You can try it by including the following in your build script (assuming you use the Gradle KT DSL):

repositories {
    maven("https://maven.frohnmeyer-wds.de/artifacts") {
        name = "Entry Widgets"
        content {
            includeGroup("io.gitlab.jfronny.libjf")
        }
    }
}

dependencies {
    include(modImplementation("io.gitlab.jfronny.libjf:libjf-resource-pack-entry-widgets-v0:3.17.0-SNAPSHOT")!!)
}

В чём конфликт?