Author | SHA1 | Message | Date |
---|---|---|---|
|
c59d48f6ec | Added split combinator to actionObservable | 4 months ago |
|
935ca358e6 |
Many changes
Updated store code - wrapped fold function in Task Added JSON logging functionality to store middleware Initial attempt at creating filter combinator for fx observables Made ListStore code use Effects Made a router using the store pattern Misc updates to fx monix implicits |
4 months ago |
|
857fd03bf1 | Cleanup and added some methods to actionObservable | 4 months ago |
|
f95f50574e | Added proper todo buttons reactively connected | 4 months ago |
|
f95ecc2cb3 | Wrapped error handle in UIO | 4 months ago |
|
1f06c536c8 | Updated monix version to 3.3.0 (was 3.2.2) | 4 months ago |
|
8f1fe0cc84 | Cleanup and refactoring | 4 months ago |
|
b988ad267e |
Added store pattern using monix
also added relevant implicits to use it |
4 months ago |
|
536f1b0af3 | Added scalafx wrappers for jfoenix and ikonli | 4 months ago |
@ -1,2 +1,2 @@ | |||
sbt.version=1.3.10 | |||
sbt.version=1.4.3 | |||
@ -1,37 +1,32 @@ | |||
package nova.monadic_sfx | |||
import monix.eval.Task | |||
// import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend | |||
// import sttp.client._ | |||
// import sttp.client.circe._ | |||
// import io.circe.generic.auto._ | |||
import nova.monadic_sfx.executors._ | |||
import cats.effect.Resource | |||
import sttp.client.asynchttpclient.monix.AsyncHttpClientMonixBackend | |||
import io.odin.syntax._ | |||
import io.odin.monix._ | |||
import monix.eval.TaskApp | |||
import _root_.monix.bio.BIOApp | |||
import _root_.monix.bio.Task | |||
import _root_.monix.bio.UIO | |||
import cats.effect.ExitCode | |||
import cats.implicits._ | |||
import cats.effect.Resource | |||
import com.softwaremill.macwire._ | |||
import io.odin._ | |||
import nova.monadic_sfx.executors._ | |||
// import nova.monadic_sfx.util.IOUtils._ | |||
// import sttp.client.httpclient.monix.HttpClientMonixBackend | |||
object Main extends MainModule with BIOApp { | |||
object Main extends MainModule with TaskApp { | |||
def appResource(startTime: Long) = | |||
for { | |||
implicit0(logger: Logger[Task]) <- makeLogger | |||
schedulers = new Schedulers() | |||
// backend <- Resource.make( | |||
// toIO(HttpClientMonixBackend()(schedulers.async)) | |||
// )(c => toIO(c.close())) | |||
// actorSystem <- actorSystemResource(logger) | |||
_ <- Resource.liftF(wire[MainApp].program) | |||
} yield () | |||
override def run(args: List[String]): Task[ExitCode] = { | |||
// val startTime = Task.clock | |||
// .monotonic(scala.concurrent.duration.MILLISECONDS) | |||
// .map(Duration.fromNanos(_)) | |||
lazy val appResource = for { | |||
// clock <- Resource.liftF(Task(Task.clock)) | |||
logger <- consoleLogger().withAsync() | |||
backend <- AsyncHttpClientMonixBackend.resource() | |||
actorSystem <- actorSystemResource(logger) | |||
reqs <- Resource.liftF(Task(wireWith(requesters _))) | |||
schedulers <- Resource.liftF(Task(new Schedulers())) | |||
fxApp <- wireWith(fxAppResource _) | |||
} yield (fxApp) | |||
appResource | |||
.use(fxApp => Task(fxApp.main(args.toArray))) | |||
override def run(args: List[String]): UIO[ExitCode] = | |||
appResource(System.currentTimeMillis()) | |||
.use(_ => Task.unit) | |||
.onErrorHandleWith(ex => UIO(ex.printStackTrace())) | |||
.as(ExitCode.Success) | |||
} | |||
} |
@ -0,0 +1,189 @@ | |||
package nova.monadic_sfx | |||
import com.softwaremill.macwire._ | |||
import io.odin.Logger | |||
import monix.bio.Task | |||
import nova.monadic_sfx.executors.Schedulers | |||
import nova.monadic_sfx.implicits.JFXButton | |||
import nova.monadic_sfx.implicits.JavaFXMonixObservables._ | |||
import nova.monadic_sfx.ui.MyFxApp | |||
import nova.monadic_sfx.ui.components.router.BrainNotWorking | |||
import nova.monadic_sfx.ui.components.router.FXRouter | |||
import nova.monadic_sfx.ui.components.todo.TodoListStore | |||
import nova.monadic_sfx.ui.components.todo.TodoListView | |||
import org.gerweck.scalafx.util._ | |||
import scalafx.Includes._ | |||
import scalafx.application.JFXApp.PrimaryStage | |||
import scalafx.beans.property.ObjectProperty | |||
import scalafx.beans.property.StringProperty | |||
import scalafx.collections.ObservableBuffer | |||
import scalafx.geometry.Insets | |||
import scalafx.scene.Scene | |||
import scalafx.scene.control.TableColumn | |||
import scalafx.scene.control.TableView | |||
import scalafx.scene.layout.HBox | |||
import scalafx.scene.paint.Color | |||
import scalafx.scene.shape.Rectangle | |||
class MainApp( | |||
// spawnProtocol: ActorSystem[SpawnProtocol.Command], | |||
schedulers: Schedulers, | |||
startTime: Long | |||
)(implicit logger: Logger[Task]) { | |||
lazy val addTodoButton = new JFXButton { | |||
text = "Add" | |||
} | |||
lazy val addTodoObs = addTodoButton.observableAction | |||
// lazy val todoListView = TodoListView.defaultListView | |||
lazy val _scene = new Scene { | |||
root = new HBox { | |||
padding = Insets(20) | |||
content = new Rectangle { | |||
width = 400 | |||
height = 200 | |||
fill = Color.DeepSkyBlue | |||
} | |||
children ++= Seq( | |||
// new JFXButton { | |||
// text = "DummyButton" | |||
// }, | |||
// new JFXButton { | |||
// text = "DummyButton2" | |||
// }, | |||
// addTodoButton, | |||
// Test.ttv | |||
// todoListView | |||
) | |||
} | |||
} | |||
private lazy val stage = new PrimaryStage { | |||
title = "Simple ScalaFX App" | |||
scene = _scene | |||
width = 1000 | |||
height = 400 | |||
} | |||
// implicit val l = logger | |||
// implicit val sp = spawnProtocol | |||
val program = for { | |||
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage) | |||
// _ <- Task(fxApp.stage = stage) | |||
// .executeOn(schedulers.fx) | |||
// .delayExecution(2000.millis) | |||
// todoComponent <- createTodoComponent | |||
// _ <- toIO( | |||
// addTodoObs | |||
// .mapEval(_ => | |||
// toTask(todoComponent.send(TodoListComponent.Add(Todo(1, "blah")))) | |||
// ) | |||
// .completedL | |||
// .executeOn(schedulers.fx) | |||
// .startAndForget | |||
// ) | |||
_ <- createTodoComponent.executeOn(schedulers.fx) | |||
router <- Task(BrainNotWorking.router) | |||
routerStore <- router.store(BrainNotWorking.Page.Home, logger) | |||
routerNode <- for { | |||
node <- | |||
Task | |||
.deferAction(implicit s => | |||
Task(new HBox { | |||
children <-- router | |||
.render(BrainNotWorking.resolver)(routerStore) | |||
.map(_.delegate) | |||
}) | |||
) | |||
.executeOn(schedulers.fx) | |||
_ <- Task.deferFuture( | |||
routerStore.onNext(FXRouter.Replace(BrainNotWorking.Page.UserHome(1))) | |||
) | |||
} yield node | |||
// _ <- | |||
// BrainNotWorking | |||
// .routerTask(logger) | |||
// .flatMap(node => Task(_scene.getChildren += node)) | |||
// .executeOn(schedulers.fx) | |||
_ <- Task(_scene.getChildren += routerNode).executeOn(schedulers.fx) | |||
_ <- logger.info( | |||
s"Application started in ${(System.currentTimeMillis() - startTime) / 1000f} seconds" | |||
) | |||
_ <- fxAppFib.join | |||
} yield () | |||
// def createTodoComponent: Task[TodoListComponent] = { | |||
// for { | |||
// channel <- | |||
// ConcurrentChannel | |||
// .of[Task, TodoListComponent.Complete, TodoListComponent.Command] | |||
// scheduler = schedulers.fx | |||
// lv <- TodoListView.defaultListView2.executeOn(scheduler) | |||
// // todoLV = new TodoListView(lv) | |||
// todoComponent <- wire[TodoListComponent.Props].create | |||
// // TODO make this a "message pass" instead of mutating directly | |||
// _ <- Task(_scene.getChildren += lv).executeOn(scheduler) | |||
// // _ <- toIO( | |||
// // delObs | |||
// // .doOnNext(_ => toTask(logger.debug("Pressed delete"))) | |||
// // .doOnNext(todo => | |||
// // toTask( | |||
// // for { | |||
// // _ <- logger.debug(s"Got todo $todo") | |||
// // _ <- todoComponent.send(TodoListComponent.Delete(todo.id)) | |||
// // // _ <- Task.sequence( | |||
// // // lst.map(todo => | |||
// // // todoComponent.send(TodoListComponent.Delete(todo.id)) | |||
// // // ) | |||
// // // ) | |||
// // } yield () | |||
// // ) | |||
// // ) | |||
// // .completedL | |||
// // ).startAndForget | |||
// // _ <- toIO( | |||
// // editObs | |||
// // .doOnNext(_ => toTask(logger.debug("Pressed edit"))) | |||
// // .completedL | |||
// // ).startAndForget | |||
// } yield todoComponent | |||
// } | |||
def createTodoComponent: Task[Unit] = | |||
for { | |||
store <- TodoListStore(logger) | |||
rootNode <- TodoListView(store) | |||
_ <- Task(_scene.getChildren += rootNode) | |||
} yield () | |||
} | |||
class TestModel(_name: String, _age: Int) { | |||
val name = StringProperty(_name).readOnly | |||
val age = ObjectProperty(_age).readOnly | |||
} | |||
object Test { | |||
val items = ObservableBuffer( | |||
new TestModel("hmm", 1), | |||
new TestModel("hmm2", 2) | |||
) | |||
val ttv = new TableView[TestModel](items) { | |||
columns ++= Seq( | |||
new TableColumn[TestModel, String] { | |||
text = "Name" | |||
cellValueFactory = { _.value.name } | |||
}, | |||
new TableColumn[TestModel, Int] { | |||
text = "Age" | |||
cellValueFactory = { _.value.age } | |||
} | |||
) | |||
} | |||
} |
@ -1,7 +1,41 @@ | |||
package nova.monadic_sfx | |||
import scala.concurrent.duration._ | |||
import _root_.monix.bio.Task | |||
import cats.implicits._ | |||
import io.odin._ | |||
import io.odin.config._ | |||
import io.odin.syntax._ | |||
import nova.monadic_sfx.actors.ActorModule | |||
import nova.monadic_sfx.ui.UiModule | |||
import nova.monadic_sfx.http.HttpModule | |||
import nova.monadic_sfx.ui.UiModule | |||
import nova.monadic_sfx.util.reactive.Middlewares | |||
trait MainModule extends ActorModule with UiModule with HttpModule { | |||
def routerLogger(defaultLogger: Logger[Task], storeLogger: Logger[Task]) = | |||
enclosureRouting[Task]( | |||
"nova.monadic_sfx.util.reactive.Middlewares" -> storeLogger, | |||
"nova.monadic_sfx.util.reactive.Store" -> storeLogger | |||
) | |||
.withFallback(defaultLogger) | |||
.withAsync() | |||
trait MainModule extends ActorModule with UiModule with HttpModule | |||
def makeLogger = | |||
for { | |||
defaultLogger <- consoleLogger[Task]() | |||
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task]( | |||
"application.log" | |||
).withAsync() | |||
middlewareLogger <- | |||
consoleLogger[ | |||
Task | |||
](formatter = Middlewares.format) | |||
.withMinimalLevel(Level.Trace) | |||
.withAsync() |+| fileLogger[Task]( | |||
"stores.log", | |||
formatter = Middlewares.format | |||
).withAsync() | |||
routerLogger <- routerLogger(defaultLogger, middlewareLogger) | |||
} yield (routerLogger) | |||
} |
@ -1,13 +1,14 @@ | |||
package nova.monadic_sfx | |||
import java.nio.ByteBuffer | |||
import monix.eval.Task | |||
import sttp.client.SttpBackend | |||
import monix.reactive.Observable | |||
import sttp.client.asynchttpclient.WebSocketHandler | |||
import java.nio.ByteBuffer | |||
import sttp.client.SttpBackend | |||
import sttp.client.httpclient.WebSocketHandler | |||
trait AppTypes {} | |||
object AppTypes { | |||
trait AppTypes { | |||
type HttpBackend = | |||
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] | |||
} | |||
object AppTypes extends AppTypes {} |
@ -1,74 +1,28 @@ | |||
package nova.monadic_sfx.actors | |||
import io.odin.Logger | |||
import monix.eval.Task | |||
import cats.effect.Resource | |||
import akka.actor.typed.scaladsl.Behaviors | |||
import com.softwaremill.macwire._ | |||
import akka.util.Timeout | |||
import scala.concurrent.duration._ | |||
import scala.concurrent.Future | |||
import akka.actor.typed._ | |||
import akka.actor.typed.scaladsl.AskPattern._ | |||
import scala.concurrent.Await | |||
import nova.monadic_sfx.executors.Schedulers | |||
import akka.util.Timeout | |||
import cats.effect.Resource | |||
import io.odin.Logger | |||
import monix.bio.Task | |||
trait ActorModule { | |||
import scala.concurrent.ExecutionContext | |||
implicit val timeout: Timeout = Timeout(3.seconds) | |||
implicit def timeout: Timeout = Timeout(3.seconds) | |||
def actorSystemResource( | |||
logger: Logger[Task] | |||
): Resource[Task, ActorSystem[SpawnProtocol.Command]] = | |||
Resource.make(logger.info("Creating Actor System") >> Task { | |||
ActorSystem(HelloWorldMain(), name = "FXActorSystem") | |||
ActorSystem(SpawnProtocol(), name = "FXActorSystem") | |||
})(sys => | |||
logger.info("Shutting down actor system") >> Task( | |||
sys.terminate() | |||
) >> logger.info("Actor System terminated") | |||
for { | |||
_ <- Task(sys.terminate()) | |||
_ <- Task.fromFuture(sys.whenTerminated) | |||
_ <- logger.info("Actor System Terminated") | |||
} yield () | |||
) | |||
// def actorsResource( | |||
// system: ActorSystem[SpawnProtocol.Command], | |||
// logger: Logger[Task], | |||
// schedulers: Schedulers | |||
// ): Resource[Task, Task[ActorRef[Counter.Command]]] = { | |||
// implicit val ec: ExecutionContext = system.executionContext | |||
// implicit val scheduler = system.scheduler | |||
// Resource.make( | |||
// Task { | |||
// val actor = Task.deferFuture { | |||
// system.ask[ActorRef[Counter.Command]]( | |||
// SpawnProtocol.Spawn( | |||
// behavior = Counter(), | |||
// name = "counterActor", | |||
// // DispatcherSelector.fromConfig("javafx-dispatcher"), | |||
// // Props.empty, | |||
// _ | |||
// ) | |||
// ) | |||
// } | |||
// // system. | |||
// actor | |||
// } | |||
// )(actorTask => | |||
// for { | |||
// actor <- actorTask | |||
// _ <- logger.info("Stopping actor counter") | |||
// t <- Task(actor ! Counter.Stop) | |||
// _ <- logger.info("Counter actor stopped") | |||
// } yield () | |||
// ) | |||
// } | |||
} | |||
object HelloWorldMain { | |||
def apply(): Behavior[SpawnProtocol.Command] = | |||
Behaviors.setup { context => | |||
// Start initial tasks | |||
// context.spawn(...) | |||
SpawnProtocol() | |||
} | |||
} |
@ -1,9 +1,28 @@ | |||
package nova.monadic_sfx.executors | |||
import com.typesafe.scalalogging.Logger | |||
import monix.execution.Scheduler | |||
import monix.execution.UncaughtExceptionReporter | |||
import monix.execution.schedulers.TracingScheduler | |||
class Schedulers( | |||
val blockingIO: Scheduler = Scheduler.io(), | |||
val cpu: Scheduler = Scheduler.global, | |||
val fx: Scheduler = JFXExecutionContexts.fxScheduler | |||
val blocking: Scheduler = TracingScheduler( | |||
Scheduler | |||
.io() | |||
.withUncaughtExceptionReporter(Schedulers.reporter) | |||
), | |||
val async: Scheduler = Scheduler.traced | |||
.withUncaughtExceptionReporter(Schedulers.reporter), | |||
val fx: Scheduler = TracingScheduler( | |||
JFXExecutionContexts.fxScheduler | |||
.withUncaughtExceptionReporter(Schedulers.reporter) | |||
) | |||
) | |||
object Schedulers { | |||
val reporter = UncaughtExceptionReporter { ex => | |||
val logger = Logger[Schedulers] | |||
logger.error("Uncaught exception", ex) | |||
} | |||
} |
@ -1,43 +1,17 @@ | |||
package nova.monadic_sfx.http.requests | |||
import nova.monadic_sfx.AppTypes | |||
import nova.monadic_sfx.AppTypes.HttpBackend | |||
import monix.eval.Task | |||
import nova.monadic_sfx.models._ | |||
import sttp.client._ | |||
import sttp.client.circe._ | |||
import io.circe.generic.auto._ | |||
import nova.monadic_sfx.models._ | |||
import cats.data.EitherT | |||
class DummyRequest(backend: HttpBackend) extends AppTypes { | |||
private implicit val _backend = backend | |||
def send() = { | |||
Task | |||
.suspend { | |||
for { | |||
req <- | |||
basicRequest | |||
.get(uri"https://httpbin.org/get") | |||
.response(asJson[HttpBinResponse]) | |||
.send() | |||
} yield (req) | |||
} | |||
} | |||
def send = | |||
basicRequest | |||
.get(uri"https://httpbin.org/get") | |||
.response(asJson[HttpBinResponse]) | |||
.send() | |||
def test() = { | |||
for { | |||
res <- send() | |||
res3 <- Task { res.body } | |||
res2 <- Task { | |||
res3.fold( | |||
err => { | |||
err.toString() | |||
}, | |||
value => value.toString() | |||
) | |||
} | |||
} yield (res3) | |||
} | |||
} |
@ -0,0 +1,98 @@ | |||
package nova.monadic_sfx.implicits | |||
import monix.execution.Cancelable | |||
import monix.execution.Scheduler | |||
import monix.execution.cancelables.CompositeCancelable | |||
import monix.reactive.Observable | |||
import monix.reactive.Observer | |||
import monix.{eval => me} | |||
class ActionObservableExecutor[T]( | |||
private val delegate: Observable[T] | |||
) extends AnyVal { | |||
def -->(sub: Observer[T])(implicit s: Scheduler) = | |||
delegate | |||
.doOnNext(el => me.Task(sub.onNext(el))) | |||
.subscribe() | |||
} | |||
class ActionObservableBuilder[A]( | |||
private val observableAction: Observable[A] | |||
) extends AnyVal { | |||
def useLazyEval[T](v: => me.Task[T]) = | |||
new ActionObservableExecutor[T](observableAction.mapEval(_ => v)) | |||
def useEval[T](cb: A => me.Task[T]) = | |||
new ActionObservableExecutor[T]( | |||
observableAction.mapEval(cb) | |||
) | |||
def useIterableEval[T](cb: A => collection.immutable.Iterable[T]) = | |||
new ActionObservableExecutor[T]( | |||
observableAction.flatMap(a => | |||
Observable.suspend(Observable.fromIterable(cb(a))) | |||
) | |||
) | |||
def doOnNext(cb: A => me.Task[Unit]): ActionObservableBuilder[A] = | |||
new ActionObservableBuilder(observableAction.doOnNext(cb)) | |||
def mapEval[B](cb: A => me.Task[B]) = | |||
new ActionObservableBuilder(observableAction.mapEval(cb)) | |||
def underlying = observableAction | |||
// Caution: Experimental stuff below.. | |||
def useEval2[B, C](f: A => me.Task[B], g: A => me.Task[C]) = | |||
new ActionObservableExecutor[(B, C)]( | |||
observableAction.publishSelector(conn => | |||
conn | |||
.mapEval(f) | |||
.switchMap(b => | |||
conn.mapEval(a => | |||
for { | |||
c <- g(a) | |||
} yield (b, c) | |||
) | |||
) | |||
) | |||
) | |||
def bifurcate[B, C]( | |||
f: ActionObservableBuilder[A] => B, | |||
g: ActionObservableBuilder[A] => C | |||
)(implicit s: Scheduler) = | |||
observableAction | |||
.publishSelector(conn => | |||
Observable( | |||
Observable.unit.doOnNext(_ => | |||
me.Task(f(new ActionObservableBuilder[A](conn))) >> me.Task.unit | |||
), | |||
Observable.unit.doOnNext(_ => | |||
me.Task(g(new ActionObservableBuilder[A](conn))) >> me.Task.unit | |||
) | |||
).merge | |||
) | |||
.subscribe() | |||
def split( | |||
lst: (ActionObservableBuilder[A] => Cancelable)* | |||
)(implicit s: Scheduler): Cancelable = { | |||
val comp = CompositeCancelable() | |||
comp += observableAction | |||
.publishSelector(conn => | |||
Observable( | |||
lst.map(f => | |||
Observable.unit.doOnNext(_ => | |||
me.Task( | |||
comp += f(new ActionObservableBuilder[A](conn)) | |||
) >> me.Task.unit | |||
) | |||
): _* | |||
).merge | |||
) | |||
.subscribe() | |||
} | |||
} |
@ -0,0 +1,44 @@ | |||
package nova.monadic_sfx.implicits | |||
import javafx.{scene => jfxs} | |||
import org.kordamp.ikonli.{javafx => ikonlifx} | |||
import scalafx.scene.paint.Paint | |||
import scalafx.scene.text.Text | |||
object FontIcon { | |||
implicit def sfxText2jfx(v: FontIcon): jfxs.text.Text = | |||
if (v != null) v.delegate else null | |||
} | |||
// extends Shape(delegate) | |||
// with PositionDelegate[ikonlifx.FontIcon] | |||
// with SFXDelegate[ikonlifx.FontIcon] | |||
class FontIcon(override val delegate: ikonlifx.FontIcon = new ikonlifx.FontIcon) | |||
extends Text(delegate) { | |||
// def iconCode_=(v: Ikon) = delegate.setIconCode(v) | |||
def iconColor = delegate.getIconColor() | |||
def iconColor_=(color: Paint) = delegate.setIconColor(color) | |||
def iconSize = delegate.getIconSize() | |||
def iconSize_=(size: Int) = delegate.setIconSize(size) | |||
def iconLiteral = delegate.getIconLiteral() | |||
def iconLiteral_=(literal: IconLiteral) = | |||
delegate.setIconLiteral(literal.value) | |||
def iconLiteral_=(literal: String) = delegate.setIconLiteral(literal) | |||
} | |||
sealed abstract class IconLiteral(val value: String) | |||
object IconLiteral { | |||
// fab-accusoft | |||
case object Gmi10k extends IconLiteral("gmi-10k") | |||
} |
@ -0,0 +1,42 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.{controls => jfoenixc} | |||
import javafx.{scene => jfxs} | |||
import scalafx.Includes._ | |||
import scalafx.beans.property.ObjectProperty | |||
import scalafx.scene.Node | |||
import scalafx.scene.control.Button | |||
import jfxs.{paint => jfxsp} | |||
object JFXButton { | |||
implicit def sfxButton2jfx(v: JFXButton): jfoenixc.JFXButton = | |||
if (v != null) v.delegate else null | |||
} | |||
class JFXButton( | |||
override val delegate: jfoenixc.JFXButton = new jfoenixc.JFXButton | |||
) extends Button(delegate) { | |||
/** | |||
* Creates a button with the specified text as its label. | |||
*/ | |||
def this(text: String) = this(new jfoenixc.JFXButton(text)) | |||
/** | |||
* Creates a button with the specified text and icon for its label. | |||
*/ | |||
def this(text: String, graphic: Node) = | |||
this(new jfoenixc.JFXButton(text, graphic)) | |||
def ripplerFill: ObjectProperty[jfxsp.Paint] = | |||
jfxObjectProperty2sfx(delegate.ripplerFillProperty) | |||
def ripplerFill_=(b: jfxsp.Paint): Unit = { | |||
ripplerFill() = b | |||
} | |||
def obsAction = | |||
new ActionObservableBuilder(this.observableAction()) | |||
} |
@ -0,0 +1,36 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.{controls => jfoenixc} | |||
import javafx.scene.{control => jfxsc} | |||
import scalafx.Includes._ | |||
import scalafx.beans.property.ReadOnlyObjectProperty | |||
import scalafx.delegate.SFXDelegate | |||
import scalafx.scene.control.IndexedCell | |||
import scalafx.scene.control.ListView | |||
object JFXListCell { | |||
implicit def sfxListCell2jfx[T]( | |||
l: JFXListCell[T] | |||
): jfoenixc.JFXListCell[T] = | |||
if (l != null) l.delegate else null | |||
} | |||
class JFXListCell[T]( | |||
override val delegate: jfoenixc.JFXListCell[T] = new jfoenixc.JFXListCell[T] | |||
) extends IndexedCell(delegate) | |||
with SFXDelegate[jfoenixc.JFXListCell[T]] { | |||
/** | |||
* The ListView associated with this Cell. | |||
*/ | |||
def listView: ReadOnlyObjectProperty[jfxsc.ListView[T]] = | |||
delegate.listViewProperty | |||
/** | |||
* Updates the ListView associated with this Cell. | |||
*/ | |||
def updateListView(listView: ListView[T]): Unit = { | |||
delegate.updateListView(listView) | |||
} | |||
} |
@ -0,0 +1,48 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.{controls => jfoenixc} | |||
import monix.execution.Scheduler | |||
import monix.reactive.Observable | |||
import scalafx.Includes._ | |||
import scalafx.collections.ObservableBuffer | |||
import scalafx.scene.control.ListView | |||
object JFXListView { | |||
implicit def sfxListView2jfx[T](l: JFXListView[T]): jfoenixc.JFXListView[T] = | |||
if (l != null) l.delegate else null | |||
} | |||
// extends Control(delegate) | |||
// with SFXDelegate[jfoenixc.JFXListView[T]] | |||
class JFXListView[T]( | |||
override val delegate: jfoenixc.JFXListView[T] = new jfoenixc.JFXListView[T] | |||
) extends ListView[T] { | |||
// def items_=( | |||
// v: Observable[ObservableBuffer[T]] | |||
// )(implicit s: Scheduler): Unit = { | |||
// v.foreach { items() = _ } | |||
// } | |||
def items_=( | |||
v: Observable[Seq[T]] | |||
)(implicit s: Scheduler): Unit = { | |||
v | |||
.map { | |||
// case buf: ObservableBuffer[T] => buf | |||
case other => ObservableBuffer.from(other) | |||
} | |||
// .map(myDiff(items(), _)) | |||
.foreach { items() = _ } | |||
} | |||
def depth = delegate.depthProperty() | |||
def depth_=(depth: Int) = delegate.setDepth(depth) | |||
def expanded = delegate.expandedProperty() | |||
def expanded_=(v: Boolean) = expanded() = v | |||
} |
@ -0,0 +1,29 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.{controls => jfoenixc} | |||
import scalafx.scene.control.ProgressIndicator | |||
object JFXSpinner { | |||
implicit def sfxSpinner2jfx( | |||
v: JFXSpinner | |||
): jfoenixc.JFXSpinner = if (v != null) v.delegate else null | |||
} | |||
// extends Control(delegate) | |||
// with SFXDelegate[jfoenixc.JFXSpinner] | |||
/** | |||
* Wraps [[JFoenix JFXSpinner]] | |||
*/ | |||
class JFXSpinner( | |||
override val delegate: jfoenixc.JFXSpinner = new jfoenixc.JFXSpinner | |||
) extends ProgressIndicator(delegate) { | |||
def radius = delegate.getRadius() | |||
def radius_=(radius: Double) = delegate.setRadius(radius) | |||
def startingAngle = delegate.startingAngleProperty() | |||
def startingAngle_=(angle: Double) = delegate.setStartingAngle(angle) | |||
} |
@ -0,0 +1,40 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.{controls => jfoenixc} | |||
import scalafx.Includes._ | |||
import scalafx.beans.property.BooleanProperty | |||
import scalafx.scene.control.TextArea | |||
import scalafx.scene.paint.Paint | |||
object JFXTextArea { | |||
implicit def sfxTextArea2jfx(v: JFXTextArea): jfoenixc.JFXTextArea = | |||
if (v != null) v.delegate else null | |||
} | |||
// extends TextInputControl(delegate) | |||
// with SFXDelegate[jfoenixc.JFXTextArea] | |||
class JFXTextArea( | |||
override val delegate: jfoenixc.JFXTextArea = new jfoenixc.JFXTextArea() | |||
) extends TextArea(delegate) { | |||
/** | |||
* Creates a TextArea with initial text content. | |||
* | |||
* @param text - A string for text content. | |||
*/ | |||
def this(text: String) = this(new jfoenixc.JFXTextArea(text)) | |||
def labelFloat = delegate.labelFloatProperty() | |||
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v) | |||
def focusColor: Paint = delegate.getFocusColor() | |||
def focusColor_=(color: Paint) = delegate.setFocusColor(color) | |||
def unFocusColor = delegate.getUnFocusColor() | |||
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color) | |||
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty() | |||
def disableAnimation_=(disable: Boolean) = | |||
delegate.setDisableAnimation(disable) | |||
} |
@ -0,0 +1,35 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.{controls => jfoenixc} | |||
import scalafx.Includes._ | |||
import scalafx.beans.property.BooleanProperty | |||
import scalafx.scene.control.TextField | |||
import scalafx.scene.paint.Paint | |||
object JFXTextField { | |||
implicit def sfxTextField2jfx(v: JFXTextField): jfoenixc.JFXTextField = | |||
if (v != null) v.delegate else null | |||
} | |||
// TextInputControl(delegate) | |||
// with AlignmentDelegate[jfoenixc.JFXTextField] | |||
// with SFXDelegate[jfoenixc.JFXTextField] { | |||
class JFXTextField( | |||
override val delegate: jfoenixc.JFXTextField = new jfoenixc.JFXTextField | |||
) extends TextField(delegate) { | |||
def labelFloat = delegate.labelFloatProperty() | |||
def labelFloat_=(v: Boolean) = delegate.setLabelFloat(v) | |||
def focusColor: Paint = delegate.getFocusColor() | |||
def focusColor_=(color: Paint) = delegate.setFocusColor(color) | |||
def unFocusColor = delegate.getUnFocusColor() | |||
def unFocusColor_=(color: Paint) = delegate.setUnFocusColor(color) | |||
def disableAnimation: BooleanProperty = delegate.disableAnimationProperty() | |||
def disableAnimation_=(disable: Boolean) = | |||
delegate.setDisableAnimation(disable) | |||
} |
@ -0,0 +1,62 @@ | |||
package nova.monadic_sfx.implicits | |||
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject | |||
import com.jfoenix.{controls => jfoenixc} | |||
import javafx.scene.{control => jfxsc} | |||
import scalafx.collections.ObservableBuffer | |||
import scalafx.scene.control.TreeItem | |||
import scalafx.scene.control.TreeTableView | |||
class RecursiveTreeItem[G <: RecursiveTreeObject[G]]( | |||
override val delegate: jfoenixc.RecursiveTreeItem[G] = | |||
new jfoenixc.RecursiveTreeItem[G]((item: RecursiveTreeObject[G]) => | |||
item.getChildren() | |||
) | |||
) extends TreeItem[G](delegate) { | |||
def this(value: G) = | |||
this( | |||
new jfoenixc.RecursiveTreeItem[G]( | |||
value, | |||
(item: RecursiveTreeObject[G]) => item.getChildren() | |||
) | |||
) | |||
def this(items: ObservableBuffer[G]) = | |||
this( | |||
new jfoenixc.RecursiveTreeItem[G]( | |||
items, | |||
(item: RecursiveTreeObject[G]) => item.getChildren() | |||
) | |||
) | |||
} | |||
object RecursiveTreeItem { | |||
implicit def sfxTreeItem2jfxTreeItem[G <: RecursiveTreeObject[G]]( | |||
v: RecursiveTreeItem[G] | |||
): jfoenixc.RecursiveTreeItem[G] = v.delegate | |||
} | |||
// @formatter:off | |||
class JFXTreeTableView[S <: RecursiveTreeObject[S]]( | |||
override val delegate: jfoenixc.JFXTreeTableView[S] = new jfoenixc.JFXTreeTableView[S] | |||
) extends TreeTableView(delegate) { | |||
def this(root: TreeItem[S]) = this(new jfoenixc.JFXTreeTableView[S](root)) | |||
// def this(root: TreeItem[S], items: ObservableBuffer[S]) = this(new jfoenixc.JFXTreeTableView[S](root, items)) | |||
// @formatter:on | |||
def currentItemsCount = delegate.currentItemsCountProperty() | |||
def predicate = delegate.predicateProperty() | |||
// delegate.set | |||
// override def treeColumn_=(v: TreeTableColumn[S, _]): Unit = ??? | |||
// delegate.setTreeColumn() | |||
} | |||
// @formatter:off | |||
object JFXTreeTableView { | |||
implicit def sfxTreeTableView2jfx[S <: RecursiveTreeObject[S]]( | |||
v: JFXTreeTableView[S] | |||
): jfxsc.TreeTableView[S] = if (v != null) v.delegate else null | |||
} | |||
// @formatter:on |
@ -0,0 +1,327 @@ | |||
package nova.monadic_sfx.implicits | |||
import javafx.beans.property.ObjectProperty | |||
import javafx.collections.ObservableList | |||
import javafx.event.ActionEvent | |||
import javafx.event.EventHandler | |||
import javafx.scene.{input => jfxsi} | |||
import javafx.{event => jfxe} | |||
import monix.bio.Task | |||
import monix.eval.Coeval | |||
import monix.execution.Ack | |||
import monix.execution.Cancelable | |||
import monix.execution.Scheduler | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
import monix.reactive.Observable | |||
import monix.reactive.Observer | |||
import monix.reactive.OverflowStrategy | |||
import monix.tail.Iterant | |||
import monix.{eval => me} | |||
import org.gerweck.scalafx.util._ | |||
import scalafx.Includes._ | |||
import scalafx.beans.property.Property | |||
import scalafx.beans.property.ReadOnlyProperty | |||
import scalafx.collections.ObservableBuffer | |||
import scalafx.event.subscriptions.Subscription | |||
import scalafx.scene.Scene | |||
import scalafx.scene.control.ButtonBase | |||
import scalafx.scene.control.MenuItem | |||
object JavaFXMonixObservables { | |||
implicit final class SceneObservables(private val scene: Scene) | |||
extends AnyVal { | |||
def observableMousePressed(): Observable[jfxsi.MouseEvent] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { | |||
override def handle(event: jfxsi.MouseEvent): Unit = { | |||
if (sub.onNext(event) == Ack.Stop) c.cancel() | |||
} | |||
} | |||
scene.onMousePressed = l | |||
c := Cancelable(() => | |||
scene.removeEventHandler( | |||
jfxsi.MouseEvent.MOUSE_PRESSED, | |||
l | |||
) | |||
) | |||
c | |||
} | |||
} | |||
def observableMouseDragged(): Observable[jfxsi.MouseEvent] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val l = new jfxe.EventHandler[jfxsi.MouseEvent] { | |||
override def handle(event: jfxsi.MouseEvent): Unit = { | |||
if (sub.onNext(event) == Ack.Stop) c.cancel() | |||
} | |||
} | |||
scene.onMouseDragged = l | |||
c := Cancelable(() => | |||
scene.removeEventHandler( | |||
jfxsi.MouseEvent.MOUSE_DRAGGED, | |||
l | |||
) | |||
) | |||
c | |||
} | |||
} | |||
} | |||
implicit final class BindObs[T, J](private val prop: Property[T, J]) | |||
extends AnyVal { | |||
def -->(op: Observer[T]) = { | |||
op.onNext(prop.value) | |||
} | |||
def ==>(op: Property[T, J]) = { | |||
op <== prop | |||
} | |||
def <--(obs: Observable[T])(implicit s: Scheduler) = { | |||
obs.doOnNextF(v => Coeval(prop.value = v)).subscribe() | |||
} | |||
def asOption = prop.map(Option(_)) | |||
def observableChange[J1 >: J]: Observable[J1] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val canc = | |||
prop.onChange((a, b, c1) => | |||
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() | |||
) | |||
c := Cancelable(() => canc.cancel()) | |||
c | |||
} | |||
} | |||
} | |||
implicit final class BindObs2[A](private val prop: ObjectProperty[A]) | |||
extends AnyVal { | |||
def -->(sub: Observer[A]) = | |||
prop.onChange((a, b, c) => if (c != null) sub.onNext(c)) | |||
def -->(op: Property[A, A]) = { | |||
prop.onChange((a, b, c) => if (c != null) op() = c) | |||
} | |||
def <--(obs: Observable[A])(implicit s: Scheduler) = { | |||
obs.doOnNextF(v => Coeval(prop() = v)).subscribe() | |||
} | |||
def observableChange[J1 >: A]: Observable[J1] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val canc = prop.onChange((_, _, c1) => | |||
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() | |||
) | |||
c := Cancelable(() => canc.cancel()) | |||
c | |||
} | |||
} | |||
} | |||
implicit final class ObservableListExt[A]( | |||
private val buffer: ObservableList[A] | |||
) extends AnyVal { | |||
// def -->(sub: Observer[A]) = | |||
// buffer.onChange((a, b, c) => if (c != null) sub.onNext(c)) | |||
// def -->(op: Property[A, A]) = { | |||
// buffer.onChange((a, b, c) => if (c != null) op() = c) | |||
// } | |||
def <--(obs: Observable[A])(implicit s: Scheduler) = { | |||
obs | |||
.doOnNextF(v => | |||
for { | |||
_ <- Coeval(buffer.clear()) | |||
_ <- Coeval(buffer += v) | |||
} yield () | |||
) | |||
.subscribe() | |||
} | |||
def observableChange[J1 >: A]: Observable[J1] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
implicit val s = sub.scheduler | |||
val canc = | |||
buffer.onChange((buf, _) => | |||
loop(sub, buf.toIterable.iterator, c).runToFuture | |||
) | |||
c := Cancelable(() => canc.cancel()) | |||
c | |||
} | |||
} | |||
private def loop( | |||
sub: Observer[A], | |||
it: Iterator[A], | |||
c: Cancelable | |||
): Task[Unit] = | |||
if (it.hasNext) { | |||
val next = it.next() | |||
Task.deferFuture(sub.onNext(next)).flatMap { | |||
case Ack.Continue => loop(sub, it, c) | |||
case Ack.Stop => Task(c.cancel()) | |||
} | |||
} else Task.unit | |||
} | |||
implicit final class BindObs3[T, J](private val prop: ReadOnlyProperty[T, J]) | |||
extends AnyVal { | |||
def -->(op: Observer[T]) = { | |||
op.onNext(prop.value) | |||
} | |||
def ==>(op: Property[T, J]) = { | |||
op <== prop | |||
} | |||
def observableChange[J1 >: J]: Observable[J1] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val canc = prop.onChange((a, b, c1) => | |||
if (c1 != null && sub.onNext(c1) == Ack.Stop) c.cancel() | |||
) | |||
c := Cancelable(() => canc.cancel()) | |||
c | |||
} | |||
} | |||
} | |||
implicit final class ObjectPropertyObservableListExt[A]( | |||
private val prop: ObjectProperty[ObservableList[A]] | |||
) extends AnyVal { | |||
def <--(obs: Observable[Seq[A]])(implicit s: Scheduler) = { | |||
obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() | |||
} | |||
def -->(sub: Observer[A])(implicit s: Scheduler) = { | |||
val c = SingleAssignCancelable() | |||
val subs: Subscription = prop.onChange((a, b, c1) => | |||
if (c1 != null) | |||
Iterant[Task] | |||
.fromIterable(c1.toIterable) | |||
.consume | |||
.use(consume(sub, c, _)) | |||
.runToFuture | |||
) | |||
c := Cancelable(() => subs.cancel()) | |||
} | |||
private def loop(sub: Observer[A], it: Iterator[A]): Task[Unit] = | |||
if (it.hasNext) { | |||
val next = it.next() | |||
Task.deferFuture(sub.onNext(next)).flatMap { | |||
case Ack.Continue => loop(sub, it) | |||
case Ack.Stop => Task.unit | |||
} | |||
} else Task.unit | |||
private def consume( | |||
sub: Observer[A], | |||
c: Cancelable, | |||
consumer: Iterant.Consumer[Task, A] | |||
): Task[Unit] = | |||
consumer.pull.flatMap { | |||
case Left(value) => Task.unit | |||
case Right(value) => | |||
Task.deferFuture(sub.onNext(value)).flatMap { | |||
case Ack.Continue => consume(sub, c, consumer) | |||
case Ack.Stop => Task(c.cancel()) | |||
} | |||
} | |||
} | |||
implicit final class ObjectPropertyActionEvent( | |||
private val prop: ObjectProperty[EventHandler[ActionEvent]] | |||
) extends AnyVal { | |||
// def <--(obs: Observable[ActionEvent])(implicit s: Scheduler) = { | |||
// obs.doOnNext(v => me.Task(prop() = ObservableBuffer.from(v))).subscribe() | |||
// } | |||
// def -->(sub: Observer[ActionEvent]) = | |||
// prop(). | |||
} | |||
// def emit(prop: ObjectProperty[EventHandler[ActionEvent]]) = | |||
implicit final class OnActionObservable( | |||
private val button: ButtonBase | |||
) extends AnyVal { | |||
def observableAction: Observable[jfxe.ActionEvent] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val l = new jfxe.EventHandler[jfxe.ActionEvent] { | |||
override def handle(event: jfxe.ActionEvent): Unit = { | |||
if (sub.onNext(event) == Ack.Stop) c.cancel() | |||
} | |||
} | |||
button.onAction = l | |||
c := Cancelable(() => | |||
button.removeEventHandler( | |||
jfxe.ActionEvent.ACTION, | |||
l | |||
) | |||
) | |||
c | |||
} | |||
} | |||
} | |||
implicit final class MenuItemActionObservable( | |||
private val item: MenuItem | |||
) extends AnyVal { | |||
def observableAction: Observable[jfxe.ActionEvent] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val l = new jfxe.EventHandler[jfxe.ActionEvent] { | |||
override def handle(event: jfxe.ActionEvent): Unit = { | |||
if (sub.onNext(event) == Ack.Stop) c.cancel() | |||
} | |||
} | |||
item.onAction = l | |||
c := Cancelable(() => | |||
item.removeEventHandler( | |||
jfxe.ActionEvent.ACTION, | |||
l | |||
) | |||
) | |||
c | |||
} | |||
} | |||
} | |||
} |
@ -0,0 +1,9 @@ | |||
package nova.monadic_sfx.implicits | |||
import scalafx.scene.{control => sfxc} | |||
import JavaFXMonixObservables._ | |||
class MenuItem extends sfxc.MenuItem { | |||
def obsAction = new ActionObservableBuilder(this.observableAction) | |||
} |
@ -0,0 +1,50 @@ | |||
package nova.monadic_sfx | |||
import javafx.event.ActionEvent | |||
import monix.execution.Ack | |||
import monix.execution.Cancelable | |||
import monix.reactive.Observable | |||
import monix.reactive.OverflowStrategy | |||
import scalafx.scene.control._ | |||
package object implicits { | |||
implicit class MyButtonExt(val button: Button) extends AnyVal { | |||
def observableAction(): Observable[ActionEvent] = { | |||
import monix.execution.cancelables.SingleAssignCancelable | |||
Observable.create(OverflowStrategy.Unbounded) { sub => | |||
val c = SingleAssignCancelable() | |||
val l = new javafx.event.EventHandler[ActionEvent] { | |||
override def handle(event: ActionEvent): Unit = { | |||
if (sub.onNext(event) == Ack.Stop) c.cancel() | |||
} | |||
} | |||
button.onAction = l | |||
c := Cancelable(() => | |||
button.removeEventHandler( | |||
ActionEvent.ACTION, | |||
l | |||
) | |||
) | |||
c | |||
} | |||
} | |||
} | |||
// implicit class NodeExt(val node: Node) { | |||
// def lookup2[T <: SFXDelegate[_]]( | |||
// selector: String | |||
// )(implicit c: ClassTag[T]) = { | |||
// val t = c.runtimeClass | |||
// Option(node.delegate.lookup(selector)) match { | |||
// case Some(value) => | |||
// if (value.getClass == t) Some(value) else None | |||
// case None => None | |||
// } | |||
// } | |||
// val x = node.lookup2("") | |||
// } | |||
} |
@ -1,7 +1,10 @@ | |||
package nova.monadic_sfx.models | |||
case class RequestPayload(data: String) | |||
final case class HttpBinResponse( | |||
import io.circe.generic.JsonCodec | |||
final case class RequestPayload(data: String) | |||
@JsonCodec final case class HttpBinResponse( | |||
url: String, | |||
origin: String, | |||
headers: Map[String, String] | |||
@ -1,55 +1,52 @@ | |||
package nova.monadic_sfx.ui | |||
import nova.monadic_sfx.implicits.JFXSpinner | |||
import scalafx.geometry.Insets | |||
import scalafx.geometry.Pos | |||
import scalafx.scene.Scene | |||
import scalafx.scene.effect.DropShadow | |||
import scalafx.scene.layout.HBox | |||
import scalafx.scene.layout.VBox | |||
import scalafx.scene.paint.Color._ | |||
import scalafx.scene.paint._ | |||
import scalafx.scene.text.Text | |||
import monix.eval.Coeval | |||
class DefaultUI { | |||
object DefaultUI { | |||
val scene = | |||
new Scene { | |||
fill = Color.rgb(38, 38, 38) | |||
content = new HBox { | |||
content = new VBox { | |||
alignment = Pos.Center | |||
padding = Insets(50, 80, 50, 80) | |||
children = Seq( | |||
new Text { | |||
text = "Scala" | |||
style = "-fx-font: normal bold 100pt sans-serif" | |||
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) | |||
}, | |||
new Text { | |||
text = "FX" | |||
style = "-fx-font: italic bold 100pt sans-serif" | |||
fill = new LinearGradient( | |||
endX = 0, | |||
stops = Stops(White, DarkGray) | |||
new HBox { | |||
padding = Insets(50, 80, 50, 80) | |||
children = Seq( | |||
new Text { | |||
text = "Scala" | |||
style = "-fx-font: normal bold 100pt sans-serif" | |||
fill = new LinearGradient(endX = 0, stops = Stops(Red, DarkRed)) | |||
}, | |||
new Text { | |||
text = "FX" | |||
style = "-fx-font: italic bold 100pt sans-serif" | |||
fill = new LinearGradient( | |||
endX = 0, | |||
stops = Stops(White, DarkGray) | |||
) | |||
effect = new DropShadow { | |||
color = DarkGray | |||
radius = 15 | |||
spread = 0.25 | |||
} | |||
} | |||
) | |||
effect = new DropShadow { | |||
color = DarkGray | |||
radius = 15 | |||
spread = 0.25 | |||
} | |||
}, | |||
new JFXSpinner { | |||
radius = 50 | |||
// style = "-fx-text-fill: red" | |||
} | |||
) | |||
} | |||
} | |||
// val program = Coeval | |||
// .suspend { | |||
// Coeval(println("hello")) >> | |||
// Coeval(println(Thread.currentThread().getName())) >> | |||