Browse Source

gse

development
Rohan Sircar 3 years ago
parent
commit
81765c6dcd
  1. 1
      .gitignore
  2. 9
      build.sbt
  3. 2
      project/build.properties
  4. 389
      src/main/resources/static/css/main.css
  5. 4
      src/main/scala/nova/monadic_sfx/Main.scala
  6. 388
      src/main/scala/nova/monadic_sfx/MainApp.scala
  7. 10
      src/main/scala/nova/monadic_sfx/MainModule.scala
  8. 2
      src/main/scala/nova/monadic_sfx/Types.scala
  9. 13
      src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
  10. 11
      src/main/scala/nova/monadic_sfx/implicits/package.scala
  11. 71
      src/main/scala/nova/monadic_sfx/ui/FXComponent.scala
  12. 66
      src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
  13. 49
      src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
  14. 23
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
  15. 35
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
  16. 45
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
  17. 2
      src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
  18. 37
      src/main/scala/nova/monadic_sfx/util/BoundedStack.scala
  19. 18
      src/main/scala/nova/monadic_sfx/util/CssPath.scala
  20. 50
      src/main/scala/nova/monadic_sfx/util/History.scala
  21. 31
      src/main/scala/nova/monadic_sfx/util/MediaPlayerResource.scala
  22. 47
      src/main/scala/nova/monadic_sfx/util/WebSocket.scala
  23. 3
      src/main/scala/nova/monadic_sfx/util/controls/EmbeddedMediaPlayer.scala
  24. 12
      src/main/scala/nova/monadic_sfx/util/controls/JFXButton.scala
  25. 22
      src/main/scala/nova/monadic_sfx/util/controls/JFXDialog.scala
  26. 23
      src/main/scala/nova/monadic_sfx/util/controls/JFXListCell.scala
  27. 12
      src/main/scala/nova/monadic_sfx/util/controls/JFXListView.scala
  28. 37
      src/main/scala/nova/monadic_sfx/util/controls/JFXRippler.scala
  29. 15
      src/main/scala/nova/monadic_sfx/util/controls/VideoView.scala
  30. 12
      src/main/scala/nova/monadic_sfx/util/reactive/store/Middlewares.scala
  31. 79
      src/main/scala/nova/monadic_sfx/util/reactive/store/Store.scala
  32. 32
      src/main/scala/nova/monadic_sfx/util/test.txt
  33. 107
      src/test/scala/HistoryTest.scala
  34. 83
      src/test/scala/ProSubjectTest.scala
  35. 61
      src/test/scala/WebSocketTest.scala
  36. 25
      src/test/scala/WebSocketTestServer.py

1
.gitignore

@ -24,3 +24,4 @@ metals.sbt
/project/project /project/project
.bsp .bsp
.attach_pid**

9
build.sbt

@ -8,7 +8,7 @@ version := "14-R19"
scalaVersion := "2.13.4" scalaVersion := "2.13.4"
// Add dependency on ScalaFX library // Add dependency on ScalaFX library
libraryDependencies += "org.scalafx" %% "scalafx" % "14-R19"
libraryDependencies += "org.scalafx" %% "scalafx" % "15.0.1-R20"
resolvers += Resolver.sonatypeRepo("snapshots") resolvers += Resolver.sonatypeRepo("snapshots")
enablePlugins(JavaFxPlugin) enablePlugins(JavaFxPlugin)
@ -26,7 +26,8 @@ libraryDependencies ++= Seq(
"com.softwaremill.sttp.client" %% "httpclient-backend-monix" % "2.2.9", "com.softwaremill.sttp.client" %% "httpclient-backend-monix" % "2.2.9",
"com.softwaremill.quicklens" %% "quicklens" % "1.6.1", "com.softwaremill.quicklens" %% "quicklens" % "1.6.1",
"com.typesafe.akka" %% "akka-actor-typed" % "2.6.8", "com.typesafe.akka" %% "akka-actor-typed" % "2.6.8",
"com.softwaremill.macwire" %% "util" % "2.3.7",
// "com.softwaremill.macwire" %% "util" % "2.3.7",
"com.softwaremill.common" %% "tagging" % "2.2.1",
"com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided", "com.softwaremill.macwire" %% "macros" % "2.3.6" % "provided",
"com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided", "com.softwaremill.macwire" %% "macrosakka" % "2.3.6" % "provided",
"com.github.valskalla" %% "odin-monix" % "0.9.1", "com.github.valskalla" %% "odin-monix" % "0.9.1",
@ -48,7 +49,9 @@ libraryDependencies ++= Seq(
"fr.brouillard.oss" % "cssfx" % "11.4.0", "fr.brouillard.oss" % "cssfx" % "11.4.0",
"com.lihaoyi" %% "sourcecode" % "0.2.1", "com.lihaoyi" %% "sourcecode" % "0.2.1",
"eu.timepit" %% "refined" % "0.9.19", "eu.timepit" %% "refined" % "0.9.19",
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
"org.scalatest" %% "scalatest" % "3.2.2" % "test",
"uk.co.caprica" % "vlcj" % "4.7.0",
"uk.co.caprica" % "vlcj-javafx" % "1.0.2"
) )
scalacOptions ++= Seq( scalacOptions ++= Seq(

2
project/build.properties

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

389
src/main/resources/static/css/main.css

@ -1,342 +1,123 @@
.root { .root {
-fx-font-family: Roboto;
src: "/resources/roboto/Roboto-Regular.ttf";
-fx-font-family: Roboto;
src: "/resources/roboto/Roboto-Regular.ttf";
-fx-text-fill: white;
-fx-background-color: #14161d;
/* rgb(38,38,38) */
} }
/* Burgers Demo */
.jfx-hamburger {
-fx-spacing: 5;
-fx-cursor: hand;
}
.jfx-hamburger StackPane {
-fx-pref-width: 40px;
-fx-pref-height: 7px;
-fx-background-color: #D63333;
-fx-background-radius: 5px;
}
/* Input Demo */
.text-field {
-fx-max-width: 300;
}
.jfx-text-field, .jfx-password-field {
-fx-background-color: WHITE;
-fx-font-weight: BOLD;
-fx-prompt-text-fill: #808080;
-fx-alignment: top-left;
-jfx-focus-color: #4059A9;
-jfx-unfocus-color: #4d4d4d;
-fx-max-width: 300;
}
.jfx-decorator {
-fx-decorator-color: RED;
}
.jfx-decorator .jfx-decorator-buttons-container {
-fx-background-color: -fx-decorator-color;
}
.jfx-decorator .resize-border {
-fx-border-color: -fx-decorator-color;
-fx-border-width: 0 4 4 4;
}
.jfx-text-area, .text-area {
-fx-font-weight: BOLD;
}
.jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error {
-jfx-focus-color: #D34336;
-jfx-unfocus-color: #D34336;
}
.jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label {
-fx-text-fill: #D34336;
-fx-font-size: 0.75em;
}
.jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon {
-fx-text-fill: #D34336;
-fx-font-size: 1em;
}
/* Progress Bar Demo */
.progress-bar > .bar {
-fx-min-width: 500;
}
.jfx-progress-bar > .bar {
-fx-min-width: 500;
}
.jfx-progress-bar {
-fx-progress-color: #0F9D58;
-fx-stroke-width: 3;
.text-white {
-fx-text-fill: white;
} }
/* Icons Demo */
.icon {
-fx-text-fill: #FE774D;
-fx-padding: 10;
-fx-cursor: hand;
.clear-list-view .scroll-bar:horizontal .track,
.clear-list-view .scroll-bar:vertical .track {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-background-radius: 0em;
-fx-border-radius: 2em;
} }
.icons-rippler {
-jfx-rippler-fill: BLUE;
-jfx-mask-type: CIRCLE;
.clear-list-view .scroll-bar:horizontal .increment-button,
.clear-list-view .scroll-bar:horizontal .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 0 10 0;
} }
.icons-rippler:hover {
-fx-cursor: hand;
.clear-list-view .scroll-bar:vertical .increment-button,
.clear-list-view .scroll-bar:vertical .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 10 0 0;
} }
.jfx-check-box {
-fx-font-weight: BOLD;
}
.custom-jfx-check-box {
-jfx-checked-color: RED;
-jfx-unchecked-color: BLACK;
.clear-list-view .scroll-bar .increment-arrow,
.clear-list-view .scroll-bar .decrement-arrow {
-fx-shape: " ";
-fx-padding: 0;
} }
/* Button */
.button {
-fx-padding: 0.7em 0.57em;
-fx-font-size: 14px;
.clear-list-view .scroll-bar:horizontal .thumb,
.clear-list-view .scroll-bar:vertical .thumb {
-fx-background-color: derive(black, 90%);
-fx-background-insets: 2, 0, 0;
-fx-background-radius: 2em;
} }
.button-raised {
-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;
.scroll-pane .scroll-bar:horizontal .track,
.scroll-pane .scroll-bar:vertical .track {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-background-radius: 0em;
-fx-border-radius: 2em;
} }
/* The main scrollbar **track** CSS class */
.mylistview .scroll-bar:horizontal .track,
.mylistview .scroll-bar:vertical .track {
-fx-background-color: transparent;
-fx-border-color: transparent;
-fx-background-radius: 0em;
-fx-border-radius: 2em;
.scroll-pane .scroll-bar:horizontal .increment-button,
.scroll-pane .scroll-bar:horizontal .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 0 10 0;
} }
/* The increment and decrement button CSS class of scrollbar */
.mylistview .scroll-bar:horizontal .increment-button,
.mylistview .scroll-bar:horizontal .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 0 10 0;
.scroll-pane .scroll-bar:vertical .increment-button,
.scroll-pane .scroll-bar:vertical .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 10 0 0;
} }
/* The increment and decrement button CSS class of scrollbar */
.mylistview .scroll-bar:vertical .increment-button,
.mylistview .scroll-bar:vertical .decrement-button {
-fx-background-color: transparent;
-fx-background-radius: 0em;
-fx-padding: 0 10 0 0;
.scroll-pane .scroll-bar .increment-arrow,
.scroll-pane .scroll-bar .decrement-arrow {
-fx-shape: " ";
-fx-padding: 0;
} }
.mylistview .scroll-bar .increment-arrow,
.mylistview .scroll-bar .decrement-arrow {
-fx-shape: " ";
-fx-padding: 0;
.scroll-pane .scroll-bar:horizontal .thumb,
.scroll-pane .scroll-bar:vertical .thumb {
-fx-background-color: derive(black, 90%);
-fx-background-insets: 2, 0, 0;
-fx-background-radius: 2em;
} }
/* The main scrollbar **thumb** CSS class which we drag every time (movable) */
.mylistview .scroll-bar:horizontal .thumb,
.mylistview .scroll-bar:vertical .thumb {
-fx-background-color: derive(black, 90%);
-fx-background-insets: 2, 0, 0;
-fx-background-radius: 2em;
.scroll-pane {
-fx-background-color: transparent;
} }
.jfx-list-cell-container {
-fx-alignment: center-left;
.scroll-pane > .viewport {
-fx-background-color: transparent;
} }
.jfx-list-cell-container > .label {
-fx-text-fill: BLACK;
.clear-list-view {
-fx-background-color: rgba(28, 28, 28, 28, 0.4);
-fx-text-fill: white;
-fx-background-radius: 20px;
-fx-padding: 5px 10px;
} }
.jfx-list-cell:odd:selected > .jfx-rippler > StackPane, .jfx-list-cell:even:selected > .jfx-rippler > StackPane {
-fx-background-color: rgba(0, 0, 255, 0.2);
.clear-list-view .list-cell {
-fx-background-color: transparent;
-fx-text-fill: white;
} }
.jfx-list-cell {
-fx-background-insets: 0.0;
-fx-text-fill: BLACK;
.clear-list-view .list-cell:selected {
-fx-background-color: rgba(0, 0, 0, 0.2);
-fx-text-fill: white;
-fx-background-radius: 20px;
} }
.jfx-list-cell:odd, .jfx-list-cell:even {
-fx-background-color: WHITE;
.clear-list-view .list-cell:hover {
-fx-background-color: rgba(0, 0, 0, 0.1);
-fx-text-fill: white;
-fx-background-radius: 20px;
} }
.jfx-list-cell:filled:hover {
-fx-text-fill: black;
.clear-list-view .list-cell:selected:hover {
-fx-background-color: rgba(0, 0, 0, 0.3);
-fx-text-fill: white;
-fx-background-radius: 20px;
} }
.jfx-list-cell .jfx-rippler {
-jfx-rippler-fill: BLUE;
.clear-list-view:focused .list-cell:selected {
-fx-background-color: rgba(0, 0, 0, 0.3);
-fx-text-fill: white;
-fx-background-radius: 20px;
} }
.jfx-list-view {
-fx-background-insets: 0;
-jfx-cell-horizontal-margin: 0.0;
-jfx-cell-vertical-margin: 5.0;
-jfx-vertical-gap: 10;
-jfx-expanded: false;
-fx-pref-width: 200;
}
.jfx-toggle-button {
-jfx-toggle-color: RED;
}
.jfx-tool-bar {
-fx-font-size: 20;
-fx-font-weight: BOLD;
-fx-background-color: #5264AE;
-fx-pref-width: 100%;
-fx-pref-height: 64px;
}
.jfx-tool-bar HBox {
-fx-alignment: center;
-fx-spacing: 25;
-fx-padding: 0 10;
}
.jfx-tool-bar Label {
-fx-text-fill: WHITE;
}
.jfx-popup-container {
-fx-background-color: WHITE;
}
.jfx-snackbar-content {
-fx-background-color: #323232;
-fx-padding: 5;
-fx-spacing: 5;
}
.jfx-snackbar-toast {
-fx-text-fill: WHITE;
}
.jfx-snackbar-action {
-fx-text-fill: #ff4081;
}
.jfx-list-cell-content-container {
-fx-alignment: center-left;
}
.jfx-list-cell-container .label {
-fx-text-fill: BLACK;
}
.combo-box-popup .list-view .jfx-list-cell:odd:selected .jfx-list-cell-container,
.combo-box-popup .list-view .jfx-list-cell:even:selected .jfx-list-cell-container {
-fx-background-color: rgba(0.0, 0.0, 255.0, 0.2);
}
.combo-box-popup .list-view .jfx-list-cell {
-fx-background-insets: 0.0;
-fx-text-fill: BLACK;
}
.combo-box-popup .list-view .jfx-list-cell:odd,
.combo-box-popup .list-view .jfx-list-cell:even {
-fx-background-color: WHITE;
}
.combo-box-popup .list-view .jfx-list-cell:filled:hover {
-fx-text-fill: black;
}
/*.combo-box .combo-box-button-container{
-fx-border-color:BLACK;-fx-border-width: 0 0 1 0;
}
.combo-box .combo-box-selected-value-container{
-fx-border-color:BLACK;
} */
/*
* TREE TABLE CSS
*/
.tree-table-view {
-fx-tree-table-color: rgba(255, 0, 0, 0.2);
-fx-tree-table-rippler-color: rgba(255, 0, 0, 0.4);
}
.tree-table-view .jfx-text-field{
-fx-background-color: transparent;
}
.animated-option-button {
-fx-background-color: #F1F1F1;
-fx-background-radius: 50px;
-fx-pref-height: 50px;
-fx-pref-width: 50px;
-fx-min-width: -fx-pref-width;
-fx-max-width: -fx-pref-width;
-fx-min-height: -fx-pref-height;
-fx-max-height: -fx-pref-height;
-jfx-button-type: RAISED;
}
.animated-option-button .jfx-rippler{
-jfx-rippler-fill: rgb(113, 118, 114);
}
.sub-icon{
-fx-fill: rgb(113, 118, 114);
}
.main-button {
-fx-pref-width: 60px;
-fx-background-color: #0F9D58;
-fx-background-radius: 60px;
-fx-pref-height: 60px;
-fx-min-width: -fx-pref-width;
-fx-max-width: -fx-pref-width;
-fx-min-height: -fx-pref-height;
-fx-max-height: -fx-pref-height;
-jfx-button-type: RAISED;
}
.main-button .jfx-rippler{
-jfx-rippler-fill: rgba(255,255,255, .87);
}
.main-icon{
-fx-fill: rgba(255,255,255, .87);
}
.animated-option-sub-button {
-fx-background-color: #43609C;
}
.animated-option-sub-button2 {
-fx-background-color: rgb(203, 104, 96);
}
.tree-table-view .menu-item:focused {
-fx-background-color: -fx-tree-table-color;
}
.tree-table-view .menu-item .label {
-fx-padding: 5 0 5 0;
}

4
src/main/scala/nova/monadic_sfx/Main.scala

@ -9,10 +9,11 @@ import cats.effect.Resource
import com.softwaremill.macwire._ import com.softwaremill.macwire._
import io.odin._ import io.odin._
import nova.monadic_sfx.executors._ import nova.monadic_sfx.executors._
import nova.monadic_sfx.util.MediaPlayerResource
// import nova.monadic_sfx.util.IOUtils._ // import nova.monadic_sfx.util.IOUtils._
// import sttp.client.httpclient.monix.HttpClientMonixBackend // import sttp.client.httpclient.monix.HttpClientMonixBackend
object Main extends MainModule with BIOApp { object Main extends MainModule with BIOApp {
lazy val schedulers = new Schedulers()
val schedulers = new Schedulers()
override def scheduler: Scheduler = schedulers.async override def scheduler: Scheduler = schedulers.async
@ -25,6 +26,7 @@ object Main extends MainModule with BIOApp {
// toIO(HttpClientMonixBackend()(schedulers.async)) // toIO(HttpClientMonixBackend()(schedulers.async))
// )(c => toIO(c.close())) // )(c => toIO(c.close()))
// actorSystem <- actorSystemResource(logger) // actorSystem <- actorSystemResource(logger)
MediaPlayerResource <- MediaPlayerResource()
_ <- Resource.liftF(wire[MainApp].program) _ <- Resource.liftF(wire[MainApp].program)
} yield () } yield ()

388
src/main/scala/nova/monadic_sfx/MainApp.scala

@ -4,12 +4,15 @@ import java.util.concurrent.TimeUnit
import scala.util.Random import scala.util.Random
import com.jfoenix.controls.JFXDialog
import cats.effect.Resource
import cats.syntax.eq._
import cats.syntax.option._
import com.softwaremill.macwire._ import com.softwaremill.macwire._
import io.odin.Logger import io.odin.Logger
import monix.bio.IO import monix.bio.IO
import monix.bio.Task import monix.bio.Task
import monix.eval.Coeval import monix.eval.Coeval
import monix.execution.cancelables.CompositeCancelable
import monix.{eval => me} import monix.{eval => me}
import nova.monadic_sfx.executors.Schedulers import nova.monadic_sfx.executors.Schedulers
import nova.monadic_sfx.implicits._ import nova.monadic_sfx.implicits._
@ -18,31 +21,38 @@ import nova.monadic_sfx.ui.components.router.FXRouter
import nova.monadic_sfx.ui.components.router.Page import nova.monadic_sfx.ui.components.router.Page
import nova.monadic_sfx.ui.components.todo.TodoListStore import nova.monadic_sfx.ui.components.todo.TodoListStore
import nova.monadic_sfx.ui.components.todo.TodoListView import nova.monadic_sfx.ui.components.todo.TodoListView
import nova.monadic_sfx.util.MutHistory
import nova.monadic_sfx.util.MediaPlayerResource
import nova.monadic_sfx.util.controls.JFXButton 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.gerweck.scalafx.util._
import org.kordamp.bootstrapfx.BootstrapFX import org.kordamp.bootstrapfx.BootstrapFX
import scalafx.Includes._ import scalafx.Includes._
import scalafx.application.JFXApp.PrimaryStage
import scalafx.application.JFXApp3.PrimaryStage
import scalafx.beans.property.ObjectProperty import scalafx.beans.property.ObjectProperty
import scalafx.beans.property.StringProperty import scalafx.beans.property.StringProperty
import scalafx.collections.ObservableBuffer import scalafx.collections.ObservableBuffer
import scalafx.geometry.Insets import scalafx.geometry.Insets
import scalafx.geometry.Pos import scalafx.geometry.Pos
import scalafx.scene.Node
import scalafx.scene.Parent import scalafx.scene.Parent
import scalafx.scene.Scene import scalafx.scene.Scene
import scalafx.scene.control.Button
import scalafx.scene.control.Label import scalafx.scene.control.Label
import scalafx.scene.control.TableColumn import scalafx.scene.control.TableColumn
import scalafx.scene.control.TableView import scalafx.scene.control.TableView
import scalafx.scene.control.Tooltip
import scalafx.scene.layout.BorderPane import scalafx.scene.layout.BorderPane
import scalafx.scene.layout.HBox import scalafx.scene.layout.HBox
import scalafx.scene.layout.Priority import scalafx.scene.layout.Priority
import scalafx.scene.layout.StackPane import scalafx.scene.layout.StackPane
import scalafx.util.Duration
class MainApp( class MainApp(
// spawnProtocol: ActorSystem[SpawnProtocol.Command], // spawnProtocol: ActorSystem[SpawnProtocol.Command],
schedulers: Schedulers, schedulers: Schedulers,
startTime: Long
startTime: Long,
mediaPlayer: MediaPlayerResource
)(implicit logger: Logger[Task]) { )(implicit logger: Logger[Task]) {
private lazy val _scene = new Scene { private lazy val _scene = new Scene {
@ -60,153 +70,269 @@ class MainApp(
private lazy val stage = new PrimaryStage { private lazy val stage = new PrimaryStage {
title = "Simple ScalaFX App" title = "Simple ScalaFX App"
scene = _scene scene = _scene
width = 640
height = 480
minWidth = 700
minHeight = 520
width = 1280
height = 720
// resizable = false // resizable = false
} }
val program = for {
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage)
_ <-
wire[MainAppDelegate].init
.flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode))
.executeOn(schedulers.fx)
_ <- 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 ()
(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)(implicit logger: Logger[Task]) {
class MainAppDelegate(
schedulers: Schedulers,
mediaPlayer: MediaPlayerResource,
_scene: Scene
)(implicit logger: Logger[Task]) {
val buttonStyle = """| -fx-padding: 0.7em 0.57em; val buttonStyle = """| -fx-padding: 0.7em 0.57em;
| -fx-font-size: 14px; | -fx-font-size: 14px;
| -jfx-button-type: RAISED; | -jfx-button-type: RAISED;
| -fx-background-color: rgb(77,102,204); | -fx-background-color: rgb(77,102,204);
| -fx-pref-width: 200; | -fx-pref-width: 200;
| -fx-text-fill: WHITE; """.stripMargin | -fx-text-fill: WHITE; """.stripMargin
val router = new FXRouter[Page]
val init =
for {
//FXRouter does not allocate mutable state so it's ok to use pure here
history <- Task(new MutHistory[Page](Page.Home))
router <- Task.pure(new FXRouter[Page](history))
routerStore <- router.store(Page.Home, logger)
todoStore <- TodoListStore(logger)
todoComponent <- TodoListView(todoStore)
resolver: PartialFunction[Page, Task[Parent]] = {
case Page.Home =>
Task(new Label {
styleClass ++= Seq("text-white")
text = "HomePage"
})
case Page.UserHome(id0) =>
Task(new Label {
styleClass ++= Seq("text-white")
text = s"User Home, Id = $id0"
// 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)
}) })
case Page.Todo =>
Task.pure(todoComponent)
}
routerNode <-
Task
.deferAction(implicit s =>
Task(new HBox {
alignment = Pos.Center
//TODO find a better way to do this
var oldValue: Option[Parent] = None
children <-- router
.render(resolver)(routerStore)
// .scanEvalF[Coeval, (Option[Parent], Option[Parent])](
// Coeval.pure(None -> None)
// ) {
// case (oldValue, newValue) => Coeval(None -> None)
// }
// call cancel on the old component to cancel all subscriptions
.doOnNextF(newValue =>
Coeval { oldValue.foreach(_ => ()) } >> Coeval {
oldValue = Some(newValue)
}
)
.map(_.delegate)
})
)
)
mainSceneNode <- Task.deferAction(implicit s =>
Task(new StackPane { root =>
mainSceneNode <- Task.deferAction(implicit s =>
Task(new StackPane { root =>
alignment = Pos.Center
hgrow = Priority.Always
vgrow = Priority.Always
children = new BorderPane {
hgrow = Priority.Always hgrow = Priority.Always
vgrow = Priority.Always vgrow = Priority.Always
children = new BorderPane {
hgrow = Priority.Always
vgrow = Priority.Always
center = routerNode
bottom = new HBox {
alignment = Pos.Center
spacing = 20
children = Seq(
new JFXButton {
text = "Forward"
style = buttonStyle
onAction = () => {
history.forward()
routerStore.onNext(FXRouter.HistoryEvent(history.current))
}
},
new JFXButton {
text = "Backward"
style = buttonStyle
onAction = () => {
history.backward()
routerStore.onNext(FXRouter.HistoryEvent(history.current))
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
obsAction
.useLazyEval(
me.Task.pure(FXRouter.Replace(Page.Home))
) --> routerStore
},
new JFXButton {
text = "Todo"
style = buttonStyle
obsAction
.useLazyEval(
me.Task.pure(FXRouter.Replace(Page.Todo))
) --> routerStore
},
new JFXButton {
text = "UserHome"
style = buttonStyle
obsAction
.useLazyEval(
me.Task(
FXRouter.Replace(Page.UserHome(Random.nextInt(20)))
)
) --> routerStore
},
new JFXButton {
text = "Dialog"
style = buttonStyle
val d = new JFXDialog()
d.setContent(new HBox {
children = Seq(new Label("hmm"))
},
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) padding = Insets(20)
})
d.styleClass ++= Seq("text-white")
onAction = () => d.show(root)
}
} }
)
}
onAction = () => d.show(root)
}
)
} }
})
)
} yield mainSceneNode
}
})
)
} yield mainSceneNode
} }
class TestModel(_name: String, _age: Int) { class TestModel(_name: String, _age: Int) {

10
src/main/scala/nova/monadic_sfx/MainModule.scala

@ -19,23 +19,23 @@ trait MainModule extends ActorModule with UiModule with HttpModule {
"nova.monadic_sfx.util.reactive.store.Store" -> storeLogger "nova.monadic_sfx.util.reactive.store.Store" -> storeLogger
) )
.withFallback(defaultLogger) .withFallback(defaultLogger)
.withAsync(timeWindow = 1.millis)
.withAsync(timeWindow = 10.millis)
def makeLogger = def makeLogger =
for { for {
defaultLogger <- consoleLogger[Task]() defaultLogger <- consoleLogger[Task]()
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
.withAsync(timeWindow = 10.millis) |+| fileLogger[Task](
"application.log" "application.log"
).withAsync(timeWindow = 1.millis)
).withAsync(timeWindow = 10.millis)
middlewareLogger <- middlewareLogger <-
consoleLogger[ consoleLogger[
Task Task
](formatter = Middlewares.format) ](formatter = Middlewares.format)
.withMinimalLevel(Level.Trace) .withMinimalLevel(Level.Trace)
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
.withAsync(timeWindow = 10.millis) |+| fileLogger[Task](
"stores.log", "stores.log",
formatter = Middlewares.format formatter = Middlewares.format
).withAsync(timeWindow = 1.millis)
).withAsync(timeWindow = 10.millis)
routerLogger <- routerLogger(defaultLogger, middlewareLogger) routerLogger <- routerLogger(defaultLogger, middlewareLogger)
} yield (routerLogger) } yield (routerLogger)
} }

