de.sciss.lucre.swing.graph.Bang.scala Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of lucreswing_2.13.0-RC2 Show documentation
Show all versions of lucreswing_2.13.0-RC2 Show documentation
Swing support for Lucre, and common views
The newest version!
/*
* Bang.scala
* (LucreSwing)
*
* Copyright (c) 2014-2019 Hanns Holger Rutz. All rights reserved.
*
* This software is published under the GNU Lesser General Public License v2.1+
*
*
* For further information, please contact Hanns Holger Rutz at
* [email protected]
*/
package de.sciss.lucre.swing.graph
import java.awt.Dimension
import java.awt.event.{ActionEvent, ActionListener}
import java.awt.geom.Ellipse2D
import de.sciss.lucre.event.IPush.Parents
import de.sciss.lucre.event.impl.IGenerator
import de.sciss.lucre.event.{IEvent, IPull, ITargets}
import de.sciss.lucre.expr.graph.{Act, Trig}
import de.sciss.lucre.expr.{Context, IAction, IControl, ITrigger}
import de.sciss.lucre.stm
import de.sciss.lucre.stm.TxnLike.{peer => txPeer}
import de.sciss.lucre.stm.{Disposable, Sys}
import de.sciss.lucre.swing.LucreSwing.deferTx
import de.sciss.lucre.swing.View
import de.sciss.lucre.swing.graph.impl.{ComponentExpandedImpl, ComponentImpl}
import de.sciss.lucre.swing.impl.ComponentHolder
import javax.swing.{JButton, Timer}
import scala.concurrent.stm.Ref
import scala.swing.Graphics2D
import scala.swing.event.ButtonClicked
object Bang {
def apply(): Bang = Impl()
private final class Expanded[S <: Sys[S]](protected val peer: Bang)(implicit protected val targets: ITargets[S],
cursor: stm.Cursor[S])
extends View[S]
with ComponentHolder[scala.swing.Button] with ComponentExpandedImpl[S]
with IAction[S] with ITrigger[S]
with IGenerator[S, Unit] {
override def toString: String = s"Bang.Expanded@${hashCode().toHexString}"
type C = scala.swing.Button
private[this] val disposables = Ref(List.empty[Disposable[S#Tx]])
private def addDisposable(d: Disposable[S#Tx])(implicit tx: S#Tx): Unit =
disposables.transform(d :: _)
def executeAction()(implicit tx: S#Tx): Unit = {
fire(())
activate()
}
def addSource(tr: ITrigger[S])(implicit tx: S#Tx): Unit = {
// ok, this is a bit involved:
// we must either mixin the trait `Caching` or
// create an observer to not be eliminated from event
// reaction execution. If we don't do that, we'd only
// see activation when our trigger output is otherwise
// observed (e.g. goes into a `PrintLn`).
// What we do here is, first, wire the two events together,
// so that any instancing checking our trigger will observe it
// within the same event loop as the input trigger, and second,
// have the observation side effect (`activate`).
tr.changed ---> changed
val obs = tr.changed.react { implicit tx => _ => activate() }
addDisposable(obs)
}
private def activate()(implicit tx: S#Tx): Unit = {
deferTx {
setActive(true)
timer.restart()
}
}
private def setActive(value: Boolean): Unit =
if (active != value) {
active = value
val c = component
c.repaint()
// in Linux, repainting may be quite strongly delayed
// if the mouse or keyboard is not active.
// Thus force graphics state sync
c.toolkit.sync()
}
def changed: IEvent[S, Unit] = this
private[lucre] def pullUpdate(pull: IPull[S])(implicit tx: S#Tx) : Option[Unit] = {
if (pull.isOrigin(this)) Trig.Some
else {
val p: Parents[S] = pull.parents(this)
if (p.exists(pull(_).isDefined)) Trig.Some else None
}
}
private[this] var active = false
private[this] var timer: Timer = _
private def bang(): Unit =
cursor.step { implicit tx =>
executeAction()
}
override def dispose()(implicit tx: S#Tx): Unit = {
super.dispose()
disposables.swap(Nil).foreach(_.dispose())
deferTx {
timer.stop()
}
}
override def initComponent()(implicit tx: S#Tx, ctx: Context[S]): this.type = {
deferTx {
timer = new Timer(200, new ActionListener {
def actionPerformed(e: ActionEvent): Unit =
setActive(false)
})
timer.setRepeats(false)
val c: C = new scala.swing.Button {
private[this] val el = new Ellipse2D.Double()
override lazy val peer: JButton = new JButton(" ") with SuperMixin {
// XXX TODO --- hack to avoid too narrow buttons under certain look-and-feel
override def getPreferredSize: Dimension = {
val d = super.getPreferredSize
if (!isPreferredSizeSet) {
d.width = math.max(d.width, d.height)
}
d
}
}
override protected def paintComponent(g: Graphics2D): Unit = {
super.paintComponent(g)
g.setRenderingHint(java.awt.RenderingHints.KEY_ANTIALIASING,
java.awt.RenderingHints.VALUE_ANTIALIAS_ON)
g.setRenderingHint(java.awt.RenderingHints.KEY_STROKE_CONTROL,
java.awt.RenderingHints.VALUE_STROKE_PURE)
g.setColor(foreground)
val p = peer
val w = p.getWidth
val h = p.getHeight
val ext1 = math.max(0, math.min(w, h) - 14)
val _el = el
_el.setFrame((w - ext1) * 0.5, (h - ext1) * 0.5, ext1, ext1)
g.draw(_el)
// if (active) {
// val ext2 = math.max(0, ext1 - 5)
// _el.setFrame((w - ext2) * 0.5, (h - ext2) * 0.5, ext2, ext2)
// g.fill(_el)
// }
if (active) g.fill(_el) else g.draw(_el)
}
reactions += {
case ButtonClicked(_) => bang()
}
}
component = c
}
super.initComponent()
}
}
private final case class Impl() extends Bang with ComponentImpl {
override def productPrefix = "Bang" // serialization
protected def mkRepr[S <: Sys[S]](implicit ctx: Context[S], tx: S#Tx): Repr[S] = {
import ctx.{cursor, targets}
new Expanded[S](this).initComponent()
}
}
}
trait Bang extends Component with Act with Trig {
final type C = scala.swing.Button
type Repr[S <: Sys[S]] = View.T[S, C] with IControl[S] with ITrigger[S] with IAction[S]
}