diff --git a/platforms/common/src/main/java/dynamic_fps/impl/DynamicFPSMod.java b/platforms/common/src/main/java/dynamic_fps/impl/DynamicFPSMod.java index b9fd445d..5812296a 100644 --- a/platforms/common/src/main/java/dynamic_fps/impl/DynamicFPSMod.java +++ b/platforms/common/src/main/java/dynamic_fps/impl/DynamicFPSMod.java @@ -6,6 +6,7 @@ import dynamic_fps.impl.config.Config; import dynamic_fps.impl.config.DynamicFPSConfig; import dynamic_fps.impl.config.option.GraphicsState; +import dynamic_fps.impl.feature.state.ClickIgnoreHandler; import dynamic_fps.impl.service.ModCompat; import dynamic_fps.impl.feature.battery.BatteryToast; import dynamic_fps.impl.feature.battery.BatteryTracker; @@ -46,6 +47,7 @@ public class DynamicFPSMod { private static final Minecraft minecraft = Minecraft.getInstance(); private static @Nullable WindowObserver window; + private static @Nullable ClickIgnoreHandler clickHandler; private static long lastRender; @@ -143,6 +145,8 @@ public static void toggleForceLowFPS() { public static void setWindow(long address) { IdleHandler.setWindow(address); window = new WindowObserver(address); + + initClickHandler(); } public static boolean checkForRender() { @@ -198,6 +202,7 @@ public static void onBatteryStatusChanged(State before, State after) { // Internal logic private static void doInit() { + initClickHandler(); SmoothVolumeHandler.init(); if (!BatteryTracker.isFeatureEnabled()) { @@ -214,6 +219,16 @@ private static void doInit() { } } + private static void initClickHandler() { + if (window == null || clickHandler != null) { + return; + } + + + if (ClickIgnoreHandler.isFeatureActive()) { + clickHandler = new ClickIgnoreHandler(window.address()); + } + } private static void showNotification(String titleTranslationKey, String iconPath) { if (!DynamicFPSConfig.INSTANCE.batteryTracker().notifications()) { return; diff --git a/platforms/common/src/main/java/dynamic_fps/impl/compat/ClothConfig.java b/platforms/common/src/main/java/dynamic_fps/impl/compat/ClothConfig.java index b1cbf483..0fa8a0be 100644 --- a/platforms/common/src/main/java/dynamic_fps/impl/compat/ClothConfig.java +++ b/platforms/common/src/main/java/dynamic_fps/impl/compat/ClothConfig.java @@ -11,6 +11,7 @@ import dynamic_fps.impl.config.Config; import dynamic_fps.impl.config.option.IdleCondition; import dynamic_fps.impl.util.Components; +import dynamic_fps.impl.config.option.IgnoreInitialClick; import dynamic_fps.impl.util.VariableStepTransformer; import me.shedaniel.clothconfig2.api.ConfigBuilder; import me.shedaniel.clothconfig2.api.ConfigCategory; @@ -63,6 +64,19 @@ public static Screen genConfigScreen(Screen parent) { .build() ); + misc.add( + entryBuilder.startEnumSelector( + Components.translatable("config", "ignore_initial_click"), + IgnoreInitialClick.class, + config.ignoreInitialClick() + ) + .setDefaultValue(defaultConfig.ignoreInitialClick()) + .setSaveConsumer(config::setIgnoreInitialClick) + .setEnumNameProvider(ClothConfig::ignoreInitialClickMessage) + .setTooltip(Components.translatable("config", "ignore_initial_click_tooltip")) + .build() + ); + general.addEntry(misc.build()); SubCategoryBuilder idle = entryBuilder.startSubCategory(Components.translatable("config", "feature.idle")); @@ -377,6 +391,10 @@ private static Component volumeMultiplierMessage(int value) { return Components.literal(Integer.toString(value) + "%"); } + public static Component ignoreInitialClickMessage(Enum state) { + return Components.translatable("config", "ignore_initial_click_" + state.toString().toLowerCase(Locale.ROOT)); + } + public static Component IdleConditionMessage(Enum state) { return Components.translatable("config", "idle_condition_" + state.toString().toLowerCase(Locale.ROOT)); } diff --git a/platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java b/platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java index c7aa65a7..593dc41f 100644 --- a/platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java +++ b/platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java @@ -4,10 +4,12 @@ import com.google.gson.annotations.SerializedName; import dynamic_fps.impl.PowerState; +import dynamic_fps.impl.config.option.IgnoreInitialClick; public final class DynamicFPSConfig { private boolean enabled; private boolean uncapMenuFrameRate; + private IgnoreInitialClick ignoreInitialClick; private IdleConfig idle; private BatteryTrackerConfig batteryTracker; private VolumeTransitionConfig volumeTransitionSpeed; @@ -65,6 +67,14 @@ public void setUncapMenuFrameRate(boolean value) { this.uncapMenuFrameRate = value; } + public IgnoreInitialClick ignoreInitialClick() { + return this.ignoreInitialClick; + } + + public void setIgnoreInitialClick(IgnoreInitialClick value) { + this.ignoreInitialClick = value; + } + public boolean downloadNatives() { return this.downloadNatives; } diff --git a/platforms/common/src/main/java/dynamic_fps/impl/config/option/IgnoreInitialClick.java b/platforms/common/src/main/java/dynamic_fps/impl/config/option/IgnoreInitialClick.java new file mode 100644 index 00000000..cd27941f --- /dev/null +++ b/platforms/common/src/main/java/dynamic_fps/impl/config/option/IgnoreInitialClick.java @@ -0,0 +1,7 @@ +package dynamic_fps.impl.config.option; + +public enum IgnoreInitialClick { + DISABLED, + IN_WORLD, + CONSTANT; +} diff --git a/platforms/common/src/main/java/dynamic_fps/impl/feature/state/ClickIgnoreHandler.java b/platforms/common/src/main/java/dynamic_fps/impl/feature/state/ClickIgnoreHandler.java new file mode 100644 index 00000000..81d0daab --- /dev/null +++ b/platforms/common/src/main/java/dynamic_fps/impl/feature/state/ClickIgnoreHandler.java @@ -0,0 +1,69 @@ +package dynamic_fps.impl.feature.state; + +import dynamic_fps.impl.config.DynamicFPSConfig; +import dynamic_fps.impl.config.option.IgnoreInitialClick; +import net.minecraft.client.Minecraft; +import org.lwjgl.glfw.GLFW; +import org.lwjgl.glfw.GLFWMouseButtonCallback; +import org.lwjgl.glfw.GLFWWindowFocusCallback; + +import java.time.Instant; + +public class ClickIgnoreHandler { + private final long address; + private long focusedAt; + + private final GLFWWindowFocusCallback previousFocusCallback; + private final GLFWMouseButtonCallback previousClickCallback; + + private static final Minecraft MINECRAFT = Minecraft.getInstance(); + + public ClickIgnoreHandler(long address) { + this.address = address; + + this.previousFocusCallback = GLFW.glfwSetWindowFocusCallback(this.address, this::onFocusChanged); + this.previousClickCallback = GLFW.glfwSetMouseButtonCallback(this.address, this::onMouseClicked); + } + + public static boolean isFeatureActive() { + return DynamicFPSConfig.INSTANCE.ignoreInitialClick() != IgnoreInitialClick.DISABLED; + } + + private boolean shouldIgnoreClick() { + IgnoreInitialClick config = DynamicFPSConfig.INSTANCE.ignoreInitialClick(); + + if (config == IgnoreInitialClick.DISABLED) { + return false; + } + + if (config == IgnoreInitialClick.IN_WORLD && MINECRAFT.screen != null) { + return false; + } + + return this.focusedAt + 20 >= Instant.now().toEpochMilli(); + } + + private void onFocusChanged(long address, boolean focused) { + if (this.isCurrentWindow(address) && focused) { + this.focusedAt = Instant.now().toEpochMilli(); + } + + if (this.previousFocusCallback != null) { + this.previousFocusCallback.invoke(address, focused); + } + } + + private void onMouseClicked(long window, int button, int action, int mods) { + if (this.isCurrentWindow(window) && shouldIgnoreClick()) { + return; + } + + if (this.previousClickCallback != null) { + this.previousClickCallback.invoke(window, button, action, mods); + } + } + + private boolean isCurrentWindow(long address) { + return address == this.address; + } +} diff --git a/platforms/common/src/main/java/dynamic_fps/impl/feature/state/WindowObserver.java b/platforms/common/src/main/java/dynamic_fps/impl/feature/state/WindowObserver.java index 46c29b4c..a1885449 100644 --- a/platforms/common/src/main/java/dynamic_fps/impl/feature/state/WindowObserver.java +++ b/platforms/common/src/main/java/dynamic_fps/impl/feature/state/WindowObserver.java @@ -1,12 +1,17 @@ package dynamic_fps.impl.feature.state; +import dynamic_fps.impl.util.Logging; +import net.minecraft.client.Minecraft; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWCursorEnterCallback; +import org.lwjgl.glfw.GLFWMouseButtonCallback; import org.lwjgl.glfw.GLFWWindowFocusCallback; import org.lwjgl.glfw.GLFWWindowIconifyCallback; import dynamic_fps.impl.DynamicFPSMod; +import java.time.Instant; + public class WindowObserver { private final long address; diff --git a/platforms/common/src/main/resources/assets/dynamic_fps/data/default_config.json b/platforms/common/src/main/resources/assets/dynamic_fps/data/default_config.json index 36ccd02d..14bd6281 100644 --- a/platforms/common/src/main/resources/assets/dynamic_fps/data/default_config.json +++ b/platforms/common/src/main/resources/assets/dynamic_fps/data/default_config.json @@ -1,6 +1,7 @@ { "enabled": true, "uncap_menu_frame_rate": false, + "ignore_initial_click": "disabled", "idle": { "timeout": 300, "condition": "vanilla" diff --git a/platforms/common/src/main/resources/assets/dynamic_fps/lang/en_us.json b/platforms/common/src/main/resources/assets/dynamic_fps/lang/en_us.json index aa50b98b..f1d811ed 100644 --- a/platforms/common/src/main/resources/assets/dynamic_fps/lang/en_us.json +++ b/platforms/common/src/main/resources/assets/dynamic_fps/lang/en_us.json @@ -39,6 +39,13 @@ "config.dynamic_fps.uncap_menu_frame_rate": "Uncap Menu FPS", "config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Remove the 60 FPS limit in the main menu.", + "config.dynamic_fps.ignore_initial_click": "Ignore Initial Click", + "config.dynamic_fps.ignore_initial_click_tooltip": "Ignore accidental input when clicking on the unfocused game window.", + + "config.dynamic_fps.ignore_initial_click_disabled": "Disabled", + "config.dynamic_fps.ignore_initial_click_in_world": "In World", + "config.dynamic_fps.ignore_initial_click_constant": "Constant", + "config.dynamic_fps.battery_tracker": "Battery Tracking", "config.dynamic_fps.battery_tracker_tooltip": "Toggle all battery-related features on or off.",