9 Commits

Author SHA1 Message Date
  Rohan Sircar c59d48f6ec Added split combinator to actionObservable 4 months ago
  Rohan Sircar 935ca358e6 Many changes 4 months ago
  Rohan Sircar 857fd03bf1 Cleanup and added some methods to actionObservable 4 months ago
  Rohan Sircar f95f50574e Added proper todo buttons reactively connected 4 months ago
  Rohan Sircar f95ecc2cb3 Wrapped error handle in UIO 4 months ago
  Rohan Sircar 1f06c536c8 Updated monix version to 3.3.0 (was 3.2.2) 4 months ago
  Rohan Sircar 8f1fe0cc84 Cleanup and refactoring 4 months ago
  Rohan Sircar b988ad267e Added store pattern using monix 4 months ago
  Rohan Sircar 536f1b0af3 Added scalafx wrappers for jfoenix and ikonli 4 months ago
48 changed files with 2631 additions and 377 deletions
Split View
  1. +2
    -0
      .gitignore
  2. +47
    -14
      build.sbt
  3. +1
    -1
      project/build.properties
  4. +1
    -0
      project/plugin.sbt
  5. +24
    -29
      src/main/scala/nova/monadic_sfx/Main.scala
  6. +189
    -0
      src/main/scala/nova/monadic_sfx/MainApp.scala
  7. +36
    -2
      src/main/scala/nova/monadic_sfx/MainModule.scala
  8. +6
    -5
      src/main/scala/nova/monadic_sfx/Types.scala
  9. +12
    -58
      src/main/scala/nova/monadic_sfx/actors/ActorModule.scala
  10. +9
    -11
      src/main/scala/nova/monadic_sfx/actors/TestActor.scala
  11. +12
    -14
      src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala
  12. +22
    -3
      src/main/scala/nova/monadic_sfx/executors/Schedulers.scala
  13. +2
    -2
      src/main/scala/nova/monadic_sfx/http/HttpModule.scala
  14. +6
    -32
      src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala
  15. +98
    -0
      src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala
  16. +44
    -0
      src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala
  17. +42
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala
  18. +36
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala
  19. +48
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala
  20. +29
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala
  21. +40
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala
  22. +35
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala
  23. +62
    -0
      src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala
  24. +327
    -0
      src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
  25. +9
    -0
      src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala
  26. +50
    -0
      src/main/scala/nova/monadic_sfx/implicits/package.scala
  27. +5
    -2
      src/main/scala/nova/monadic_sfx/models/DummyModels.scala
  28. +31
    -34
      src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
  29. +29
    -117
      src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
  30. +129
    -0
      src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
  31. +9
    -9
      src/main/scala/nova/monadic_sfx/ui/UiModule.scala
  32. +107
    -0
      src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
  33. +378
    -0
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
  34. +89
    -0
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
  35. +134
    -0
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
  36. +95
    -0
      src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
  37. +15
    -13
      src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala
  38. +14
    -20
      src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala
  39. +1
    -1
      src/main/scala/nova/monadic_sfx/ui/screens/Screen.scala
  40. +2
    -2
      src/main/scala/nova/monadic_sfx/util/Action.scala
  41. +21
    -0
      src/main/scala/nova/monadic_sfx/util/IOUtils.scala
  42. +63
    -0
      src/main/scala/nova/monadic_sfx/util/Misc.scala
  43. +75
    -0
      src/main/scala/nova/monadic_sfx/util/reactive/Middlewares.scala
  44. +23
    -0
      src/main/scala/nova/monadic_sfx/util/reactive/MonixProSubject.scala
  45. +39
    -0
      src/main/scala/nova/monadic_sfx/util/reactive/Reducer.scala
  46. +97
    -0
      src/main/scala/nova/monadic_sfx/util/reactive/Store.scala
  47. +18
    -0
      src/main/scala/nova/monadic_sfx/util/reactive/package.scala
  48. +68
    -8
      src/main/scala/org/slf4j/impl/StaticLoggerBuilder.scala

+ 2
- 0
.gitignore View File

@ -22,3 +22,5 @@ metals.sbt
.idea/
.vscode
/project/project
.bsp

