package xapi.ui.api.event;
import xapi.event.api.EventManager;
import xapi.event.api.HasBeforeAfter;
import xapi.event.api.IsEvent;
import xapi.event.api.IsEventType;
import xapi.event.impl.EventTypes;
import xapi.fu.Filter.Filter2;
import xapi.fu.Out3;
import xapi.model.X_Model;
import xapi.ui.api.UiElement;
import xapi.ui.service.UiService;
import javax.validation.constraints.NotNull;
/**
* Created by James X. Nelson (james @wetheinter.net) on 7/17/16.
*/
public class UiEventManager <Node, Ui extends UiElement<Node, ? extends Node, Ui>> extends EventManager {
public class UiEventChain {
Ui self;
UiEventChain next;
UiEventChain prev;
public UiEventChain(Ui source) {
self = source;
}
}
private final UiService<Node, Ui> service;
public UiEventManager(UiService<Node, Ui> service) {
this.service = service;
}
@Override
public boolean fireEvent(@NotNull IsEvent<?> event) {
if (event instanceof UiEvent) {
// When we are dealing with a UiEvent, we want to run a capture/bubble phase over ancestors.
return fireUiEvent((UiEvent<?, Node, Ui>)event);
} else {
return super.fireEvent(event);
}
}
public <Payload> boolean fireUiEvent(Ui node, IsEventType type, Payload payload) {
UiEventContext<Payload, Node, Ui> context = newContext(node, type, payload);
UiEvent<Payload, Node, Ui> event = createEvent(type, context);
return fireUiEvent(event);
}
protected <Payload> UiEventContext<Payload,Node,Ui> newContext(Ui node, IsEventType type, Payload payload) {
final UiEventContext<Payload, Node, Ui> ctx = X_Model.create(UiEventContext.class);
ctx.setEventType(type.getEventType());
ctx.setNativeEventTarget(node.element());
ctx.setNativeType(type.getEventType());
ctx.setPayload(payload);
ctx.setSource(node);
return ctx;
}
public <Payload> boolean fireUiEvent(Node nativeNode, String nativeType, Payload payload) {
Ui ui = service.findContainer(nativeNode);
if (ui == null) {
throw new IllegalStateException("Unable to find registered container for " + nativeNode + " in " + service.debugDump());
}
final IsEventType type = service.convertType(nativeType);
UiEventContext<Payload, Node, Ui> context = newContext(ui, type, payload);
context.setNativeEventTarget(nativeNode);
context.setNativeType(nativeType);
UiEvent<Payload, Node, Ui> event = createEvent(type, context);
return fireUiEvent(event);
}
protected <Payload> UiEvent<Payload,Node,Ui> createEvent(IsEventType type, UiEventContext<Payload, Node, Ui> context) {
if (type instanceof EventTypes) {
switch ((EventTypes)type) {
case Click:
return (UiClickEvent<Payload, Node, Ui>)()->context;
case LongClick:
return (UiLongClickEvent<Payload, Node, Ui>)()->context;
case DoubleClick:
return (UiDoubleClickEvent<Payload, Node, Ui>)()->context;
case Move:
return (UiMoveEvent<Payload, Node, Ui>)()->context;
case Scroll:
return (UiScrollEvent<Payload, Node, Ui>)()->context;
case Hover:
return (UiHoverEvent<Payload, Node, Ui>)()->context;
case Unhover:
return (UiUnhoverEvent<Payload, Node, Ui>)()->context;
case Focus:
return (UiFocusEvent<Payload, Node, Ui>)()->context;
case Blur:
return (UiBlurEvent<Payload, Node, Ui>)()->context;
case DragStart:
return (UiDragStartEvent<Payload, Node, Ui>)()->context;
case DragEnd:
return (UiDragEndEvent<Payload, Node, Ui>)()->context;
case DragMove:
return (UiDragMoveEvent<Payload, Node, Ui>)()->context;
case Resize:
return (UiResizeEvent<Payload, Node, Ui>)()->context;
case Attach:
return (UiAttachEvent<Payload, Node, Ui>)()->context;
case Detach:
return (UiDetachEvent<Payload, Node, Ui>)()->context;
case Select:
// Gwt has problems inferring types on overloaded functional interfaces...
return new UiSelectEvent<Payload, Node, Ui>() {
@Override
public UiEventContext<Payload, Node, Ui> getSource() {
return context;
}
};
case Unselect:
return new UiUnselectEvent<Payload, Node, Ui>() {
@Override
public UiEventContext<Payload, Node, Ui> getSource() {
return context;
}
};
case Change:
Payload payload = context.getPayload();
assert payload instanceof HasBeforeAfter : "Change events must supply a payload which implements HasBeforeAfter";
HasBeforeAfter values = (HasBeforeAfter)payload;
return new UiChangeEvent<Payload, Node, Ui, Object>() {
@Override
public Out3<UiEventContext<Payload, Node, Ui>, Object, Object> values() {
return Out3.out3(context, ()->values.getBefore(), ()->values.getAfter());
}
};
case Undo:
payload = context.getPayload();
assert payload instanceof HasBeforeAfter : "Undo events must supply a payload which implements HasBeforeAfter";
values = (HasBeforeAfter)payload;
return new UiUndoEvent<Payload, Node, Ui, Object>() {
@Override
public Out3<UiEventContext<Payload, Node, Ui>, Object, Object> values() {
return Out3.out3(context, ()->values.getBefore(), ()->values.getAfter());
}
};
}
}
// No pre-canned event type to support... create an anonymous event class
return new UiEvent<Payload, Node, Ui>() {
@Override
public UiEventContext<Payload, Node, Ui> getSource() {
return context;
}
@Override
public IsEventType getType() {
return type;
}
};
}
protected boolean fireUiEvent(UiEvent<?, Node, Ui> event) {
final UiEventContext<?, Node, Ui> context = event.getSource();
if (context == null) {
throw new IllegalStateException("UiEvent context not initialized: " + event);
}
Ui source = context.getSourceElement();
if (source == null) {
throw new IllegalStateException("UiEvent source not initialized: " + event);
}
UiEventChain captureChain = buildUiChain(event, new UiEventChain(source), UiElement::handlesCapture);
while (captureChain != null) {
if (!captureChain.self.fireEventCapture(event)) {
return false;
}
captureChain = captureChain.next;
}
UiEventChain bubbleChain = new UiEventChain(source);
buildUiChain(event, bubbleChain, UiElement::handlesCapture);
while (bubbleChain != null) {
if (!bubbleChain.self.fireEventBubble(event)) {
return false;
}
bubbleChain = captureChain.prev;
}
return true;
}
protected UiEventChain buildUiChain(UiEvent<?, Node, Ui> event, UiEventChain node,
Filter2<Object, UiElement<Node, ? extends Node, Ui>, UiEvent<?, Node, Ui>> filter) {
Ui ui = node.self;
while (ui.getParent() != null) {
Ui parent = ui.getParent();
if (filter.filter2(parent, event)) {
UiEventChain prev = new UiEventChain(parent);
node.prev = prev;
prev.next = node;
node = prev;
}
ui = parent;
}
return node;
}
}