Consider the following toy example:
I have a "dynamical, interactive" graphics primitive like the following:
ClearAll[dynamicPoint];
dynamicPoint[point_List] := DynamicModule[{color = Red},
EventHandler[
Dynamic@{[email protected], color, Point@point},
{"MouseClicked" :> (
If[color === Red,
color = Green, color = Red
]
)}
]
]
If I use the above function instead of the regular primitive Point
, I get a point that changes color when I click on it:
This is all well and fine, but now I want to be able to add more dynamical points like this by clicking on the graphics.
The naive way would be to wrap the whole Graphics
into another EventHandler
with an appropriately set event, like in the following:
DynamicModule[{
allData = <|"NumberOfPoints" -> 1,
"PointsCoordinates" -> {{-1, 0}}|>
},
EventHandler[
Graphics[{
Dynamic@Table[
dynamicPoint[allData["PointsCoordinates"][[pointIndex]]],
{pointIndex, Range@allData["NumberOfPoints"]}
]
},
Frame -> True, PlotRange -> {{-2, 2}, {-2, 2}}
],
{"MouseClicked" :> (
allData["NumberOfPoints"]++;
AppendTo[allData["PointsCoordinates"], MousePosition["Graphics"]]
)},
PassEventsDown -> True
]
]
As you can see, I can now add points, but I lost all the ability to change their colors.
As I understand it, this happens because when I click on an existing point the corresponding change-color event is triggered (it's easy to check), but immediately after the EventHandler
associated with the whole Graphics
is also triggered, causing a redraw of all the points, and therefore of all the DynamicModule
s associated with them, in which the local variables are initialized again.
I think the most obvious way to avoid this problem is to prevent the outer EventHandler
to be triggered when I click on a point, but how can I accomplish that?
I can put a check inside the outer event handler to make it redraw only when the mouse click is not on an already occupied part of the graphics, but I don't want to do it in this way because that would require me to change the code in this check every time I change the actual things produced by dynamicPoint
.
In my actual project I want these objects to be also dynamically resizable, which would make this solution hardly feasible.
Another method I thought was to add an element to allData
marking whether the mouse is on a point or not.
I could easily set this flag to True
with a MouseMoved
event in the EventHandler
of every point, and then checks whether the flag is true when the outer EventHandler
is triggered.
The problem with this is that I cannot think of an easy way to set this flag back to False
when the mouse exits the point, as there seems to be no MouseExited
event.
EDIT:
As of Kuba's suggestion, to have the two EventHandler
events not interfere with each other it is enough to add the option PassEventUp -> False
to the inner EventHandler
s.
Using this we get the following:
ClearAll[dynamicPoint];
dynamicPoint[point_List] := DynamicModule[{color = Red},
EventHandler[
Dynamic@{[email protected], color, Point@point},
{"MouseClicked" :> (
If[color === Red,
color = Green, color = Red
]
)},
PassEventsUp -> False
]
]
DynamicModule[{
allData = <|"NumberOfPoints" -> 1,
"PointsCoordinates" -> {{-1, 0}}|>
},
EventHandler[
Graphics[{
Dynamic@Table[
dynamicPoint[allData["PointsCoordinates"][[pointIndex]]],
{pointIndex, Range@allData["NumberOfPoints"]}
]
},
Frame -> True, PlotRange -> {{-2, 2}, {-2, 2}}
],
{"MouseClicked" :> (
allData["NumberOfPoints"]++;
AppendTo[allData["PointsCoordinates"], MousePosition["Graphics"]]
)},
PassEventsDown -> True
]
]
As you can see, we still have the problem that redrawing all the points when a new one is added, all the DynamicModule
s reinitialize their variables and so all the objects' states reinitialize.
How can we solve this?
PassEventsUp -> False
put into the innerEventHandler
s solved the problem, great! Did you mean this with your comment? Because withPassEventDown -> False
put into the outer event handler it seems I just prevent the inner ones (of the points) to be triggered, and if I putPassEventDown -> False
into the inner event handlers it doesn't do anything $\endgroup$