+ 47
- 14
build.sbt View File

@ -5,7 +5,7 @@ name := "ScalaFX Hello World"
version := "14-R19"
// Version of Scala used by the project
scalaVersion := "2.13.3"
scalaVersion := "2.13.4"
// Add dependency on ScalaFX library
libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19"
@ -16,31 +16,55 @@ enablePlugins(JavaFxPlugin)
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.4",
"io.monix" %% "monix" % "3.2.2",
"io.monix" %% "monix-bio" % "1.0.0",
"io.monix" %% "monix" % "3.3.0",
"io.monix" %% "monix-bio" % "1.1.0",
"io.circe" %% "circe-core" % "0.13.0",
"io.circe" %% "circe-generic" % "0.13.0",
"com.softwaremill.sttp.client" %% "core" % "2.2.5",
"com.softwaremill.sttp.client" %% "monix" % "2.2.5",
"com.softwaremill.sttp.client" %% "circe" % "2.2.5",
"com.softwaremill.sttp.client" %% "async-http-client-backend-monix" % "2.2.5",
"com.github.valskalla" %% "odin-monix" % "0.8.1",
"com.softwaremill.sttp.client" %% "core" % "2.2.9",
"com.softwaremill.sttp.client" %% "monix" % "2.2.9",
"com.softwaremill.sttp.client" %% "circe" % "2.2.9",
"com.softwaremill.sttp.client" %% "httpclient-backend-monix" % "2.2.9",
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.8",
"com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-slf4j" % "0.8.1"
"com.github.valskalla" %% "odin-monix" % "0.9.1",
"com.github.valskalla" %% "odin-slf4j" % "0.9.1",
"com.github.valskalla" %% "odin-json" % "0.9.1",
"com.github.valskalla" %% "odin-extras" % "0.9.1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
"com.jfoenix" % "jfoenix" % "9.0.10",
"org.kordamp.ikonli" % "ikonli-core" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-javafx" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-fontawesome5-pack" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-material-pack" % "12.0.0",
"io.github.typhon0" % "AnimateFX" % "1.2.1",
"com.beachape" %% "enumeratum" % "1.6.1",
"com.chuusai" %% "shapeless" % "2.3.3",
"org.gerweck.scalafx" %% "scalafx-utils" % "0.15.0"
)
scalacOptions ++= Seq(
"-unchecked",
"-deprecation",
"-Xcheckinit",
"-encoding",
"utf8",
"UTF-8",
"-deprecation",
"-feature",
"-Ywarn-unused:imports"
"-language:existentials",
"-language:experimental.macros",
"-language:higherKinds",
"-language:implicitConversions",
"-unchecked",
"-Xlint",
"-Ywarn-numeric-widen",
"-Ymacro-annotations",
//silence warnings for by-name implicits
"-Wconf:cat=lint-byname-implicit:s",
//give errors on non exhaustive matches
"-Wconf:msg=match may not be exhaustive:e",
"-explaintypes" // Explain type errors in more detail.
)
javacOptions ++= Seq("-source", "11", "-target", "11")
// Fork a new JVM for 'run' and 'test:run', to avoid JavaFX double initialization problems
fork := true
@ -59,3 +83,12 @@ lazy val javaFXModules =
libraryDependencies ++= javaFXModules.map(m =>
"org.openjfx" % s"javafx-$m" % "14.0.1" classifier osName
)
addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.3.1")
ThisBuild / scalafixDependencies += "com.github.liancheng" %% "organize-imports" % "0.4.3"
inThisBuild(
List(
scalaVersion := scalaVersion.value, // 2.11.12, or 2.13.3
semanticdbEnabled := true, // enable SemanticDB
semanticdbVersion := "4.4.2" // use Scalafix compatible version
)
)

+ 1
- 1
project/build.properties View File

@ -1,2 +1,2 @@
sbt.version=1.3.10
sbt.version=1.4.3

+ 1
- 0
project/plugin.sbt View File

@ -4,3 +4,4 @@ scalacOptions ++= Seq("-unchecked", "-deprecation")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
// addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.10")
addSbtPlugin("com.quadstingray" % "sbt-javafx" % "1.5.2")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.23")

+ 24
- 29
src/main/scala/nova/monadic_sfx/Main.scala View File

@ -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)
}
}

+ 189
- 0
src/main/scala/nova/monadic_sfx/MainApp.scala View File

@ -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 }
}
)
}
}

+ 36
- 2
src/main/scala/nova/monadic_sfx/MainModule.scala View File

@ -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)
}

+ 6
- 5
src/main/scala/nova/monadic_sfx/Types.scala View File

@ -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 {}

+ 12
- 58
src/main/scala/nova/monadic_sfx/actors/ActorModule.scala View File

@ -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()
}
}

+ 9
- 11
src/main/scala/nova/monadic_sfx/actors/TestActor.scala View File

@ -36,15 +36,13 @@ class Counter(context: ActorContext[Counter.Command])
}
}
override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] =
PartialFunction.fromFunction((signal: Signal) => {
signal match {
case _: Terminated =>
context.log.info("Recieved shutdown counter actor terminated")
this
case PostStop =>
context.log.info("Recieved shutdown counter actor poststop")
this
}
})
override def onSignal: PartialFunction[Signal, Behavior[Counter.Command]] = {
case _: Terminated =>
context.log.info("Recieved shutdown counter actor terminated")
this
case PostStop =>
context.log.info("Recieved shutdown counter actor poststop")
this
}
}

+ 12
- 14
src/main/scala/nova/monadic_sfx/executors/GUIExecutor.scala View File

@ -1,23 +1,21 @@
package nova.monadic_sfx.executors
import akka.dispatch.{
DispatcherPrerequisites,
ExecutorServiceFactory,
ExecutorServiceConfigurator
}
import com.typesafe.config.Config
import java.util.concurrent.{
ExecutorService,
AbstractExecutorService,
ThreadFactory,
TimeUnit
}
import java.util.Collections
import java.util.concurrent.AbstractExecutorService
import java.util.concurrent.Executor
import java.util.concurrent.ExecutorService
import java.util.concurrent.ThreadFactory
import java.util.concurrent.TimeUnit
import javax.swing.SwingUtilities
import scala.concurrent.ExecutionContext
import akka.dispatch.DispatcherPrerequisites
import akka.dispatch.ExecutorServiceConfigurator
import akka.dispatch.ExecutorServiceFactory
import com.typesafe.config.Config
import javafx.application.Platform
import monix.execution.Scheduler
import scala.concurrent.ExecutionContext
import java.util.concurrent.Executor
// First we wrap invokeLater/runLater as an ExecutorService
trait GUIExecutorService extends AbstractExecutorService {

+ 22
- 3
src/main/scala/nova/monadic_sfx/executors/Schedulers.scala View File

@ -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)
}
}

+ 2
- 2
src/main/scala/nova/monadic_sfx/http/HttpModule.scala View File

@ -1,8 +1,8 @@
package nova.monadic_sfx.http
import nova.monadic_sfx.http.requests.DummyRequest
import nova.monadic_sfx.AppTypes
import akka.actor.typed._
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.http.requests.DummyRequest
trait HttpModule {
def requesters(

+ 6
- 32
src/main/scala/nova/monadic_sfx/http/requests/DummyRequest.scala View File

@ -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)
}
}

+ 98
- 0
src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala View File

@ -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()
}
}

+ 44
- 0
src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala View File

@ -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")
}

+ 42
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala View File

@ -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())
}

+ 36
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala View File

@ -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)
}
}

+ 48
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala View File

@ -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
}

+ 29
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala View File

@ -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)
}

+ 40
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala View File

@ -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)
}

+ 35
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala View File

@ -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)
}

+ 62
- 0
src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala View File

@ -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

+ 327
- 0
src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala View File

@ -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
}
}
}
}

+ 9
- 0
src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala View File

@ -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)
}

+ 50
- 0
src/main/scala/nova/monadic_sfx/implicits/package.scala View File

@ -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("")
// }
}

+ 5
- 2
src/main/scala/nova/monadic_sfx/models/DummyModels.scala View File

@ -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]

+ 31
- 34
src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala View File

@ -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())) >>