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

de.sciss.mellite.gui.impl.timeline.TimelineObjViewBasicImpl.scala Maven / Gradle / Ivy

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

package de.sciss.mellite
package gui
package impl
package timeline

import de.sciss.lucre.expr.{IntObj, SpanLikeObj}
import de.sciss.lucre.stm
import de.sciss.lucre.stm.Obj
import de.sciss.lucre.swing.deferTx
import de.sciss.span.{Span, SpanLike}
import de.sciss.synth.Curve

import scala.swing._

trait TimelineObjViewBasicImpl[S <: stm.Sys[S]] extends TimelineObjView[S] with ObjViewImpl.Impl[S] {
  var trackIndex  : Int = _
  var trackHeight : Int = _
  // var nameOption  : Option[String] = _
  var spanValue   : SpanLike = _
  var spanH       : stm.Source[S#Tx, SpanLikeObj[S]] = _

  protected var idH  : stm.Source[S#Tx, S#ID] = _

  def span(implicit tx: S#Tx) = spanH()
  def id  (implicit tx: S#Tx) = idH()

  def initAttrs(id: S#ID, span: SpanLikeObj[S], obj: Obj[S])(implicit tx: S#Tx): this.type = {
    val attr      = obj.attr

    implicit val intTpe = IntObj
    val trackIdxView = AttrCellView[S, Int, IntObj](attr, TimelineObjView.attrTrackIndex)
    disposables ::= trackIdxView.react { implicit tx => opt =>
      deferTx {
        trackIndex = opt.getOrElse(0)
      }
      fire(ObjView.Repaint(this))
    }
    trackIndex   = trackIdxView().getOrElse(0)

    val trackHView = AttrCellView[S, Int, IntObj](attr, TimelineObjView.attrTrackHeight)
    disposables ::= trackHView.react { implicit tx => opt =>
      deferTx {
        trackHeight = opt.getOrElse(4)
      }
      fire(ObjView.Repaint(this))
    }
    trackHeight   = trackHView().getOrElse(4)

    spanH         = tx.newHandle(span)
    spanValue     = span.value
    idH           = tx.newHandle(id)
    initAttrs(obj)
  }

  protected def paintInner(g: Graphics2D, tlv: TimelineView[S], r: TimelineRendering, selected: Boolean): Unit = ()

  /** These are updated by paintBack and will thus be valid in paintFront as well. */
  var px  = 0
  var py  = 0
  var pw  = 0
  var ph  = 0

  var pStart = 0L
  var pStop  = 0L

  /** Inner (after title bar) */
  protected var phi   = 0
  /** Inner (after title bar) */
  protected var pyi   = 0
  /** Clipped left */
  protected var px1c  = 0
  /** Clipped right */
  protected var px2c  = 0

  def paintBack(g: Graphics2D, tlv: TimelineView[S], r: TimelineRendering): Unit = {
    val selected        = tlv.selectionModel.contains(this)
    val canvas          = tlv.canvas
    val trackTools      = canvas.trackTools
    val regionViewMode  = trackTools.regionViewMode
    var x1              = -5
    val peer            = canvas.canvasComponent.peer
    val w               = peer.getWidth
    // val h            = peer.getHeight
    var x2              = w + 5

    // var move            = 0L
    import canvas.{frameToScreen, trackToScreen, framesToScreen}
    import r.{ttFadeState => fadeState, ttMoveState => moveState, ttResizeState => resizeState}
    import r.clipRect

    def adjustStart(start: Long): Long =
      if (selected) {
        val dt0 = moveState.deltaTime + resizeState.deltaStart
        if (dt0 >= 0) dt0 else {
          val total = tlv.timelineModel.bounds
          math.max(-(start - total.start), dt0)
        }
      } else 0L

    def adjustStop(stop: Long): Long =
      if (selected) {
        val dt0 = moveState.deltaTime + resizeState.deltaStop
        if (dt0 >= 0) dt0 else {
          val total = tlv.timelineModel.bounds
          math.max(-(stop - total.start + TimelineView.MinDur), dt0)
        }
      } else 0L

//    def adjustMove(start: Long): Long =
//      if (selected) {
//        val dt0 = moveState.deltaTime
//        if (dt0 >= 0) dt0 else {
//          val total = tlv.timelineModel.bounds
//          math.max(-(start - total.start), dt0)
//        }
//      } else 0L

    spanValue match {
      case Span(start, stop) =>
        val dStart    = adjustStart(start)
        val dStop     = adjustStop (stop )
        pStart        = start + dStart
        pStop         = stop + dStop
        val newStop   = math.max(pStart + TimelineView.MinDur, pStop)
        x1            = frameToScreen(pStart ).toInt
        x2            = frameToScreen(newStop).toInt
        // move          = adjustMove(start)

      case Span.From(start) =>
        val dStart    = adjustStart(start)
        pStart        = start + dStart
        pStop         = Long.MaxValue
        x1            = frameToScreen(pStart).toInt
        // x2         = w + 5
        // move          = adjustMove(start)

      case Span.Until(stop) =>
        val dStop     = adjustStop(stop)
        pStart        = Long.MinValue
        pStop         = stop + dStop
        x2            = frameToScreen(pStop).toInt
      // start         = Long.MinValue
      // x1         = -5
      // move       = 0L

      case Span.All =>
       pStart         = Long.MinValue
       pStop          = Long.MaxValue
      // x1            = -5
      // x2            = w + 5
      // move            = 0L

      case _ => // don't draw Span.Void
        return
    }

    val pTrk  = if (selected) math.max(0, trackIndex + moveState.deltaTrack) else trackIndex
    py        = trackToScreen(pTrk)
    px        = x1
    pw        = x2 - x1
    ph        = trackToScreen(pTrk + trackHeight) - py

    // clipped coordinates
    px1c      = math.max(px +  1, clipRect.x - 2)
    px2c      = math.min(px + pw, clipRect.x + clipRect.width + 3)

    if (px1c < px2c) {  // skip this if we are not overlapping with clip
      import r.{pntBackground, pntFadeFill, pntFadeOutline, pntNameDark, pntNameLight, pntNameShadowDark, pntNameShadowLight, pntRegionBackground, pntRegionBackgroundMuted, pntRegionBackgroundSelected, pntRegionOutline, pntRegionOutlineSelected, regionTitleBaseline, regionTitleHeight, shape1, shape2}

      if (regionViewMode != RegionViewMode.None) {
        g.translate(px, py)
        g.setPaint(if (selected) pntRegionOutlineSelected    else pntRegionOutline   )
        g.fillRoundRect(0, 0, pw, ph, 5, 5)
        g.setPaint(if (selected) pntRegionBackgroundSelected else pntRegionBackground)
        g.fillRoundRect(1, 1, pw - 2, ph - 2, 4, 4)
        g.translate(-px, -py)
      }
      g.setPaint(pntBackground)
      g.drawLine(px - 1, py, px - 1, py + ph - 1) // better distinguish directly neighbouring regions

      val hndl = regionViewMode match {
        case RegionViewMode.None       => 0
        case RegionViewMode.Box        => 1
        case RegionViewMode.TitledBox  => regionTitleHeight
      }

      phi           = ph - (hndl + 1)
      pyi           = py + hndl
      val clipOrig  = g.getClip
      g.clipRect(px + 1, pyi, pw - 2, phi)

      paintInner(g, tlv = tlv, r = r, selected = selected)

      // --- fades ---
      def paintFade(curve: Curve, fw: Float, y1: Float, y2: Float, x: Float, x0: Float): Unit = {
        import math.{log10, max}
        shape1.reset()
        shape2.reset()
        val vScale  = phi / -3f
        val y1s     = max(-3, log10(y1)) * vScale + pyi
        shape1.moveTo(x, y1s)
        shape2.moveTo(x, y1s)
        var xs = 4
        while (xs < fw) {
          val ys = max(-3, log10(curve.levelAt(xs / fw, y1, y2))) * vScale + pyi
          shape1.lineTo(x + xs, ys)
          shape2.lineTo(x + xs, ys)
          xs += 3
        }
        val y2s     = max(-3, log10(y2)) * vScale + pyi
        shape1.lineTo(x + fw, y2s)
        shape2.lineTo(x + fw, y2s)
        shape1.lineTo(x0, pyi)
        g.setPaint(pntFadeFill)
        g.fill    (shape1)
        g.setPaint(pntFadeOutline)
        g.draw    (shape2)
      }

      this match {
        case fv: TimelineObjView.HasFade if trackTools.fadeViewMode == FadeViewMode.Curve =>
          def adjustFade(in: Curve, deltaCurve: Float): Curve = in match {
            case Curve.linear                 => Curve.parametric(math.max(-20, math.min(20, deltaCurve)))
            case Curve.parametric(curvature)  => Curve.parametric(math.max(-20, math.min(20, curvature + deltaCurve)))
            case other                        => other
          }

          val st      = if (selected) fadeState else TrackTool.NoFade
          val fdIn    = fv.fadeIn  // XXX TODO: continue here. add delta
          val fdInFr  = fdIn.numFrames + st.deltaFadeIn
          if (fdInFr > 0) {
            val fw    = framesToScreen(fdInFr).toFloat
            val fdC   = st.deltaFadeInCurve
            val shape = if (fdC != 0f) adjustFade(fdIn.curve, fdC) else fdIn.curve
            paintFade(shape, fw = fw, y1 = fdIn.floor, y2 = 1f, x = px, x0 = px)
          }
          val fdOut   = fv.fadeOut
          val fdOutFr = fdOut.numFrames + st.deltaFadeOut
          if (fdOutFr > 0) {
            val fw    = framesToScreen(fdOutFr).toFloat
            val fdC   = st.deltaFadeOutCurve
            val shape = if (fdC != 0f) adjustFade(fdOut.curve, fdC) else fdOut.curve
            val x0    = px + pw - 1
            paintFade(shape, fw = fw, y1 = 1f, y2 = fdOut.floor, x = x0 - fw, x0 = x0)
          }

        case _ =>
      }

      g.setClip(clipOrig)

      // --- label ---
      if (regionViewMode == RegionViewMode.TitledBox) {
        val isDark = if (colorOption.isEmpty) true else {
          g.translate(px, py)
          val colr0 = colorOption.get.rgba
          // XXX TODO -- a quick hack to mix with blue
          val colr  = if (selected) ((((colr0 & 0xFFFFFF) >> 1) & 0x7F7F7F) + 0x80) | (colr0 & 0xFF000000) else colr0
          val colr2 = new java.awt.Color(colr, true)
          g.setColor(colr2)
          g.fillRoundRect(1, 1, pw - 2, regionTitleHeight - 1, 4, 4)
          g.translate(-px, -py)
          // cf. https://stackoverflow.com/questions/596216/
          val b = ((colr & 0xFF0000) >> 16) * 30 + ((colr & 0xFF00) >> 8) * 59 + (colr & 0xFF) * 11
          b < 15000
        }

        g.clipRect(px + 2, py + 2, pw - 4, ph - 4)
        // possible unicodes: 2327 23DB 24DC 25C7 2715 29BB
        // val text  = if (view.muted) "\u23DB " + name else name
        val text: String = this match {
          case mv: TimelineObjView.HasMute if mv.muted => s"\u25C7 $name"
          case _ => name
        }
        val tx    = px + 4
        val ty    = py + regionTitleBaseline
        g.setPaint(if (isDark) pntNameShadowDark else pntNameShadowLight)
        g.drawString(text, tx, ty + 1)
        g.setPaint(if (isDark) pntNameDark else pntNameLight)
        g.drawString(text, tx, ty)
        //              stakeInfo(ar).foreach { info =>
        //                g2.setColor(awt.Color.yellow)
        //                g2.drawString(info, x + 4, y + hndlBaseline + hndlExtent)
        //              }
        g.setClip(clipOrig)
      }

      this match {
        case mv: TimelineObjView.HasMute if mv.muted =>
          g.setPaint(pntRegionBackgroundMuted)
          g.fillRoundRect(px, py, pw, ph, 5, 5)
        case _ =>
      }
    }
  }

  def paintFront(g: Graphics2D, tlv: TimelineView[S], r: TimelineRendering): Unit = ()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy