Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
de.sciss.mellite.impl.objview.EnvSegmentObjView.scala Maven / Gradle / Ivy
/*
* EnvSegmentObjView.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.objview
import de.sciss.desktop
import de.sciss.equal.Implicits._
import de.sciss.icons.raphael
import de.sciss.kollflitz.Vec
import de.sciss.lucre.edit.UndoManager
import de.sciss.lucre.expr.CellView
import de.sciss.lucre.swing.LucreSwing.{deferTx, requireEDT}
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.Txn
import de.sciss.lucre.{Disposable, DoubleObj, Expr, Obj, Source, Txn => LTxn}
import de.sciss.mellite.ObjGraphemeView.{HandleDiameter, HandleRadius, HasStartLevels}
import de.sciss.mellite.impl.objview.ObjViewImpl.raphaelIcon
import de.sciss.mellite.impl.{ObjGraphemeViewImpl, ObjViewCmdLineParser, WorkspaceWindowImpl}
import de.sciss.mellite.{GraphemeRendering, GraphemeView, Insets, ObjGraphemeView, ObjListView, ObjView, UniverseHandler, UniverseObjView, ViewState, WorkspaceWindow}
import de.sciss.model.impl.ModelImpl
import de.sciss.proc.Grapheme.Entry
import de.sciss.proc.Implicits._
import de.sciss.proc.{CurveObj, EnvSegment, Universe}
import de.sciss.processor.Processor.Aborted
import de.sciss.swingplus.{ComboBox, GroupPanel, Spinner}
import de.sciss.synth.Curve
import java.awt.geom.Area
import javax.swing.{Icon, SpinnerModel, SpinnerNumberModel}
import scala.concurrent.Future
import scala.swing.Swing.EmptyIcon
import scala.swing.event.{SelectionChanged, ValueChanged}
import scala.swing.{Alignment, Component, Dialog, Graphics2D, Label, Swing, TextField}
object EnvSegmentObjView extends ObjListView.Factory with ObjGraphemeView.Factory {
type E[T <: LTxn[T]] = EnvSegment.Obj[T]
type V = EnvSegment
val icon : Icon = raphaelIcon(raphael.Shapes.Connect)
val prefix : String = "EnvSegment"
def humanName : String = "Envelope Segment"
def tpe : Obj.Type = EnvSegment.Obj
def category : String = ObjView.categMisc
def canMakeObj : Boolean = true
def mkListView[T <: Txn[T]](obj: E[T])(implicit tx: T): ObjListView[T] = {
val value = obj.value
val editable = EnvSegment.Obj.Var.unapply(obj).isDefined
new ListImpl[T](tx.newHandle(obj), value = value, isEditable = editable).init(obj)
}
final case class Config[T <: LTxn[T]](name: String = prefix,
value: EnvSegment = EnvSegment.Single(0.0, Curve.lin),
const: Boolean = false)
// XXX TODO DRY with ParamSpecObjView
private final class PanelImpl(nameIn: Option[String], editable: Boolean) extends ModelImpl[Unit] {
private[this] val ggNameOpt = nameIn.map { txt0 => new TextField(txt0, 10) }
private[this] val ggName = ggNameOpt.getOrElse(Swing.HStrut(4))
private[this] val mStartLvl = new SpinnerNumberModel(0.0, -1.0e6, 1.0e6, 0.1)
private[this] val mParam = new SpinnerNumberModel(0.0, -1.0e6, 1.0e6, 0.1)
private[this] val sqCurve = Seq[Curve](
Curve.step, Curve.linear, Curve.exponential,
Curve.sine, Curve.welch, Curve.parametric(0f), Curve.squared, Curve.cubed
)
private[this] val nCurve = sqCurve.map { w => w.toString.capitalize }
private[this] val mCurve = ComboBox.Model.wrap(nCurve)
def nameOption: Option[String] = ggNameOpt.flatMap { gg =>
val s0 = gg.text
if (s0.isEmpty) None else Some(s0)
}
def name: String = nameOption.getOrElse("")
def name_=(value: String): Unit =
ggNameOpt.foreach(_.text = value)
def value: EnvSegment = {
val curveI = mCurve.selectedItem.fold(-1)(nCurve.indexOf)
val curve0 = if (curveI < 0) Curve.linear else sqCurve(curveI)
val curve = curve0 match {
case p @ Curve.parametric(_) => p.copy(curvature = mParam.getNumber.floatValue())
case other => other
}
EnvSegment.Single(
startLevel = mStartLvl.getNumber.doubleValue(), curve = curve
)
}
def value_=(value: EnvSegment): Unit = {
val startLvl = value.startLevels.headOption.getOrElse(0.0) // XXX TODO support multi
mStartLvl.setValue(startLvl)
val warpNorm = value.curve match {
case p @ Curve.parametric(c) =>
mParam.setValue(c)
p.copy(curvature = 0.0f)
case other => other
}
val warpI = sqCurve.indexOf(warpNorm)
mCurve.selectedItem = if (warpI < 0) None else Some(nCurve(warpI))
updateParam()
}
private def mkSpinner(m: SpinnerModel): Spinner = {
val res = new Spinner(m)
res.listenTo(res)
res.reactions += {
case ValueChanged(_) => dispatch(())
}
res
}
val ggStartLvl: Component = mkSpinner(mStartLvl)
private[this] val ggParam = mkSpinner(mParam)
private[this] val ggCurve = new ComboBox(mCurve) {
listenTo(selection)
reactions += {
case SelectionChanged(_) => updateParam()
}
}
private[this] val lbName = {
val res = new Label("Name:", EmptyIcon, Alignment.Right)
if (nameIn.isEmpty) res.visible = false
res
}
private[this] val lbStartLvl = new Label( "Start Level:", EmptyIcon, Alignment.Right)
private[this] val lbCurve = new Label( "Curve:", EmptyIcon, Alignment.Right)
private[this] val lbParam = new Label( "Curvature:", EmptyIcon, Alignment.Right)
private def updateParam(fire: Boolean = true): Unit = {
val v = value // updateExample(fire = false)
ggParam.enabled = editable && v.curve.isInstanceOf[Curve.parametric]
if (fire) dispatch(())
}
updateParam(fire = false)
private[this] val boxParams = new GroupPanel {
horizontal= Seq(
Par(Trailing)(lbName, lbStartLvl, lbCurve, lbParam),
Par (ggName, ggStartLvl, ggCurve, ggParam)
)
vertical = Seq(
Par(Baseline)(lbName , ggName ),
Par(Baseline)(lbStartLvl, ggStartLvl),
Par(Baseline)(lbCurve , ggCurve ),
Par(Baseline)(lbParam , ggParam )
)
}
// private[this] val box = new BoxPanel(Orientation.Vertical)
// box.contents += boxParams
// box.contents += Swing.VStrut(8)
// box.contents += boxDemo
if (!editable) {
ggStartLvl.enabled = false
ggCurve .enabled = false
ggParam .enabled = false
}
def component: Component = boxParams // box
}
// XXX TODO DRY with ParamSpecObjView
private final class ViewImpl[T <: Txn[T]](objH: Source[T, EnvSegment.Obj[T]], val editable: Boolean)
(implicit val universe: Universe[T],
val undoManager: UndoManager[T])
extends UniverseObjView[T] with View.Editable[T] with ComponentHolder[Component] {
type C = Component
override def obj(implicit tx: T): EnvSegment.Obj[T] = objH()
override def viewState: Set[ViewState] = Set.empty
private[this] var value : EnvSegment = _
private[this] var panel : PanelImpl = _
// private[this] val _dirty : Ref[Boolean] = Ref(false)
// private[this] var actionApply : Action = _
private[this] var observer : Disposable[T] = _
def init(obj0: EnvSegment.Obj[T])(implicit tx: T): this.type = {
val value0 = obj0.value
deferTx(initGUI(value0))
observer = obj0.changed.react { implicit tx => upd =>
deferTx {
value = upd.now
panel.value = upd.now
}
}
this
}
private def initGUI(value0: EnvSegment): Unit = {
this.value = value0
panel = new PanelImpl(nameIn = None, editable = editable)
panel.value = value0
panel.addListener {
case _ => save() // updateDirty()
}
// actionApply = Action("Apply") {
// save()
// updateDirty()
// }
// actionApply.enabled = false
// val ggApply = GUI.toolButton(actionApply, raphael.Shapes.Check, tooltip = "Save changes")
// val panelBot = new FlowPanel(FlowPanel.Alignment.Trailing)(ggApply)
// component = new BorderPanel {
// add(panel.component, BorderPanel.Position.Center)
// add(panelBot , BorderPanel.Position.South )
// }
component = panel.component
}
// def dirty(implicit tx: T): Boolean = _dirty.get(tx.peer)
// private def updateDirty(): Unit = {
// val valueNow = panel.value
// val isDirty = value !== valueNow
// val wasDirty = _dirty.single.swap(isDirty)
// if (isDirty !== wasDirty) actionApply.enabled = isDirty
// }
def save(): Unit = {
requireEDT()
val newValue = panel.value
var edited = false
cursor.step { implicit tx =>
val title = s"Edit $humanName"
objH() match {
case EnvSegment.Obj.Var(pVr) =>
val oldVal = pVr.value
if (newValue === oldVal) None else {
val pVal = EnvSegment.Obj.newConst[T](newValue)
EditVar.exprUndo[T, EnvSegment, EnvSegment.Obj](title, pVr, pVal)
edited = true
}
case EnvSegment.Obj.ApplySingle(DoubleObj.Var(startVr), CurveObj.Var(curveVr)) =>
undoManager.capture(title) {
val newCurve = newValue.curve
if (curveVr.value !== newCurve) {
val curveVal = CurveObj.newConst[T](newCurve)
EditVar.exprUndo[T, Curve, CurveObj](title, curveVr, curveVal)
edited = true
}
val newStart = newValue.startLevels.headOption.getOrElse(0.0) // XXX TODO
if (startVr.value !== newStart) {
val startVal = DoubleObj.newConst[T](newStart)
EditVar.exprUndo[T, Double, DoubleObj](title, startVr, startVal)
edited = true
}
}
case _ => ()
}
}
if (edited) {
// undoManager.clear()
value = newValue
}
}
def dispose()(implicit tx: T): Unit = observer.dispose()
}
private object EnvSegmentFrame extends WorkspaceWindow.Key {
type Repr[T <: LTxn[T]] = EnvSegmentFrame[T]
}
private trait EnvSegmentFrame[T <: LTxn[T]] extends WorkspaceWindow[T] {
type Repr[~ <: LTxn[~]] = EnvSegmentFrame[~]
}
// XXX TODO DRY with ParamSpecObjView
private final class FrameImpl[T <: Txn[T]](val view: ViewImpl[T], editable: Boolean)
(implicit val handler: UniverseHandler[T])
extends EnvSegmentFrame[T] with WorkspaceWindowImpl[T] {
override def key: Key = EnvSegmentFrame
override def newWindow()(implicit tx: T): EnvSegmentFrame[T] =
newFrame(view.obj, editable = editable)
override protected def resizable: Boolean = false
}
private def detectEditable[T <: Txn[T]](obj: E[T]): Boolean =
obj match {
case EnvSegment.Obj.Var(_) => true
case EnvSegment.Obj.ApplySingle(DoubleObj.Var(_), CurveObj.Var(_)) => true
case _ => false // XXX TODO --- support multi
}
def mkGraphemeView[T <: Txn[T]](entry: Entry[T], obj: E[T], mode: GraphemeView.Mode)
(implicit tx: T): ObjGraphemeView[T] = {
val value = obj.value
val editable = detectEditable(obj)
new GraphemeImpl[T](tx.newHandle(entry), tx.newHandle(obj), value = value, isEditable = editable)
.init(obj, entry)
}
// XXX TODO DRY with ParamSpecObjView
override def initMakeDialog[T <: Txn[T]](window: Option[desktop.Window])
(implicit universe: Universe[T]): MakeResult[T] = {
val panel = new PanelImpl(nameIn = Some(prefix), editable = true)
val pane = desktop.OptionPane.confirmation(panel.component, optionType = Dialog.Options.OkCancel,
messageType = Dialog.Message.Question, focus = Some(panel.ggStartLvl))
pane.title = s"New $humanName"
val res = pane.show(window)
val res1 = if (res === Dialog.Result.Ok) {
Future.successful(Config[T](name = panel.name, value = panel.value))
} else {
Future.failed(Aborted())
}
res1
}
override def initMakeCmdLine[T <: Txn[T]](args: List[String])(implicit universe: Universe[T]): MakeResult[T] = {
import CmdLineSupport._
object p extends ObjViewCmdLineParser[Config[T]](this, args) {
val startLevel: Opt[Vec[Double]] = vecArg[Double](
descr = "Starting level (single double or comma separated doubles)"
)
val curve: Opt[Curve] = trailArg(required = false,
descr = s"Parameter warp or curve (default: ${"lin" /* default.value.curve */})",
default = Some(Curve.linear))
val const: Opt[Boolean] = opt(descr = "Make constant instead of variable")
}
p.parseFut(Future.successful(Config(name = p.name(), const = p.const(), value = (p.startLevel(), p.curve()) match {
case (Seq(single), c) => EnvSegment.Single(single , c)
case (v, c) => EnvSegment.Multi (v , c)
})))
}
def makeObj[T <: Txn[T]](config: Config[T])(implicit tx: T): List[Obj[T]] = {
import config._
val obj0 = EnvSegment.Obj.newConst[T](value)
val obj = if (const) obj0 else EnvSegment.Obj.newVar(obj0)
if (name.nonEmpty) obj.name = name
obj :: Nil
}
// ---- basic ----
private abstract class Impl[T <: Txn[T]](val objH: Source[T, E[T]])
extends ObjViewImpl.Impl[T]
with ObjViewImpl.ExprLike[T, V, E] with EnvSegmentObjView[T] {
final def isViewable: Boolean = true
final def factory: ObjView.Factory = EnvSegmentObjView
final def exprType: Expr.Type[V, E] = EnvSegment.Obj
final def expr(implicit tx: T): E[T] = objH()
protected def isEditable: Boolean
override def obj(implicit tx: T): E[T] = objH()
override def openView(parent: Option[Window[T]])(implicit tx: T,
handler: UniverseHandler[T]): Option[Window[T]] = {
val _obj = obj
val res = handler(_obj, EnvSegmentFrame) {
newFrame(_obj, editable = isEditable)
}
Some(res)
}
}
private def newFrame[T <: Txn[T]](obj: E[T], editable: Boolean)(implicit tx: T,
handler: UniverseHandler[T]): FrameImpl[T] = {
val _obj = obj
import handler.universe
implicit val undo: UndoManager[T] = UndoManager()
val view = new ViewImpl[T](tx.newHandle(obj), editable = editable).init(_obj)
val nameView = CellView.name(_obj)
val fr = new FrameImpl[T](view, editable = editable)
fr.init().setTitle(nameView)
}
// ---- ListObjView ----
private final class ListImpl[T <: Txn[T]](objH: Source[T, E[T]], var value: V, val isEditable: Boolean)
extends Impl(objH) with ObjListView[T]
with ObjListViewImpl.SimpleExpr[T, V, E]
// with ListObjViewImpl.NonEditable[T]
with ObjListViewImpl.StringRenderer
with ObjListViewImpl.NonEditable[T] {
def convertEditValue(v: Any): Option[V] = None
}
// ---- GraphemeObjView ----
private final class GraphemeImpl[T <: Txn[T]](val entryH: Source[T, Entry[T]],
objH: Source[T, E[T]],
var value: V, val isEditable: Boolean)
extends Impl[T](objH)
with ObjGraphemeViewImpl.SimpleExpr[T, V, E]
with ObjGraphemeView.HasStartLevels[T] {
private[this] val allSame =
value.numChannels <= 1 || { val v0 = value.startLevels; val vh = v0.head; v0.forall(_ === vh) }
def startLevels: Vec[Double] = value.startLevels
def insets: Insets = ObjGraphemeView.DefaultInsets
private[this] var succOpt = Option.empty[HasStartLevels[T]]
override def succ_=(opt: Option[ObjGraphemeView[T]])(implicit tx: T): Unit = deferTx {
succOpt = opt.collect {
case hs: HasStartLevels[T] => hs
}
// XXX TODO --- fire repaint?
}
override def paintBack(g: Graphics2D, gv: GraphemeView[T], r: GraphemeRendering): Unit = succOpt match {
case Some(succ) =>
val startLvl = value.startLevels
val endLvl = succ .startLevels
val numChS = startLvl.size
val numChE = endLvl .size
if (numChS === 0 || numChE === 0) return
val numCh = math.max(numChS, numChE)
val c = gv.canvas
val startSelected = gv.selectionModel.contains(this)
val endSelected = gv.selectionModel.contains(succ)
val startTime0 = this.timeValue
val endTime0 = succ.timeValue
val startTime = if (startSelected ) startTime0 + r.ttMoveState.deltaTime else startTime0
val endTime = if (endSelected ) endTime0 + r.ttMoveState.deltaTime else endTime0
val x1 = c.frameToScreen(startTime)
val x2 = c.frameToScreen(endTime)
val strkOrig = g.getStroke
g.setStroke(r.strokeInletSpan)
g.setPaint (r.pntInletSpan)
val path = r.shape1
path.reset()
var ch = 0
while (ch < numCh) {
val startValue0 = startLvl(ch % numChS)
val startValue = if (startSelected ) startValue0 + r.ttMoveState.deltaModelY else startValue0
val y1 = c.modelPosToScreen(startValue)
val endValue0 = endLvl (ch % numChE)
val endValue = if (endSelected ) endValue0 + r.ttMoveState.deltaModelY else endValue0
val y2 = c.modelPosToScreen(endValue)
path.moveTo(x1, y1)
value.curve match {
case Curve.linear =>
case Curve.step =>
path.lineTo(x2, y1)
case curve =>
var x = x1 + 4
val y1f = y1.toFloat
val y2f = y2.toFloat
val dx = x2 - x1
if (dx > 0) while (x < x2) {
val pos = ((x - x1) / dx).toFloat
val y = curve.levelAt(pos, y1f, y2f)
path.lineTo(x, y)
x += 4
}
// XXX TODO (what?)
}
path.lineTo(x2, y2)
ch += 1
}
g.draw(path)
g.setStroke(strkOrig)
case _ =>
}
override def paintFront(g: Graphics2D, gv: GraphemeView[T], r: GraphemeRendering): Unit = {
if (value.numChannels === 0) return
val levels = value.startLevels
if (allSame) {
DoubleObjView.graphemePaintFront(this, levels.head, g, gv, r)
return
}
val c = gv.canvas
val jc = c.canvasComponent.peer
val h = jc.getHeight
val x = c.frameToScreen(timeValue)
val a1 = r.area1
val a2 = r.area2
val p = r.ellipse1 // r.shape1
a1.reset()
a2.reset()
val hm = h - 1
var ch = 0
val numCh = levels.size
var min = Double.MaxValue
var max = Double.MinValue
while (ch < numCh) {
val v = levels(ch)
val y = (1 - v) * hm
p.setFrame(x - 2, y - 2, 4, 4)
a1.add(new Area(p))
p.setFrame(x - HandleRadius, y - HandleRadius, HandleDiameter, HandleDiameter)
a2.add(new Area(p))
if (y < min) min = y
if (y > max) max = y
ch += 1
}
val strkOrig = g.getStroke
g.setStroke(r.strokeInletSpan)
g.setPaint (r.pntInletSpan)
val ln = r.shape1
ln.reset()
ln.moveTo(x, min)
ln.lineTo(x, max)
g.draw(ln)
g.setStroke(r.strokeNormal)
val selected = gv.selectionModel.contains(this)
g.setPaint(if (selected) r.pntRegionBackgroundSelected else r.pntRegionBackground)
g.fill(a1)
g.setPaint(if (selected) r.pntRegionOutlineSelected else r.pntRegionOutline)
g.draw(a2)
g.setStroke(strkOrig)
}
}
}
trait EnvSegmentObjView[T <: LTxn[T]] extends ObjView[T] {
type Repr = EnvSegment.Obj[T]
}