All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.sciss.mellite.impl.fscape.FScapeObjView.scala Maven / Gradle / Ivy

/*
 *  FScapeObjView.scala
 *  (Mellite)
 *
 *  Copyright (c) 2012-2023 Hanns Holger Rutz. All rights reserved.
 *
 *  This software is published under the GNU Affero General Public License v3+
 *
 *
 *  For further information, please contact Hanns Holger Rutz at
 *  [email protected]
 */

package de.sciss.mellite.impl.fscape

import de.sciss.desktop.{KeyStrokes, Util}
import de.sciss.fscape.lucre.UGenGraphBuilder.MissingIn
import de.sciss.fscape.stream.Cancelled
import de.sciss.icons.raphael
import de.sciss.log.Level
import de.sciss.lucre.edit.UndoManager
import de.sciss.lucre.swing.LucreSwing.{defer, deferTx}
import de.sciss.lucre.swing.edit.EditVar
import de.sciss.lucre.swing.impl.ComponentHolder
import de.sciss.lucre.swing.{View, Window}
import de.sciss.lucre.synth.{Executor, Txn}
import de.sciss.lucre.{Obj, Source, Txn => LTxn}
import de.sciss.mellite.impl.objview.ObjListViewImpl.NonEditable
import de.sciss.mellite.impl.objview.{NoArgsListObjViewFactory, ObjListViewImpl, ObjViewImpl}
import de.sciss.mellite.{AttrMapView, CodeFrame, CodeView, FScapeOutputsView, GUI, ObjListView, ObjView, Shapes, SplitPaneView, UniverseHandler}
import de.sciss.proc.FScape.GraphObj
import de.sciss.proc.Implicits._
import de.sciss.proc.{Code, FScape}

import javax.swing.Icon
import scala.concurrent.Promise
import scala.concurrent.stm.Ref
import scala.swing.event.Key
import scala.swing.{Action, Button, Component, Orientation, ProgressBar}
import scala.util.Failure

object FScapeObjView extends NoArgsListObjViewFactory {
  final val DEBUG_LAUNCH = false

  type E[~ <: LTxn[~]] = FScape[~]
  val icon          : Icon      = ObjViewImpl.raphaelIcon(Shapes.Sparks)
  val prefix        : String    = "FScape"
  def humanName     : String    = prefix
  def tpe           : Obj.Type  = FScape
  def category      : String    = ObjView.categComposition

//  private[this] lazy val _init: Unit = ListObjView.addFactory(this)
//
//  def init(): Unit = _init

  def mkListView[T <: Txn[T]](obj: FScape[T])
                             (implicit tx: T): FScapeObjView[T] with ObjListView[T] =
    new Impl(tx.newHandle(obj)).initAttrs(obj)

  def makeObj[T <: Txn[T]](name: String)(implicit tx: T): List[Obj[T]] = {
    val obj = FScape[T]()
    if (name.nonEmpty) obj.name = name
    obj :: Nil
  }

  final class Impl[T <: Txn[T]](val objH: Source[T, FScape[T]])
    extends FScapeObjView[T]
      with ObjListView /* .Int */[T]
      with ObjViewImpl.Impl[T]
      with ObjListViewImpl.EmptyRenderer[T]
      with NonEditable[T]
      /* with NonViewable[T] */ {

    override def obj(implicit tx: T): FScape[T] = objH()

    type E[~ <: LTxn[~]] = FScape[~]

    def factory: ObjView.Factory = FScapeObjView

    def isViewable = true

    // currently this just opens a code editor. in the future we should
    // add a scans map editor, and a convenience button for the attributes
    override def openView(parent: Option[Window[T]])(implicit tx: T,
                                                     handler: UniverseHandler[T]): Option[Window[T]] = {
      import de.sciss.mellite.Mellite.compiler
      val frame = codeFrame(obj) // CodeFrame.fscape(obj)
      Some(frame)
    }

    // ---- adapter for editing an FScape's source ----
  }