2
src/main/scala/nova/monadic_sfx/Types.scala

@ -10,5 +10,7 @@ import sttp.client.httpclient.WebSocketHandler
trait AppTypes { trait AppTypes {
type HttpBackend = type HttpBackend =
SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler] SttpBackend[Task, Observable[ByteBuffer], WebSocketHandler]
type CrossBackend =
SttpBackend[Task, Observable[ByteBuffer], Nothing]
} }
object AppTypes extends AppTypes {} object AppTypes extends AppTypes {}

13
src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala

@ -42,7 +42,7 @@ trait JavaFXMonixObservables {
) = ) =
new ReadOnlyPropertyExt[T, J](prop) new ReadOnlyPropertyExt[T, J](prop)
implicit def extendedObservableList[A]( implicit def extendedObservableList[A](
list: ObservableList[A]
list: ObservableBuffer[A]
) = new ObservableListExt(list) ) = new ObservableListExt(list)
implicit def extendedStringObservableList( implicit def extendedStringObservableList(
list: ObservableList[String] list: ObservableList[String]
@ -92,7 +92,7 @@ object JavaFXMonixObservables {
} }
} }
scene.onMouseDragged = l
scene.onMouseDragged = l;
c := Cancelable(() => c := Cancelable(() =>
scene.removeEventHandler( scene.removeEventHandler(
jfxsi.MouseEvent.MOUSE_DRAGGED, jfxsi.MouseEvent.MOUSE_DRAGGED,
@ -115,7 +115,7 @@ object JavaFXMonixObservables {
} }
def <--( def <--(
obs: Observable[_ <: T]
obs: Observable[T]
)(implicit s: Scheduler, c: CompositeCancelable): Unit = { )(implicit s: Scheduler, c: CompositeCancelable): Unit = {
c += obs.doOnNextF(v => Coeval(prop.value = v)).subscribe() c += obs.doOnNextF(v => Coeval(prop.value = v)).subscribe()
} }
@ -142,7 +142,10 @@ object JavaFXMonixObservables {
extends AnyVal { extends AnyVal {
def -->(sub: Observer[A]) = def -->(sub: Observer[A]) =
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
prop.onChange((a, b, c) =>
if (c != null)
if (sub.onNext(c) == Ack.Stop) throw new Exception("boom")
)
def ==>(op: Property[A, A]) = { def ==>(op: Property[A, A]) = {
prop.onChange((a, b, c) => if (c != null) op() = c) prop.onChange((a, b, c) => if (c != null) op() = c)
@ -168,7 +171,7 @@ object JavaFXMonixObservables {
} }
final class ObservableListExt[A]( final class ObservableListExt[A](
private val buffer: ObservableList[A]
private val buffer: ObservableBuffer[A]
) extends AnyVal { ) extends AnyVal {
// def -->(sub: Observer[A]) = // def -->(sub: Observer[A]) =

11
src/main/scala/nova/monadic_sfx/implicits/package.scala

@ -1,8 +1,17 @@
package nova.monadic_sfx package nova.monadic_sfx
import monix.eval.TaskLike
import monix.reactive.Observable
package object implicits package object implicits
extends MySfxObservableImplicits extends MySfxObservableImplicits
with JavaFXMonixObservables
with JavaFXMonixObservables {
implicit class SttpWsOps[F[_]](private val ws: sttp.client.ws.WebSocket[F])
extends AnyVal {
def observableSource(implicit F: TaskLike[F]) =
Observable.repeatEvalF(ws.receiveText())
}
}
// implicit class NodeExt(val node: Node) { // implicit class NodeExt(val node: Node) {
// def lookup2[T <: SFXDelegate[_]]( // def lookup2[T <: SFXDelegate[_]](

71
src/main/scala/nova/monadic_sfx/ui/FXComponent.scala

@ -0,0 +1,71 @@
package nova.monadic_sfx.ui
import javafx.{scene => jfxc}
import monix.bio.Task
import monix.execution.Cancelable
import monix.execution.cancelables.CompositeCancelable
import monix.reactive.subjects.ConcurrentSubject
import nova.monadic_sfx.implicits._
import scalafx.beans.property.ObjectProperty
import scalafx.scene.Node
import scalafx.scene.Parent
import scalafx.scene.control.TextField
import scalafx.scene.text.Font
import cats.effect.Resource
final class FXComponent private (
val rootNode: Parent,
val cancelable: Cancelable
)
object FXComponent {
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]) =
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 x = for {
// comp <- testComp
// res <- FXComponent.make { implicit c =>
// Task.deferAction { implicit s =>
// Task {
// new BorderPane {
// val sub = ConcurrentSubject.publish[jfxc.text.Font]
// center = FXComponent.fxComponent2Node(comp)
// bottom = new TextField {
// font <-- sub
// }
// }
// }
// }
// }
// } yield res
}

66
src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala

@ -2,41 +2,59 @@ package nova.monadic_sfx.ui
import scala.concurrent.duration._ import scala.concurrent.duration._
import cats.effect.Resource
import io.odin.Logger import io.odin.Logger
import monix.bio.Fiber
import monix.bio.Task import monix.bio.Task
import monix.execution.CancelablePromise
import nova.monadic_sfx.executors.Schedulers import nova.monadic_sfx.executors.Schedulers
import nova.monadic_sfx.ui.DefaultUI import nova.monadic_sfx.ui.DefaultUI
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.application.JFXApp3
import scalafx.application.JFXApp3.PrimaryStage
class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) { class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
private lazy val internal = new JFXApp {
stage = new PrimaryStage {
scene = DefaultUI.scene
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(())
}
} }
}
// def stage = Task(internal.stage)
// def stage_=(stage: PrimaryStage) = Task(internal.stage = stage)
// def useInternal[T](f: JFXApp => Task[T]): Task[T] =
// for {
// _ <- logger.debug("Request for using internal value")
// res <- f(internal).executeOn(schedulers.fx)
// _ <- logger.debug(s"Result was ${res.toString()}")
// } yield (res)
}
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) =
for {
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") _ <- 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 <- fib <-
Task(internal.main(Array.empty)).start.executeOn(schedulers.blocking)
_ <- Task.sleep(200.millis)
_ <- Task(internal.stage = stage)
Task(delegate.main(Array.empty)).start.executeOn(schedulers.blocking)
_ <- Task.fromCancelablePromise(startSignal)
_ <- Task.sleep(transitionDelay)
_ <- Task(delegate.stage = stage)
.executeOn(schedulers.fx) .executeOn(schedulers.fx)
.delayExecution(delay)
} yield (this, fib)
.delayExecution(transitionDelay)
} yield Task.fromCancelablePromise(stopSignal) -> fib) {
case _ -> fib => fib.cancel
}
} }

49
src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala

@ -2,6 +2,9 @@ package nova.monadic_sfx.ui.components.router
import scala.concurrent.duration._ import scala.concurrent.duration._
import cats.kernel.Eq
import cats.syntax.eq._
import com.softwaremill.quicklens._
import io.circe.Codec import io.circe.Codec
import io.circe.Decoder import io.circe.Decoder
import io.circe.Encoder import io.circe.Encoder
@ -11,8 +14,8 @@ import io.odin.Logger
import monix.bio.Task import monix.bio.Task
import monix.eval.Coeval import monix.eval.Coeval
import monix.reactive.Observable import monix.reactive.Observable
import nova.monadic_sfx.util.History
import nova.monadic_sfx.util.IOUtils import nova.monadic_sfx.util.IOUtils
import nova.monadic_sfx.util.MutHistory
import nova.monadic_sfx.util.controls.JFXSpinner import nova.monadic_sfx.util.controls.JFXSpinner
import nova.monadic_sfx.util.reactive.store.Middlewares import nova.monadic_sfx.util.reactive.store.Middlewares
import nova.monadic_sfx.util.reactive.store.Reducer import nova.monadic_sfx.util.reactive.store.Reducer
@ -21,7 +24,10 @@ import scalafx.scene.Parent
object FXRouter { object FXRouter {
final case class State[P](page: P)
final case class State[P](page: P, history: History[P])
object State {
implicit def eqForAction[T] = Eq.fromUniversalEquals[State[T]]
}
sealed abstract class Action[+T] sealed abstract class Action[+T]
final case class Replace[T](page: T) extends Action[T] final case class Replace[T](page: T) extends Action[T]
@ -31,16 +37,14 @@ object FXRouter {
object Action { object Action {
implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec
implicit def eqForAction[T] = Eq.fromUniversalEquals[Action[T]]
} }
type FXStore[P] = Store[Action[P], State[P]] type FXStore[P] = Store[Action[P], State[P]]
} }
class FXRouter[P](history: MutHistory[P])(implicit
E: Encoder[P],
D: Decoder[P]
) {
class FXRouter[P]()(implicit E: Encoder[P], D: Decoder[P]) {
import FXRouter._ import FXRouter._
def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] = def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] =
@ -52,9 +56,10 @@ class FXRouter[P](history: MutHistory[P])(implicit
) )
store <- Store.createL[Action[P], State[P]]( store <- Store.createL[Action[P], State[P]](
Replace(initialPage), Replace(initialPage),
State(initialPage),
State(initialPage, History(initialPage)),
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _), Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _),
Seq(mw) Seq(mw)
// Seq(classOf[HistoryEvent[P]])
) )
} yield store } yield store
) )
@ -66,27 +71,39 @@ class FXRouter[P](history: MutHistory[P])(implicit
action match { action match {
// case Init => (state, None) // case Init => (state, None)
case Replace(p) => case Replace(p) =>
history.push(p)
(state.copy(page = p), None)
(state.copy(page = p, history = state.history :+ p), None)
case HistoryEvent(p) => case HistoryEvent(p) =>
(state.copy(page = p), None) (state.copy(page = p), None)
case Forward => (state, None)
case Backward => (state, None)
case Forward =>
val s1 = state.modify(_.history).using(_.forward)
val s2 = s1.modify(_.page).setTo(s1.history.current)
s2 -> Some(Task.pure(HistoryEvent(s2.history.current)))
case Backward =>
val s1 = state.modify(_.history).using(_.backward)
val s2 = s1.modify(_.page).setTo(s1.history.current)
s2 -> Some(Task.pure(HistoryEvent(s2.history.current)))
} }
def render( def render(
resolver: P => Task[Parent],
resolver: P => Parent,
transitionDelay: FiniteDuration = 500.millis transitionDelay: FiniteDuration = 500.millis
)(implicit store: FXStore[P]) = )(implicit store: FXStore[P]) =
store store
.filter {
case (a, _) => a =!= FXRouter.Forward
}
.filter {
case (a, _) => a =!= FXRouter.Backward
}
.distinctUntilChanged
.flatMap { .flatMap {
case (_, FXRouter.State(p)) =>
case (_, FXRouter.State(p, _)) =>
Observable.from(Coeval(new JFXSpinner)) ++ Observable.from( Observable.from(Coeval(new JFXSpinner)) ++ Observable.from(
IOUtils.toTask( IOUtils.toTask(
Task Task
.racePair( .racePair(
Task.sleep(transitionDelay), Task.sleep(transitionDelay),
resolver(p)
Task.pure(resolver(p))
) )
.flatMap { .flatMap {
case Left(_ -> fib) => fib.join case Left(_ -> fib) => fib.join
@ -108,8 +125,10 @@ class FXRouter[P](history: MutHistory[P])(implicit
sealed trait Page sealed trait Page
object Page { object Page {
final case object Home extends Page final case object Home extends Page
final case class UserHome(id: Int) extends Page
final case object UserHome extends Page
final case object Todo extends Page final case object Todo extends Page
implicit val eqForPage = Eq.fromUniversalEquals[Page]
} }
// case class State() // case class State()

23
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala

@ -8,12 +8,10 @@ import monix.bio.Task
import monix.catnap.ConcurrentChannel import monix.catnap.ConcurrentChannel
import monix.catnap.ConsumerF import monix.catnap.ConsumerF
import monix.execution.Scheduler import monix.execution.Scheduler
import monix.reactive.Observable
import monix.reactive.Observer import monix.reactive.Observer
import nova.monadic_sfx.util.controls.FontIcon import nova.monadic_sfx.util.controls.FontIcon
import nova.monadic_sfx.util.controls.IconLiteral import nova.monadic_sfx.util.controls.IconLiteral
import nova.monadic_sfx.util.controls.JFXListView import nova.monadic_sfx.util.controls.JFXListView
import nova.monadic_sfx.implicits._
import nova.monadic_sfx.util.reactive.store._ import nova.monadic_sfx.util.reactive.store._
import scalafx.Includes._ import scalafx.Includes._
import scalafx.beans.property.StringProperty import scalafx.beans.property.StringProperty
@ -57,13 +55,12 @@ object TodoListViewOld {
): Task[JFXListView[Todo]] = ): Task[JFXListView[Todo]] =
Task.deferAction(implicit s => Task.deferAction(implicit s =>
Task { Task {
val todos =
store.map { case (_, items) => items }
val todos = store.map { case (_, items) => items }
val listView = new JFXListView[Todo] { lv => val listView = new JFXListView[Todo] { lv =>
def selectedItems = lv.selectionModel().selectedItems.view def selectedItems = lv.selectionModel().selectedItems.view
// items = todos // items = todos
items <-- todos
// items <-- todos
// .map(ObservableBuffer.from(_)) // .map(ObservableBuffer.from(_))
cellFactory = _ => cellFactory = _ =>
new ListCell[Todo] { new ListCell[Todo] {
@ -216,14 +213,14 @@ object TodoListComponentOld {
case _ => state case _ => state
} }
val store =
Store
.createL[TodoListComponentOld.Command, Vector[Todo]](
TodoListComponentOld.Delete(0),
Vector.empty[Todo],
(s: Vector[Todo], a: TodoListComponentOld.Command) =>
reducer(s, a) -> Observable.empty
)
// val store =
// Store
// .createL[TodoListComponentOld.Command, Vector[Todo]](
// TodoListComponentOld.Delete(0),
// Vector.empty[Todo],
// (s: Vector[Todo], a: TodoListComponentOld.Command) =>
// reducer(s, a) -> Observable.empty
// )
class Props( class Props(
val todoListView: TodoListViewOld, val todoListView: TodoListViewOld,

35
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala

@ -1,5 +1,6 @@
package nova.monadic_sfx.ui.components.todo package nova.monadic_sfx.ui.components.todo
import cats.kernel.Eq
import com.softwaremill.quicklens._ import com.softwaremill.quicklens._
import io.circe.generic.JsonCodec import io.circe.generic.JsonCodec
import io.odin.Logger import io.odin.Logger
@ -9,6 +10,9 @@ import nova.monadic_sfx.util.reactive.store.Reducer
import nova.monadic_sfx.util.reactive.store.Store import nova.monadic_sfx.util.reactive.store.Store
case class Todo(id: Int, content: String) case class Todo(id: Int, content: String)
object Todo {
implicit val eqForTodo = Eq.fromUniversalEquals[Todo]
}
object TodoListStore { object TodoListStore {
@ -21,8 +25,15 @@ object TodoListStore {
private case class InternalAdd(content: String) extends Action private case class InternalAdd(content: String) extends Action
private case object End extends Action private case object End extends Action
object Action {
implicit val eqForAction = Eq.fromUniversalEquals[Action]
}
case class State(todos: Vector[Todo], counter: Int) case class State(todos: Vector[Todo], counter: Int)
object State {
implicit val eqForState = Eq.fromUniversalEquals[State]
}
def reducer(logger: Logger[Task])( def reducer(logger: Logger[Task])(
state: State, state: State,
@ -32,12 +43,13 @@ object TodoListStore {
case Init => (state, None) case Init => (state, None)
case Add(content) => case Add(content) =>
val nextAction = Some(for { val nextAction = Some(for {
// do some validation
// _ <- logger.debug(s"Received $content") // _ <- logger.debug(s"Received $content")
res <- Task.pure(InternalAdd(content)) res <- Task.pure(InternalAdd(content))
} yield res) } yield res)
(state, nextAction) (state, nextAction)
case Edit(_id, content) =>
val condition: Todo => Boolean = _.id == _id
case Edit(id, content) =>
val condition: Todo => Boolean = _.id == id
val nextState = state val nextState = state
.modify(_.todos.eachWhere(condition)) .modify(_.todos.eachWhere(condition))
.using(_.copy(content = content)) .using(_.copy(content = content))
@ -46,15 +58,17 @@ object TodoListStore {
(state.copy(state.todos.filterNot(_.id == id)), None) (state.copy(state.todos.filterNot(_.id == id)), None)
case InternalAdd(content) => case InternalAdd(content) =>
val nextState = state.copy(
todos = state.todos :+ Todo(state.counter, content),
counter = state.counter + 1
)
val nextState =
state
.modify(_.todos)
.using(_ :+ Todo(state.counter, content))
.modify(_.counter)
.using(_ + 1)
(nextState, Some(logger.debug(s"Received $content") >> Task.pure(End))) (nextState, Some(logger.debug(s"Received $content") >> Task.pure(End)))
case End => (state, None) case End => (state, None)
} }
def apply(logger: Logger[Task]) =
def apply(logger: Logger[Task]): Task[Store[Action, State]] =
Task.deferAction(implicit s => Task.deferAction(implicit s =>
for { for {
logMware <- actionLoggerMiddleware[Action, State](logger, "TodoStore") logMware <- actionLoggerMiddleware[Action, State](logger, "TodoStore")
@ -64,12 +78,9 @@ object TodoListStore {
Init, Init,
State(Vector.empty[Todo], 0), State(Vector.empty[Todo], 0),
Reducer.withOptionalEffects(reducer(logger) _), Reducer.withOptionalEffects(reducer(logger) _),
Seq(
// actionLoggerMiddleware(logger, "TodoStore2")
logMware
)
Seq(logMware)
) )
} yield (store)
} yield store
) )
} }

45
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala

@ -1,30 +1,33 @@
package nova.monadic_sfx.ui.components.todo package nova.monadic_sfx.ui.components.todo
import monix.bio.Task import monix.bio.Task
import monix.eval.Coeval
import monix.execution.cancelables.CompositeCancelable import monix.execution.cancelables.CompositeCancelable
import monix.{eval => me} import monix.{eval => me}
import nova.monadic_sfx.implicits._
import nova.monadic_sfx.util.controls.FontIcon import nova.monadic_sfx.util.controls.FontIcon
import nova.monadic_sfx.util.controls.IconLiteral import nova.monadic_sfx.util.controls.IconLiteral
import nova.monadic_sfx.util.controls.JFXButton import nova.monadic_sfx.util.controls.JFXButton
import nova.monadic_sfx.util.controls.JFXListView import nova.monadic_sfx.util.controls.JFXListView
import nova.monadic_sfx.util.controls.JFXTextField import nova.monadic_sfx.util.controls.JFXTextField
import nova.monadic_sfx.util.controls.MenuItem import nova.monadic_sfx.util.controls.MenuItem
import nova.monadic_sfx.implicits._
import nova.monadic_sfx.util.reactive.store._ import nova.monadic_sfx.util.reactive.store._
import org.gerweck.scalafx.util._ import org.gerweck.scalafx.util._
import scalafx.Includes._ import scalafx.Includes._
import scalafx.beans.property.ObjectProperty import scalafx.beans.property.ObjectProperty
import scalafx.beans.property.StringProperty import scalafx.beans.property.StringProperty
import scalafx.collections.ObservableBuffer
import scalafx.geometry.Insets import scalafx.geometry.Insets
import scalafx.geometry.Pos
import scalafx.scene.Parent import scalafx.scene.Parent
import scalafx.scene.control.ContextMenu import scalafx.scene.control.ContextMenu
import scalafx.scene.control.Label
import scalafx.scene.control.ListCell import scalafx.scene.control.ListCell
import scalafx.scene.control.SelectionMode import scalafx.scene.control.SelectionMode
import scalafx.scene.layout.HBox
import scalafx.scene.text.Text
import scalafx.geometry.Pos
import scalafx.scene.layout.BorderPane import scalafx.scene.layout.BorderPane
import scalafx.scene.layout.HBox
import scalafx.scene.layout.Priority import scalafx.scene.layout.Priority
import scalafx.scene.paint.Color
object TodoListView { object TodoListView {
def apply( def apply(
@ -32,23 +35,29 @@ object TodoListView {
): Task[Parent] = ): Task[Parent] =
Task.deferAction(implicit s => Task.deferAction(implicit s =>
Task { Task {
val cc = CompositeCancelable()
val todos = store.map { case (_, state) => state.todos }
implicit val cc = CompositeCancelable()
val todos = store
.map { case (_, state) => state.todos }
.distinctUntilChanged
.map(ObservableBuffer.from)
.doOnNextF(item => Coeval(println(s"Received item: $item")))
val _selectedItems = ObjectProperty(Seq.empty[Todo]) val _selectedItems = ObjectProperty(Seq.empty[Todo])
new BorderPane { new BorderPane {
padding = Insets(5) padding = Insets(5)
hgrow = Priority.Always
val _content = StringProperty("") val _content = StringProperty("")
center = new HBox { center = new HBox {
padding = Insets(5) padding = Insets(5)
children ++= Seq(new JFXListView[Todo] { children ++= Seq(new JFXListView[Todo] {
id = "todoList"
hgrow = Priority.Always hgrow = Priority.Always
def selectedItems = selectionModel().selectedItems.view def selectedItems = selectionModel().selectedItems.view
styleClass ++= Seq("text-white")
styleClass ++= Seq("text-white", "clear-list-view")
selectionModel().selectionMode = SelectionMode.Multiple selectionModel().selectionMode = SelectionMode.Multiple
selectionModel().selectedItems.observableSeqValue ==> _selectedItems selectionModel().selectedItems.observableSeqValue ==> _selectedItems
cc += items <-- todos
items <-- todos.map(_.delegate)
val emptyCell = ObjectProperty(new HBox) val emptyCell = ObjectProperty(new HBox)
cellFactory = _ => cellFactory = _ =>
@ -56,12 +65,16 @@ object TodoListView {
val _text = StringProperty("") val _text = StringProperty("")
val _graphic = ObjectProperty( val _graphic = ObjectProperty(
new HBox { new HBox {
styleClass ++= Seq("text-white", "strong", "todo-cell")
children = Seq( children = Seq(
new FontIcon { new FontIcon {
iconSize = 10
iconSize = 20
iconLiteral = IconLiteral.Gmi10k iconLiteral = IconLiteral.Gmi10k
fill = Color.White
}, },
new Text {
new Label {
style = "-fx-text-fill: white "
styleClass ++= Seq("text-white", "strong")
text <== _text text <== _text
} }
) )
@ -100,6 +113,7 @@ object TodoListView {
) )
} }
}) })
} }
bottom = new HBox { bottom = new HBox {
@ -107,22 +121,29 @@ object TodoListView {
padding = Insets(5) padding = Insets(5)
children = Seq( children = Seq(
new JFXTextField { new JFXTextField {
id = "todoInputField"
style = "-fx-background-color: rgb(38,38,38);"
styleClass += "text-white"
text ==> _content text ==> _content
vgrow = Priority.Always
}, },
new JFXButton { new JFXButton {
id = "todoAddButton"
text = "Add" text = "Add"
alignment = Pos.Center alignment = Pos.Center
// disable <== _selectedItems.map(_.length > 0) // disable <== _selectedItems.map(_.length > 0)
styleClass = Seq("btn", "btn-primary") styleClass = Seq("btn", "btn-primary")
obsAction obsAction
.useLazyEval(me.Task(TodoListStore.Add(_content()))) --> store
.useLazyEval(
me.Task(TodoListStore.Add(_content()))
) --> store
}, },
new JFXButton { new JFXButton {
id = "todoEditButton"
text = "Edit" text = "Edit"
alignment = Pos.Center alignment = Pos.Center
disable <== _selectedItems.map(_.length > 1) disable <== _selectedItems.map(_.length > 1)
styleClass = Seq("btn", "btn-info") styleClass = Seq("btn", "btn-info")
style = ""
obsAction.useLazyEval( obsAction.useLazyEval(
me.Task( me.Task(
TodoListStore.Edit( TodoListStore.Edit(

2
src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala

@ -6,13 +6,13 @@ import animatefx.animation.FadeIn
import animatefx.util.{SequentialAnimationFX => SeqFX} import animatefx.util.{SequentialAnimationFX => SeqFX}
import cats.effect.Sync import cats.effect.Sync
import monix.eval.Task import monix.eval.Task
import nova.monadic_sfx.ui.components.todo.TodoListComponentOld
import nova.monadic_sfx.util.controls.FontIcon import nova.monadic_sfx.util.controls.FontIcon
import nova.monadic_sfx.util.controls.IconLiteral import nova.monadic_sfx.util.controls.IconLiteral
import nova.monadic_sfx.util.controls.JFXButton import nova.monadic_sfx.util.controls.JFXButton
import nova.monadic_sfx.util.controls.JFXListView import nova.monadic_sfx.util.controls.JFXListView
import nova.monadic_sfx.util.controls.JFXTextArea import nova.monadic_sfx.util.controls.JFXTextArea
import nova.monadic_sfx.util.controls.JFXTextField import nova.monadic_sfx.util.controls.JFXTextField
import nova.monadic_sfx.ui.components.todo.TodoListComponentOld
import scalafx.collections.ObservableBuffer import scalafx.collections.ObservableBuffer
import scalafx.scene.control.Label import scalafx.scene.control.Label
import scalafx.scene.layout.HBox import scalafx.scene.layout.HBox

37
src/main/scala/nova/monadic_sfx/util/BoundedStack.scala

@ -0,0 +1,37 @@
package nova.monadic_sfx.util
final class BoundedStack private (
val underlying: List[Int],
val maxSize: Int,
val sp: Int
) {
def push(v: Int): Either[String, BoundedStack] =
if (sp < maxSize) Right(new BoundedStack(v :: underlying, maxSize, sp + 1))
else Left("overflow")
def pushAll(v: List[Int]) =
if (sp + v.length < maxSize)
Right(new BoundedStack(v ::: underlying, maxSize, sp + v.length))
else Left("overflow")
def pop =
if (sp == 0) Left("Underflow")
else
Right(
underlying(sp) -> new BoundedStack(
underlying.splitAt(sp)._1,
maxSize,
sp - 1
)
)
}
object BoundedStack {
def apply(
// defaultValues: List[Int] = List.empty,
maxSize: Int = 10
// sp: Int = 0
) =
new BoundedStack(List.empty, maxSize, 0)
}

18
src/main/scala/nova/monadic_sfx/util/CssPath.scala

@ -0,0 +1,18 @@
package nova.monadic_sfx.util
import os.RelPath
trait CssPath[T] {
def path(self: T): String
}
object CssPath {
implicit val cssPathForString = new CssPath[String] {
def path(self: String): String = self
}
implicit val cssPathForOsRelPath = new CssPath[os.RelPath] {
def path(self: RelPath): String = self.toString()
}
implicit def any2CssPath[T](t: T)(implicit C: CssPath[T]): CssPath[T] = C
}

50
src/main/scala/nova/monadic_sfx/util/History.scala

@ -1,6 +1,7 @@
package nova.monadic_sfx.util package nova.monadic_sfx.util
import com.softwaremill.quicklens._
class MutHistory[T](initValue: T) {
final class MutHistory[T](initValue: T) {
private var _values = Vector(initValue) private var _values = Vector(initValue)
def values = _values def values = _values
private var _sp = 0 private var _sp = 0
@ -29,3 +30,50 @@ class MutHistory[T](initValue: T) {
} }
} }
} }
final class History[T] private (val state: History.State[T]) {
def current = state.values(state.sp)
def push(v: T) = {
val nextState =
// state.copy(state.values.splitAt(state.sp)._1 :+ v, state.sp + 1)
if (state.sp < state.values.length - 1) {
state.modify(_.values).using(_.splitAt(state.sp)._1 :+ v)
} else {
val s1 = state
.modify(_.values)
.using(_ :+ v)
s1.modify(_.sp)
.setTo(s1.values.length - 1)
}
new History(nextState)
}
def forward = {
// val nextState =
if (!state.values.isEmpty && state.sp < state.values.length - 1)
new History(state.modify(_.sp).using(_ + 1))
else this
// new History(nextState)
}
def backward = {
// val nextState =
if (state.sp > 0) new History(state.modify(_.sp).using(_ - 1)) else this
// new History(nextState)
}
}
// final class HistoryImpl()
object History {
case class State[T](values: Vector[T], sp: Int)
def apply[T](intialValue: T) =
new History(State(Vector(intialValue), 0))
// val history = new History(History.State(Vector.empty, 0))
implicit class HistoryOps[T](private val h: History[T]) extends AnyVal {
def :+(v: T) = h.push(v)
}
}
// History.history.push(1).forward

31
src/main/scala/nova/monadic_sfx/util/MediaPlayerResource.scala

@ -0,0 +1,31 @@
package nova.monadic_sfx.util
import cats.effect.Resource
import monix.bio.Task
import uk.co.caprica.vlcj.factory.MediaPlayerFactory
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer
class MediaPlayerResource(
val factory: MediaPlayerFactory,
val controller: EmbeddedMediaPlayer
)
object MediaPlayerResource {
val mediaPlayerFactoryResource =
Resource.make(Task(new MediaPlayerFactory()))(factory =>
Task(factory.release())
)
def mediaPlayerControllerResource(factory: MediaPlayerFactory) =
Resource.make(Task {
val players = factory.mediaPlayers()
players.newEmbeddedMediaPlayer()
})(controller => Task(controller.release()))
def apply() =
for {
factory <- mediaPlayerFactoryResource
controller <- mediaPlayerControllerResource(factory)
} yield new MediaPlayerResource(factory, controller)
}

47
src/main/scala/nova/monadic_sfx/util/WebSocket.scala

@ -0,0 +1,47 @@
package nova.monadic_sfx.util
import monix.bio.Task
import nova.monadic_sfx.AppTypes
import nova.monadic_sfx.implicits._
import sttp.client._
import sttp.client.httpclient.monix.MonixWebSocketHandler
// class WebSocket()(implicit backend: AppTypes.HttpBackend) {
// // val source = for {
// // } yield ()
// }
object WebSocket {
def apply()(implicit backend: AppTypes.HttpBackend) = {
Task
.deferAction(implicit s =>
IOUtils.toIO(
basicRequest.get(uri"").openWebsocketF(MonixWebSocketHandler())
)
)
.flatMap { r =>
val ws = r.result
// val source = Observable.repeatEvalF(ws.receive)
val source2 = ws.observableSource
// ws.send()
val source3 = source2.map {
case Left(value) => ()
case Right(value) => value
}
// ws.send()
// val sink = new Observer[WebSocketFrame] {
// override def onNext(elem: WebSocketFrame): Future[Ack] = ws.send(elem)
// override def onError(ex: Throwable): Unit = ex.printStackTrace()
// override def onComplete(): Unit = ws.close
// }
Task.unit
}
}
}

3
src/main/scala/nova/monadic_sfx/util/controls/EmbeddedMediaPlayer.scala

@ -0,0 +1,3 @@
package nova.monadic_sfx.util.controls

12
src/main/scala/nova/monadic_sfx/util/controls/JFXButton.scala

@ -2,11 +2,11 @@ package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc} import com.jfoenix.{controls => jfoenixc}
import javafx.{scene => jfxs} import javafx.{scene => jfxs}
import nova.monadic_sfx.implicits._
import scalafx.Includes._ import scalafx.Includes._
import scalafx.beans.property.ObjectProperty import scalafx.beans.property.ObjectProperty
import scalafx.scene.Node import scalafx.scene.Node
import scalafx.scene.control.Button import scalafx.scene.control.Button
import nova.monadic_sfx.implicits._
import jfxs.{paint => jfxsp} import jfxs.{paint => jfxsp}
@ -30,14 +30,10 @@ class JFXButton(
def this(text: String, graphic: Node) = def this(text: String, graphic: Node) =
this(new jfoenixc.JFXButton(text, graphic)) this(new jfoenixc.JFXButton(text, graphic))
def ripplerFill: ObjectProperty[jfxsp.Paint] =
jfxObjectProperty2sfx(delegate.ripplerFillProperty)
def ripplerFill: ObjectProperty[jfxsp.Paint] = delegate.ripplerFillProperty
def ripplerFill_=(b: jfxsp.Paint): Unit = {
ripplerFill() = b
}
def ripplerFill_=(b: jfxsp.Paint): Unit = ripplerFill() = b
def obsAction =
new ActionObservableBuilder(this.observableAction)
def obsAction = new ActionObservableBuilder(this.observableAction)
} }

22
src/main/scala/nova/monadic_sfx/util/controls/JFXDialog.scala

@ -0,0 +1,22 @@
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.layout.Region
import scalafx.scene.layout.StackPane
class JFXDialog(
override val delegate: jfoenixc.JFXDialog = new jfoenixc.JFXDialog
) extends StackPane(delegate) {
def show() = delegate.show()
def show(sp: StackPane) = delegate.show(sp)
def content = delegate.getContent()
def content_=(r: Region) = delegate.setContent(r)
def overlayClose = delegate.overlayCloseProperty()
def overlayClose_=(v: Boolean) = delegate.setOverlayClose(v)
def cacheContainer = delegate.cacheContainerProperty()
def cacheContainer_=(v: Boolean) = delegate.setCacheContainer(v)
}
object JFXDialog {
implicit def sfxJfXDialog2Jfx(v: JFXDialog): jfoenixc.JFXDialog = v.delegate
}

23
src/main/scala/nova/monadic_sfx/util/controls/JFXListCell.scala

@ -6,17 +6,34 @@ import scalafx.Includes._
import scalafx.beans.property.ReadOnlyObjectProperty import scalafx.beans.property.ReadOnlyObjectProperty
import scalafx.delegate.SFXDelegate import scalafx.delegate.SFXDelegate
import scalafx.scene.control.IndexedCell import scalafx.scene.control.IndexedCell
import scalafx.scene.control.ListCell
import scalafx.scene.control.ListView import scalafx.scene.control.ListView
object JFXListCell { object JFXListCell {
implicit def sfxListCell2jfx[T]( implicit def sfxListCell2jfx[T](
l: JFXListCell[T] l: JFXListCell[T]
): jfoenixc.JFXListCell[T] =
): ListCell[T] =
if (l != null) l.delegate else null if (l != null) l.delegate else null
} }
class JFXListCell[T]( class JFXListCell[T](
override val delegate: jfoenixc.JFXListCell[T] = new jfoenixc.JFXListCell[T]
override val delegate: jfoenixc.JFXListCell[T] =
new jfoenixc.JFXListCell[T] {
override def updateItem(
item: T,
empty: Boolean
): Unit = {
super.updateItem(item, empty)
// setText(null)
setText(getText())
setGraphic(getGraphic())
// setGraphic(null)
// remove empty (Trailing cells)
// setMouseTransparent(true)
// setStyle("-fx-background-color:TRANSPARENT;")
}
override def makeChildrenTransparent(): Unit = {}
}
) extends IndexedCell(delegate) ) extends IndexedCell(delegate)
with SFXDelegate[jfoenixc.JFXListCell[T]] { with SFXDelegate[jfoenixc.JFXListCell[T]] {
@ -33,4 +50,6 @@ class JFXListCell[T](
delegate.updateListView(listView) delegate.updateListView(listView)
} }
// delegate.cell
} }

12
src/main/scala/nova/monadic_sfx/util/controls/JFXListView.scala

@ -25,16 +25,8 @@ class JFXListView[T](
// v.foreach { items() = _ } // v.foreach { items() = _ }
// } // }
def items_=(
v: Observable[Seq[T]]
)(implicit s: Scheduler): Unit = {
v
.map {
// case buf: ObservableBuffer[T] => buf
case other => ObservableBuffer.from(other)
}
// .map(myDiff(items(), _))
.foreach { items() = _ }
def items_=(v: Observable[Seq[T]])(implicit s: Scheduler): Unit = {
v.map(ObservableBuffer.from).foreach(items() = _)
} }
def depth = delegate.depthProperty() def depth = delegate.depthProperty()

37
src/main/scala/nova/monadic_sfx/util/controls/JFXRippler.scala

@ -0,0 +1,37 @@
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.Node
import scalafx.scene.layout.StackPane
import scalafx.scene.paint.Paint
class JFXRippler(
override val delegate: jfoenixc.JFXRippler = new jfoenixc.JFXRippler
) extends StackPane(delegate) {
import JFXRippler._
def control = delegate.getControl()
def control_=(v: Node) = delegate.setControl(v)
def enabled_=(v: Boolean) = delegate.setEnabled(v)
def ripplerPos = delegate.getPosition()
def ripplerPos_=(pos: RipplerPos) = delegate.setPosition(pos)
def ripplerDisabled = delegate.ripplerDisabledProperty()
def ripplerDisabled_=(v: Boolean) = delegate.setRipplerDisabled(v)
def ripplerFill = delegate.ripplerFillProperty()
def ripplerFill_=(v: Paint) = delegate.setRipplerFill(v)
def ripplerRecenter = delegate.ripplerRecenterProperty()
def ripplerRecenter_=(v: Boolean) = delegate.setRipplerRecenter(v)
def ripplerRadius = delegate.ripplerRadiusProperty()
def ripplerRadius_=(v: Int) = delegate.setRipplerRadius(v)
}
object JFXRippler {
abstract class RipplerPos(val delegate: jfoenixc.JFXRippler.RipplerPos)
case object Front extends RipplerPos(jfoenixc.JFXRippler.RipplerPos.FRONT)
case object Back extends RipplerPos(jfoenixc.JFXRippler.RipplerPos.BACK)
object RipplerPos {
implicit def sfxRipplerPos2jfxRipplerPos(
v: RipplerPos
): jfoenixc.JFXRippler.RipplerPos = v.delegate
}
implicit def sfxRippler2jfxRippler(v: JFXRippler): jfoenixc.JFXRippler =
v.delegate
}

15
src/main/scala/nova/monadic_sfx/util/controls/VideoView.scala

@ -0,0 +1,15 @@
package nova.monadic_sfx.util.controls
import scalafx.scene.image.ImageView
import uk.co.caprica.vlcj.javafx.videosurface.ImageViewVideoSurfaceFactory.videoSurfaceForImageView
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer
class VideoView(val mediaPlayer: EmbeddedMediaPlayer) extends ImageView {
// private val _mediaPlayer = ObjectProperty[Option[EmbeddedMediaPlayer]](None)
// def mediaPlayer = _mediaPlayer
// def mediaPlayer_=(v: EmbeddedMediaPlayer): Unit = {
// v.videoSurface().set(videoSurfaceForImageView(this))
// _mediaPlayer.value = Some(v)
// }
mediaPlayer.videoSurface().set(videoSurfaceForImageView(this))
}

12
src/main/scala/nova/monadic_sfx/util/reactive/store/Middlewares.scala

@ -13,9 +13,6 @@ import io.odin.formatter.options.ThrowableFormat
import io.odin.meta.Render import io.odin.meta.Render
import monix.bio.Task import monix.bio.Task
import monix.reactive.Observable import monix.reactive.Observable
// object Middleware {
// def apply[A,M,T](ob: Observable[(A,M)], cb: (A,M) => T): Observable[(A,M)] = ob
// }
@JsonCodec @JsonCodec
final case class StoreInfo[A]( final case class StoreInfo[A](
@ -34,19 +31,12 @@ object StoreInfo {
object Middlewares { object Middlewares {
// val encoder: Encoder[LoggerMessage] =
// Encoder.forProduct1("message")(m => m.message.value)
val format = create(ThrowableFormat.Default, PositionFormat.Full) val format = create(ThrowableFormat.Default, PositionFormat.Full)
def create( def create(
throwableFormat: ThrowableFormat, throwableFormat: ThrowableFormat,
positionFormat: PositionFormat positionFormat: PositionFormat
): io.odin.formatter.Formatter = {
// val encoder: Encoder[LoggerMessage] =
// Encoder.forProduct1("message")(m => m.message.value)
(msg: LoggerMessage) => msg.message.value
}
): io.odin.formatter.Formatter = (msg: LoggerMessage) => msg.message.value
def actionStateLoggerMiddleware[A, M]( def actionStateLoggerMiddleware[A, M](
logger: Logger[Task] logger: Logger[Task]

79
src/main/scala/nova/monadic_sfx/util/reactive/store/Store.scala

@ -1,12 +1,8 @@
package nova.monadic_sfx.util.reactive.store package nova.monadic_sfx.util.reactive.store
import java.time.LocalDateTime
import io.circe.Encoder
import io.odin.Logger
import monix.bio.Task import monix.bio.Task
import monix.eval.Coeval import monix.eval.Coeval
import monix.reactive.Observable
import monix.reactive.Observer
import monix.reactive.OverflowStrategy import monix.reactive.OverflowStrategy
import monix.reactive.subjects.ConcurrentSubject import monix.reactive.subjects.ConcurrentSubject
@ -34,73 +30,26 @@ object Store {
} }
} }
val obs = Observable.suspend(
subject
.scanEval0F[Coeval, (A, M)](
Coeval.pure(initialAction -> initialState)
)(fold)
.behavior(initialAction -> initialState)
.refCount
)
val res = middlewares.foldLeft(obs) {
case (obs, middleware) => middleware(obs)
}
MonixProSubject.from(
subject,
res
)
}
}
def createJsonL[A: Encoder, M](
initialAction: A,
initialState: M,
reducer: Reducer[A, M],
storeName: String,
logger: Logger[Task],
middlewares: Seq[Middleware[A, M]] = Seq.empty,
overflowStrategy: OverflowStrategy.Synchronous[A] =
OverflowStrategy.DropOld(50)
): Task[Store[A, M]] =
Task.deferAction { implicit s =>
Task {
val subject = ConcurrentSubject.publish[A](overflowStrategy)
val fold: ((A, M), A) => Task[(A, M)] = {
case ((_, state), action) =>
Task {
val (newState, effects) = reducer(state, action)
effects.subscribe(subject.onNext _)
action -> newState
}
}
val obs = subject val obs = subject
.doOnNextF(action =>
Task(LocalDateTime.now()).flatMap(curTime =>
logger.debug(
StoreInfo(storeName, action, curTime)
)
)
)
// .doOnNextF(action => Coeval(println(action)))
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))(
fold
)
.scanEval0F[Coeval, (A, M)](
Coeval.pure(initialAction -> initialState)
)(fold)
val res = middlewares
.foldLeft(obs) {
case (obs, middleware) => middleware(obs)
}
.doOnNextF(i => Coeval(println(s"Emitted item 1: $i")))
.behavior(initialAction -> initialState) .behavior(initialAction -> initialState)
.refCount .refCount
// val res = middlewares.foldLeft(obs) {
// case (obs, middleware) => middleware(obs)
// }
res.subscribe(Observer.empty)
// .doOnNextF(i => Coeval(println(s"Emitted item 2: $i")))
MonixProSubject.from( MonixProSubject.from(
subject, subject,
obs
res
) )
} }
} }

32
src/main/scala/nova/monadic_sfx/util/test.txt

@ -0,0 +1,32 @@
Vector()
0
//push 1
Vector(1)
0
//push 2
Vector(1,2)
1
//forward
Vector(1,2)
1
//backward
Vector(1,2)
0
//backward
Vector(1,2)
0
//push 3
Vector(1,2,3)
sp 2
//push 4
Vector(1,2,3,4)
sp 3
//push 5
Vector(1,2,3,4,5)
sp 4
//backward
Vector(1,2,3,4,5)
sp 3
//push 6
Vector(1,2,3,4,6)
sp 3

107
src/test/scala/HistoryTest.scala

@ -0,0 +1,107 @@
import org.scalatest.funsuite.AnyFunSuite
import nova.monadic_sfx.util.History
import monix.execution.atomic.Atomic
class HistoryTest extends AnyFunSuite {
val historyRef = Atomic(History(0))
test("init") {
val h = historyRef.get()
assert(h.state.values == Vector(0))
assert(h.state.sp == 0)
assert(h.current == 0)
}
test("push 1") {
val h = historyRef.transformAndGet(_.push(1))
// logger.debug(mutHistory.ints.toString)
assert(h.state.values == Vector(0, 1))
assert(h.state.values.length - 1 == h.state.sp)
assert(h.current == 1)
}
test("push 2") {
val h = historyRef.transformAndGet(_.push(2))
// logger.debug(mutHistory.ints.toString)
assert(h.state.values == Vector(0, 1, 2))
assert(h.state.values.length - 1 == h.state.sp)
assert(h.current == 2)
}
test("first forward") {
// logger.debug(mutHistory.ints.toString)
// logger.debug(mutHistory.sp.toString)
val h = historyRef.transformAndGet(_.forward)
assert(h.state.values == Vector(0, 1, 2))
assert(h.state.sp == 2)
assert(h.current == 2)
}
test("second forward") {
// logger.debug(mutHistory.ints.toString)
// logger.debug(mutHistory.sp.toString)
val h = historyRef.transformAndGet(_.forward)
assert(h.state.values == Vector(0, 1, 2))
assert(h.state.sp == 2)
assert(h.current == 2)
}
test("first backward") {
val h = historyRef.transformAndGet(_.backward)
assert(h.state.values == Vector(0, 1, 2))
assert(h.state.sp == 1)
assert(h.current == 1)
}
test("second backward") {
val h = historyRef.transformAndGet(_.backward)
assert(h.state.values == Vector(0, 1, 2))
assert(h.state.sp == 0)
assert(h.current == 0)
}
test("third backward") {
val h = historyRef.transformAndGet(_.backward)
assert(h.state.values == Vector(0, 1, 2))
assert(h.state.sp == 0)
assert(h.current == 0)
}
test("push 3") {
val h = historyRef.transformAndGet(_.push(3))
// logger.debug(mutHistory.ints.toString)
assert(h.state.values == Vector(3))
assert(h.state.sp == 0)
assert(h.current == 3)
}
test("fourth backward") {
val h = historyRef.transformAndGet(_.backward)
assert(h.state.values == Vector(3))
assert(h.state.sp == 0)
assert(h.current == 3)
}
test("lastly") {
val h1 = historyRef.transformAndGet(
_.push(4)
.push(5)
.push(6)
.push(7)
.push(8)
)
assert(h1.state.values == Vector(3, 4, 5, 6, 7, 8))
assert(h1.state.sp == 5)
assert(h1.current == 8)
val h2 = historyRef.transformAndGet(_.backward.backward)
assert(h2.current == 6)
assert(h2.state.sp == 3)
val h3 = historyRef.transformAndGet(_.push(9))
assert(h3.state.values == Vector(3, 4, 5, 9))
assert(h3.state.sp == 3)
assert(h3.current == 9)
for (i <- 1 to 4) historyRef.transform(_.backward)
// assert(h.current == None)
// assert(h.ints == Vector.empty)
val h4 = historyRef.get()
assert(h4.state.sp == 0)
assert(h4.current == 3)
val h5 = historyRef.transformAndGet(_.push(1))
assert(h5.current == 1)
assert(h5.state.sp == 0)
}
}

83
src/test/scala/ProSubjectTest.scala

@ -0,0 +1,83 @@
import org.scalatest.funsuite.AnyFunSuite
import monix.eval.Task
import monix.reactive.subjects.ConcurrentSubject
import monix.eval.Coeval
import com.typesafe.scalalogging.LazyLogging
import monix.execution.Scheduler.global
import nova.monadic_sfx.util.reactive.store.MonixProSubject
import scala.concurrent.duration._
import scala.concurrent.Await
import monix.reactive.Observable
class ProSubjectTest extends AnyFunSuite with LazyLogging {
test("task1") {
implicit val s = global
val x = Await.result(task1.runToFuture, 10.seconds)
assert(x == 1)
}
test("task2") {
implicit val s = global
val x = Await.result(task2.runToFuture, 10.seconds)
}
def task1 =
Task
.deferAction(implicit s =>
Task {
val sub = ConcurrentSubject.publish[Int]
// val obs = sub.scan0(0)(_ + _).behavior(2).refCount
val obs =
sub
.scanEval0F(Task.pure(0))((a, b) => Task(a + b))
.behavior(2)
.refCount
.doOnNextF(i => Coeval(println(s"Emitted1: $i")))
// type MonixProSubject[-I, +O] = Observable[O] with Observer[I]
// def from[I, O](
// observer: Observer[I],
// observable: Observable[O]
// ): MonixProSubject[I, O] =
// new Observable[O] with Observer[I] {
// override def onNext(elem: I): Future[Ack] = observer.onNext(elem)
// override def onError(ex: Throwable): Unit = observer.onError(ex)
// override def onComplete(): Unit = observer.onComplete()
// override def unsafeSubscribeFn(
// subscriber: Subscriber[O]
// ): Cancelable =
// observable.unsafeSubscribeFn(subscriber)
// }
MonixProSubject.from(sub, obs)
}.flatMap {
case ps =>
Task {
ps
.doOnNextF(i => Coeval(println(s"Emitted item 1: $i")))
.subscribe()
} >> Task {
ps
.doOnNextF(i => Coeval(println(s"Emitted item 2: $i")))
.subscribe()
} >> Task {
(0 to 5).foreach { i => ps.onNext(i) }
} >> Task(1)
}
)
val task2 = Task.deferAction(implicit s =>
Task(
Observable(1, 2, 3, 4, 5)
.scan0(0)(_ + _)
.behavior(2)
.refCount
// .doOnNextF(i => Coeval(println(s"Emitted2: $i")))
.delayExecution(10.millis)
)
.flatMap(res =>
Task(res.doOnNextF(i => Coeval(println(s"1: $i"))).subscribe()).start >>
Task(res.doOnNextF(i => Coeval(println(s"2: $i"))).subscribe()).start
)
)
}

61
src/test/scala/WebSocketTest.scala

@ -0,0 +1,61 @@
import org.scalatest.funsuite.AnyFunSuite
import sttp.client._
import sttp.client.httpclient.monix.HttpClientMonixBackend
import monix.execution.Scheduler
import sttp.client.httpclient.monix.MonixWebSocketHandler
import org.scalatest.BeforeAndAfterAll
import monix.eval.Task
import sttp.model.ws.WebSocketFrame
import scala.concurrent.duration._
import nova.monadic_sfx.implicits._
import monix.catnap.ConcurrentQueue
class WebSocketTest extends AnyFunSuite with BeforeAndAfterAll {
implicit val sched = Scheduler.global
implicit val backend = HttpClientMonixBackend().runSyncUnsafe()
val ws = basicRequest
.get(uri"ws://localhost:6789")
.openWebsocketF(MonixWebSocketHandler())
.map(_.result)
.runSyncUnsafe()
test("open websocket") {
(for {
isOpen <- ws.isOpen
_ <- Task(assert(isOpen == true))
_ <- Task(assert(isOpen != false))
} yield ()).runSyncUnsafe(10.seconds)
}
test("send message") {
(for {
_ <- ws.send(WebSocketFrame.text("Test Message"))
// _ <- Task.sleep(1.second)
// _ <- Task(assert(isOpen == true))
} yield ()).runSyncUnsafe(10.seconds)
}
test("receive messages observable") {
(for {
queue <- ConcurrentQueue.bounded[Task, Int](10)
_ <-
ws.observableSource
.filter(_.isRight)
.map(_.right.get)
.doOnNext(s =>
Task(println(s"Received item: $s")) >>
s.toIntOption.fold(Task.unit)(queue.offer)
)
.take(5)
.completedL
.timeout(5.seconds)
items <- queue.drain(5, 5).timeout(5.seconds)
_ <- Task(assert(items == Seq(1, 2, 3, 4, 5)))
_ <- Task(assert(items != Seq(1, 2, 3, 4, 5, 6)))
} yield ()).runSyncUnsafe(10.seconds)
}
override def afterAll() = {
ws.close.runSyncUnsafe()
backend.close()
}
}

25
src/test/scala/WebSocketTestServer.py

@ -0,0 +1,25 @@
#!/usr/bin/env python3
import asyncio
import websockets
async def server(websocket, path):
while True:
# Get received data from websocket
data = await websocket.recv()
# Send response back to client to acknowledge receiving message
print("Received data {}".format(data))
# await websocket.send(data)
# for i in range(1, 6):
# print("Sending item: {}".format(i))
# await websocket.send(str(i))
# Create websocket server
start_server = websockets.serve(server, "localhost", 6789)
# Start and run websocket server forever
asyncio.get_event_loop().run_until_complete(start_server)
print("Starting websocket server")
asyncio.get_event_loop().run_forever()
Loading…
Cancel
Save