Listeners and reactions in Scala Swing

后端 未结 1 791
爱一瞬间的悲伤
爱一瞬间的悲伤 2021-01-05 18:03

I\'ve done a fair bit of searching and some trial and error in eclipse, but there seems to be a gap in my understanding of listeners and reactions when writing a GUI in Scal

相关标签:
1条回答
  • 2021-01-05 18:39

    Swing is educational, and Scala-Swing is educational. Especially if the course is "History of Swing: The Rise and Fall."

    My first Scala program also used Swing. I've forgotten the details, but I'll share what I can see in the source.

    Apparently, I had a main UI component called LightBox that handled some UI events, and also a mediator component LightBoxMediator that coordinated.

    The interesting part would be, using cake pattern for composition, and moving business logic (or game logic) interaction into a component that "mediates" for the UI proper. The LightBox publishes events, too.

    So the answer to your question would be: exploit the publisher framework, but distinguish UI events from application events. (This little game also had actor-based controllers.)

    Maybe this suffices to illustrate the separation of concerns:

    /**
     * Draws the House of Mirrors.
     * The LightBox is just a list of rays (line segments) and gates (various objects).
     * The UI emits requests to move and rotate gates.
     */
    class LightBox extends Panel {
    
      this.peer.addComponentListener(
        new ComponentAdapter {
          override def componentResized(e: ComponentEvent) {
            if (e.getID == ComponentEvent.COMPONENT_RESIZED && e.getComponent == LightBox.this.peer) {
              calculateScale()
            }
          }
        }
      )
    
      listenTo(mouse.clicks, mouse.moves, mouse.wheel, keys)
    
      reactions += {
        case KeyPressed(_, Key.N, _, _) => highlightNextMoveableGate()
        case KeyPressed(_, Key.P, _, _) => highlightPreviousMoveableGate()
        case e: MousePressed => startDrag(e)
        case e: MouseDragged => doDrag(e)
        case e: MouseReleased => endDrag(e)
        case e: MouseWheelMoved => wheeling(e)
        case _ => null // println ("Unreacted event")
      }
    

    and the mediator

    trait ViewComponents {
      this: ControllerComponents with ModelComponents =>
    
      val lightBoxMediator: LightBoxMediator
      val statusBarMediator: StatusBarMediator
      val statusIconMediator: StatusIconMediator
      val applicationMediator: ApplicationMediator
    
      /**
       * Handles update notifications from the application
       * and user input from the LightBox.
       */
      class LightBoxMediator(val ui: LightBox) extends Reactor with Observing {
    
        /** Attempt to track our selection across updates: the point is where the gate should end up. */
        private var selectionContinuity: (Option[Gate], Option[Point]) = (None, None)
    
        listenTo(ui, ui.keys, ui.mouse.clicks)
    
        reactions += {
          case KeyPressed(_, Key.Q, _, _) => sys.exit()
          case KeyPressed(_, Key.Space, _, _) => rotateSelectedGate()
          case KeyPressed(_, Key.Enter, _, _) => rotateOtherwiseSelectedGate()
          case KeyPressed(_, Key.Up, _, _) => moveUp()
          case KeyPressed(_, Key.Down, _, _) => moveDown()
          case KeyPressed(_, Key.Left, _, _) => moveLeft()
          case KeyPressed(_, Key.Right, _, _) => moveRight()
          case KeyPressed(_, Key.PageUp, _, _) => previousLevel()
          case KeyPressed(_, Key.PageDown, _, _) => nextLevel()
          case DragEvent(from, to) => handleDrag(from, to)
          case ClickEvent(where, button) => handleClick(where, button)
          //case x => println("Unreacted event " + x)
        }
    
        observe(controller.modelEvents) { e => e match {
            case LevelLoaded(v) => onLevelLoaded(v)
            case TraceResult(s) => onTrace(s)
            case unknown => println("Lightbox mediator ignored: "+ unknown)
          }
          true
        }
    

    Just noticed the additional questions. By coincidence, I was cleaning up old code, actually a tiny app to grab images from sfgate.com (which stopped working when they changed the site, of course; but usually you can right-click-save now), and I happened to notice the following comment about resubscribing. I vaguely remember the bit about UIElement being a LazyPublisher, because I remember the head slap. But if I hadn't written the meager comment, that info would have been lost to ancient history.

    I think somebody wants to support scala-swing and will probably take care of the head slaps.

    package com.maqicode.sfg.jfc
    
    import java.awt.Color
    import java.awt.Color.{WHITE => White, RED => Red}
    import java.net.{URI, URISyntaxException}
    import javax.swing._
    
    import swing.TextField
    import swing.event.{EditDone, MouseEntered, ValueChanged}
    
    import com.maqicode.sfg.BadGateURLException
    import com.maqicode.sfg.GateUrlTranslator.translate
    
    abstract class URIField extends TextField {
    
      reactions += {
        case e: EditDone => editDone(e)
        case other: ValueChanged => editing(other)
        case m: MouseEntered => onMouseEntered()
        case _ => null
      }
      // necessary to resubscribe this so that onFirstSubscribe registers ActionListener
      listenTo(this, mouse.moves)
    
      def onMouseEntered() {
        val t: Option[String] = ClipboardInput.contents
        if (t.isDefined && t.get != this.text) {
          this.text = t.get
          submitURL(t.get)
        }
      }
    
      def editing(e: ValueChanged) {
        clearError()
      }
    
      def editDone(e: EditDone) {
        submitURL(this.text)
      }
    
      def submitURL(s: String) {
        val u = s.trim
        if (!u.isEmpty)
          try {
            submitURI(translate(new URI(u)))
            clearError()
          } catch {
            case t: BadGateURLException => flagError()
            case t: URISyntaxException => flagError()
          }
      }
    
      def flagError() {
        colorCode(Red)
      }
    
      def clearError() {
        colorCode(White)
      }
    
      private def colorCode(c: Color) {
        if (this.background != c) this.background = c
      }
    
      def submitURI(uri: URI): Unit
    }
    
    0 讨论(0)
提交回复
热议问题