Hey @Dunhou,
I am still not 100% clear about what you are trying to do. But I guess what you want to do is distinguish a single-drag -click, i.e., the user is dragging something, from a single click. The issue with that is that we are in your code inside a while loop which just polls the input state as fast as it can and not in message stream, where we only get events for state changes. So, this means unless there is Speedy Gonzales at the mouse, even the quickest of single clicks will produce more than one iteration in the loop.
What is still unclear to me why you are doing all this, as knowing that the mouse is outside of the UA does not mean that we know if the user dropped the payload on an object. But this is how I would solve distinguishing a 'light click' (a single click) from a drag event.
A cleaner solution might be to let the convenance function InputEvent be a convenance function and move to the source Message. There you should be issue start and stop events for drag operations. But since you want to start it yourself, we are sort of in a pickle. I would have to play around a bit with the code to see if there is a better way with Message,
Cheers,
Ferdinand
def InputEvent(self, msg: c4d.BaseContainer) -> bool:
"""Called by Cinema 4D when the user area receives input events.
Here we implement creating drag events when the user drags from this user area. The type of
drag event which is initiated is determined by the drag type selected in the combo box
of the dialog.
"""
# When this is not a left mouse button event on this user area, we just get out without
# consuming the event (by returning False).
if msg[c4d.BFM_INPUT_DEVICE] != c4d.BFM_INPUT_MOUSE and msg[c4d.BFM_INPUT_CHANNEL] != c4d.BFM_INPUT_MOUSELEFT:
return False
dragType: int = self._host.GetInt32(self._host.ID_DRAG_TYPE)
mx = int(msg[c4d.BFM_INPUT_X])
my = int(msg[c4d.BFM_INPUT_Y])
mx -= self.Local2Global()["x"]
my -= self.Local2Global()["y"]
state = c4d.BaseContainer()
self.MouseDragStart(c4d.BFM_INPUT_MOUSELEFT,mx,my,c4d.MOUSEDRAGFLAGS_DONTHIDEMOUSE|c4d.MOUSEDRAGFLAGS_NOMOVE)
lastPos: tuple[float, float] | None = None
while True:
res, dx, dy, channels = self.MouseDrag()
if res != c4d.MOUSEDRAGRESULT_CONTINUE:
break
self.GetInputState(c4d.BFM_INPUT_MOUSE, c4d.BFM_INPUT_MOUSELEFT, state)
# This is how I debugged this, GetContainerTreeString (in the beta it might be already
# contained) is a feature of a future version of the SDK.
# print(f"{mxutils.GetContainerTreeString(state, 'BFM_')}")
# State: Root (None , id = -1):
# ├── BFM_INPUT_QUALIFIER (DTYPE_LONG): 0
# ├── BFM_INPUT_MODIFIERS (DTYPE_LONG): 0
# ├── BFM_INPUT_DEVICE (DTYPE_LONG): 1836021107
# ├── BFM_INPUT_CHANNEL (DTYPE_LONG): 1
# ├── BFM_INPUT_VALUE (DTYPE_LONG): 1
# ├── BFM_INPUT_VALUE_REAL (DTYPE_REAL): 0.0001
# ├── BFM_INPUT_X (DTYPE_REAL): 203.13671875
# ├── BFM_INPUT_Y (DTYPE_REAL): 88.0390625
# ├── BFM_INPUT_Z (DTYPE_REAL): 0.0
# ├── BFM_INPUT_ORIENTATION (DTYPE_REAL): 0.0
# ├── 1768977011 (DTYPE_REAL): 1.0
# ├── BFM_INPUT_TILT (DTYPE_REAL): 0.0
# ├── BFM_INPUT_FINGERWHEEL (DTYPE_REAL): 0.0
# ├── BFM_INPUT_P_ROTATION (DTYPE_REAL): 0.0
# └── BFM_INPUT_DOUBLECLICK (DTYPE_LONG): 0
# I.e., we are unfortunately neither being issued a BFM_DRAGSTART nor an
# c4d.BFM_INTERACTSTART, I assume both or only emitted in the direct Message() loop.
# But we can write code like this.
# if state[c4d.BFM_INPUT_DOUBLECLICK]:
# print(f"Double click detected at {mx}, {my}")
# break
# elif state[c4d.BFM_INPUT_VALUE] != 1:
# print(f"Mouse button not pressed anymore at {mx}, {my}")
# break
# else:
# print(f"Non double click at {mx}, {my}")
# The issue with this is that we are here just in a loop polling the current left button
# state, not inside a message function where we get a state stream. So, for a single
# click, we end up with somewhat like this, and here I made sure to click really fast
# Non double click at 96.8515625, 58.37109375
# Non double click at 96.8515625, 58.37109375
# Non double click at 96.8515625, 58.37109375
# Non double click at 96.8515625, 58.37109375
# Mouse button not pressed anymore at 96.8515625, 58.37109375
# And this is a short drag event.
# Non double click at 84.875, 56.5859375
# Non double click at 84.875, 56.5859375
# Non double click at 84.875, 56.5859375
# Non double click at 84.875, 56.5859375
# Non double click at 84.59765625, 56.5859375
# Non double click at 83.49609375, 56.94921875
# Non double click at 83.49609375, 56.94921875
# Non double click at 82.39453125, 57.3125
# Non double click at 82.39453125, 57.3125
# Non double click at 80.74609375, 58.1328125
# Non double click at 80.74609375, 58.1328125
# Non double click at 77.7265625, 58.6328125
# ...
# Non double click at -8.35546875, 80.16796875
# Non double click at -8.35546875, 80.16796875
# Non double click at -8.35546875, 80.16796875
# Mouse button not pressed anymore at -8.35546875, 80.16796875
# So they are very similar, and we cannot go by the pure logic "when the coordinates
# do not change, we are in a drag event" because this is not an event stream, i.e., we
# might poll the same input state multiple times, depending on how fast our #while loop
# runs.
# But what we could do, is postpone all actions until we see a change. In extreme cases,
# where the user is swiping very fast with the mouse and then clicks on a tile, this might
# fail.
mx -= dx
my -= dy
currentPos: tuple[float, float] = (mx, my)
if lastPos is None and currentPos != lastPos:
lastPos = currentPos
# The mouse is not being pressed anymore.
if not state[c4d.BFM_INPUT_VALUE]:
if currentPos != lastPos:
print("Drag event")
else:
print("Click event")
break
return True
Click event
Drag event
Click event
Click event
Click event
Drag event