Skip to content
This repository was archived by the owner on Aug 13, 2026. It is now read-only.

Commit e26780a

Browse files
authored
Interaction delay improvements. (#18)
- onInteractionDelay for ViewConfig - interactionDelay per Element - onInteractionDelay per Element
1 parent 4965b75 commit e26780a

11 files changed

Lines changed: 174 additions & 1 deletion

File tree

‎common/src/main/java/net/infumia/frame/element/Element.java‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package net.infumia.frame.element;
22

3+
import java.time.Duration;
34
import java.util.Collection;
45
import java.util.concurrent.CompletableFuture;
6+
import java.util.function.Consumer;
57
import java.util.function.Predicate;
8+
import net.infumia.frame.context.element.ContextElementClick;
69
import net.infumia.frame.context.element.ContextElementRender;
710
import net.infumia.frame.service.ConsumerService;
811
import net.infumia.frame.state.State;
@@ -16,6 +19,12 @@ public interface Element {
1619

1720
boolean updateOnClick();
1821

22+
@Nullable
23+
Duration interactionDelay();
24+
25+
@Nullable
26+
Consumer<ContextElementClick> onInteractionDelay();
27+
1928
@Nullable
2029
Predicate<ContextElementRender> displayIf();
2130

‎common/src/main/java/net/infumia/frame/element/ElementBuilder.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package net.infumia.frame.element;
22

3+
import java.time.Duration;
34
import java.util.function.BooleanSupplier;
5+
import java.util.function.Consumer;
46
import java.util.function.Predicate;
7+
import net.infumia.frame.context.element.ContextElementClick;
58
import net.infumia.frame.context.element.ContextElementRender;
69
import net.infumia.frame.state.State;
710
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
812

913
public interface ElementBuilder {
1014
@NotNull
@@ -25,6 +29,12 @@ public interface ElementBuilder {
2529
@NotNull
2630
ElementBuilder updateOnClick(boolean updateOnClick);
2731

32+
@NotNull
33+
ElementBuilder interactionDelay(@Nullable Duration interactionDelay);
34+
35+
@NotNull
36+
ElementBuilder onInteractionDelay(@NotNull Consumer<ContextElementClick> onInteractionDelay);
37+
2838
@NotNull
2939
ElementBuilder updateOnStateChange(@NotNull State<?> state, @NotNull State<?>... otherStates);
3040

‎common/src/main/java/net/infumia/frame/view/config/ViewConfig.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import java.time.Duration;
44
import java.util.Collection;
55
import java.util.Map;
6+
import java.util.function.Consumer;
7+
import net.infumia.frame.context.view.ContextClick;
68
import net.infumia.frame.type.InvType;
79
import net.infumia.frame.view.config.option.ViewConfigOption;
810
import org.jetbrains.annotations.NotNull;
@@ -27,6 +29,9 @@ public interface ViewConfig {
2729
@Nullable
2830
Duration interactionDelay();
2931

32+
@Nullable
33+
Consumer<ContextClick> onInteractionDelay();
34+
3035
@NotNull
3136
@UnmodifiableView
3237
Map<ViewConfigOption<Object>, Object> options();

‎core/src/main/java/net/infumia/frame/config/ViewConfigBuilderImpl.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.util.HashMap;
77
import java.util.HashSet;
88
import java.util.Map;
9+
import java.util.function.Consumer;
10+
import net.infumia.frame.context.view.ContextClick;
911
import net.infumia.frame.type.InvType;
1012
import net.infumia.frame.util.Preconditions;
1113
import net.infumia.frame.util.Reflection;
@@ -32,6 +34,7 @@ final class ViewConfigBuilderImpl implements ViewConfigBuilderRich {
3234
String[] layout;
3335
Duration updateInterval;
3436
Duration interactionDelay;
37+
Consumer<ContextClick> onInteractionDelay;
3538

3639
ViewConfigBuilderImpl(@NotNull final ViewConfigImpl config) {
3740
this.options = new HashMap<>(config.options);
@@ -42,6 +45,7 @@ final class ViewConfigBuilderImpl implements ViewConfigBuilderRich {
4245
this.layout = config.layout;
4346
this.updateInterval = config.updateInterval;
4447
this.interactionDelay = config.interactionDelay;
48+
this.onInteractionDelay = config.onInteractionDelay;
4549
}
4650

4751
ViewConfigBuilderImpl() {
@@ -84,6 +88,12 @@ public Duration interactionDelay() {
8488
return this.interactionDelay;
8589
}
8690

91+
@Nullable
92+
@Override
93+
public Consumer<ContextClick> onInteractionDelay() {
94+
return this.onInteractionDelay;
95+
}
96+
8797
@NotNull
8898
@Override
8999
@UnmodifiableView

‎core/src/main/java/net/infumia/frame/config/ViewConfigImpl.java‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import java.util.Collections;
66
import java.util.Map;
77
import java.util.Optional;
8+
import java.util.function.Consumer;
9+
import net.infumia.frame.context.view.ContextClick;
810
import net.infumia.frame.type.InvType;
911
import net.infumia.frame.view.config.ViewConfigModifier;
1012
import net.infumia.frame.view.config.option.ViewConfigOption;
@@ -22,6 +24,7 @@ final class ViewConfigImpl implements ViewConfigRich {
2224
final String[] layout;
2325
final Duration updateInterval;
2426
final Duration interactionDelay;
27+
final Consumer<ContextClick> onInteractionDelay;
2528

2629
ViewConfigImpl(@NotNull final ViewConfigBuilderImpl builder) {
2730
this.options = Collections.unmodifiableMap(builder.options);
@@ -32,6 +35,7 @@ final class ViewConfigImpl implements ViewConfigRich {
3235
this.layout = builder.layout;
3336
this.updateInterval = builder.updateInterval;
3437
this.interactionDelay = builder.interactionDelay;
38+
this.onInteractionDelay = builder.onInteractionDelay;
3539
}
3640

3741
@Nullable
@@ -75,6 +79,12 @@ public Duration interactionDelay() {
7579
return this.interactionDelay;
7680
}
7781

82+
@Nullable
83+
@Override
84+
public Consumer<ContextClick> onInteractionDelay() {
85+
return this.onInteractionDelay;
86+
}
87+
7888
@NotNull
7989
@Override
8090
@UnmodifiableView

‎core/src/main/java/net/infumia/frame/element/ElementBuilderImpl.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,27 @@
11
package net.infumia.frame.element;
22

3+
import java.time.Duration;
34
import java.util.Collection;
45
import java.util.Collections;
56
import java.util.HashSet;
67
import java.util.function.BooleanSupplier;
8+
import java.util.function.Consumer;
79
import java.util.function.Predicate;
810
import net.infumia.frame.context.ContextBase;
11+
import net.infumia.frame.context.element.ContextElementClick;
912
import net.infumia.frame.context.element.ContextElementRender;
1013
import net.infumia.frame.state.State;
1114
import org.jetbrains.annotations.NotNull;
15+
import org.jetbrains.annotations.Nullable;
1216

1317
public class ElementBuilderImpl implements ElementBuilderRich {
1418

1519
Element root;
1620
boolean cancelOnClick;
1721
boolean closeOnClick;
1822
boolean updateOnClick;
23+
Duration interactionDelay;
24+
Consumer<ContextElementClick> onInteractionDelay;
1925
Predicate<ContextElementRender> displayIf;
2026
Collection<State<?>> updateOnStateChange;
2127
Collection<State<?>> updateOnStateAccess;
@@ -25,6 +31,8 @@ public ElementBuilderImpl(@NotNull final Element element) {
2531
this.cancelOnClick = element.cancelOnClick();
2632
this.closeOnClick = element.closeOnClick();
2733
this.updateOnClick = element.updateOnClick();
34+
this.interactionDelay = element.interactionDelay();
35+
this.onInteractionDelay = element.onInteractionDelay();
2836
this.displayIf = element.displayIf();
2937
this.updateOnStateChange = element.updateOnStateChange();
3038
this.updateOnStateAccess = element.updateOnStateAccess();
@@ -84,6 +92,22 @@ public ElementBuilder updateOnClick(final boolean updateOnClick) {
8492
return this;
8593
}
8694

95+
@NotNull
96+
@Override
97+
public ElementBuilder interactionDelay(@Nullable final Duration interactionDelay) {
98+
this.interactionDelay = interactionDelay;
99+
return this;
100+
}
101+
102+
@NotNull
103+
@Override
104+
public ElementBuilder onInteractionDelay(
105+
@NotNull final Consumer<ContextElementClick> onInteractionDelay
106+
) {
107+
this.onInteractionDelay = onInteractionDelay;
108+
return this;
109+
}
110+
87111
@NotNull
88112
@Override
89113
public ElementBuilder updateOnStateChange(

‎core/src/main/java/net/infumia/frame/element/ElementImpl.java‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package net.infumia.frame.element;
22

3+
import java.time.Duration;
34
import java.util.Collection;
45
import java.util.UUID;
56
import java.util.concurrent.CompletableFuture;
7+
import java.util.function.Consumer;
68
import java.util.function.Predicate;
79
import net.infumia.frame.context.ContextBase;
10+
import net.infumia.frame.context.element.ContextElementClick;
811
import net.infumia.frame.context.element.ContextElementRender;
912
import net.infumia.frame.pipeline.executor.PipelineExecutorElement;
1013
import net.infumia.frame.service.ConsumerService;
@@ -20,6 +23,8 @@ public class ElementImpl implements ElementRich {
2023
final boolean cancelOnClick;
2124
final boolean updateOnClick;
2225
final boolean closeOnClick;
26+
final Duration interactionDelay;
27+
final Consumer<ContextElementClick> onInteractionDelay;
2328
final Predicate<ContextElementRender> displayIf;
2429
final Collection<State<?>> updateOnStateChange;
2530
final Collection<State<?>> updateOnStateAccess;
@@ -33,6 +38,8 @@ public ElementImpl(
3338
this.cancelOnClick = builder.cancelOnClick;
3439
this.updateOnClick = builder.updateOnClick;
3540
this.closeOnClick = builder.closeOnClick;
41+
this.interactionDelay = builder.interactionDelay;
42+
this.onInteractionDelay = builder.onInteractionDelay;
3643
this.displayIf = builder.displayIf;
3744
this.updateOnStateChange = builder.updateOnStateChange;
3845
this.updateOnStateAccess = builder.updateOnStateAccess;
@@ -98,6 +105,18 @@ public boolean updateOnClick() {
98105
return this.updateOnClick;
99106
}
100107

108+
@Nullable
109+
@Override
110+
public Duration interactionDelay() {
111+
return this.interactionDelay;
112+
}
113+
114+
@Nullable
115+
@Override
116+
public Consumer<ContextElementClick> onInteractionDelay() {
117+
return this.onInteractionDelay;
118+
}
119+
101120
@Nullable
102121
@Override
103122
public Predicate<ContextElementRender> displayIf() {

‎core/src/main/java/net/infumia/frame/metadata/MetadataKeyHolder.java‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,9 @@ public interface MetadataKeyHolder {
2929
new TypeToken<Map<String, Long>>() {},
3030
"last-interaction"
3131
);
32+
33+
TypedKey<Map<String, Long>> LAST_INTERACTION_ELEMENT = TypedKey.of(
34+
new TypeToken<Map<String, Long>>() {},
35+
"last-interaction-element"
36+
);
3237
}

‎core/src/main/java/net/infumia/frame/pipeline/holder/PipelineHolderElement.java‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import net.infumia.frame.pipeline.service.element.ServiceClick;
1111
import net.infumia.frame.pipeline.service.element.ServiceClickCancelOnClick;
1212
import net.infumia.frame.pipeline.service.element.ServiceClickCloseOnClick;
13+
import net.infumia.frame.pipeline.service.element.ServiceClickInteractionDelay;
1314
import net.infumia.frame.pipeline.service.element.ServiceClickLogging;
1415
import net.infumia.frame.pipeline.service.element.ServiceClickUpdateOnClick;
1516
import net.infumia.frame.pipeline.service.element.ServiceRender;
@@ -42,6 +43,7 @@ public final class PipelineHolderElement implements Cloned<PipelineHolderElement
4243
.register(ServiceClickCloseOnClick.INSTANCE)
4344
.register(ServiceClickUpdateOnClick.INSTANCE)
4445
.register(ServiceClick.INSTANCE)
46+
.register(ServiceClickInteractionDelay.INSTANCE)
4547
.register(ServiceClickCancelOnClick.INSTANCE),
4648
new PipelineConsumerImpl<>(
4749
new TypeToken<PipelineServiceConsumer<PipelineContextElement.Update>>() {},
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package net.infumia.frame.pipeline.service.element;
2+
3+
import java.time.Duration;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import java.util.function.Consumer;
7+
import net.infumia.frame.context.element.ContextElementClick;
8+
import net.infumia.frame.element.ElementRich;
9+
import net.infumia.frame.metadata.MetadataAccess;
10+
import net.infumia.frame.metadata.MetadataKeyHolder;
11+
import net.infumia.frame.pipeline.PipelineServiceConsumer;
12+
import net.infumia.frame.pipeline.context.PipelineContextElement;
13+
import net.infumia.frame.viewer.Viewer;
14+
import org.jetbrains.annotations.NotNull;
15+
16+
public final class ServiceClickInteractionDelay
17+
implements PipelineServiceConsumer<PipelineContextElement.Click> {
18+
19+
public static final PipelineServiceConsumer<PipelineContextElement.Click> INSTANCE =
20+
new ServiceClickInteractionDelay();
21+
22+
public static final String KEY = "interaction-delay";
23+
24+
@NotNull
25+
@Override
26+
public String key() {
27+
return ServiceClickInteractionDelay.KEY;
28+
}
29+
30+
@Override
31+
public void accept(@NotNull final PipelineContextElement.Click ctx) {
32+
final ContextElementClick context = ctx.context();
33+
final ElementRich element = (ElementRich) context.element();
34+
final Duration delay = element.interactionDelay();
35+
if (delay == null || delay.isZero() || delay.isNegative()) {
36+
return;
37+
}
38+
final Viewer clicker = context.clicker();
39+
final MetadataAccess metadata = clicker.metadata();
40+
Map<String, Long> lastInteractions = metadata.get(
41+
MetadataKeyHolder.LAST_INTERACTION_ELEMENT
42+
);
43+
if (lastInteractions == null) {
44+
lastInteractions = new HashMap<>();
45+
metadata.setFixed(MetadataKeyHolder.LAST_INTERACTION_ELEMENT, lastInteractions);
46+
}
47+
final String key = element.key();
48+
final Long lastInteraction = lastInteractions.get(key);
49+
final long now = System.currentTimeMillis();
50+
if (lastInteraction == null) {
51+
lastInteractions.put(key, now);
52+
return;
53+
}
54+
final long elapsed = now - lastInteraction;
55+
if (elapsed > delay.toMillis()) {
56+
lastInteractions.put(key, now);
57+
} else {
58+
context
59+
.frame()
60+
.logger()
61+
.debug("Click event of viewer '%s' cancelled due to interaction delay.", clicker);
62+
ctx.cancelled(true);
63+
context.cancelled(true);
64+
final Consumer<ContextElementClick> onInteractionDelay = element.onInteractionDelay();
65+
if (onInteractionDelay != null) {
66+
onInteractionDelay.accept(context);
67+
}
68+
}
69+
}
70+
71+
private ServiceClickInteractionDelay() {}
72+
}

0 commit comments

Comments
 (0)