From 415de56c13924b82fd9bb31fc5eabd46a5d52cd1 Mon Sep 17 00:00:00 2001 From: "DESKTOP-U45FQMV\\34355" <3435578673@qq.com> Date: Thu, 9 Jul 2026 01:13:21 +0800 Subject: [PATCH] feat(ui): implement iOS-style release trigger and pressed state for ButtonWidget Modify ButtonWidget to fire onClick on mouse release (mouseUp & isMouseOver) rather than on mouse press (mouseDown), aligning interaction behavior with iOS HIG (Human Interface Guidelines). Also introduce a focus state that persists while the button is held down, providing focus feedback prior to the click being finalized. This enhances the overall Liquid Glass UX parity with native mobile interactions. --- .../extensions/PressableWidgetExtension.java | 10 +++ .../mixin/widgets/ClickableWidgetMixin.java | 52 ++++++++++++++ .../mixin/widgets/DrawContextMixin.java | 44 ------------ .../mixin/widgets/PressableWidgetMixin.java | 68 +++++++++++++++++++ src/main/resources/reglass.mixins.json | 3 +- 5 files changed, 132 insertions(+), 45 deletions(-) create mode 100644 src/main/java/restudio/reglass/extensions/PressableWidgetExtension.java create mode 100644 src/main/java/restudio/reglass/mixin/widgets/ClickableWidgetMixin.java delete mode 100644 src/main/java/restudio/reglass/mixin/widgets/DrawContextMixin.java create mode 100644 src/main/java/restudio/reglass/mixin/widgets/PressableWidgetMixin.java diff --git a/src/main/java/restudio/reglass/extensions/PressableWidgetExtension.java b/src/main/java/restudio/reglass/extensions/PressableWidgetExtension.java new file mode 100644 index 0000000..3ae66f1 --- /dev/null +++ b/src/main/java/restudio/reglass/extensions/PressableWidgetExtension.java @@ -0,0 +1,10 @@ +package restudio.reglass.extensions; + +import org.spongepowered.asm.mixin.Unique; + +public interface PressableWidgetExtension { + @Unique + boolean reglass$isDragging(); + @Unique + void reglass$setIsDragging(boolean isDragging); +} diff --git a/src/main/java/restudio/reglass/mixin/widgets/ClickableWidgetMixin.java b/src/main/java/restudio/reglass/mixin/widgets/ClickableWidgetMixin.java new file mode 100644 index 0000000..d9b0b1c --- /dev/null +++ b/src/main/java/restudio/reglass/mixin/widgets/ClickableWidgetMixin.java @@ -0,0 +1,52 @@ +package restudio.reglass.mixin.widgets; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.widget.ClickableWidget; +import net.minecraft.client.gui.widget.PressableWidget; +import net.minecraft.client.sound.SoundManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import restudio.reglass.extensions.PressableWidgetExtension; + +@Mixin(ClickableWidget.class) +public abstract class ClickableWidgetMixin { + @Shadow + public abstract void playDownSound(SoundManager soundManager); + + @Shadow + public abstract void onClick(double mouseX, double mouseY); + + @Redirect(method = "mouseClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ClickableWidget;playDownSound(Lnet/minecraft/client/sound/SoundManager;)V")) + private void redirectPlayDownSound(ClickableWidget instance, SoundManager soundManager) { + if ((Object) this instanceof PressableWidget) { + // Just play nothing. + return; + } + playDownSound(soundManager); + } + + @Redirect(method = "mouseClicked", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ClickableWidget;onClick(DD)V")) + private void redirectOnClick(ClickableWidget instance, double mouseX, double mouseY) { + if ((Object) this instanceof PressableWidget) { + ((PressableWidgetExtension) this).reglass$setIsDragging(true); + // Just play nothing. + return; + } + onClick(mouseX, mouseY); + } + + @Inject(method = "mouseReleased", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/gui/widget/ClickableWidget;onRelease(DD)V")) + private void modifyOnRelease(double mouseX, double mouseY, int button, CallbackInfoReturnable cir) { + if ((Object) this instanceof PressableWidget pressableWidget) { + if (pressableWidget.isMouseOver(mouseX, mouseY)) { + pressableWidget.playDownSound(MinecraftClient.getInstance().getSoundManager()); + pressableWidget.onClick(mouseX, mouseY); + } + ((PressableWidgetExtension) pressableWidget).reglass$setIsDragging(false); + } + } +} diff --git a/src/main/java/restudio/reglass/mixin/widgets/DrawContextMixin.java b/src/main/java/restudio/reglass/mixin/widgets/DrawContextMixin.java deleted file mode 100644 index ddca137..0000000 --- a/src/main/java/restudio/reglass/mixin/widgets/DrawContextMixin.java +++ /dev/null @@ -1,44 +0,0 @@ -package restudio.reglass.mixin.widgets; - -import com.mojang.blaze3d.pipeline.RenderPipeline; -import net.minecraft.client.gui.DrawContext; -import net.minecraft.util.Identifier; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; -import restudio.reglass.client.api.ReGlassApi; -import restudio.reglass.client.api.ReGlassConfig; -import restudio.reglass.client.api.WidgetStyle; - -@Mixin(DrawContext.class) -public abstract class DrawContextMixin { - - @Unique - private static final Identifier BUTTON_TEXTURE = Identifier.ofVanilla("widget/button"); - @Unique - private static final Identifier BUTTON_DISABLED_TEXTURE = Identifier.ofVanilla("widget/button_disabled"); - @Unique - private static final Identifier BUTTON_HIGHLIGHTED_TEXTURE = Identifier.ofVanilla("widget/button_highlighted"); - - @Inject(method = "drawGuiTexture(Lcom/mojang/blaze3d/pipeline/RenderPipeline;Lnet/minecraft/util/Identifier;IIIII)V", - at = @At("HEAD"), cancellable = true) - private void onDrawTexture(RenderPipeline pipeline, Identifier sprite, int x, int y, int width, int height, int color, CallbackInfo ci) { - boolean isButtonTexture = sprite.getPath().equals(BUTTON_TEXTURE.getPath()) - || sprite.getPath().equals(BUTTON_DISABLED_TEXTURE.getPath()) - || sprite.getPath().equals(BUTTON_HIGHLIGHTED_TEXTURE.getPath()); - - if (isButtonTexture && (ReGlassConfig.INSTANCE.features.enableRedesign && ReGlassConfig.INSTANCE.features.buttons)) { - boolean isHighlighted = sprite.getPath().equals(BUTTON_HIGHLIGHTED_TEXTURE.getPath()); - boolean isDisabled = sprite.getPath().equals(BUTTON_DISABLED_TEXTURE.getPath()); - ReGlassApi.create((DrawContext)(Object) this) - .position(x, y) - .size(width, height) - .hover(isHighlighted ? 1f : 0f) - .style(WidgetStyle.create().tint(isDisabled ? 0xFF000000 : 0xFFFFFFFF, isDisabled ? 0.4f : 0f)) - .render(); - ci.cancel(); - } - } -} \ No newline at end of file diff --git a/src/main/java/restudio/reglass/mixin/widgets/PressableWidgetMixin.java b/src/main/java/restudio/reglass/mixin/widgets/PressableWidgetMixin.java new file mode 100644 index 0000000..f5bc52f --- /dev/null +++ b/src/main/java/restudio/reglass/mixin/widgets/PressableWidgetMixin.java @@ -0,0 +1,68 @@ +package restudio.reglass.mixin.widgets; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.font.TextRenderer; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.gui.widget.PressableWidget; +import net.minecraft.util.math.ColorHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import restudio.reglass.client.LiquidGlassUniforms; +import restudio.reglass.client.api.ReGlassApi; +import restudio.reglass.client.api.ReGlassConfig; +import restudio.reglass.client.api.WidgetStyle; +import restudio.reglass.extensions.PressableWidgetExtension; + +@Mixin(PressableWidget.class) +public abstract class PressableWidgetMixin implements PressableWidgetExtension { + @Unique + private boolean reglass$isDragging; + @Shadow + public abstract void drawMessage(DrawContext context, TextRenderer textRenderer, int color); + + @Inject(method = "renderWidget", at = @At("HEAD"), cancellable = true) + private void modifyRenderWidget(DrawContext context, int mouseX, int mouseY, float deltaTicks, CallbackInfo ci) { + if ((Object) this instanceof PressableWidget pressableWidget) { + if (!ReGlassConfig.INSTANCE.features.enableRedesign || !ReGlassConfig.INSTANCE.features.buttons) { + return; + } + + boolean isDisabled = !pressableWidget.active; + boolean isHighlighted = pressableWidget.isHovered(); + boolean isFocused = isHighlighted && reglass$isDragging; + + if (ReGlassConfig.INSTANCE.features.enableRedesign && ReGlassConfig.INSTANCE.features.buttons) { + ReGlassApi.create(context) + .position(pressableWidget.getX(), pressableWidget.getY()) + .size(pressableWidget.getWidth(), pressableWidget.getHeight()) + .hover(isHighlighted ? 1f : 0f) + .focus(isFocused ? 1.0f : 0.0f) + .style(WidgetStyle.create().tint(isDisabled ? 0xFF000000 : 0xFFFFFFFF, isDisabled ? 0.4f : 0f)) + .render(); + + LiquidGlassUniforms.get().tryApplyBlur(context); + + TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer; + int i = ColorHelper.withAlpha(1.0f, pressableWidget.active ? -1 : -6250336); + drawMessage(context, textRenderer, i); + // context.drawCenteredTextWithShadow(textRenderer, buttonWidget.getMessage(), buttonWidget.getX() + buttonWidget.getWidth() / 2, buttonWidget.getY() + (buttonWidget.getHeight() - 8) / 2, finalColor); + + ci.cancel(); + } + } + } + + @Override + public void reglass$setIsDragging(boolean isDragging) { + this.reglass$isDragging = isDragging; + } + + @Override + public boolean reglass$isDragging() { + return reglass$isDragging; + } +} diff --git a/src/main/resources/reglass.mixins.json b/src/main/resources/reglass.mixins.json index a90c76e..bc5d299 100644 --- a/src/main/resources/reglass.mixins.json +++ b/src/main/resources/reglass.mixins.json @@ -24,6 +24,7 @@ "client.ScreenMixin", "client.TitleScreenMixin", "logical.GameRendererMixin", - "widgets.DrawContextMixin" + "widgets.ClickableWidgetMixin", + "widgets.PressableWidgetMixin" ] } \ No newline at end of file