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._ import nova.monadic_sfx.ui.MyFxApp import nova.monadic_sfx.ui.components.router.FXRouter import nova.monadic_sfx.ui.components.router.Page import nova.monadic_sfx.ui.components.todo.TodoListStore import nova.monadic_sfx.ui.components.todo.TodoListView import nova.monadic_sfx.util.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.layout.Priority import scalafx.scene.layout.StackPane class MainApp( // spawnProtocol: ActorSystem[SpawnProtocol.Command], schedulers: Schedulers, startTime: Long )(implicit logger: Logger[Task]) { private lazy val _scene = new Scene { root = new HBox { padding = Insets(20) // style = """| -fx-background-color: rgb(38, 38, 38); // | -fx-text-fill: white;""".stripMargin stylesheets ++= Seq( BootstrapFX.bootstrapFXStylesheet, os.rel / "static" / "css" / "main.css" ) } } private lazy val stage = new PrimaryStage { title = "Simple ScalaFX App" scene = _scene width = 640 height = 480 // resizable = false } val program = for { (fxApp, fxAppFib) <- wire[MyFxApp].init(stage) _ <- wire[MainAppDelegate].init .flatMap(mainSceneNode => Task(_scene.getChildren += mainSceneNode)) .executeOn(schedulers.fx) _ <- Task(stage.resizable = false).executeOn(schedulers.fx) currentTime <- IO.clock.realTime(TimeUnit.MILLISECONDS) _ <- logger.info( s"Application started in ${(currentTime - startTime) / 1000f} seconds" ) // _ <- Task(CSSFX.start(stage)) _ <- fxAppFib.join } yield () } 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 { //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) { val name = StringProperty(_name).readOnly val age = ObjectProperty(_age).readOnly } object Test { val items = ObservableBuffer( new TestModel("hmm", 1), new TestModel("hmm2", 2) ) val ttv = new TableView[TestModel](items) { columns ++= Seq( new TableColumn[TestModel, String] { text = "Name" cellValueFactory = { _.value.name } }, new TableColumn[TestModel, Int] { text = "Age" cellValueFactory = { _.value.age } } ) } }