19 Commits

Author SHA1 Message Date
Rohan Sircar 5b50f161d2 Used MutHistory to implement forward/backward 3 years ago
Rohan Sircar d18c884168 Added some dependencies 3 years ago
Rohan Sircar b010ad0842 Added mutable history container 3 years ago
Rohan Sircar b0b914424b Minor additions jfx monix implicits 3 years ago
Rohan Sircar 2f63119c6c TodoListView is a borderpane now 3 years ago
Rohan Sircar 08fe33b5d8 Test adding syles to todoListView 3 years ago
Rohan Sircar cfc11368b7 Router render fn now shows transition with a 3 years ago
Rohan Sircar 918b893fe8 Updated reactiv store code to suspend side effects 3 years ago
Rohan Sircar 338f7b16f9 Added main.css file 3 years ago
Rohan Sircar ae27d15af6 Added os-lib dep 3 years ago
Rohan Sircar 052e4f0fcb Refactored sfx control wrappers 3 years ago
Rohan Sircar 9155fdac96 Refactored reactive store 3 years ago
Rohan Sircar f721768098 Minor improvements 3 years ago
Rohan Sircar 7aa80f54f7 Converted implicit classes to implicit methods 3 years ago
Rohan Sircar 80b32b063e Added implicit conversion for fx obs filter 3 years ago
Rohan Sircar a905c5efaf Added bootstrapfx dependency 3 years ago
Rohan Sircar 6ef5c9778e Refactored FX code into it's own class + 3 years ago
Rohan Sircar 029cbbd5ac Added note about future use of backend and 3 years ago
Rohan Sircar 9dc83bbc56 Updated cats deps versions 3 years ago
  1. 12
      build.sbt
  2. 342
      src/main/resources/static/css/main.css
  3. 7
      src/main/scala/nova/monadic_sfx/Main.scala
  4. 274
      src/main/scala/nova/monadic_sfx/MainApp.scala
  5. 14
      src/main/scala/nova/monadic_sfx/MainModule.scala
  6. 77
      src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
  7. 36
      src/main/scala/nova/monadic_sfx/implicits/MySfxObservableImplicits.scala
  8. 62
      src/main/scala/nova/monadic_sfx/implicits/package.scala
  9. 2
      src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala
  10. 3
      src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
  11. 102
      src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
  12. 10
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
  13. 6
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
  14. 106
      src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
  15. 12
      src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
  16. 21
      src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala
  17. 31
      src/main/scala/nova/monadic_sfx/util/History.scala
  18. 44
      src/main/scala/nova/monadic_sfx/util/SynchedObject.scala
  19. 4
      src/main/scala/nova/monadic_sfx/util/controls/ActionObservable.scala
  20. 2
      src/main/scala/nova/monadic_sfx/util/controls/FontIcon.scala
  21. 5
      src/main/scala/nova/monadic_sfx/util/controls/JFXButton.scala
  22. 2
      src/main/scala/nova/monadic_sfx/util/controls/JFXListCell.scala
  23. 4
      src/main/scala/nova/monadic_sfx/util/controls/JFXListView.scala
  24. 2
      src/main/scala/nova/monadic_sfx/util/controls/JFXSpinner.scala
  25. 2
      src/main/scala/nova/monadic_sfx/util/controls/JFXTextArea.scala
  26. 2
      src/main/scala/nova/monadic_sfx/util/controls/JFXTextField.scala
  27. 2
      src/main/scala/nova/monadic_sfx/util/controls/JFXTreeTableView.scala
  28. 6
      src/main/scala/nova/monadic_sfx/util/controls/MenuItem.scala
  29. 7
      src/main/scala/nova/monadic_sfx/util/reactive/store/Middlewares.scala
  30. 2
      src/main/scala/nova/monadic_sfx/util/reactive/store/MonixProSubject.scala
  31. 2
      src/main/scala/nova/monadic_sfx/util/reactive/store/Reducer.scala
  32. 39
      src/main/scala/nova/monadic_sfx/util/reactive/store/Store.scala
  33. 4
      src/main/scala/nova/monadic_sfx/util/reactive/store/package.scala
  34. 116
      src/test/scala/MutHistoryTest.scala

12
build.sbt

@ -14,8 +14,8 @@ resolvers += Resolver.sonatypeRepo("snapshots")
enablePlugins(JavaFxPlugin)
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-core" % "2.1.1",
"org.typelevel" %% "cats-effect" % "2.1.4",
"org.typelevel" %% "cats-core" % "2.2.0",
"org.typelevel" %% "cats-effect" % "2.2.0",
"io.monix" %% "monix" % "3.3.0",
"io.monix" %% "monix-bio" % "1.1.0",
"io.circe" %% "circe-core" % "0.13.0",
@ -35,14 +35,20 @@ libraryDependencies ++= Seq(
"com.github.valskalla" %% "odin-extras" % "0.9.1",
"com.typesafe.scala-logging" %% "scala-logging" % "3.9.2",
"com.jfoenix" % "jfoenix" % "9.0.10",
"com.lihaoyi" %% "os-lib" % "0.7.1",
"org.kordamp.ikonli" % "ikonli-core" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-javafx" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-fontawesome5-pack" % "12.0.0",
"org.kordamp.ikonli" % "ikonli-material-pack" % "12.0.0",
"org.kordamp.bootstrapfx" % "bootstrapfx-core" % "0.4.0",
"io.github.typhon0" % "AnimateFX" % "1.2.1",
"com.beachape" %% "enumeratum" % "1.6.1",
"com.chuusai" %% "shapeless" % "2.3.3",
"org.gerweck.scalafx" %% "scalafx-utils" % "0.15.0"
"org.gerweck.scalafx" %% "scalafx-utils" % "0.15.0",
"fr.brouillard.oss" % "cssfx" % "11.4.0",
"com.lihaoyi" %% "sourcecode" % "0.2.1",
"eu.timepit" %% "refined" % "0.9.19",
"org.scalatest" %% "scalatest" % "3.2.2" % "test"
)
scalacOptions ++= Seq(

342
src/main/resources/static/css/main.css

@ -0,0 +1,342 @@
.root {
-fx-font-family: Roboto;
src: "/resources/roboto/Roboto-Regular.ttf";
}
/* 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;
}
/* Icons Demo */
.icon {
-fx-text-fill: #FE774D;
-fx-padding: 10;
-fx-cursor: hand;
}
.icons-rippler {
-jfx-rippler-fill: BLUE;
-jfx-mask-type: CIRCLE;
}
.icons-rippler:hover {
-fx-cursor: hand;
}
.jfx-check-box {
-fx-font-weight: BOLD;
}
.custom-jfx-check-box {
-jfx-checked-color: RED;
-jfx-unchecked-color: BLACK;
}
/* Button */
.button {
-fx-padding: 0.7em 0.57em;
-fx-font-size: 14px;
}
.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;
}
/* 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;
}
/* 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;
}
/* 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;
}
.mylistview .scroll-bar .increment-arrow,
.mylistview .scroll-bar .decrement-arrow {
-fx-shape: " ";
-fx-padding: 0;
}
/* 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;
}
.jfx-list-cell-container {
-fx-alignment: center-left;
}
.jfx-list-cell-container > .label {
-fx-text-fill: BLACK;
}
.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);
}
.jfx-list-cell {
-fx-background-insets: 0.0;
-fx-text-fill: BLACK;
}
.jfx-list-cell:odd, .jfx-list-cell:even {
-fx-background-color: WHITE;
}
.jfx-list-cell:filled:hover {
-fx-text-fill: black;
}
.jfx-list-cell .jfx-rippler {
-jfx-rippler-fill: BLUE;
}
.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;
}

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

@ -3,6 +3,7 @@ package nova.monadic_sfx
import _root_.monix.bio.BIOApp
import _root_.monix.bio.Task
import _root_.monix.bio.UIO
import _root_.monix.execution.Scheduler
import cats.effect.ExitCode
import cats.effect.Resource
import com.softwaremill.macwire._
@ -11,11 +12,15 @@ import nova.monadic_sfx.executors._
// import nova.monadic_sfx.util.IOUtils._
// import sttp.client.httpclient.monix.HttpClientMonixBackend
object Main extends MainModule with BIOApp {
lazy val schedulers = new Schedulers()
override def scheduler: Scheduler = schedulers.async
def appResource(startTime: Long) =
for {
implicit0(logger: Logger[Task]) <- makeLogger
schedulers = new Schedulers()
// backend and actorsystem are for future use
// backend <- Resource.make(
// toIO(HttpClientMonixBackend()(schedulers.async))
// )(c => toIO(c.close()))

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

@ -1,29 +1,43 @@
package nova.monadic_sfx
import java.util.concurrent.TimeUnit
import scala.util.Random
import com.jfoenix.controls.JFXDialog
import com.softwaremill.macwire._
import io.odin.Logger
import monix.bio.IO
import monix.bio.Task
import monix.eval.Coeval
import monix.{eval => me}
import nova.monadic_sfx.executors.Schedulers
import nova.monadic_sfx.implicits.JFXButton
import nova.monadic_sfx.implicits.JavaFXMonixObservables._
import nova.monadic_sfx.implicits._
import nova.monadic_sfx.ui.MyFxApp
import nova.monadic_sfx.ui.components.router.BrainNotWorking
import nova.monadic_sfx.ui.components.router.FXRouter
import nova.monadic_sfx.ui.components.router.Page
import nova.monadic_sfx.ui.components.todo.TodoListStore
import nova.monadic_sfx.ui.components.todo.TodoListView
import nova.monadic_sfx.util.MutHistory
import nova.monadic_sfx.util.controls.JFXButton
import org.gerweck.scalafx.util._
import org.kordamp.bootstrapfx.BootstrapFX
import scalafx.Includes._
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.ObjectProperty
import scalafx.beans.property.StringProperty
import scalafx.collections.ObservableBuffer
import scalafx.geometry.Insets
import scalafx.geometry.Pos
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.layout.BorderPane
import scalafx.scene.layout.HBox
import scalafx.scene.paint.Color
import scalafx.scene.shape.Rectangle
import scalafx.scene.layout.Priority
import scalafx.scene.layout.StackPane
class MainApp(
// spawnProtocol: ActorSystem[SpawnProtocol.Command],
@ -31,32 +45,14 @@ class MainApp(
startTime: Long
)(implicit logger: Logger[Task]) {
lazy val addTodoButton = new JFXButton {
text = "Add"
}
lazy val addTodoObs = addTodoButton.observableAction
// lazy val todoListView = TodoListView.defaultListView
lazy val _scene = new Scene {
private lazy val _scene = new Scene {
root = new HBox {
padding = Insets(20)
content = new Rectangle {
width = 400
height = 200
fill = Color.DeepSkyBlue
}
children ++= Seq(
// new JFXButton {
// text = "DummyButton"
// },
// new JFXButton {
// text = "DummyButton2"
// },
// addTodoButton,
// Test.ttv
// todoListView
// style = """| -fx-background-color: rgb(38, 38, 38);
// | -fx-text-fill: white;""".stripMargin
stylesheets ++= Seq(
BootstrapFX.bootstrapFXStylesheet,
os.rel / "static" / "css" / "main.css"
)
}
}
@ -64,103 +60,153 @@ class MainApp(
private lazy val stage = new PrimaryStage {
title = "Simple ScalaFX App"
scene = _scene
width = 1000
height = 400
width = 640
height = 480
// resizable = false
}
// implicit val l = logger
// implicit val sp = spawnProtocol
val program = for {
(fxApp, fxAppFib) <- wire[MyFxApp].init(stage)
// _ <- Task(fxApp.stage = stage)
// .executeOn(schedulers.fx)
// .delayExecution(2000.millis)
// todoComponent <- createTodoComponent
// _ <- toIO(
// addTodoObs
// .mapEval(_ =>
// toTask(todoComponent.send(TodoListComponent.Add(Todo(1, "blah"))))
// )
// .completedL
// .executeOn(schedulers.fx)
// .startAndForget
// )
_ <- createTodoComponent.executeOn(schedulers.fx)
router <- Task(BrainNotWorking.router)
routerStore <- router.store(BrainNotWorking.Page.Home, logger)
routerNode <- for {
node <-
Task
.deferAction(implicit s =>
Task(new HBox {
children <-- router
.render(BrainNotWorking.resolver)(routerStore)
.map(_.delegate)
})
)
.executeOn(schedulers.fx)
_ <- Task.deferFuture(
routerStore.onNext(FXRouter.Replace(BrainNotWorking.Page.UserHome(1)))
)
} yield node
// _ <-
// BrainNotWorking
// .routerTask(logger)
// .flatMap(node => Task(_scene.getChildren += node))
// .executeOn(schedulers.fx)
_ <- Task(_scene.getChildren += routerNode).executeOn(schedulers.fx)
_ <-
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 ${(System.currentTimeMillis() - startTime) / 1000f} seconds"
s"Application started in ${(currentTime - startTime) / 1000f} seconds"
)
// _ <- Task(CSSFX.start(stage))
_ <- fxAppFib.join
} yield ()
// def createTodoComponent: Task[TodoListComponent] = {
// for {
// channel <-
// ConcurrentChannel
// .of[Task, TodoListComponent.Complete, TodoListComponent.Command]
// scheduler = schedulers.fx
// lv <- TodoListView.defaultListView2.executeOn(scheduler)
// // todoLV = new TodoListView(lv)
// todoComponent <- wire[TodoListComponent.Props].create
// // TODO make this a "message pass" instead of mutating directly
// _ <- Task(_scene.getChildren += lv).executeOn(scheduler)
// // _ <- toIO(
// // delObs
// // .doOnNext(_ => toTask(logger.debug("Pressed delete")))
// // .doOnNext(todo =>
// // toTask(
// // for {
// // _ <- logger.debug(s"Got todo $todo")
// // _ <- todoComponent.send(TodoListComponent.Delete(todo.id))
// // // _ <- Task.sequence(
// // // lst.map(todo =>
// // // todoComponent.send(TodoListComponent.Delete(todo.id))
// // // )
// // // )
// // } yield ()
// // )
// // )
// // .completedL
// // ).startAndForget
// // _ <- toIO(
// // editObs
// // .doOnNext(_ => toTask(logger.debug("Pressed edit")))
// // .completedL
// // ).startAndForget
// } yield todoComponent
// }
}
def createTodoComponent: Task[Unit] =
class MainAppDelegate(schedulers: Schedulers)(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 init =
for {
store <- TodoListStore(logger)
rootNode <- TodoListView(store)
_ <- Task(_scene.getChildren += rootNode)
} yield ()
//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"
})
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 =>
hgrow = 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))
}
},
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"))
padding = Insets(20)
})
d.styleClass ++= Seq("text-white")
onAction = () => d.show(root)
}
)
}
}
})
)
} yield mainSceneNode
}
class TestModel(_name: String, _age: Int) {

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

@ -10,32 +10,32 @@ import io.odin.syntax._
import nova.monadic_sfx.actors.ActorModule
import nova.monadic_sfx.http.HttpModule
import nova.monadic_sfx.ui.UiModule
import nova.monadic_sfx.util.reactive.Middlewares
import nova.monadic_sfx.util.reactive.store.Middlewares
trait MainModule extends ActorModule with UiModule with HttpModule {
def routerLogger(defaultLogger: Logger[Task], storeLogger: Logger[Task]) =
enclosureRouting[Task](
"nova.monadic_sfx.util.reactive.Middlewares" -> storeLogger,
"nova.monadic_sfx.util.reactive.Store" -> storeLogger
"nova.monadic_sfx.util.reactive.store.Middlewares" -> storeLogger,
"nova.monadic_sfx.util.reactive.store.Store" -> storeLogger
)
.withFallback(defaultLogger)
.withAsync()
.withAsync(timeWindow = 1.millis)
def makeLogger =
for {
defaultLogger <- consoleLogger[Task]()
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
"application.log"
).withAsync()
).withAsync(timeWindow = 1.millis)
middlewareLogger <-
consoleLogger[
Task
](formatter = Middlewares.format)
.withMinimalLevel(Level.Trace)
.withAsync() |+| fileLogger[Task](
.withAsync(timeWindow = 1.millis) |+| fileLogger[Task](
"stores.log",
formatter = Middlewares.format
).withAsync()
).withAsync(timeWindow = 1.millis)
routerLogger <- routerLogger(defaultLogger, middlewareLogger)
} yield (routerLogger)
}

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

@ -11,6 +11,7 @@ import monix.eval.Coeval
import monix.execution.Ack
import monix.execution.Cancelable
import monix.execution.Scheduler
import monix.execution.cancelables.CompositeCancelable
import monix.execution.cancelables.SingleAssignCancelable
import monix.reactive.Observable
import monix.reactive.Observer
@ -27,10 +28,38 @@ import scalafx.scene.Scene
import scalafx.scene.control.ButtonBase
import scalafx.scene.control.MenuItem
trait JavaFXMonixObservables {
import JavaFXMonixObservables._
implicit def extendedScene(scene: Scene) = new SceneExt(scene)
implicit def extendedProperty[T, J](
propery: Property[T, J]
): PropertyExt[T, J] =
new PropertyExt(propery)
implicit def extendedObjectPropety[A](prop: ObjectProperty[A]) =
new ObjectPropertyExt[A](prop)
implicit def extendedReadOnlyObjectPropety[T, J](
prop: ReadOnlyProperty[T, J]
) =
new ReadOnlyPropertyExt[T, J](prop)
implicit def extendedObservableList[A](
list: ObservableList[A]
) = new ObservableListExt(list)
implicit def extendedStringObservableList(
list: ObservableList[String]
) = new StringObservableListExt(list)
implicit def extendedObjectPropertyObservableList[A](
prop: ObjectProperty[ObservableList[A]]
) = new ObjectPropertyObservableListExt(prop)
implicit def extendedButton(button: ButtonBase) = new ButtonBaseExt(button)
implicit def extendedMenuItem(item: MenuItem) = new MenuItemExt(item)
// implicit val implShowForOsRelPath = Show.fromToString[os.Path]
implicit def osRelPath2String(path: os.RelPath): String = path.toString()
}
object JavaFXMonixObservables {
implicit final class SceneObservables(private val scene: Scene)
extends AnyVal {
final class SceneExt(private val scene: Scene) extends AnyVal {
def observableMousePressed(): Observable[jfxsi.MouseEvent] = {
import monix.execution.cancelables.SingleAssignCancelable
@ -75,18 +104,20 @@ object JavaFXMonixObservables {
}
}
implicit final class BindObs[T, J](private val prop: Property[T, J])
final class PropertyExt[T, J](private val prop: Property[T, J])
extends AnyVal {
def -->(op: Observer[T]) = {
op.onNext(prop.value)
def -->[J1 >: J](sub: Observer[J1]) = {
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
}
def ==>(op: Property[T, J]) = {
op <== prop
}
def <--(obs: Observable[T])(implicit s: Scheduler) = {
obs.doOnNextF(v => Coeval(prop.value = v)).subscribe()
def <--(
obs: Observable[_ <: T]
)(implicit s: Scheduler, c: CompositeCancelable): Unit = {
c += obs.doOnNextF(v => Coeval(prop.value = v)).subscribe()
}
def asOption = prop.map(Option(_))
@ -107,13 +138,13 @@ object JavaFXMonixObservables {
}
}
implicit final class BindObs2[A](private val prop: ObjectProperty[A])
final class ObjectPropertyExt[A](private val prop: ObjectProperty[A])
extends AnyVal {
def -->(sub: Observer[A]) =
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
def -->(op: Property[A, A]) = {
def ==>(op: Property[A, A]) = {
prop.onChange((a, b, c) => if (c != null) op() = c)
}
@ -136,7 +167,7 @@ object JavaFXMonixObservables {
}
}
implicit final class ObservableListExt[A](
final class ObservableListExt[A](
private val buffer: ObservableList[A]
) extends AnyVal {
@ -189,10 +220,20 @@ object JavaFXMonixObservables {
} else Task.unit
}
implicit final class BindObs3[T, J](private val prop: ReadOnlyProperty[T, J])
extends AnyVal {
def -->(op: Observer[T]) = {
op.onNext(prop.value)
final class StringObservableListExt(
private val buffer: ObservableList[String]
) extends AnyVal {
// def ++=[T](that: Seq[T])(implicit S: Show[T]): Unit =
// buffer ++= that.map(S.show)
// def ++=[T](that: Seq[T])(implicit C: CssPath[T]): Unit =
// buffer ++= that.map(C.path)
}
final class ReadOnlyPropertyExt[T, J](
private val prop: ReadOnlyProperty[T, J]
) extends AnyVal {
def -->[J1 >: J](sub: Observer[J1]) = {
prop.onChange((a, b, c) => if (c != null) sub.onNext(c))
}
def ==>(op: Property[T, J]) = {
@ -214,7 +255,7 @@ object JavaFXMonixObservables {
}
}
implicit final class ObjectPropertyObservableListExt[A](
final class ObjectPropertyObservableListExt[A](
private val prop: ObjectProperty[ObservableList[A]]
) extends AnyVal {
def <--(obs: Observable[Seq[A]])(implicit s: Scheduler) = {
@ -259,7 +300,7 @@ object JavaFXMonixObservables {
}
implicit final class ObjectPropertyActionEvent(
final class ObjectPropertyActionEvent(
private val prop: ObjectProperty[EventHandler[ActionEvent]]
) extends AnyVal {
// def <--(obs: Observable[ActionEvent])(implicit s: Scheduler) = {
@ -273,7 +314,7 @@ object JavaFXMonixObservables {
// def emit(prop: ObjectProperty[EventHandler[ActionEvent]]) =
implicit final class OnActionObservable(
final class ButtonBaseExt(
private val button: ButtonBase
) extends AnyVal {
@ -299,7 +340,7 @@ object JavaFXMonixObservables {
}
}
implicit final class MenuItemActionObservable(
final class MenuItemExt(
private val item: MenuItem
) extends AnyVal {

36
src/main/scala/nova/monadic_sfx/util/Misc.scala → src/main/scala/nova/monadic_sfx/implicits/MySfxObservableImplicits.scala

@ -1,21 +1,42 @@
package nova.monadic_sfx.util
package nova.monadic_sfx.implicits
import scalafx.beans.property.ObjectProperty
import scalafx.beans.property.ReadOnlyObjectProperty
import scalafx.beans.value.ObservableValue
object Misc {
/**
* experimental implicits to be incorporated better later
*/
trait MySfxObservableImplicits {
import nova.monadic_sfx.implicits.ExtraObservableImplicits._
implicit def myRichObservable[A, C](
observable: ObservableValue[A, C]
): MyRichObservable[A, C] = new MyRichObservable(observable)
}
object ExtraObservableImplicits {
implicit final class MyRichObservable[A, C](val self: ObservableValue[A, C])
final class MyRichObservable[A, C](val self: ObservableValue[A, C])
extends AnyVal {
def filter(f: A => Boolean): ReadOnlyObjectProperty[A] =
Method.filter(self)(f)
def filterNull: ReadOnlyObjectProperty[A] = Method.filterNull(self)
Methods.filter(self)(f)
/**
* Simply creates a new observable that mirrors the source observable but
* doesn't emit null values. JavaFX likes to work with null values in scene
* nodes/properties (shrugs) and observables by default emit null values
* that can cause crashes if you forget to null check. ScalaFX does not
* offer any *fixes* for this.
*/
def filterNull: ReadOnlyObjectProperty[A] = Methods.filterNull(self)
}
}
object Method {
object Types {
type Observable[A] = ObservableValue[A, _]
}
object Methods {
import Types._
def filter[B](
a: Observable[B]
@ -38,7 +59,8 @@ object Method {
* Simply creates a new observable that mirrors the source observable but
* doesn't emit null values. JavaFX likes to work with null values in scene
* nodes/properties (shrugs) and observables by default emit null values
* that can cause crashes. ScalaFX does not offer any *fixes* for this
* that can cause crashes if you forget to null check. ScalaFX does not
* offer any *fixes* for this.
*
* @param a
* @return

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

@ -1,50 +1,20 @@
package nova.monadic_sfx
import javafx.event.ActionEvent
import monix.execution.Ack
import monix.execution.Cancelable
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import scalafx.scene.control._
package object implicits
extends MySfxObservableImplicits
with JavaFXMonixObservables
package object implicits {
// implicit class NodeExt(val node: Node) {
// def lookup2[T <: SFXDelegate[_]](
// selector: String
// )(implicit c: ClassTag[T]) = {
// val t = c.runtimeClass
// Option(node.delegate.lookup(selector)) match {
// case Some(value) =>
// if (value.getClass == t) Some(value) else None
// case None => None
// }
// }
implicit class MyButtonExt(val button: Button) extends AnyVal {
def observableAction(): Observable[ActionEvent] = {
import monix.execution.cancelables.SingleAssignCancelable
Observable.create(OverflowStrategy.Unbounded) { sub =>
val c = SingleAssignCancelable()
val l = new javafx.event.EventHandler[ActionEvent] {
override def handle(event: ActionEvent): Unit = {
if (sub.onNext(event) == Ack.Stop) c.cancel()
}
}
button.onAction = l
c := Cancelable(() =>
button.removeEventHandler(
ActionEvent.ACTION,
l
)
)
c
}
}
}
// implicit class NodeExt(val node: Node) {
// def lookup2[T <: SFXDelegate[_]](
// selector: String
// )(implicit c: ClassTag[T]) = {
// val t = c.runtimeClass
// Option(node.delegate.lookup(selector)) match {
// case Some(value) =>
// if (value.getClass == t) Some(value) else None
// case None => None
// }
// }
// val x = node.lookup2("")
// }
}
// val x = node.lookup2("")
// }

2
src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala

@ -1,6 +1,6 @@
package nova.monadic_sfx.ui
import nova.monadic_sfx.implicits.JFXSpinner
import nova.monadic_sfx.util.controls.JFXSpinner
import scalafx.geometry.Insets
import scalafx.geometry.Pos
import scalafx.scene.Scene

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

@ -31,7 +31,8 @@ class MyFxApp(val schedulers: Schedulers)(implicit logger: Logger[Task]) {
def init(stage: => PrimaryStage, delay: FiniteDuration = 200.millis) =
for {
_ <- logger.info("Starting FX App")
fib <- Task(internal.main(Array.empty)).start
fib <-
Task(internal.main(Array.empty)).start.executeOn(schedulers.blocking)
_ <- Task.sleep(200.millis)
_ <- Task(internal.stage = stage)
.executeOn(schedulers.fx)

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

@ -1,47 +1,54 @@
package nova.monadic_sfx.ui.components.router
import enumeratum._
import io.circe.Codec
import io.circe.Decoder
import io.circe.Encoder
import io.circe.generic.JsonCodec
import io.circe.generic.semiauto._
import io.odin.Logger
import monix.bio.Task
import nova.monadic_sfx.util.IOUtils
import nova.monadic_sfx.util.reactive.Reducer
import nova.monadic_sfx.util.reactive.Store
import nova.monadic_sfx.util.reactive.store.Middlewares
import nova.monadic_sfx.util.reactive.store.Reducer
import nova.monadic_sfx.util.reactive.store.Store
import scalafx.scene.Parent
import scalafx.scene.control.Label
import monix.reactive.Observable
import nova.monadic_sfx.util.controls.JFXSpinner
import scala.concurrent.duration._
import monix.eval.Coeval
object FXRouter {
final case class State[P](page: P)
@JsonCodec
sealed abstract class Action[T]
// final case object Init extends Action
final case class Replace[T](p: T) extends Action[T]
sealed abstract class Action[+T]
final case class Replace[T](page: T) extends Action[T]
type FXStore[P] = Store[Action[P], State[P]]
// def resolver2 = resolver.lift.andThen(_.getOrElse(notFound))
object Action {
implicit def codec[T: Encoder: Decoder]: Codec[Action[T]] = deriveCodec
}
// def resolver: PartialFunction[P <: Enum[P]][P, Parent] = {
// case Home => new TextField
// }
type FXStore[P] = Store[Action[P], State[P]]
}
class FXRouter[P <: EnumEntry](
)(implicit E: Encoder[P]) {
class FXRouter[P]()(implicit E: Encoder[P], D: Decoder[P]) {
import FXRouter._
def store(initialPage: P, logger: Logger[Task]) =
def store(initialPage: P, logger: Logger[Task]): Task[FXStore[P]] =
Task.deferAction(implicit s =>
Store.createL[Action[P], State[P]](
Replace(initialPage),
State(initialPage),
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _)
// Seq(actionLoggerMiddleware(logger, "RouterStore"))
)
for {
mw <- Middlewares.actionLoggerMiddleware[Action[P], State[P]](
logger,
"RouterStore"
)
store <- Store.createL[Action[P], State[P]](
Replace(initialPage),
State(initialPage),
Reducer.withOptionalEffects[Task, Action[P], State[P]](reducer _),
Seq(mw)
)
} yield store
)
def reducer(
@ -55,9 +62,26 @@ class FXRouter[P <: EnumEntry](
}
def render(
resolver: P => Task[Parent]
resolver: P => Task[Parent],
transitionDelay: FiniteDuration = 500.millis
)(implicit store: FXStore[P]) =
store.mapEval { case (_, FXRouter.State(p)) => IOUtils.toTask(resolver(p)) }
store
.flatMap {
case (_, FXRouter.State(p)) =>
Observable.from(Coeval(new JFXSpinner)) ++ Observable.from(
IOUtils.toTask(
Task
.racePair(
Task.sleep(transitionDelay),
resolver(p)
)
.flatMap {
case Left(_ -> fib) => fib.join
case Right(fib -> res) => fib.join >> Task.pure(res)
}
)
)
}
def link(
page: P,
@ -67,28 +91,12 @@ class FXRouter[P <: EnumEntry](
}
}
object BrainNotWorking {
@JsonCodec
sealed trait Page extends EnumEntry
object Page extends Enum[Page] {
val values = findValues
final case object Home extends Page
final case class UserHome(id: Int) extends Page
}
def resolver: PartialFunction[Page, Task[Parent]] = {
case Page.Home =>
Task(new Label {
text = "HomePage"
})
case Page.UserHome(id0) =>
Task(new Label {
text = s"User Home, Id = $id0"
})
}
val router = new FXRouter[Page]
@JsonCodec
sealed trait Page
object Page {
final case object Home extends Page
final case class UserHome(id: Int) extends Page
final case object Todo extends Page
}
// case class State()

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

@ -10,11 +10,11 @@ import monix.catnap.ConsumerF
import monix.execution.Scheduler
import monix.reactive.Observable
import monix.reactive.Observer
import nova.monadic_sfx.implicits.FontIcon
import nova.monadic_sfx.implicits.IconLiteral
import nova.monadic_sfx.implicits.JFXListView
import nova.monadic_sfx.implicits.JavaFXMonixObservables._
import nova.monadic_sfx.util.reactive._
import nova.monadic_sfx.util.controls.FontIcon
import nova.monadic_sfx.util.controls.IconLiteral
import nova.monadic_sfx.util.controls.JFXListView
import nova.monadic_sfx.implicits._
import nova.monadic_sfx.util.reactive.store._
import scalafx.Includes._
import scalafx.beans.property.StringProperty
import scalafx.collections.ObservableBuffer

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

@ -4,9 +4,9 @@ import com.softwaremill.quicklens._
import io.circe.generic.JsonCodec
import io.odin.Logger
import monix.bio.Task
import nova.monadic_sfx.util.reactive.Middlewares.actionLoggerMiddleware
import nova.monadic_sfx.util.reactive.Reducer
import nova.monadic_sfx.util.reactive.Store
import nova.monadic_sfx.util.reactive.store.Middlewares.actionLoggerMiddleware
import nova.monadic_sfx.util.reactive.store.Reducer
import nova.monadic_sfx.util.reactive.store.Store
case class Todo(id: Int, content: String)

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

@ -3,51 +3,48 @@ package nova.monadic_sfx.ui.components.todo
import monix.bio.Task
import monix.execution.cancelables.CompositeCancelable
import monix.{eval => me}
import nova.monadic_sfx.implicits.FontIcon
import nova.monadic_sfx.implicits.IconLiteral
import nova.monadic_sfx.implicits.JFXButton
import nova.monadic_sfx.implicits.JFXListView
import nova.monadic_sfx.implicits.JFXTextField
import nova.monadic_sfx.implicits.JavaFXMonixObservables._
import nova.monadic_sfx.implicits.MenuItem
import nova.monadic_sfx.util.reactive._
import nova.monadic_sfx.util.controls.FontIcon
import nova.monadic_sfx.util.controls.IconLiteral
import nova.monadic_sfx.util.controls.JFXButton
import nova.monadic_sfx.util.controls.JFXListView
import nova.monadic_sfx.util.controls.JFXTextField
import nova.monadic_sfx.util.controls.MenuItem
import nova.monadic_sfx.implicits._
import nova.monadic_sfx.util.reactive.store._
import org.gerweck.scalafx.util._
import scalafx.Includes._
import scalafx.beans.property.ObjectProperty
import scalafx.beans.property.StringProperty
import scalafx.geometry.Insets
import scalafx.scene.Node
import scalafx.scene.Parent
import scalafx.scene.control.ContextMenu
import scalafx.scene.control.ListCell
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.Priority
object TodoListView {
def apply(
store: MonixProSubject[
TodoListStore.Action,
(TodoListStore.Action, TodoListStore.State)
]
): Task[Node] =
store: Store[TodoListStore.Action, TodoListStore.State]
): Task[Parent] =
Task.deferAction(implicit s =>
Task {
val cc = CompositeCancelable()
val todos =
store.map { case (_, state) => state.todos }
// Todo(-1, "").some
val todos = store.map { case (_, state) => state.todos }
val _selectedItems = ObjectProperty(Seq.empty[Todo])
new HBox {
new BorderPane {
padding = Insets(5)
val _content = StringProperty("")
children = Seq(
new JFXTextField {
text ==> _content
},
new JFXListView[Todo] {
center = new HBox {
padding = Insets(5)
children ++= Seq(new JFXListView[Todo] {
hgrow = Priority.Always
def selectedItems = selectionModel().selectedItems.view
styleClass ++= Seq("text-white")
selectionModel().selectionMode = SelectionMode.Multiple
selectionModel().selectedItems.observableSeqValue ==> _selectedItems
@ -83,10 +80,6 @@ object TodoListView {
contextMenu = new ContextMenu {
items ++= Seq(
new MenuItem {
text = "Add"
// obsAction.useLazyEval(TodoListStore.Add("blah3")) --> store
},
new MenuItem {
text = "Delete"
obsAction.useIterableEval(_ =>
@ -106,28 +99,43 @@ object TodoListView {
}
)
}
},
new JFXButton {
text = "Add"
// disable <== _selectedItems.map(_.length > 0)
obsAction
.useLazyEval(me.Task(TodoListStore.Add(_content()))) --> store
},
new JFXButton {
text = "Edit"
disable <== _selectedItems.map(_.length > 1)
obsAction.useLazyEval(
me.Task(
TodoListStore.Edit(
_selectedItems
.map(_.headOption.map(_.id).getOrElse(-1))
.value,
_content()
})
}
bottom = new HBox {
spacing = 5
padding = Insets(5)
children = Seq(
new JFXTextField {
text ==> _content
},
new JFXButton {
text = "Add"
alignment = Pos.Center
// disable <== _selectedItems.map(_.length > 0)
styleClass = Seq("btn", "btn-primary")
obsAction
.useLazyEval(me.Task(TodoListStore.Add(_content()))) --> store
},
new JFXButton {
text = "Edit"
alignment = Pos.Center
disable <== _selectedItems.map(_.length > 1)
styleClass = Seq("btn", "btn-info")
style = ""
obsAction.useLazyEval(
me.Task(
TodoListStore.Edit(
_selectedItems
.map(_.headOption.map(_.id).getOrElse(-1))
.value,
_content()
)
)
)
) --> store
}
)
) --> store
}
)
}
}
}
)

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

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

21
src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala

@ -19,18 +19,17 @@ class HomeScreen(
// onAction = () => Action.asyncT(onLogout())
}
val myObs = myButton.observableAction()
val myObs = myButton.observableAction
// myObs.foreachL(_ => ())
private lazy val root = Task.deferAction { implicit s =>
Task {
new HBox {
children = List(
new Text {
text = "hello"
},
myButton
)
}
private lazy val root = Task {
new HBox {
children = List(
new Text {
text = "hello"
},
myButton
)
}
}

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

@ -0,0 +1,31 @@
package nova.monadic_sfx.util
class MutHistory[T](initValue: T) {
private var _values = Vector(initValue)
def values = _values
private var _sp = 0
def sp = _sp
// def current = if (ints.isEmpty) None else Some(ints(sp))
def current = _values(_sp)
def push(v: T) = {
if (_sp < _values.length - 1) {
_values = _values.splitAt(_sp)._1
_values = _values :+ v
} else {
_values = _values :+ v
_sp = _values.length - 1
}
}
def forward() = {
if (!_values.isEmpty && _sp < _values.length - 1) _sp += 1
}
def backward() = {
if (_sp > 0) {
_sp -= 1
}
}
}

44
src/main/scala/nova/monadic_sfx/util/SynchedObject.scala

@ -0,0 +1,44 @@
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
}

4
src/main/scala/nova/monadic_sfx/implicits/ActionObservable.scala → src/main/scala/nova/monadic_sfx/util/controls/ActionObservable.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import monix.execution.Cancelable
import monix.execution.Scheduler
@ -12,7 +12,7 @@ class ActionObservableExecutor[T](
) extends AnyVal {
def -->(sub: Observer[T])(implicit s: Scheduler) =
delegate
.doOnNext(el => me.Task(sub.onNext(el)))
.doOnNext(el => me.Task.deferFuture(sub.onNext(el)) >> me.Task.unit)
.subscribe()
}

2
src/main/scala/nova/monadic_sfx/implicits/FontIcon.scala → src/main/scala/nova/monadic_sfx/util/controls/FontIcon.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import javafx.{scene => jfxs}
import org.kordamp.ikonli.{javafx => ikonlifx}

5
src/main/scala/nova/monadic_sfx/implicits/JFXButton.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXButton.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import javafx.{scene => jfxs}
@ -6,6 +6,7 @@ import scalafx.Includes._
import scalafx.beans.property.ObjectProperty
import scalafx.scene.Node
import scalafx.scene.control.Button
import nova.monadic_sfx.implicits._
import jfxs.{paint => jfxsp}
@ -37,6 +38,6 @@ class JFXButton(
}
def obsAction =
new ActionObservableBuilder(this.observableAction())
new ActionObservableBuilder(this.observableAction)
}

2
src/main/scala/nova/monadic_sfx/implicits/JFXListCell.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXListCell.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import javafx.scene.{control => jfxsc}

4
src/main/scala/nova/monadic_sfx/implicits/JFXListView.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXListView.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import monix.execution.Scheduler
@ -43,6 +43,4 @@ class JFXListView[T](
def expanded = delegate.expandedProperty()
def expanded_=(v: Boolean) = expanded() = v
}

2
src/main/scala/nova/monadic_sfx/implicits/JFXSpinner.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXSpinner.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.scene.control.ProgressIndicator

2
src/main/scala/nova/monadic_sfx/implicits/JFXTextArea.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXTextArea.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.Includes._

2
src/main/scala/nova/monadic_sfx/implicits/JFXTextField.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXTextField.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.{controls => jfoenixc}
import scalafx.Includes._

2
src/main/scala/nova/monadic_sfx/implicits/JFXTreeTableView.scala → src/main/scala/nova/monadic_sfx/util/controls/JFXTreeTableView.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import com.jfoenix.controls.datamodels.treetable.RecursiveTreeObject
import com.jfoenix.{controls => jfoenixc}

6
src/main/scala/nova/monadic_sfx/implicits/MenuItem.scala → src/main/scala/nova/monadic_sfx/util/controls/MenuItem.scala

@ -1,9 +1,7 @@
package nova.monadic_sfx.implicits
package nova.monadic_sfx.util.controls
import nova.monadic_sfx.implicits._
import scalafx.scene.{control => sfxc}
import JavaFXMonixObservables._
class MenuItem extends sfxc.MenuItem {
def obsAction = new ActionObservableBuilder(this.observableAction)
}

7
src/main/scala/nova/monadic_sfx/util/reactive/Middlewares.scala → src/main/scala/nova/monadic_sfx/util/reactive/store/Middlewares.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.util.reactive
package nova.monadic_sfx.util.reactive.store
import java.time.LocalDateTime
@ -21,7 +21,7 @@ import monix.reactive.Observable
final case class StoreInfo[A](
name: String,
action: A,
time: LocalDateTime = LocalDateTime.now()
time: LocalDateTime
)
object StoreInfo {
@ -68,7 +68,8 @@ object Middlewares {
Task((obs: Observable[(A, M)]) =>
obs.doOnNextF {
case (a, _) =>
logger.debug(StoreInfo(name, a))
Task(LocalDateTime.now())
.flatMap(curTime => logger.debug(StoreInfo(name, a, curTime)))
}
)
)

2
src/main/scala/nova/monadic_sfx/util/reactive/MonixProSubject.scala → src/main/scala/nova/monadic_sfx/util/reactive/store/MonixProSubject.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.util.reactive
package nova.monadic_sfx.util.reactive.store
import scala.concurrent.Future

2
src/main/scala/nova/monadic_sfx/util/reactive/Reducer.scala → src/main/scala/nova/monadic_sfx/util/reactive/store/Reducer.scala

@ -1,4 +1,4 @@
package nova.monadic_sfx.util.reactive
package nova.monadic_sfx.util.reactive.store
import cats.implicits._
import monix.reactive.Observable

39
src/main/scala/nova/monadic_sfx/util/reactive/Store.scala → src/main/scala/nova/monadic_sfx/util/reactive/store/Store.scala

@ -1,8 +1,12 @@
package nova.monadic_sfx.util.reactive
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.eval.Coeval
import monix.reactive.Observable
import monix.reactive.OverflowStrategy
import monix.reactive.subjects.ConcurrentSubject
@ -19,20 +23,25 @@ object Store {
Task {
val subject = ConcurrentSubject.publish[A](overflowStrategy)
val fold: ((A, M), A) => (A, M) = {
case ((_, state), action) => {
val (newState, effects) = reducer(state, action)
val fold: ((A, M), A) => Coeval[(A, M)] = {
case ((_, state), action) =>
Coeval {
val (newState, effects) = reducer(state, action)
effects.subscribe(subject.onNext _)
effects.subscribe(subject.onNext _)
action -> newState
}
action -> newState
}
}
val obs = subject
.scan[(A, M)](initialAction -> initialState)(fold)
.behavior(initialAction -> initialState)
.refCount
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)
@ -72,9 +81,11 @@ object Store {
val obs = subject
.doOnNextF(action =>
logger.debug(
StoreInfo(storeName, action)
) // .executeOn(Scheduler.global)
Task(LocalDateTime.now()).flatMap(curTime =>
logger.debug(
StoreInfo(storeName, action, curTime)
)
)
)
// .doOnNextF(action => Coeval(println(action)))
.scanEvalF[Task, (A, M)](Task.pure(initialAction -> initialState))(

4
src/main/scala/nova/monadic_sfx/util/reactive/package.scala → src/main/scala/nova/monadic_sfx/util/reactive/store/package.scala

@ -1,9 +1,9 @@
package nova.monadic_sfx.util
package nova.monadic_sfx.util.reactive
import monix.reactive.Observable
import monix.reactive.Observer
package object reactive {
package object store {
type MonixProSubject[-I, +O] = Observable[O] with Observer[I]
type Middleware[A, M] = Observable[(A, M)] => Observable[(A, M)]
type Store[A, M] = MonixProSubject[A, (A, M)]

116
src/test/scala/MutHistoryTest.scala

@ -0,0 +1,116 @@
import org.scalatest.funsuite.AnyFunSuite
import nova.monadic_sfx.util.MutHistory
import com.typesafe.scalalogging.LazyLogging
class MutHistoryTest extends AnyFunSuite with LazyLogging {
val h = new MutHistory(0)
test("init") {
assert(h.values == Vector(0))
assert(h.sp == 0)
assert(h.current == 0)
}
test("push 1") {
h.push(1)
// logger.debug(mutHistory.ints.toString)
assert(h.values == Vector(0, 1))
assert(h.values.length - 1 == h.sp)
assert(h.current == 1)
}
test("push 2") {
h.push(2)
// logger.debug(mutHistory.ints.toString)
assert(h.values == Vector(0, 1, 2))
assert(h.values.length - 1 == h.sp)
assert(h.current == 2)
}
test("first forward") {
// logger.debug(mutHistory.ints.toString)
// logger.debug(mutHistory.sp.toString)
h.forward()
assert(h.values == Vector(0, 1, 2))
assert(h.sp == 2)
assert(h.current == 2)
}
test("second forward") {
// logger.debug(mutHistory.ints.toString)
// logger.debug(mutHistory.sp.toString)
h.forward()
assert(h.values == Vector(0, 1, 2))
assert(h.sp == 2)
assert(h.current == 2)
}
test("first backward") {
h.backward()
assert(h.values == Vector(0, 1, 2))
assert(h.sp == 1)
assert(h.current == 1)
}
test("second backward") {
h.backward()
assert(h.values == Vector(0, 1, 2))
assert(h.sp == 0)
assert(h.current == 0)
}
test("third backward") {
h.backward()
assert(h.values == Vector(0, 1, 2))
assert(h.sp == 0)
assert(h.current == 0)
}
test("push 3") {
h.push(3)
assert(h.sp == 0)
assert(h.values == Vector(3))
assert(h.current == 3)
}
test("fourth backward") {
h.backward()
assert(h.values == Vector(3))
assert(h.sp == 0)
assert(h.current == 3)
}
test("lastly") {
h.push(4)
h.push(5)
h.push(6)
h.push(7)
h.push(8)
assert(h.values == Vector(3, 4, 5, 6, 7, 8))
assert(h.sp == 5)
assert(h.current == 8)
h.backward()
h.backward()
assert(h.current == 6)
assert(h.sp == 3)
h.push(9)
assert(h.values == Vector(3, 4, 5, 9))
assert(h.sp == 3)
assert(h.current == 9)
for (i <- 1 to 4) h.backward()
// assert(h.current == None)
// assert(h.ints == Vector.empty)
assert(h.sp == 0)
assert(h.current == 3)
h.push(1)
assert(h.current == 1)
assert(h.sp == 0)
}
}
// test("main") {
// assert(mutHistory.ints == Vector.empty)
// assert(mutHistory.sp == -1)
// mutHistory.push(1)
// assert(mutHistory.ints == Vector(1))
// assert(mutHistory.sp == 0)
// mutHistory.push(2)
// assert(mutHistory.ints == Vector(1, 2))
// assert(mutHistory.sp == 1)
// // mutHistory.forward()
// // assert(mutHistory.sp == 1)
// // mutHistory.forward()
// // assert(mutHistory.sp == 1)
// }
//
Loading…
Cancel
Save