scalamouseeventscalafx

ScalaFX PickResult with subclassed nodes


I have a simple scalafx app but I am struggling to get simple mouse interactions working.

I have custom Canvas node class called Square :

case class Square(val index: Int) extends Canvas

I then have a custom GridPane that is filled with squares :

class BoardPane extends GridPane
{
  val squares: Array[Square] = (0 to 63).toArray.map(index => Square(index))
  (0 to 63).foreach(index => add(squares(index), index%8, 7-index/8))
}

When I try to get the pick result from a mouse event :

class PlayableBoardPane extends BoardPane
{
  onMouseDragged = (event => {
    val node: Option[Node] = event.pickResult.intersectedNode
  ...
  })
}

The intersected node is not a Square but a "scalafx.scene.LowerPriorityIncludes$$anon$4" (with implicit conversion)

Is there a way I can map this node back to my squares?

Without that I fail to see how to even use node picking with subclassed nodes at all.


Solution

  • Keep in mind that ScalaFX helps you build a JavaFX application - the underlaying structures are JavaFX. That is, when you build a scene graph in ScalaFX it is composed of JavaFX objects.

    For that reason, ScalaFX application should be build trough composition not inheritance. When you simply inherit from a ScalaFX wrapper, like in your example:

        case class Square(val index: Int) extends Canvas
    

    it is extending a wrapper not the actual JavaFX class. When you build a scene graph and then examine it through picking you will see JavaFX objects not the wrappers you created. You can deal with that is various ways. A simple one is to set userData that is passed to underlying JavaFX object. I complete example from fragments you posed would be like this:

    import scalafx.Includes._
    import scalafx.application.JFXApp
    import scalafx.application.JFXApp.PrimaryStage
    import scalafx.scene.Scene
    import scalafx.scene.canvas.Canvas
    import scalafx.scene.layout.GridPane
    
    object PickResultWithSubclassedNodes extends JFXApp {
    
      case class Square(index: Int) extends Canvas {
        height = 25
        width = 25
        userData = index.toString
      }
    
      class BoardPane extends GridPane {
        val squares: Array[Square] = (0 to 63).toArray.map(index => Square(index))
        (0 to 63).foreach(index => add(squares(index), index % 8, 7 - index / 8))
      }
    
      class PlayableBoardPane extends BoardPane {
        onMouseReleased = (event => {
          val node = event.pickResult.intersectedNode
          node match {
            case Some(n) => println("Picked: " + n.userData)
            case None =>
          }
        })
      }
    
      stage = new PrimaryStage {
        title = "Blocking"
        scene = new Scene {
          root = new PlayableBoardPane()
        }
      }
    }
    

    Clicking on the window will produce output like this:

    Picked: 40
    Picked: 42
    Picked: 17
    Picked: 36
    Picked: 52
    Picked: 38
    Picked: 27
    

    No issue with unusual implicit classes.