Skip to content

Commit 7634eb7

Browse files
committed
Allow ignoring accidental input clicking on the unfocused window
Signed-off-by: Lilly Rose Berner <lilly@lostluma.net>
1 parent da8db9f commit 7634eb7

File tree

8 files changed

+132
-0
lines changed

8 files changed

+132
-0
lines changed

platforms/common/src/main/java/dynamic_fps/impl/DynamicFPSMod.java

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import dynamic_fps.impl.config.Config;
77
import dynamic_fps.impl.config.DynamicFPSConfig;
88
import dynamic_fps.impl.config.option.GraphicsState;
9+
import dynamic_fps.impl.feature.state.ClickIgnoreHandler;
910
import dynamic_fps.impl.service.ModCompat;
1011
import dynamic_fps.impl.feature.battery.BatteryToast;
1112
import dynamic_fps.impl.feature.battery.BatteryTracker;
@@ -46,6 +47,7 @@ public class DynamicFPSMod {
4647
private static final Minecraft minecraft = Minecraft.getInstance();
4748

4849
private static @Nullable WindowObserver window;
50+
private static @Nullable ClickIgnoreHandler clickHandler;
4951

5052
private static long lastRender;
5153

@@ -143,6 +145,8 @@ public static void toggleForceLowFPS() {
143145
public static void setWindow(long address) {
144146
IdleHandler.setWindow(address);
145147
window = new WindowObserver(address);
148+
149+
initClickHandler();
146150
}
147151

148152
public static boolean checkForRender() {
@@ -198,6 +202,7 @@ public static void onBatteryStatusChanged(State before, State after) {
198202
// Internal logic
199203

200204
private static void doInit() {
205+
initClickHandler();
201206
SmoothVolumeHandler.init();
202207

203208
if (!BatteryTracker.isFeatureEnabled()) {
@@ -214,6 +219,16 @@ private static void doInit() {
214219
}
215220
}
216221

222+
private static void initClickHandler() {
223+
if (window == null || clickHandler != null) {
224+
return;
225+
}
226+
227+
228+
if (ClickIgnoreHandler.isFeatureActive()) {
229+
clickHandler = new ClickIgnoreHandler(window.address());
230+
}
231+
}
217232
private static void showNotification(String titleTranslationKey, String iconPath) {
218233
if (!DynamicFPSConfig.INSTANCE.batteryTracker().notifications()) {
219234
return;

platforms/common/src/main/java/dynamic_fps/impl/compat/ClothConfig.java

+18
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import dynamic_fps.impl.config.Config;
1212
import dynamic_fps.impl.config.option.IdleCondition;
1313
import dynamic_fps.impl.util.Components;
14+
import dynamic_fps.impl.config.option.IgnoreInitialClick;
1415
import dynamic_fps.impl.util.VariableStepTransformer;
1516
import me.shedaniel.clothconfig2.api.ConfigBuilder;
1617
import me.shedaniel.clothconfig2.api.ConfigCategory;
@@ -63,6 +64,19 @@ public static Screen genConfigScreen(Screen parent) {
6364
.build()
6465
);
6566

67+
misc.add(
68+
entryBuilder.startEnumSelector(
69+
Components.translatable("config", "ignore_initial_click"),
70+
IgnoreInitialClick.class,
71+
config.ignoreInitialClick()
72+
)
73+
.setDefaultValue(defaultConfig.ignoreInitialClick())
74+
.setSaveConsumer(config::setIgnoreInitialClick)
75+
.setEnumNameProvider(ClothConfig::ignoreInitialClickMessage)
76+
.setTooltip(Components.translatable("config", "ignore_initial_click_tooltip"))
77+
.build()
78+
);
79+
6680
general.addEntry(misc.build());
6781
SubCategoryBuilder idle = entryBuilder.startSubCategory(Components.translatable("config", "feature.idle"));
6882

@@ -377,6 +391,10 @@ private static Component volumeMultiplierMessage(int value) {
377391
return Components.literal(Integer.toString(value) + "%");
378392
}
379393

394+
public static Component ignoreInitialClickMessage(Enum<IdleCondition> state) {
395+
return Components.translatable("config", "ignore_initial_click_" + state.toString().toLowerCase(Locale.ROOT));
396+
}
397+
380398
public static Component IdleConditionMessage(Enum<IdleCondition> state) {
381399
return Components.translatable("config", "idle_condition_" + state.toString().toLowerCase(Locale.ROOT));
382400
}

platforms/common/src/main/java/dynamic_fps/impl/config/DynamicFPSConfig.java

+10
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44

55
import com.google.gson.annotations.SerializedName;
66
import dynamic_fps.impl.PowerState;
7+
import dynamic_fps.impl.config.option.IgnoreInitialClick;
78

89
public final class DynamicFPSConfig {
910
private boolean enabled;
1011
private boolean uncapMenuFrameRate;
12+
private IgnoreInitialClick ignoreInitialClick;
1113
private IdleConfig idle;
1214
private BatteryTrackerConfig batteryTracker;
1315
private VolumeTransitionConfig volumeTransitionSpeed;
@@ -65,6 +67,14 @@ public void setUncapMenuFrameRate(boolean value) {
6567
this.uncapMenuFrameRate = value;
6668
}
6769

70+
public IgnoreInitialClick ignoreInitialClick() {
71+
return this.ignoreInitialClick;
72+
}
73+
74+
public void setIgnoreInitialClick(IgnoreInitialClick value) {
75+
this.ignoreInitialClick = value;
76+
}
77+
6878
public boolean downloadNatives() {
6979
return this.downloadNatives;
7080
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dynamic_fps.impl.config.option;
2+
3+
public enum IgnoreInitialClick {
4+
DISABLED,
5+
IN_WORLD,
6+
CONSTANT;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package dynamic_fps.impl.feature.state;
2+
3+
import dynamic_fps.impl.config.DynamicFPSConfig;
4+
import dynamic_fps.impl.config.option.IgnoreInitialClick;
5+
import net.minecraft.client.Minecraft;
6+
import org.lwjgl.glfw.GLFW;
7+
import org.lwjgl.glfw.GLFWMouseButtonCallback;
8+
import org.lwjgl.glfw.GLFWWindowFocusCallback;
9+
10+
import java.time.Instant;
11+
12+
public class ClickIgnoreHandler {
13+
private final long address;
14+
private long focusedAt;
15+
16+
private final GLFWWindowFocusCallback previousFocusCallback;
17+
private final GLFWMouseButtonCallback previousClickCallback;
18+
19+
private static final Minecraft MINECRAFT = Minecraft.getInstance();
20+
21+
public ClickIgnoreHandler(long address) {
22+
this.address = address;
23+
24+
this.previousFocusCallback = GLFW.glfwSetWindowFocusCallback(this.address, this::onFocusChanged);
25+
this.previousClickCallback = GLFW.glfwSetMouseButtonCallback(this.address, this::onMouseClicked);
26+
}
27+
28+
public static boolean isFeatureActive() {
29+
return DynamicFPSConfig.INSTANCE.ignoreInitialClick() != IgnoreInitialClick.DISABLED;
30+
}
31+
32+
private boolean shouldIgnoreClick() {
33+
IgnoreInitialClick config = DynamicFPSConfig.INSTANCE.ignoreInitialClick();
34+
35+
if (config == IgnoreInitialClick.DISABLED) {
36+
return false;
37+
}
38+
39+
if (config == IgnoreInitialClick.IN_WORLD && MINECRAFT.screen != null) {
40+
return false;
41+
}
42+
43+
return this.focusedAt + 20 >= Instant.now().toEpochMilli();
44+
}
45+
46+
private void onFocusChanged(long address, boolean focused) {
47+
if (this.isCurrentWindow(address) && focused) {
48+
this.focusedAt = Instant.now().toEpochMilli();
49+
}
50+
51+
if (this.previousFocusCallback != null) {
52+
this.previousFocusCallback.invoke(address, focused);
53+
}
54+
}
55+
56+
private void onMouseClicked(long window, int button, int action, int mods) {
57+
if (this.isCurrentWindow(window) && shouldIgnoreClick()) {
58+
return;
59+
}
60+
61+
if (this.previousClickCallback != null) {
62+
this.previousClickCallback.invoke(window, button, action, mods);
63+
}
64+
}
65+
66+
private boolean isCurrentWindow(long address) {
67+
return address == this.address;
68+
}
69+
}

platforms/common/src/main/java/dynamic_fps/impl/feature/state/WindowObserver.java

+5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package dynamic_fps.impl.feature.state;
22

3+
import dynamic_fps.impl.util.Logging;
4+
import net.minecraft.client.Minecraft;
35
import org.lwjgl.glfw.GLFW;
46
import org.lwjgl.glfw.GLFWCursorEnterCallback;
7+
import org.lwjgl.glfw.GLFWMouseButtonCallback;
58
import org.lwjgl.glfw.GLFWWindowFocusCallback;
69
import org.lwjgl.glfw.GLFWWindowIconifyCallback;
710

811
import dynamic_fps.impl.DynamicFPSMod;
912

13+
import java.time.Instant;
14+
1015
public class WindowObserver {
1116
private final long address;
1217

platforms/common/src/main/resources/assets/dynamic_fps/data/default_config.json

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"enabled": true,
33
"uncap_menu_frame_rate": false,
4+
"ignore_initial_click": "disabled",
45
"idle": {
56
"timeout": 300,
67
"condition": "vanilla"

platforms/common/src/main/resources/assets/dynamic_fps/lang/en_us.json

+7
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@
3939
"config.dynamic_fps.uncap_menu_frame_rate": "Uncap Menu FPS",
4040
"config.dynamic_fps.uncap_menu_frame_rate_tooltip": "Remove the 60 FPS limit in the main menu.",
4141

42+
"config.dynamic_fps.ignore_initial_click": "Ignore Initial Click",
43+
"config.dynamic_fps.ignore_initial_click_tooltip": "Ignore accidental input when clicking on the unfocused game window.",
44+
45+
"config.dynamic_fps.ignore_initial_click_disabled": "Disabled",
46+
"config.dynamic_fps.ignore_initial_click_in_world": "In World",
47+
"config.dynamic_fps.ignore_initial_click_constant": "Constant",
48+
4249
"config.dynamic_fps.battery_tracker": "Battery Tracking",
4350
"config.dynamic_fps.battery_tracker_tooltip": "Toggle all battery-related features on or off.",
4451

0 commit comments

Comments
 (0)