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 { |
javafx-dispatcher { |
||||
type = "Dispatcher" |
type = "Dispatcher" |
||||
executor = "nova.monadic_sfx.executors.JavaFXEventThreadExecutorServiceConfigurator" |
|
||||
|
executor = "nova.monadic_sfx.concurrent.JavaFXEventThreadExecutorServiceConfigurator" |
||||
throughput = 1 |
throughput = 1 |
||||
} |
} |
||||
akka.jvm-exit-on-fatal-error = on |
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.Collections |
||||
import java.util.concurrent.AbstractExecutorService |
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