Created
April 5, 2012 14:29
-
-
Save drdozer/2311463 to your computer and use it in GitHub Desktop.
ultra-lightweight reactive swing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** A mixin that exposes the text and document of any Swing component using the Document abstraction. | |
The text updates when the input is accepted by the component. The document text reflects what is typed. */ | |
trait TextVar { | |
/** Things that must be provided by any class that this is mixed into. */ | |
self : { | |
def getText(): String | |
def setText(s: String): Unit | |
def getDocument(): Document | |
def addActionListener(al: ActionListener): Unit | |
} => | |
/** The text as a `Var`. */ | |
val textVar = new Var[String](getText()) { | |
protected override def update(t: String) { | |
setText(t) | |
super.update(t) | |
} | |
} | |
self.addActionListener(new ActionListener { | |
def actionPerformed(e: ActionEvent) { | |
textVar := getText() | |
} | |
}) | |
private def dTxt = { | |
val d = getDocument() | |
d.getText(0, d.getLength) | |
} | |
/** The document text as a var. */ | |
val documentText = new Var[String](dTxt) | |
// initialize the listeners | |
getDocument().addDocumentListener(new DocumentListener { | |
def changedUpdate(e: DocumentEvent) { documentText := dTxt } | |
def removeUpdate(e: DocumentEvent) { documentText := dTxt } | |
def insertUpdate(e: DocumentEvent) { documentText := dTxt } | |
}) | |
} | |
/** Mixin for components that can be enabled and disabled. */ | |
trait EnabledVar { | |
self : { | |
def setEnabled(b: Boolean): Unit | |
def isEnabled(): Boolean | |
def addPropertyChangeListener(propertyName: String, listener: PropertyChangeListener): Unit | |
} => | |
val enabledVar = new Var[Boolean](isEnabled()) { | |
enabledSelf => | |
protected override def update(b: Boolean) { | |
setEnabled(b) | |
} | |
self.addPropertyChangeListener("enabled", new PropertyChangeListener() { | |
def propertyChange(evt: PropertyChangeEvent): Unit = enabledSelf := isEnabled() | |
}) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import javax.swing._ | |
object SwingDemo { | |
def main(args: Array[String]) { | |
val frame = new JFrame("Swing demo") { | |
frameSelf => | |
add(new Box(BoxLayout.Y_AXIS) { | |
val firstName = Var[Option[String]](None) | |
val lastName = Var[Option[String]](None) | |
val fullName = firstName * lastName | |
add(new Box(BoxLayout.X_AXIS) { | |
add(new JLabel("First name:")) | |
add(new JTextField with TextVar { | |
setColumns(30) | |
documentText =>> (Option(_)) =>> (_ filter(_.length > 0)) =>> firstName | |
}) | |
}) | |
add(new Box(BoxLayout.X_AXIS) { | |
add(new JLabel("Last name:")) | |
add(new JTextField with TextVar { | |
setColumns(30) | |
documentText =>> (Option(_)) =>> (_ filter(_.length > 0)) =>> lastName | |
}) | |
}) | |
add(new Box(BoxLayout.X_AXIS) { | |
add(new JLabel("Full name:")) | |
add(new JLabel() { | |
fullName ->> { case (f, l) => | |
val fn = f getOrElse "{first name}" | |
val ln = l getOrElse "{last name}" | |
setText(fn + " " + ln) | |
} | |
}) | |
}) | |
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE) | |
}) | |
} | |
frame.pack() | |
frame.setVisible(true) | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** A value that can be retrieved. */ | |
trait Value[T] { | |
def apply(): T | |
} | |
/** A value that can be set. */ | |
trait Settable[T] { | |
/** Set the value, and perform any necessary side-effects. */ | |
def :=(t: T): Unit = update(t) | |
/** Do the actual value update. This should purely update the value. Do not trigger any side-effects. */ | |
protected def update(t: T): Unit | |
} | |
/** A retrievable value that can be listened to for changes. */ | |
trait Listenable[T] extends Value[T] { | |
listenableSelf => | |
private var listeners: immutable.List[T => Unit] = Nil | |
/** Add a call-back for changes. */ | |
def ->> (l: T => Unit): Unit = listeners = l::listeners | |
/** Inform all listeners of a change. */ | |
protected def informListeners(): Unit = { | |
val t = apply() | |
for(l <- listeners) l(t) | |
} | |
/** Make a new `Listenable` from these two `Listenable`s that fires when either change their values. */ | |
def * [U](lu: Listenable[U]): Listenable[(T, U)] = new Listenable[(T, U)] { | |
listenableSelf ->> (_ => informListeners()) | |
lu ->> (_ => informListeners()) | |
def apply(): (T, U) = (listenableSelf(), lu()) | |
} | |
/** Get a new `Var` that contains this value mapped by some function. */ | |
def =>> [U](f: (T => U)): Var[U] = { | |
val vu = Var(f(this())) | |
this ->> { t => vu := f(t) } | |
vu | |
} | |
/** Bind a `Settable` to this value. When ever this value changes, the `Settable` will be synced. */ | |
def =>> (vt: Settable[T]): Unit = { | |
this ->> { vt := _ } | |
} | |
} | |
/** A value that exposes a value that you can change, listen to changes and set. */ | |
trait Assignable[T] extends Listenable[T] with Settable[T] { | |
/** Assigning will both set the value and inform all listeners if the value is new. */ | |
override abstract def :=(t: T): Unit = if(t != this()) { | |
val old = this() | |
super.:=(t) | |
if(old != this()) informListeners() | |
} | |
/** Apply a transformation function to this value. */ | |
// fixme: This is badly named | |
def lift(f: T => T): Unit = this := (f(this())) | |
/** Generate a `Var` which is synced in both directions with this, according to a `f`orward and `backward` function. */ | |
def <=> [U](f: (T => U), b: (U => T)): Var[U] = { | |
val vu = Var(f(this())) | |
this ->> { t => vu := f(t) } | |
vu ->> { u => this := b(u) } | |
vu | |
} | |
/** Sync this value directly with a `Var`, such that changes to either one will be reflected in the other. */ | |
def <=> (vt: Var[T]) = { | |
this ->> { vt := _ } | |
vt ->> { this := _ } | |
vt := this() | |
} | |
} | |
/** An assignable variable. This stores the value directly. Use for your 'model' state. */ | |
class Var[T] private () extends Assignable[T] { | |
private var value: T = _ | |
def this(t: T) = { | |
this() | |
this.:=(t) | |
} | |
def apply(): T= value | |
protected def update(t: T): Unit = if(t != value) { | |
value = t | |
} | |
} | |
object Var { | |
def apply[T](t: T): Var[T] = new Var(t) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment