cleanup/refactorings
This commit is contained in:
parent
672851aeeb
commit
5b0cfcae8c
@ -15,9 +15,7 @@ resolvers += "jitpack" at "https://jitpack.io"
|
||||
enablePlugins(JavaFxPlugin)
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"org.typelevel" %% "cats-core" % "2.2.0",
|
||||
"org.typelevel" %% "cats-effect" % "2.2.0",
|
||||
"io.monix" %% "monix" % "3.3.0",
|
||||
"io.monix" %% "monix" % "3.4.0",
|
||||
"io.monix" %% "monix-bio" % "1.1.0",
|
||||
"io.circe" %% "circe-core" % "0.13.0",
|
||||
"io.circe" %% "circe-generic" % "0.13.0",
|
||||
|
@ -1,6 +1,6 @@
|
||||
javafx-dispatcher {
|
||||
type = "Dispatcher"
|
||||
executor = "nova.monadic_sfx.executors.JavaFXEventThreadExecutorServiceConfigurator"
|
||||
executor = "nova.monadic_sfx.concurrent.JavaFXEventThreadExecutorServiceConfigurator"
|
||||
throughput = 1
|
||||
}
|
||||
akka.jvm-exit-on-fatal-error = on
|
350
src/main/scala/nova/monadic_sfx/App.scala
Normal file
350
src/main/scala/nova/monadic_sfx/App.scala
Normal file
@ -0,0 +1,350 @@
|
||||
package nova.monadic_sfx
|
||||
|
||||
import cats.syntax.eq._
|
||||
import io.odin.Logger
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.cancelables.CompositeCancelable
|
||||
import monix.{eval => me}
|
||||
import nova.monadic_sfx.concurrent.Schedulers
|
||||
import nova.monadic_sfx.implicits._
|
||||
import nova.monadic_sfx.ui.FX
|
||||
import nova.monadic_sfx.ui.components.router.FXRouter
|
||||
import nova.monadic_sfx.ui.components.router.Page
|
||||
import nova.monadic_sfx.ui.components.todo.TodoListStore
|
||||
import nova.monadic_sfx.ui.components.todo.TodoListView
|
||||
import nova.monadic_sfx.util.MediaPlayerResource
|
||||
import nova.monadic_sfx.util.controls.JFXButton
|
||||
import nova.monadic_sfx.util.controls.JFXDialog
|
||||
import nova.monadic_sfx.util.controls.JFXTextField
|
||||
import nova.monadic_sfx.util.controls.VideoView
|
||||
import org.gerweck.scalafx.util._
|
||||
import org.kordamp.bootstrapfx.BootstrapFX
|
||||
import scalafx.Includes._
|
||||
import scalafx.application.JFXApp3.PrimaryStage
|
||||
import scalafx.beans.property.ObjectProperty
|
||||
import scalafx.beans.property.StringProperty
|
||||
import scalafx.collections.ObservableBuffer
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos
|
||||
import scalafx.scene.Node
|
||||
import scalafx.scene.Parent
|
||||
import scalafx.scene.Scene
|
||||
import scalafx.scene.control.Label
|
||||
import scalafx.scene.control.TableColumn
|
||||
import scalafx.scene.control.TableView
|
||||
import scalafx.scene.control.Tooltip
|
||||
import scalafx.scene.layout.BorderPane
|
||||
import scalafx.scene.layout.HBox
|
||||
import scalafx.scene.layout.Priority
|
||||
import scalafx.scene.layout.StackPane
|
||||
import scalafx.util.Duration
|
||||
import nova.monadic_sfx.util.controls.ActionObservableDsl
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.util.Random
|
||||
import cats.effect.Resource
|
||||
import nova.monadic_sfx.ui.FXComponent
|
||||
|
||||
object App {
|
||||
|
||||
val _scene = Coeval(new Scene {
|
||||
root = new HBox {
|
||||
padding = Insets(20)
|
||||
// style = """| -fx-background-color: rgb(38, 38, 38);
|
||||
// | -fx-text-fill: white;""".stripMargin
|
||||
stylesheets ++= Seq(
|
||||
BootstrapFX.bootstrapFXStylesheet,
|
||||
os.rel / "static" / "css" / "main.css"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
val stage = Coeval(new PrimaryStage {
|
||||
title = "Simple ScalaFX App"
|
||||
scene = _scene.value()
|
||||
minWidth = 700
|
||||
minHeight = 520
|
||||
width = 1280
|
||||
height = 720
|
||||
// resizable = false
|
||||
})
|
||||
}
|
||||
final class App(
|
||||
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
||||
fx: FX,
|
||||
schedulers: Schedulers,
|
||||
startTime: Long,
|
||||
mediaPlayer: MediaPlayerResource
|
||||
)(implicit logger: Logger[Task]) {
|
||||
|
||||
// val program = MyFxApp
|
||||
// .resource(schedulers, stage)
|
||||
// .evalMap { fxApp =>
|
||||
// new MainAppDelegate(schedulers, mediaPlayer, _scene).init
|
||||
// .flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
|
||||
// .executeOn(schedulers.fx) >> Task.pure(fxApp)
|
||||
// }
|
||||
// .use { fxApp =>
|
||||
// for {
|
||||
// // _ <- Task(stage.resizable = false).executeOn(schedulers.fx)
|
||||
// currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS)
|
||||
// _ <- logger.info(
|
||||
// s"Application started in ${(currentTime - startTime) / 1000f} seconds"
|
||||
// )
|
||||
// // _ <- Task(CSSFX.start(stage))
|
||||
|
||||
val buttonStyle = """| -fx-padding: 0.7em 0.57em;
|
||||
| -fx-font-size: 14px;
|
||||
| -jfx-button-type: RAISED;
|
||||
| -fx-background-color: rgb(77,102,204);
|
||||
| -fx-pref-width: 200;
|
||||
| -fx-text-fill: WHITE; """.stripMargin
|
||||
val router = new FXRouter[Page]
|
||||
|
||||
// val players = mediaPlayerFactory.mediaPlayers
|
||||
// val videoPlayerController = players.newEmbeddedMediaPlayer()
|
||||
// mediaPlayer.controller.videoSurface.set(
|
||||
// videoSurfaceForImageView(videoView)
|
||||
// )
|
||||
|
||||
// val videoPlayerControllerCleanup =
|
||||
// Task(videoPlayerController.controls().stop()) >>
|
||||
// Task(videoPlayerController.release())
|
||||
|
||||
val videoPage = Coeval {
|
||||
new BorderPane { pane =>
|
||||
// val mp = new MediaPlayer(
|
||||
// new Media(
|
||||
// "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
|
||||
// // "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4"
|
||||
// )
|
||||
// ) {
|
||||
// autoPlay = true
|
||||
// }
|
||||
// obs(pane).subscribe()(schedulers.async)
|
||||
hgrow = Priority.Always
|
||||
center = new VideoView(mediaPlayer.controller) {
|
||||
alignmentInParent = Pos.Center
|
||||
preserveRatio = true
|
||||
// hgrow = Priority.Always
|
||||
// this.prefWidth <== pane.prefWidth
|
||||
// fitWidth = _scene.width.value - 40
|
||||
// _scene.width
|
||||
// .map(_ - 40)
|
||||
// .onChange((_, _, value) => fitWidth.value = value)
|
||||
// (new DoubleProperty).<==(_scene.width.map(_ - 10))
|
||||
// fitWidth.<==(_scene.width.map(_ - 10))
|
||||
}
|
||||
// videoPlayerController.video().videoDimension().setSize()
|
||||
padding = Insets(0, 0, 5, 0)
|
||||
bottom = new HBox {
|
||||
val mrl = new StringProperty
|
||||
spacing = 5
|
||||
children ++= Seq(
|
||||
new JFXTextField {
|
||||
text = "https://www.youtube.com/watch?v=0QKQlf8r7ls"
|
||||
text ==> mrl
|
||||
prefWidth = 100
|
||||
minWidth = 80
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Play Video"
|
||||
style = buttonStyle
|
||||
onAction = _ => {
|
||||
if (mediaPlayer.controller.media().isValid())
|
||||
mediaPlayer.controller.controls().stop()
|
||||
|
||||
mediaPlayer.controller
|
||||
.media()
|
||||
// .play(
|
||||
// "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
|
||||
// )
|
||||
// .play("https://www.youtube.com/watch?v=yZIummTz9mM")
|
||||
.play(mrl.value)
|
||||
}
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Resume"
|
||||
style = buttonStyle
|
||||
onAction = _ => mediaPlayer.controller.controls().play()
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Pause"
|
||||
style = buttonStyle
|
||||
onAction = _ => mediaPlayer.controller.controls().pause()
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Stop"
|
||||
style = buttonStyle
|
||||
tooltip = new Tooltip {
|
||||
text = "Stop"
|
||||
showDelay = Duration(200)
|
||||
}
|
||||
onAction = _ => mediaPlayer.controller.controls().stop()
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Get Status"
|
||||
style = buttonStyle
|
||||
onAction = _ => {
|
||||
println(mediaPlayer.controller.status().state())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val init = for {
|
||||
routerStore <- Resource.eval(router.store2(Page.Home, logger))
|
||||
todoStore <- Resource.eval(TodoListStore(logger))
|
||||
todoComponent <- TodoListView(todoStore)
|
||||
videoPage <- Resource.eval(videoPage.to[Task])
|
||||
resolver: PartialFunction[Page, Parent] = {
|
||||
case Page.Home => videoPage
|
||||
// engine.load("https://www.youtube.com/embed/qmlegXdlnqI")
|
||||
// engine.load("https://youtube.com/embed/aqz-KE-bpKQ")
|
||||
// engine.load("http://www.youtube.com/embed/IyaFEBI_L24")
|
||||
case Page.UserHome =>
|
||||
new Label {
|
||||
styleClass ++= Seq("text-white")
|
||||
text = s"User Home, Id = ${Random.nextInt()}"
|
||||
}
|
||||
case Page.Todo => todoComponent.node
|
||||
}
|
||||
routerNode <- FXComponent(implicit cc =>
|
||||
Task
|
||||
.deferAction(implicit s =>
|
||||
Task(new HBox { box =>
|
||||
// implicit val cc = CompositeCancelable()
|
||||
alignment = Pos.Center
|
||||
//TODO find a better way to do this
|
||||
// videoView.fitWidth <== box.prefWidth
|
||||
children <-- router
|
||||
.render2(resolver)(routerStore)
|
||||
// call cancel on the old component to cancel all subscriptions
|
||||
.scan[Parent](new Label("empty")) { case (a, b) => b }
|
||||
.doOnNextF(s => logger.debug(s"Actual receive: $s"))
|
||||
.map(_.delegate)
|
||||
})
|
||||
)
|
||||
)
|
||||
|
||||
mainSceneNode <- FXComponent(implicit cc =>
|
||||
Task.deferAction(implicit s =>
|
||||
Task(new StackPane { root =>
|
||||
import ActionObservableDsl._
|
||||
alignment = Pos.Center
|
||||
hgrow = Priority.Always
|
||||
vgrow = Priority.Always
|
||||
children = new BorderPane {
|
||||
hgrow = Priority.Always
|
||||
vgrow = Priority.Always
|
||||
center = routerNode.node
|
||||
bottom = new HBox {
|
||||
// implicit val cc = CompositeCancelable()
|
||||
alignment = Pos.Center
|
||||
spacing = 20
|
||||
children = Seq(
|
||||
new JFXButton {
|
||||
text = "Forward"
|
||||
style = buttonStyle
|
||||
obsAction.map(_ => FXRouter.Forward) --> routerStore.sink
|
||||
disable <-- routerStore.source.map {
|
||||
case (_, FXRouter.State(_, h)) =>
|
||||
h.state.sp == h.state.values.size - 1
|
||||
}
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Backward"
|
||||
style = buttonStyle
|
||||
|
||||
obsAction.mapEval(_ =>
|
||||
me.Task(println("Fired")) >> me.Task.pure(FXRouter.Backward)
|
||||
) --> routerStore.sink
|
||||
disable <-- routerStore.source
|
||||
.doOnNextF(b => Coeval(println(s"Received1: $b")))
|
||||
.map {
|
||||
case (_, FXRouter.State(_, h)) => h.state.sp == 0
|
||||
}
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Home"
|
||||
style = buttonStyle
|
||||
disable <-- routerStore.source
|
||||
.map { case (_, FXRouter.State(p, _)) => p === Page.Home }
|
||||
obsAction.map(_ =>
|
||||
FXRouter.Replace(Page.Home)
|
||||
) --> routerStore.sink
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Todo"
|
||||
style = buttonStyle
|
||||
disable <-- routerStore.source
|
||||
.map { case (_, FXRouter.State(p, _)) => p === Page.Todo }
|
||||
obsAction.map(_ =>
|
||||
FXRouter.Replace(Page.Todo)
|
||||
) --> routerStore.sink
|
||||
},
|
||||
new JFXButton {
|
||||
text = "UserHome"
|
||||
style = buttonStyle
|
||||
disable <-- routerStore.source
|
||||
.map {
|
||||
case (_, FXRouter.State(p, _)) => p === Page.UserHome
|
||||
}
|
||||
obsAction.map(_ =>
|
||||
FXRouter.Replace(Page.UserHome)
|
||||
) --> routerStore.sink
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Dialog"
|
||||
style = buttonStyle
|
||||
val d = new JFXDialog {
|
||||
content = new HBox {
|
||||
style = "-fx-background-color: black"
|
||||
children = Seq(new Label {
|
||||
styleClass ++= Seq("text-white")
|
||||
text = "Sample Dialog"
|
||||
})
|
||||
padding = Insets(20)
|
||||
}
|
||||
}
|
||||
onAction = () => d.show(root)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
)
|
||||
} yield mainSceneNode
|
||||
|
||||
}
|
||||
|
||||
final 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 }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
@ -7,12 +7,9 @@ import cats.implicits._
|
||||
import io.odin._
|
||||
import io.odin.config._
|
||||
import io.odin.syntax._
|
||||
import nova.monadic_sfx.actors.ActorModule
|
||||
import nova.monadic_sfx.http.HttpModule
|
||||
import nova.monadic_sfx.ui.UiModule
|
||||
import nova.monadic_sfx.util.reactive.store.Middlewares
|
||||
|
||||
trait MainModule extends ActorModule with UiModule with HttpModule {
|
||||
object Loggers {
|
||||
def routerLogger(defaultLogger: Logger[Task], storeLogger: Logger[Task]) =
|
||||
enclosureRouting[Task](
|
||||
"nova.monadic_sfx.util.reactive.store.Middlewares" -> storeLogger,
|
||||
@ -37,5 +34,5 @@ trait MainModule extends ActorModule with UiModule with HttpModule {
|
||||
formatter = Middlewares.format
|
||||
).withAsync(timeWindow = 10.millis)
|
||||
routerLogger <- routerLogger(defaultLogger, middlewareLogger)
|
||||
} yield (routerLogger)
|
||||
} yield routerLogger
|
||||
}
|
@ -1,37 +1,52 @@
|
||||
package nova.monadic_sfx
|
||||
|
||||
import _root_.monix.bio.BIOApp
|
||||
import _root_.monix.bio.Task
|
||||
import _root_.monix.bio.UIO
|
||||
import _root_.monix.execution.Scheduler
|
||||
import cats.effect.ExitCode
|
||||
import cats.effect.Resource
|
||||
import com.softwaremill.macwire._
|
||||
import io.odin._
|
||||
import nova.monadic_sfx.executors._
|
||||
import monix.bio.BIOApp
|
||||
import monix.bio.Task
|
||||
import monix.bio.UIO
|
||||
import monix.execution.Scheduler
|
||||
import nova.monadic_sfx.concurrent._
|
||||
import nova.monadic_sfx.util.MediaPlayerResource
|
||||
import nova.monadic_sfx.ui.FX
|
||||
// import nova.monadic_sfx.util.IOUtils._
|
||||
// import sttp.client.httpclient.monix.HttpClientMonixBackend
|
||||
object Main extends MainModule with BIOApp {
|
||||
val schedulers = new Schedulers()
|
||||
object Main extends BIOApp {
|
||||
val schedulers = Schedulers.default
|
||||
|
||||
override def scheduler: Scheduler = schedulers.async
|
||||
override def scheduler: Scheduler = schedulers.async.value
|
||||
|
||||
def appResource(startTime: Long) =
|
||||
for {
|
||||
implicit0(logger: Logger[Task]) <- makeLogger
|
||||
|
||||
// backend and actorsystem are for future use
|
||||
// backend <- Resource.make(
|
||||
// toIO(HttpClientMonixBackend()(schedulers.async))
|
||||
// )(c => toIO(c.close()))
|
||||
// actorSystem <- actorSystemResource(logger)
|
||||
MediaPlayerResource <- MediaPlayerResource()
|
||||
_ <- Resource.liftF(wire[MainApp].program)
|
||||
} yield ()
|
||||
val appResource = for {
|
||||
startTime <- Resource.eval(Task(System.currentTimeMillis()))
|
||||
logger <- Loggers.makeLogger
|
||||
// backend and actorsystem are for future use
|
||||
// backend <- Resource.make(
|
||||
// toIO(HttpClientMonixBackend()(schedulers.async))
|
||||
// )(c => toIO(c.close()))
|
||||
// actorSystem <- actorSystemResource(logger)
|
||||
fx <- FX.resource(schedulers, App.stage)(logger)
|
||||
mediaPlayerResource <- MediaPlayerResource()
|
||||
rootComponent = new App(fx, schedulers, startTime, mediaPlayerResource)(
|
||||
logger
|
||||
).init
|
||||
_ <- Resource.eval(
|
||||
rootComponent
|
||||
.evalMap(c =>
|
||||
for {
|
||||
_ <- Task.unit
|
||||
_ <- fx.addToScene(UIO.pure(c.node))
|
||||
_ <- fx.await
|
||||
} yield ()
|
||||
)
|
||||
.use(_ => Task.unit)
|
||||
.executeOn(schedulers.fx.value)
|
||||
)
|
||||
// _ <- Resource.eval(f)
|
||||
// _ <- Resource.eval(fx.await)
|
||||
} yield ()
|
||||
|
||||
override def run(args: List[String]): UIO[ExitCode] =
|
||||
appResource(System.currentTimeMillis())
|
||||
appResource
|
||||
.use(_ => Task.unit)
|
||||
.onErrorHandleWith(ex => UIO(ex.printStackTrace()))
|
||||
.as(ExitCode.Success)
|
||||
|
@ -1,361 +0,0 @@
|
||||
package nova.monadic_sfx
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
import scala.util.Random
|
||||
|
||||
import cats.effect.Resource
|
||||
import cats.syntax.eq._
|
||||
import cats.syntax.option._
|
||||
import com.softwaremill.macwire._
|
||||
import io.odin.Logger
|
||||
import monix.bio.IO
|
||||
import monix.bio.Task
|
||||
import monix.eval.Coeval
|
||||
import monix.execution.cancelables.CompositeCancelable
|
||||
import monix.{eval => me}
|
||||
import nova.monadic_sfx.executors.Schedulers
|
||||
import nova.monadic_sfx.implicits._
|
||||
import nova.monadic_sfx.ui.MyFxApp
|
||||
import nova.monadic_sfx.ui.components.router.FXRouter
|
||||
import nova.monadic_sfx.ui.components.router.Page
|
||||
import nova.monadic_sfx.ui.components.todo.TodoListStore
|
||||
import nova.monadic_sfx.ui.components.todo.TodoListView
|
||||
import nova.monadic_sfx.util.MediaPlayerResource
|
||||
import nova.monadic_sfx.util.controls.JFXButton
|
||||
import nova.monadic_sfx.util.controls.JFXDialog
|
||||
import nova.monadic_sfx.util.controls.JFXTextField
|
||||
import nova.monadic_sfx.util.controls.VideoView
|
||||
import org.gerweck.scalafx.util._
|
||||
import org.kordamp.bootstrapfx.BootstrapFX
|
||||
import scalafx.Includes._
|
||||
import scalafx.application.JFXApp3.PrimaryStage
|
||||
import scalafx.beans.property.ObjectProperty
|
||||
import scalafx.beans.property.StringProperty
|
||||
import scalafx.collections.ObservableBuffer
|
||||
import scalafx.geometry.Insets
|
||||
import scalafx.geometry.Pos
|
||||
import scalafx.scene.Node
|
||||
import scalafx.scene.Parent
|
||||
import scalafx.scene.Scene
|
||||
import scalafx.scene.control.Button
|
||||
import scalafx.scene.control.Label
|
||||
import scalafx.scene.control.TableColumn
|
||||
import scalafx.scene.control.TableView
|
||||
import scalafx.scene.control.Tooltip
|
||||
import scalafx.scene.layout.BorderPane
|
||||
import scalafx.scene.layout.HBox
|
||||
import scalafx.scene.layout.Priority
|
||||
import scalafx.scene.layout.StackPane
|
||||
import scalafx.util.Duration
|
||||
class MainApp(
|
||||
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
|
||||
schedulers: Schedulers,
|
||||
startTime: Long,
|
||||
mediaPlayer: MediaPlayerResource
|
||||
)(implicit logger: Logger[Task]) {
|
||||
|
||||
private lazy val _scene = new Scene {
|
||||
root = new HBox {
|
||||
padding = Insets(20)
|
||||
// style = """| -fx-background-color: rgb(38, 38, 38);
|
||||
// | -fx-text-fill: white;""".stripMargin
|
||||
stylesheets ++= Seq(
|
||||
BootstrapFX.bootstrapFXStylesheet,
|
||||
os.rel / "static" / "css" / "main.css"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private lazy val stage = new PrimaryStage {
|
||||
title = "Simple ScalaFX App"
|
||||
scene = _scene
|
||||
minWidth = 700
|
||||
minHeight = 520
|
||||
width = 1280
|
||||
height = 720
|
||||
// resizable = false
|
||||
}
|
||||
|
||||
(for {
|
||||
(stopSignal, fxAppFib) <- MyFxApp.resource(schedulers, stage)
|
||||
i <- Resource.make(Task(1))(_ => Task.unit)
|
||||
} yield (stopSignal, fxAppFib, i)).use {
|
||||
case (a, b, c) => Task.unit
|
||||
}
|
||||
|
||||
val program = MyFxApp
|
||||
.resource(schedulers, stage)
|
||||
.evalMap {
|
||||
case (stopSignal, fxAppFib) =>
|
||||
wire[MainAppDelegate].init
|
||||
.flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
|
||||
.executeOn(schedulers.fx) >> Task.pure(stopSignal -> fxAppFib)
|
||||
}
|
||||
.use {
|
||||
case (stopSignal, fxAppFib) =>
|
||||
for {
|
||||
// _ <- Task(stage.resizable = false).executeOn(schedulers.fx)
|
||||
currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS)
|
||||
_ <- logger.info(
|
||||
s"Application started in ${(currentTime - startTime) / 1000f} seconds"
|
||||
)
|
||||
// _ <- Task(CSSFX.start(stage))
|
||||
_ <- fxAppFib.join
|
||||
} yield ()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class MainAppDelegate(
|
||||
schedulers: Schedulers,
|
||||
mediaPlayer: MediaPlayerResource,
|
||||
_scene: Scene
|
||||
)(implicit logger: Logger[Task]) {
|
||||
val buttonStyle = """| -fx-padding: 0.7em 0.57em;
|
||||
| -fx-font-size: 14px;
|
||||
| -jfx-button-type: RAISED;
|
||||
| -fx-background-color: rgb(77,102,204);
|
||||
| -fx-pref-width: 200;
|
||||
| -fx-text-fill: WHITE; """.stripMargin
|
||||
val router = new FXRouter[Page]
|
||||
|
||||
// val players = mediaPlayerFactory.mediaPlayers
|
||||
// val videoPlayerController = players.newEmbeddedMediaPlayer()
|
||||
// mediaPlayer.controller.videoSurface.set(
|
||||
// videoSurfaceForImageView(videoView)
|
||||
// )
|
||||
|
||||
// val videoPlayerControllerCleanup =
|
||||
// Task(videoPlayerController.controls().stop()) >>
|
||||
// Task(videoPlayerController.release())
|
||||
|
||||
val videoPage = new BorderPane { pane =>
|
||||
// val mp = new MediaPlayer(
|
||||
// new Media(
|
||||
// "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
|
||||
// // "https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_5mb.mp4"
|
||||
// )
|
||||
// ) {
|
||||
// autoPlay = true
|
||||
// }
|
||||
// obs(pane).subscribe()(schedulers.async)
|
||||
hgrow = Priority.Always
|
||||
center = new VideoView(mediaPlayer.controller) {
|
||||
alignmentInParent = Pos.Center
|
||||
preserveRatio = true
|
||||
// hgrow = Priority.Always
|
||||
// this.prefWidth <== pane.prefWidth
|
||||
fitWidth = _scene.width.value - 40
|
||||
_scene.width
|
||||
.map(_ - 40)
|
||||
.onChange((_, _, value) => fitWidth.value = value)
|
||||
// (new DoubleProperty).<==(_scene.width.map(_ - 10))
|
||||
// fitWidth.<==(_scene.width.map(_ - 10))
|
||||
}
|
||||
// videoPlayerController.video().videoDimension().setSize()
|
||||
padding = Insets(0, 0, 5, 0)
|
||||
bottom = new HBox {
|
||||
val mrl = new StringProperty
|
||||
spacing = 5
|
||||
children ++= Seq(
|
||||
new JFXTextField {
|
||||
text = "https://www.youtube.com/watch?v=0QKQlf8r7ls"
|
||||
text ==> mrl
|
||||
prefWidth = 100
|
||||
minWidth = 80
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Play Video"
|
||||
style = buttonStyle
|
||||
onAction = _ => {
|
||||
if (mediaPlayer.controller.media().isValid())
|
||||
mediaPlayer.controller.controls().stop()
|
||||
|
||||
mediaPlayer.controller
|
||||
.media()
|
||||
// .play(
|
||||
// "https://download.oracle.com/otndocs/products/javafx/oow2010-2.flv"
|
||||
// )
|
||||
// .play("https://www.youtube.com/watch?v=yZIummTz9mM")
|
||||
.play(mrl.value)
|
||||
}
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Resume"
|
||||
style = buttonStyle
|
||||
onAction = _ => mediaPlayer.controller.controls().play()
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Pause"
|
||||
style = buttonStyle
|
||||
onAction = _ => mediaPlayer.controller.controls().pause()
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Stop"
|
||||
style = buttonStyle
|
||||
tooltip = new Tooltip {
|
||||
text = "Stop"
|
||||
showDelay = Duration(200)
|
||||
}
|
||||
onAction = _ => mediaPlayer.controller.controls().stop()
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Get Status"
|
||||
style = buttonStyle
|
||||
onAction = _ => {
|
||||
println(mediaPlayer.controller.status().state())
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
val init: Task[Node] = for {
|
||||
routerStore <- router.store(Page.Home, logger)
|
||||
todoStore <- TodoListStore(logger)
|
||||
todoComponent <- TodoListView(todoStore)
|
||||
resolver: PartialFunction[Page, Parent] = {
|
||||
case Page.Home => videoPage
|
||||
// engine.load("https://www.youtube.com/embed/qmlegXdlnqI")
|
||||
// engine.load("https://youtube.com/embed/aqz-KE-bpKQ")
|
||||
// engine.load("http://www.youtube.com/embed/IyaFEBI_L24")
|
||||
case Page.UserHome =>
|
||||
new Label {
|
||||
styleClass ++= Seq("text-white")
|
||||
text = s"User Home, Id = ${Random.nextInt()}"
|
||||
}
|
||||
case Page.Todo => todoComponent
|
||||
}
|
||||
routerNode: Node <-
|
||||
Task
|
||||
.deferAction(implicit s =>
|
||||
Task(new HBox { box =>
|
||||
alignment = Pos.Center
|
||||
//TODO find a better way to do this
|
||||
// videoView.fitWidth <== box.prefWidth
|
||||
children <-- router
|
||||
.render(resolver)(routerStore)
|
||||
// call cancel on the old component to cancel all subscriptions
|
||||
.scan[Parent](new Label("empty")) { case (a, b) => b }
|
||||
.doOnNextF(s => logger.debug(s"Actual receive: $s"))
|
||||
.map(_.delegate)
|
||||
})
|
||||
)
|
||||
|
||||
mainSceneNode <- Task.deferAction(implicit s =>
|
||||
Task(new StackPane { root =>
|
||||
alignment = Pos.Center
|
||||
hgrow = Priority.Always
|
||||
vgrow = Priority.Always
|
||||
children = new BorderPane {
|
||||
hgrow = Priority.Always
|
||||
vgrow = Priority.Always
|
||||
center = routerNode
|
||||
bottom = new HBox {
|
||||
implicit val cc = CompositeCancelable()
|
||||
alignment = Pos.Center
|
||||
spacing = 20
|
||||
children = Seq(
|
||||
new JFXButton {
|
||||
text = "Forward"
|
||||
style = buttonStyle
|
||||
obsAction.useLazyEval(
|
||||
me.Task.pure(FXRouter.Forward)
|
||||
) --> routerStore
|
||||
disable <-- routerStore.map {
|
||||
case (_, FXRouter.State(_, h)) =>
|
||||
h.state.sp == h.state.values.size - 1
|
||||
}
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Backward"
|
||||
style = buttonStyle
|
||||
|
||||
obsAction.useLazyEval(
|
||||
me.Task(println("Fired")) >> me.Task.pure(FXRouter.Backward)
|
||||
) --> routerStore
|
||||
disable <-- routerStore
|
||||
.doOnNextF(b => Coeval(println(s"Received1: $b")))
|
||||
.map {
|
||||
case (_, FXRouter.State(_, h)) => h.state.sp == 0
|
||||
}
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Home"
|
||||
style = buttonStyle
|
||||
disable <-- routerStore
|
||||
.map { case (_, FXRouter.State(p, _)) => p === Page.Home }
|
||||
obsAction
|
||||
.useLazyEval(
|
||||
me.Task.pure(FXRouter.Replace(Page.Home))
|
||||
) --> routerStore
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Todo"
|
||||
style = buttonStyle
|
||||
disable <-- routerStore
|
||||
.map { case (_, FXRouter.State(p, _)) => p === Page.Todo }
|
||||
obsAction
|
||||
.useLazyEval(
|
||||
me.Task.pure(FXRouter.Replace(Page.Todo))
|
||||
) --> routerStore
|
||||
},
|
||||
new JFXButton {
|
||||
text = "UserHome"
|
||||
style = buttonStyle
|
||||
disable <-- routerStore
|
||||
.map { case (_, FXRouter.State(p, _)) => p == Page.UserHome }
|
||||
obsAction
|
||||
.useLazyEval(
|
||||
me.Task.pure(FXRouter.Replace(Page.UserHome))
|
||||
) --> routerStore
|
||||
},
|
||||
new JFXButton {
|
||||
text = "Dialog"
|
||||
style = buttonStyle
|
||||
val d = new JFXDialog {
|
||||
content = new HBox {
|
||||
style = "-fx-background-color: black"
|
||||
children = Seq(new Label {
|
||||
styleClass ++= Seq("text-white")
|
||||
text = "Sample Dialog"
|
||||
})
|
||||
padding = Insets(20)
|
||||
}
|
||||
}
|
||||
onAction = () => d.show(root)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
} yield mainSceneNode
|
||||
}
|
||||
|
||||
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,4 +1,4 @@
|
||||
package nova.monadic_sfx.executors
|
||||
package nova.monadic_sfx.concurrent
|
||||
|
||||
import java.util.Collections
|
||||
import java.util.concurrent.AbstractExecutorService
|
45
src/main/scala/nova/monadic_sfx/concurrent/Schedulers.scala
Normal file
45
src/main/scala/nova/monadic_sfx/concurrent/Schedulers.scala
Normal file
@ -0,0 +1,45 @@
|
||||
package nova.monadic_sfx.concurrent
|
||||
|
||||
import com.typesafe.scalalogging.Logger
|
||||
import monix.execution.Scheduler
|
||||
import monix.execution.UncaughtExceptionReporter
|
||||
import monix.execution.schedulers.TracingScheduler
|
||||
import cats.effect.Blocker
|
||||
|
||||
final case class Schedulers(
|
||||
io: Schedulers.IoScheduler,
|
||||
async: Schedulers.AsyncScheduler,
|
||||
fx: Schedulers.FxScheduler
|
||||
)
|
||||
|
||||
object Schedulers {
|
||||
val reporter = UncaughtExceptionReporter { ex =>
|
||||
val logger = Logger[Schedulers]
|
||||
logger.error("Uncaught exception", ex)
|
||||
}
|
||||
|
||||
val default = Schedulers(
|
||||
IoScheduler(
|
||||
Scheduler
|
||||
.io()
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
),
|
||||
AsyncScheduler(
|
||||
Scheduler
|
||||
.computation()
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
),
|
||||
FxScheduler(
|
||||
TracingScheduler(
|
||||
JFXExecutionContexts.fxScheduler
|
||||
.withUncaughtExceptionReporter(Schedulers.reporter)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
final case class AsyncScheduler(value: Scheduler)
|
||||
final case class IoScheduler(value: Scheduler) {
|
||||
val blocker = Blocker.liftExecutionContext(value)
|
||||
}
|
||||
final case class FxScheduler(value: Scheduler)
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package nova.monadic_sfx.executors
|
||||
|
||||
trait ExecutorsModule {
|
||||
lazy val schedulers = new Schedulers()
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
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 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)
|
||||
}
|
||||
|
||||
}
|
@ -27,6 +27,7 @@ import scalafx.event.subscriptions.Subscription
|
||||
import scalafx.scene.Scene
|
||||
import scalafx.scene.control.ButtonBase
|
||||
import scalafx.scene.control.MenuItem
|
||||
import nova.monadic_sfx.util.reactive.store.Sink2
|
||||
|
||||
trait JavaFXMonixObservables {
|
||||
import JavaFXMonixObservables._
|
||||
@ -106,9 +107,9 @@ object JavaFXMonixObservables {
|
||||
|
||||
final class PropertyExt[T, J](private val prop: Property[T, J])
|
||||
extends AnyVal {
|
||||
def -->[J1 >: J](sub: Observer[J1]) = {
|
||||
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||
}
|
||||
// def -->[J1 >: J](sub: Observer[J1]) = {
|
||||
// prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||
// }
|
||||
|
||||
def ==>(op: Property[T, J]) = {
|
||||
op <== prop
|
||||
@ -141,18 +142,24 @@ object JavaFXMonixObservables {
|
||||
final class ObjectPropertyExt[A](private val prop: ObjectProperty[A])
|
||||
extends AnyVal {
|
||||
|
||||
def -->(sub: Observer[A]) =
|
||||
prop.onChange((a, b, c) =>
|
||||
if (c != null)
|
||||
if (sub.onNext(c) == Ack.Stop) throw new Exception("boom")
|
||||
)
|
||||
// def -->(sub: Observer[A]) =
|
||||
// prop.onChange((a, b, c) =>
|
||||
// if (c != null)
|
||||
// if (sub.onNext(c) == Ack.Stop) throw new Exception("boom")
|
||||
// )
|
||||
|
||||
def ==>(op: Property[A, A]) = {
|
||||
prop.onChange((a, b, c) => if (c != null) op() = c)
|
||||
def -->(sink: Sink2[A])(implicit s: Scheduler, c: CompositeCancelable) =
|
||||
c += observableChange.doOnNextF(sink.offer).subscribe()
|
||||
|
||||
def ==>(op: Property[A, A])(implicit c: CompositeCancelable) = {
|
||||
val canc = prop.onChange((a, b, c) => if (c != null) op() = c)
|
||||
c += Cancelable(() => canc.cancel())
|
||||
}
|
||||
|
||||
def <--(obs: Observable[A])(implicit s: Scheduler) = {
|
||||
obs.doOnNextF(v => Coeval(prop() = v)).subscribe()
|
||||
def <--(
|
||||
obs: Observable[A]
|
||||
)(implicit s: Scheduler, c: CompositeCancelable) = {
|
||||
c += obs.doOnNextF(v => Coeval(prop() = v)).subscribe()
|
||||
}
|
||||
|
||||
def observableChange[J1 >: A]: Observable[J1] = {
|
||||
@ -170,9 +177,8 @@ object JavaFXMonixObservables {
|
||||
}
|
||||
}
|
||||
|
||||
final class ObservableListExt[A](
|
||||
private val buffer: ObservableBuffer[A]
|
||||
) extends AnyVal {
|
||||
final class ObservableListExt[A](private val buffer: ObservableBuffer[A])
|
||||
extends AnyVal {
|
||||
|
||||
// def -->(sub: Observer[A]) =
|
||||
// buffer.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||
@ -181,8 +187,10 @@ object JavaFXMonixObservables {
|
||||
// buffer.onChange((a, b, c) => if (c != null) op() = c)
|
||||
// }
|
||||
|
||||
def <--(obs: Observable[A])(implicit s: Scheduler) = {
|
||||
obs
|
||||
def <--(
|
||||
obs: Observable[A]
|
||||
)(implicit s: Scheduler, c: CompositeCancelable) = {
|
||||
c += obs
|
||||
.doOnNextF(v =>
|
||||
for {
|
||||
_ <- Coeval(buffer.clear())
|
||||
@ -192,35 +200,35 @@ object JavaFXMonixObservables {
|
||||
.subscribe()
|
||||
}
|
||||
|
||||
def observableChange[J1 >: A]: Observable[J1] = {
|
||||
import monix.execution.cancelables.SingleAssignCancelable
|
||||
Observable.create(OverflowStrategy.Unbounded) { sub =>
|
||||
val c = SingleAssignCancelable()
|
||||
// def observableChange[J1 >: A]: Observable[J1] = {
|
||||
// import monix.execution.cancelables.SingleAssignCancelable
|
||||
// Observable.create(OverflowStrategy.Unbounded) { sub =>
|
||||
// val c = SingleAssignCancelable()
|
||||
|
||||
implicit val s = sub.scheduler
|
||||
// implicit val s = sub.scheduler
|
||||
|
||||
val canc =
|
||||
buffer.onChange((buf, _) =>
|
||||
loop(sub, buf.toIterable.iterator, c).runToFuture
|
||||
)
|
||||
// val canc =
|
||||
// buffer.onChange((buf, _) =>
|
||||
// loop(sub, buf.toIterable.iterator, c).runToFuture
|
||||
// )
|
||||
|
||||
c := Cancelable(() => canc.cancel())
|
||||
c
|
||||
}
|
||||
}
|
||||
// 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
|
||||
// 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
|
||||
}
|
||||
|
||||
final class StringObservableListExt(
|
||||
@ -235,9 +243,9 @@ object JavaFXMonixObservables {
|
||||
final class ReadOnlyPropertyExt[T, J](
|
||||
private val prop: ReadOnlyProperty[T, J]
|
||||
) extends AnyVal {
|
||||
def -->[J1 >: J](sub: Observer[J1]) = {
|
||||
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||
}
|
||||
// def -->[J1 >: J](sub: Observer[J1]) = {
|
||||
// prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
|
||||
// }
|
||||
|
||||
def ==>(op: Property[T, J]) = {
|
||||
op <== prop
|
||||
@ -261,45 +269,50 @@ object JavaFXMonixObservables {
|
||||
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 <--(
|
||||
obs: Observable[Seq[A]]
|
||||
)(implicit s: Scheduler, c: CompositeCancelable) = {
|
||||
c += 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())
|
||||
}
|
||||
// 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 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())
|
||||
}
|
||||
}
|
||||
// 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())
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
|
@ -2,10 +2,23 @@ package nova.monadic_sfx
|
||||
|
||||
import monix.eval.TaskLike
|
||||
import monix.reactive.Observable
|
||||
import monix.bio.IO
|
||||
|
||||
package object implicits
|
||||
extends MySfxObservableImplicits
|
||||
with JavaFXMonixObservables {
|
||||
|
||||
implicit final class MonixEvalTaskExt[T](private val task: monix.eval.Task[T])
|
||||
extends AnyVal {
|
||||
def toIO = IO.deferAction(implicit s => IO.from(task))
|
||||
}
|
||||
|
||||
implicit final class MonixBioTaskExt[T](private val task: monix.bio.Task[T])
|
||||
extends AnyVal {
|
||||
def toTask =
|
||||
monix.eval.Task.deferAction(implicit s => monix.eval.Task.from(task))
|
||||
}
|
||||
|
||||
implicit class SttpWsOps[F[_]](private val ws: sttp.client.ws.WebSocket[F])
|
||||
extends AnyVal {
|
||||
def observableSource(implicit F: TaskLike[F]) =
|
||||
|
@ -10,10 +10,11 @@ import scalafx.scene.layout.VBox
|
||||
import scalafx.scene.paint.Color._
|
||||
import scalafx.scene.paint._
|
||||
import scalafx.scene.text.Text
|
||||
import monix.eval.Coeval
|
||||
|
||||
object DefaultUI {
|
||||
val scene =
|
||||
new Scene {
|
||||
object DefaultScene {
|
||||
def apply() =
|
||||
Coeval(new Scene {
|
||||
fill = Color.rgb(38, 38, 38)
|
||||
content = new VBox {
|
||||
alignment = Pos.Center
|
||||
@ -48,5 +49,5 @@ object DefaultUI {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
84
src/main/scala/nova/monadic_sfx/ui/FX.scala
Normal file
84
src/main/scala/nova/monadic_sfx/ui/FX.scala
Normal file
@ -0,0 +1,84 @@
|
||||
package nova.monadic_sfx.ui
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import cats.effect.Resource
|
||||
import io.odin.Logger
|
||||
import monix.bio.Task
|
||||
import monix.execution.CancelablePromise
|
||||
import nova.monadic_sfx.concurrent.Schedulers
|
||||
import nova.monadic_sfx.ui.DefaultScene
|
||||
import scalafx.application.JFXApp3
|
||||
import scalafx.application.JFXApp3.PrimaryStage
|
||||
import monix.bio.UIO
|
||||
import monix.eval.Coeval
|
||||
import scalafx.scene.Scene
|
||||
import scalafx.scene.Parent
|
||||
import scalafx.Includes._
|
||||
import monix.bio.IO
|
||||
import scalafx.scene.Node
|
||||
|
||||
final class FX private (
|
||||
schedulers: Schedulers,
|
||||
delegate: JFXApp3,
|
||||
val await: Task[Unit],
|
||||
val awaitStop: Task[Unit]
|
||||
)(implicit logger: Logger[Task]) {
|
||||
logger.debug("whoopie")
|
||||
|
||||
def runOnFx[E, A](io: IO[E, A]) = io.executeOn(schedulers.fx.value)
|
||||
|
||||
def addToScene(node: UIO[Node]) =
|
||||
runOnFx {
|
||||
for {
|
||||
p <- node
|
||||
_ <- UIO(delegate.stage.scene().getChildren += p)
|
||||
} yield ()
|
||||
}
|
||||
}
|
||||
|
||||
object FX {
|
||||
def resource(
|
||||
schedulers: Schedulers,
|
||||
stage: Coeval[PrimaryStage],
|
||||
initialScene: Coeval[Scene] = DefaultScene(),
|
||||
transitionDelay: FiniteDuration = 500.millis
|
||||
)(implicit
|
||||
logger: Logger[Task]
|
||||
): Resource[Task, FX] =
|
||||
Resource
|
||||
.make(for {
|
||||
_ <- logger.info("Starting FX App")
|
||||
makePromise = UIO(CancelablePromise[Unit]())
|
||||
startSignal <- makePromise
|
||||
stopSignal <- makePromise
|
||||
delegate <- Task(new JFXApp3 {
|
||||
def start(): Unit = {
|
||||
stage = new PrimaryStage {
|
||||
scene = initialScene.value()
|
||||
}
|
||||
startSignal.success(())
|
||||
}
|
||||
|
||||
override def stopApp(): Unit = {
|
||||
stopSignal.success(())
|
||||
}
|
||||
})
|
||||
fib <-
|
||||
Task(delegate.main(Array.empty)).start.executeOn(schedulers.io.value)
|
||||
fxApp <- Task(
|
||||
new FX(
|
||||
schedulers,
|
||||
delegate,
|
||||
fib.join,
|
||||
Task.fromCancelablePromise(stopSignal)
|
||||
)
|
||||
)
|
||||
_ <- Task.fromCancelablePromise(startSignal)
|
||||
_ <- Task.sleep(transitionDelay)
|
||||
_ <- Task(delegate.stage = stage.value())
|
||||
.executeOn(schedulers.fx.value)
|
||||
.delayExecution(transitionDelay)
|
||||
} yield fxApp -> fib) { case _ -> fib => fib.cancel }
|
||||
.map { case a -> _ => a }
|
||||
}
|
@ -14,43 +14,42 @@ import scalafx.scene.text.Font
|
||||
import cats.effect.Resource
|
||||
|
||||
final class FXComponent private (
|
||||
val rootNode: Parent,
|
||||
val cancelable: Cancelable
|
||||
)
|
||||
val node: Parent,
|
||||
private val cancelable: Cancelable
|
||||
) {
|
||||
|
||||
// def toNode(implicit c: CompositeCancelable): Node = {
|
||||
// c += cancelable
|
||||
// rootNode
|
||||
// }
|
||||
}
|
||||
|
||||
object FXComponent {
|
||||
def acquire(f: CompositeCancelable => Task[Parent]) =
|
||||
private def acquire(f: CompositeCancelable => Task[Parent]) =
|
||||
for {
|
||||
c <- Task(CompositeCancelable())
|
||||
p <- f(c)
|
||||
} yield new FXComponent(p, c)
|
||||
|
||||
def fxComponent2Node(
|
||||
component: FXComponent
|
||||
)(implicit c: CompositeCancelable): Node = {
|
||||
c += component.cancelable
|
||||
component.rootNode
|
||||
}
|
||||
|
||||
def resource(f: CompositeCancelable => Task[Parent]) =
|
||||
def apply(f: CompositeCancelable => Task[Parent]) =
|
||||
Resource.make(acquire(f))(comp => Task(comp.cancelable.cancel()))
|
||||
}
|
||||
|
||||
object TestFXComp {
|
||||
val testComp =
|
||||
FXComponent.resource { implicit c =>
|
||||
Task.deferAction { implicit s =>
|
||||
Task {
|
||||
val sub = ConcurrentSubject.publish[jfxc.text.Font]
|
||||
val f = ObjectProperty(Font("hmm"))
|
||||
sub.onNext(f())
|
||||
new TextField {
|
||||
font <-- sub
|
||||
font <== f
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// val testComp =
|
||||
// FXComponent.resource { implicit c =>
|
||||
// Task.deferAction { implicit s =>
|
||||
// Task {
|
||||
// val sub = ConcurrentSubject.publish[jfxc.text.Font]
|
||||
// val f = ObjectProperty(Font("hmm"))
|
||||
// sub.onNext(f())
|
||||
// new TextField {
|
||||
// font <-- sub
|
||||
// font <== f
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// val x = for {
|
||||
// comp <- testComp
|
||||
|
@ -1,60 +0,0 @@
|
||||
package nova.monadic_sfx.ui
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import cats.effect.Resource
|
||||
import io.odin.Logger
|
||||
import monix.bio.Fiber
|
||||
import monix.bio.Task
|
||||
import monix.execution.CancelablePromise
|
||||
import nova.monadic_sfx.executors.Schedulers
|
||||
import nova.monadic_sfx.ui.DefaultUI
|
||||
import scalafx.application.JFXApp3
|
||||
import scalafx.application.JFXApp3.PrimaryStage
|
||||
|
||||
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
|
||||
|
||||
private def internal(
|
||||
startSignal: CancelablePromise[Unit],
|
||||
stopSignal: CancelablePromise[Unit]
|
||||
) =
|
||||
new JFXApp3 {
|
||||
def start(): Unit = {
|
||||
stage = new PrimaryStage {
|
||||
scene = DefaultUI.scene
|
||||
}
|
||||
startSignal.success(())
|
||||
}
|
||||
|
||||
override def stopApp(): Unit = {
|
||||
stopSignal.success(())
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object MyFxApp {
|
||||
def resource(
|
||||