  private def codeFrame[T <: Txn[T]](obj: FScape[T])
                                    (implicit tx: T, universeHandler: UniverseHandler[T],
                                     compiler: Code.Compiler): CodeFrame[T] =
    universeHandler(obj, CodeFrame) {
      import de.sciss.mellite.impl.code.CodeFrameImpl.{mkSource, newInstance}
      import universeHandler.universe

      val codeObj     = mkSource(obj = obj, codeTpe = FScape.Code)()
      val swapObjOpt  = codeObj.attr.$[Code.Obj](CodeView.attrSwap)
      val built0      = swapObjOpt.isEmpty

      val objH        = tx.newHandle(obj)
      val code0       = (swapObjOpt getOrElse codeObj).value match {
        case cs: FScape.Code => cs
        case other => sys.error(s"FScape source code does not produce fscape.Graph: ${other.tpe.humanName}")
      }

      import de.sciss.fscape.Graph

      val handler = new CodeView.Handler[T, Unit, Graph] {
        override def in(): Unit = ()

        override def save(in: Unit, out: Graph)(implicit tx: T, undo: UndoManager[T]): Unit = {
          val obj = objH()
          EditVar.exprUndo[T, Graph, GraphObj]("Change FScape Graph", obj.graph, GraphObj.newConst[T](out))
        }

        def dispose()(implicit tx: T): Unit = ()
      }

      val renderRef = Ref(Option.empty[FScape.Rendering[T]])

      def disposeRender()(implicit tx: T): Unit =
        renderRef.swap(None)(tx.peer).foreach(_.cancel())

      lazy val ggProgress: ProgressBar = new ProgressBar {
        max = 160
      }

      val viewProgress = View.wrap[T, ProgressBar](ggProgress)

      lazy val actionCancel: swing.Action = new swing.Action(null) {
        def apply(): Unit = {
          import universe.cursor
          cursor.step { implicit tx =>
            disposeRender()
          }
        }
        enabled = false
      }

      val viewCancel = View.wrap[T, Button] {
        GUI.toolButton(actionCancel, raphael.Shapes.Cross, tooltip = "Abort Rendering")
      }

      var debugLaunchP  = Option.empty[Promise[Unit]]
      var debugLaunchC  = 0

      val viewRender: View[T] = new View[T] with ComponentHolder[Component] {
        type C = Component

        override def dispose()(implicit tx: T): Unit = disposeRender()

        deferTx(initGUI())

        private def initGUI(): Unit = {
          val actionRender = new swing.Action("Render") { self =>
            def apply(): Unit = {
              import universe.cursor
              cursor.step { implicit tx =>
                if (renderRef.get(tx.peer).isEmpty) {
                  val obj       = objH()
                  val config    = FScape.defaultConfig.toBuilder
                  config.progressReporter = { report =>
                    defer {
                      ggProgress.value = (report.total * ggProgress.max).toInt
                    }
                  }
                  // config.blockSize
                  // config.nodeBufferSize
                  // config.executionContext
                  // config.seed
                  if (DEBUG_LAUNCH) {
                    val pDebug              = Promise[Unit]()
                    debugLaunchP            = Some(pDebug)
                    debugLaunchC            = 0
                    config.debugWaitLaunch  = Some(pDebug.future)
                  }

                  def finished()(implicit tx: T): Unit = {
                    renderRef.set(None)(tx.peer)
                    deferTx {
                      actionCancel.enabled  = false
                      self.enabled          = true
                    }
                  }

                  try {
                    val rendering = obj.run(config)
                    deferTx {
                      actionCancel.enabled = true
                      self        .enabled = false
                    }
                    /* val obs = */ rendering.reactNow { implicit tx => {
                      case FScape.Rendering.Completed =>
                        finished()
                        rendering.result.foreach {
                          case Failure(Cancelled()) => // ignore
                          case Failure(ex) =>
                            deferTx(ex.printStackTrace())
                          case _ =>
                        }
                      case _ =>
                    }}
                    renderRef.set(Some(rendering))(tx.peer)
                  } catch {
                    case MissingIn(key) =>
                      println(s"Attribute input '$key' is missing.")
                    //                throw ex
                  }
                }
              }
            }
          }
          val ks  = KeyStrokes.shift + Key.F10
          val res = GUI.toolButton(actionRender, Shapes.Sparks)
          Util.addGlobalKey(res, ks)
          res.tooltip = s"Run Rendering (${GUI.keyStrokeText(ks)})"
          component = res
        }
      }

      val viewDebug = View.wrap[T, Button] {
        new Button("Debug") {
          action = Action(text) {
            renderRef.single.get.foreach { r =>
              val ctrl = r.control
              if (debugLaunchC == 1 && DEBUG_LAUNCH) {
                debugLaunchP.foreach(_.trySuccess(()))
              } else {
                ctrl.debugDotGraph()
                import Executor.executionContext
                ctrl.stats.foreach(println)
              }
              debugLaunchC += 1
            }
          }
        }
      }

      val bottom = viewProgress :: viewCancel :: viewRender :: viewDebug :: Nil

      implicit val undo: UndoManager[T] = UndoManager()
      val outputsView = FScapeOutputsView [T](obj)
      val attrView    = AttrMapView       [T](obj)
      val rightView   = SplitPaneView(attrView, outputsView, Orientation.Vertical)

      import de.sciss.fscape.Log.{control, stream}

      val actToggleControl = Action("Toggle Control Debug") {
        control.level = if (control.level == Level.Debug) Level.Off else Level.Debug
        println(s"Control log level is ${control.level}")
      }
      val actToggleStream = Action("Toggle Stream Debug") {
        stream.level = if (stream.level == Level.Debug) Level.Off else Level.Debug
        println(s"Stream log level is ${stream.level}")
      }

      newInstance(obj, objH, codeObj,
        code0           = code0,
        handler         = Some(handler),
        bottom          = bottom,
        rightViewOpt    = Some(("In/Out", rightView)),
        debugMenuItems  = List(actToggleControl, actToggleStream),
        built0          = built0,
        canBounce       = false   // XXX TODO --- perhaps a standard bounce option would be useful
      )
    }
}
trait FScapeObjView[T <: LTxn[T]] extends ObjView[T] {
  type Repr = FScape[T]
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy