19 Commits

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

+ 9
- 3
build.sbt View File

@ -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
- 0
src/main/resources/static/css/main.css View File

@ -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;
}

+ 6
- 1
src/main/scala/nova/monadic_sfx/Main.scala View File

@ -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()))

+ 160
- 114
src/main/scala/nova/monadic_sfx/MainApp.scala View File

@ -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) {

+ 7
- 7
src/main/scala/nova/monadic_sfx/MainModule.scala View File

@ -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)
}

+ 59
- 18
src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala View File

@ -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 {

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

@ -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

+ 16
- 46
src/main/scala/nova/monadic_sfx/implicits/package.scala View File

@ -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("")
// }

+ 1
- 1
src/main/scala/nova/monadic_sfx/ui/DefaultUI.scala View File

@ -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

+ 2
- 1
src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala View File

@ -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)

+ 55
- 47
src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala View File

@ -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()

+ 5
- 5
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala View File

@ -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

+ 3
- 3
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala View File

@ -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)

+ 57
- 49
src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala View File

@ -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
@ -84,10 +81,6 @@ object TodoListView {
contextMenu = new ContextMenu {
items ++= Seq(
new MenuItem {
text = "Add"
// obsAction.useLazyEval(TodoListStore.Add("blah3")) --> store
},
new MenuItem {
text = "Delete"
obsAction.useIterableEval(_ =>
selectedItems
@ -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
}
)
}
}
}
)

+ 6
- 6
src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala View File

@ -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

+ 10
- 11
src/main/scala/nova/monadic_sfx/ui/screens/HomeScreen.scala View File

@ -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
- 0
src/main/scala/nova/monadic_sfx/util/History.scala View File

@ -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
- 0
src/main/scala/nova/monadic_sfx/util/SynchedObject.scala View File

@ -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(())