Rohan Sircar
2 years ago
33 changed files with 1215 additions and 1014 deletions
-
4build.sbt
-
2src/main/resources/application.conf
-
350src/main/scala/nova/monadic_sfx/App.scala
-
7src/main/scala/nova/monadic_sfx/Loggers.scala
-
61src/main/scala/nova/monadic_sfx/Main.scala
-
361src/main/scala/nova/monadic_sfx/MainApp.scala
-
2src/main/scala/nova/monadic_sfx/concurrent/GUIExecutor.scala
-
45src/main/scala/nova/monadic_sfx/concurrent/Schedulers.scala
-
5src/main/scala/nova/monadic_sfx/executors/ExecutorsModule.scala
-
28src/main/scala/nova/monadic_sfx/executors/Schedulers.scala
-
175src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
-
13src/main/scala/nova/monadic_sfx/implicits/package.scala
-
9src/main/scala/nova/monadic_sfx/ui/DefaultScene.scala
-
84src/main/scala/nova/monadic_sfx/ui/FX.scala
-
51src/main/scala/nova/monadic_sfx/ui/FXComponent.scala
-
60src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
-
129src/main/scala/nova/monadic_sfx/ui/MyFxAppOld.scala
-
37src/main/scala/nova/monadic_sfx/ui/UiModule.scala
-
54src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
-
6src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
-
239src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
-
2src/main/scala/nova/monadic_sfx/ui/screens/LoginScreen.scala
-
2src/main/scala/nova/monadic_sfx/util/History.scala
-
44src/main/scala/nova/monadic_sfx/util/SynchedObject.scala
-
173src/main/scala/nova/monadic_sfx/util/controls/ActionObservable.scala
-
2src/main/scala/nova/monadic_sfx/util/controls/JFXButton.scala
-
2src/main/scala/nova/monadic_sfx/util/controls/MenuItem.scala
-
49src/main/scala/nova/monadic_sfx/util/reactive/store/Sink.scala
-
67src/main/scala/nova/monadic_sfx/util/reactive/store/Store.scala
-
1src/main/scala/nova/monadic_sfx/util/reactive/store/package.scala
-
46src/test/scala/BackpressuredStoreTest.scala
-
104src/test/scala/ObservableTest.scala
-
15src/test/scala/WebSocketTest.scala
@ -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 |
@ -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 } |
|||
} |
|||
) |
|||
} |
|||
} |
@ -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 |
@ -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) |
|||
} |
|||
|
|||
} |
@ -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 } |
|||
} |
@ -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( |
|||
schedulers: Schedulers, |
|||
stage: => PrimaryStage, |
|||
transitionDelay: FiniteDuration = 500.millis |
|||
)(implicit |
|||
logger: Logger[Task] |
|||
): Resource[Task, (Task[Unit], Fiber[Throwable, Unit])] = |
|||
Resource.make(for { |
|||
_ <- logger.info("Starting FX App") |
|||
fxApp <- Task(new MyFxApp(schedulers)) |
|||
startSignal <- Task(CancelablePromise[Unit]()) |
|||
stopSignal <- Task(CancelablePromise[Unit]()) |
|||
delegate <- Task(fxApp.internal(startSignal, stopSignal)) |
|||
fib <- |
|||
Task(delegate.main(Array.empty)).start.executeOn(schedulers.blocking) |
|||
_ <- Task.fromCancelablePromise(startSignal) |
|||
_ <- Task.sleep(transitionDelay) |
|||
_ <- Task(delegate.stage = stage) |
|||
.executeOn(schedulers.fx) |
|||
.delayExecution(transitionDelay) |
|||
} yield Task.fromCancelablePromise(stopSignal) -> fib) { |
|||
case _ -> fib => fib.cancel |
|||
} |
|||
} |
@ -1,129 +0,0 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import scala.concurrent.duration._ |
|||
|
|||
import akka.actor.typed._ |
|||
import akka.util.Timeout |
|||
import com.softwaremill.macwire._ |
|||
import io.odin.Logger |
|||
import monix.eval.Task |
|||
import monix.execution.Callback |
|||
import monix.execution.Scheduler |
|||
import nova.monadic_sfx.AppTypes |
|||
import nova.monadic_sfx.actors.Counter |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.http.Requesters |
|||
import scalafx.application.JFXApp |
|||
|
|||
class MyFxAppOld( |
|||
logger: Logger[Task], |
|||
backend: AppTypes.HttpBackend, |
|||
actorSystem: ActorSystem[SpawnProtocol.Command], |
|||
requesters: Requesters, |
|||
schedulers: Schedulers |
|||
) extends JFXApp { |
|||
|
|||
implicit lazy val defaultScheduler: Scheduler = schedulers.fx |
|||
|
|||
// lazy val fxActor: Task[ActorRef[Counter.Command]] = wireWith( |
|||
// MyFxApp.makeCounterActor _ |
|||
// ) |
|||
|
|||
lazy val application = |
|||
for { |
|||
appStage <- Task(wireWith(UiModule.makePrimaryStage _)) |
|||
|
|||
// _ <- Task { |
|||
// val counterActor = testActor(actorSystem) |
|||
// counterActor ! (Counter.Increment) |
|||
// } |
|||
// ta <- testActor2(actorSystem) |
|||
// actor <- |
|||
// actorTask.bracket(actor => Task(actor ! (Counter.Increment)))(actor => |
|||
// Task(actor ! (Counter.Stop)) |
|||
// ) |
|||
// actor <- actorTask |
|||
// actor <- fxActor |
|||
// _ <- Task(actor ! Counter.Increment) |
|||
_ <- Task { stage = appStage } |
|||
_ <- Task.sleep(2.seconds) |
|||
// loginScene <- wire[LoginScreen].render |
|||
// _ <- Task { |
|||
// // appStage.maximized = true |
|||
// appStage.height = 800 |
|||
// appStage.width = 800 |
|||
// appStage |
|||
// .scene() |
|||
// .setRoot( |
|||
// loginScene |
|||
// ) |
|||
// } |
|||
} yield () |
|||
|
|||
// def testActor( |
|||
// system: ActorSystem |
|||
// ): akka.actor.typed.ActorRef[Counter.Command] = { |
|||
// val behaviour: Behavior[Counter.Command] = |
|||
// Behaviors.setup(context => wire[Counter]) |
|||
// system.spawn( |
|||
// behaviour, |
|||
// "CounterActor", |
|||
// DispatcherSelector.fromConfig("javafx-dispatcher") |
|||
// ) |
|||
// } |
|||
|
|||
application.timed.runAsync( |
|||
new Callback[Throwable, (FiniteDuration, Unit)] { |
|||
|
|||
override def onSuccess(value: (FiniteDuration, Unit)): Unit = { |
|||
val (duration, _) = value |
|||
println( |
|||
s"Application started successfully in ${duration.toSeconds} seconds" |
|||
) |
|||
} |
|||
|
|||
override def onError(e: Throwable): Unit = { |
|||
println("Application start failed. Reason -") |
|||
e.printStackTrace() |
|||
} |
|||
|
|||
} |
|||
) |
|||
|
|||
override def stopApp() = { |
|||
// val stop = for { |
|||
// actor <- fxActor |
|||
// _ <- logger.info("Stopping actor counter") |
|||
// // _ <- Task.fromFuture { actor.ask[Counter.Value](Counter.GetValue) } |
|||
// t <- Task(actor ! Counter.Stop) |
|||
// // _ <- Task.sleep(1.second) |
|||
// _ <- logger.info("Counter actor stopped") |
|||
// } yield () |
|||
// stop.runAsyncAndForget |
|||
// // Platform.exit() |
|||
} |
|||
} |
|||
|
|||
object MyFxAppOld { |
|||
def makeCounterActor( |
|||
system: ActorSystem[SpawnProtocol.Command], |
|||
logger: Logger[Task] |
|||
): Task[ActorRef[Counter.Command]] = { |
|||
import akka.actor.typed.scaladsl.AskPattern._ |
|||
import scala.concurrent.ExecutionContext |
|||
|
|||
implicit val timeout: Timeout = Timeout(3.seconds) |
|||
implicit val ec: ExecutionContext = system.executionContext |
|||
implicit val scheduler = system.scheduler |
|||
Task.fromFuture { |
|||
system.ask( |
|||
SpawnProtocol.Spawn( |
|||
behavior = wireWith(Counter.apply _), |
|||
name = "counterActor", |
|||
DispatcherSelector.fromConfig("javafx-dispatcher"), |
|||
_ |
|||
) |
|||
) |
|||
} |
|||
} |
|||
} |
@ -1,37 +0,0 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import akka.actor.typed._ |
|||
import cats.effect.Resource |
|||
import com.softwaremill.macwire._ |
|||
import io.odin.Logger |
|||
import monix.eval.Task |
|||
import nova.monadic_sfx.AppTypes |
|||
import nova.monadic_sfx.executors.Schedulers |
|||
import nova.monadic_sfx.http.Requesters |
|||
import scalafx.application.JFXApp |
|||
import scalafx.application.JFXApp.PrimaryStage |
|||
|
|||
trait UiModule { |
|||
def fxAppResource( |
|||
logger: Logger[Task], |
|||
backend: AppTypes.HttpBackend, |
|||
actorSystem: ActorSystem[SpawnProtocol.Command], |
|||
requesters: Requesters, |
|||
schedulers: Schedulers |
|||
): Resource[Task, JFXApp] = |
|||
Resource.make(for { |
|||
_ <- logger.info("Creating FX Application") |
|||
app <- Task { wire[MyFxAppOld] } |
|||
} yield (app))(app => logger.info("Stopping FX Application")) |
|||
} |
|||
|
|||
object UiModule { |
|||
def makePrimaryStage( |
|||
backend: AppTypes.HttpBackend, |
|||
actorSystem: ActorSystem[SpawnProtocol.Command] |
|||
) = { |
|||
new PrimaryStage { |
|||
scene = DefaultUI.scene |
|||
} |
|||
} |
|||
} |
@ -1,44 +0,0 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import monix.bio.Task |
|||
import monix.bio.UIO |
|||
import monix.catnap.MVar |
|||
|
|||
/** |
|||
* Synchronization wrapper for a mutable object |
|||
* |
|||
* @param obj the mutable object |
|||
* @param lock lock for synchronization |
|||
*/ |
|||
class SynchedObject[A](obj: A, lock: MLock) { |
|||
|
|||
def modify(f: A => Task[Unit]): Task[Unit] = |
|||
lock.greenLight(f(obj)) |
|||
|
|||
def get: Task[A] = lock.greenLight(Task(obj)) |
|||
} |
|||
|
|||
object SynchedObject { |
|||
def apply[A](obj: A) = |
|||
MVar[Task] |
|||
.of(()) |
|||
.map(m => new MLock(m)) |
|||
.flatMap(lock => Task(new SynchedObject(obj, lock))) |
|||
} |
|||
|
|||
final class MLock(mvar: MVar[Task, Unit]) { |
|||
def acquire: Task[Unit] = |
|||
mvar.take |
|||
|
|||
def release: Task[Unit] = |
|||
mvar.put(()) |
|||
|
|||
def greenLight[A](fa: Task[A]): Task[A] = |
|||
for { |
|||
_ <- acquire |
|||
a <- fa.doOnCancel( |
|||
release.onErrorHandleWith(ex => UIO(println(ex.getMessage()))) |
|||
) |
|||
_ <- release |
|||
} yield a |
|||
} |
@ -0,0 +1,49 @@ |
|||
package nova.monadic_sfx.util.reactive.store |
|||
|
|||
import monix.eval.Task |
|||
import monix.catnap.ConcurrentQueue |
|||
import monix.reactive.Observer |
|||
|
|||
trait Sink[A, -B] { |
|||
def offer(inst: A, b: B): Task[Unit] |
|||
} |
|||
|
|||
object Sink { |
|||
implicit def sinkForCq[B] = |
|||
new Sink[ConcurrentQueue[Task, B], B] { |
|||
|
|||
override def offer(queue: ConcurrentQueue[Task, B], b: B): Task[Unit] = |
|||
queue.offer(b) |
|||
|
|||
} |
|||
|
|||
implicit def sinkForObserver[B] = |
|||
new Sink[Observer[B], B] { |
|||
|
|||
override def offer(inst: Observer[B], b: B): Task[Unit] = |
|||
Task.deferFuture(inst.onNext(b)).void |
|||
|
|||
} |
|||
|
|||
implicit def sinkForStore[B, C] = |
|||
new Sink[Store[B, C], B] { |
|||
|
|||
override def offer(inst: Store[B, C], b: B): Task[Unit] = |
|||
Task.deferFuture(inst.onNext(b)).void |
|||
|
|||
} |
|||
|
|||
// implicitly[Sink[Store[Int, Int], Int]] |
|||
|
|||
} |
|||
|
|||
trait Sink2[-B] { |
|||
def offer(b: B): monix.bio.Task[Unit] |
|||
} |
|||
|
|||
object Sink2 { |
|||
def concurrentQueue[B](queue: ConcurrentQueue[monix.bio.Task, B]): Sink2[B] = |
|||
new Sink2[B] { |
|||
def offer(b: B): monix.bio.Task[Unit] = queue.offer(b) |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
import org.scalatest.funsuite.AnyFunSuite |
|||
import scala.concurrent.duration._ |
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.util.reactive.store.Store |
|||
import nova.monadic_sfx.util.History |
|||
import nova.monadic_sfx.util.reactive.store.Reducer |
|||
import nova.monadic_sfx.ui.components.router.Page |
|||
import nova.monadic_sfx.util.IOUtils.toIO |
|||
import monix.reactive.Observable |
|||
import cats.effect.Resource |
|||
import nova.monadic_sfx.ui.components.router.FXRouter |
|||
import io.odin.consoleLogger |
|||
class BackpressuredStoreTest extends AnyFunSuite { |
|||
import monix.execution.Scheduler.Implicits.global |
|||
val logger = consoleLogger[Task]() |
|||
test("backpressed store test") { |
|||
(for { |
|||
_ <- |
|||
Resource |
|||
.eval(new FXRouter[Page].store2(Page.Home, logger)) |
|||
.use { myStore => |
|||
for { |
|||
_ <- toIO( |
|||
myStore.source |
|||
.doOnNextF(_ => Task.sleep(1.seconds)) |
|||
.doOnNextF(item => logger.debug(s"Task1: Got Item $item")) |
|||
.completedL |
|||
).startAndForget |
|||
_ <- toIO( |
|||
myStore.source |
|||
.doOnNextF(_ => Task.sleep(3.seconds)) |
|||
.doOnNextF(item => logger.debug(s"Task2: Got Item $item")) |
|||
.completedL |
|||
).startAndForget |
|||
_ <- myStore.sink.offer(FXRouter.Replace(Page.Home)) |
|||
_ <- myStore.sink.offer(FXRouter.Replace(Page.Todo)) |
|||
_ <- myStore.sink.offer(FXRouter.Replace(Page.UserHome)) |
|||
_ <- myStore.sink.offer(FXRouter.Backward) |
|||
_ <- Task.sleep(25.seconds) |
|||
} yield () |
|||
|
|||
} |
|||
// _ <- Task.sleep(2.seconds) |
|||
} yield ()).runSyncUnsafe(26.seconds) |
|||
} |
|||
} |
@ -0,0 +1,104 @@ |
|||
import org.scalatest.funsuite.AnyFunSuite |
|||
import monix.catnap.ConcurrentQueue |
|||
import monix.eval.Task |
|||
import monix.reactive.Observable |
|||
import scala.concurrent.duration._ |
|||
|
|||
class ObservableTest extends AnyFunSuite { |
|||
import monix.execution.Scheduler.Implicits.global |
|||
test("observable state machine") { |
|||
(for { |
|||
_ <- Task.unit |
|||
sm <- MonixStateMachine() |
|||
_ <- |
|||
Task |
|||
.parSequence( |
|||
List( |
|||
sm.source |
|||
.doOnNext(item => Task(println(s"Task 1: Got $item"))) |
|||
.completedL, |
|||
sm.source |
|||
.doOnNext(item => Task(println(s"Task 2: Got $item"))) |
|||
.completedL, |
|||
sm.tell(MonixStateMachine.Start) >> |
|||
Observable |
|||
// .interval(1.second) |
|||
.interval(500.millis) |
|||
.doOnNext(_ => sm.tell(MonixStateMachine.Incr)) |
|||
.takeUntil(Observable.unit.delayExecution(5.seconds)) |
|||
.completedL >> |
|||
sm.tell(MonixStateMachine.Stop) >> |
|||
sm.tell(MonixStateMachine.Incr) |
|||
) |
|||
) |
|||
.void |
|||
.start |
|||
.bracket(_ => Task.sleep(8.seconds))(_.cancel) |
|||
} yield ()).runSyncUnsafe(10.seconds) |
|||
} |
|||
} |
|||
|
|||
class MonixStateMachine( |
|||
queue: ConcurrentQueue[Task, MonixStateMachine.Command], |
|||
val source: Observable[(MonixStateMachine.State, MonixStateMachine.Data)] |
|||
) { |
|||
import MonixStateMachine._ |
|||
|
|||
def tell(item: Command) = queue.offer(item) |
|||
|
|||
} |
|||
object MonixStateMachine { |
|||
|
|||
sealed trait State |
|||
case object Idle extends State |
|||
case object Active extends State |
|||
|
|||
sealed trait Command |
|||
case object Incr extends Command |
|||
case object Start extends Command |
|||
case object Stop extends Command |
|||
|
|||
case class Data(num: Int) |
|||
|
|||
private def source(queue: ConcurrentQueue[Task, Command]) = |
|||
Task.deferAction(implicit s => |
|||
Task( |
|||
Observable |
|||
.repeatEvalF(queue.poll) |
|||
.scan((Idle: State, Data(0))) { |
|||
case ((state, data), command) => |
|||
state match { |
|||
case Idle => |
|||
println("Entered idle") |
|||
command match { |
|||
case Incr => |
|||
println("Not active ") |
|||
(Idle, data) |
|||
case Start => (Active, data) |
|||
case Stop => |
|||
println("Already stopped") |
|||
(Idle, data) |
|||
|
|||
} |
|||
case Active => |
|||
println("Entered Active") |
|||
command match { |
|||
case Incr => (Active, data.copy(num = data.num + 1)) |
|||
case Start => |
|||
println("Already started") |
|||
(Active, data) |
|||
case Stop => (Idle, data) |
|||
} |
|||
} |
|||
} |
|||
.publish |
|||
.refCount |
|||
) |
|||
) |
|||
|
|||
def apply() = |
|||
for { |
|||
queue <- ConcurrentQueue.bounded[Task, Command](10) |
|||
source <- source(queue) |
|||
} yield new MonixStateMachine(queue, source) |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue