Rohan Sircar
3 years ago
36 changed files with 1201 additions and 625 deletions
-
1.gitignore
-
9build.sbt
-
2project/build.properties
-
389src/main/resources/static/css/main.css
-
4src/main/scala/nova/monadic_sfx/Main.scala
-
388src/main/scala/nova/monadic_sfx/MainApp.scala
-
10src/main/scala/nova/monadic_sfx/MainModule.scala
-
2src/main/scala/nova/monadic_sfx/Types.scala
-
13src/main/scala/nova/monadic_sfx/implicits/JavaFxMonixObservables.scala
-
11src/main/scala/nova/monadic_sfx/implicits/package.scala
-
71src/main/scala/nova/monadic_sfx/ui/FXComponent.scala
-
66src/main/scala/nova/monadic_sfx/ui/MyFxApp.scala
-
49src/main/scala/nova/monadic_sfx/ui/components/router/FXRouter.scala
-
23src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListComponentOld.scala
-
35src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListStore.scala
-
45src/main/scala/nova/monadic_sfx/ui/components/todo/TodoListView.scala
-
2src/main/scala/nova/monadic_sfx/ui/controller/TodoController.scala
-
37src/main/scala/nova/monadic_sfx/util/BoundedStack.scala
-
18src/main/scala/nova/monadic_sfx/util/CssPath.scala
-
50src/main/scala/nova/monadic_sfx/util/History.scala
-
31src/main/scala/nova/monadic_sfx/util/MediaPlayerResource.scala
-
47src/main/scala/nova/monadic_sfx/util/WebSocket.scala
-
3src/main/scala/nova/monadic_sfx/util/controls/EmbeddedMediaPlayer.scala
-
12src/main/scala/nova/monadic_sfx/util/controls/JFXButton.scala
-
22src/main/scala/nova/monadic_sfx/util/controls/JFXDialog.scala
-
23src/main/scala/nova/monadic_sfx/util/controls/JFXListCell.scala
-
12src/main/scala/nova/monadic_sfx/util/controls/JFXListView.scala
-
37src/main/scala/nova/monadic_sfx/util/controls/JFXRippler.scala
-
15src/main/scala/nova/monadic_sfx/util/controls/VideoView.scala
-
12src/main/scala/nova/monadic_sfx/util/reactive/store/Middlewares.scala
-
79src/main/scala/nova/monadic_sfx/util/reactive/store/Store.scala
-
32src/main/scala/nova/monadic_sfx/util/test.txt
-
107src/test/scala/HistoryTest.scala
-
83src/test/scala/ProSubjectTest.scala
-
61src/test/scala/WebSocketTest.scala
-
25src/test/scala/WebSocketTestServer.py
@ -1,2 +1,2 @@ |
|||
sbt.version=1.4.3 |
|||
sbt.version=1.4.5 |
|||
|
@ -1,342 +1,123 @@ |
|||
.root { |
|||
-fx-font-family: Roboto; |
|||
src: "/resources/roboto/Roboto-Regular.ttf"; |
|||
-fx-font-family: Roboto; |
|||
src: "/resources/roboto/Roboto-Regular.ttf"; |
|||
-fx-text-fill: white; |
|||
-fx-background-color: #14161d; |
|||
/* rgb(38,38,38) */ |
|||
} |
|||
|
|||
/* Burgers Demo */ |
|||
|
|||
.jfx-hamburger { |
|||
-fx-spacing: 5; |
|||
-fx-cursor: hand; |
|||
} |
|||
|
|||
.jfx-hamburger StackPane { |
|||
-fx-pref-width: 40px; |
|||
-fx-pref-height: 7px; |
|||
-fx-background-color: #D63333; |
|||
-fx-background-radius: 5px; |
|||
} |
|||
|
|||
/* Input Demo */ |
|||
|
|||
.text-field { |
|||
-fx-max-width: 300; |
|||
} |
|||
|
|||
.jfx-text-field, .jfx-password-field { |
|||
-fx-background-color: WHITE; |
|||
-fx-font-weight: BOLD; |
|||
-fx-prompt-text-fill: #808080; |
|||
-fx-alignment: top-left; |
|||
-jfx-focus-color: #4059A9; |
|||
-jfx-unfocus-color: #4d4d4d; |
|||
-fx-max-width: 300; |
|||
} |
|||
|
|||
.jfx-decorator { |
|||
-fx-decorator-color: RED; |
|||
} |
|||
|
|||
.jfx-decorator .jfx-decorator-buttons-container { |
|||
-fx-background-color: -fx-decorator-color; |
|||
} |
|||
|
|||
.jfx-decorator .resize-border { |
|||
-fx-border-color: -fx-decorator-color; |
|||
-fx-border-width: 0 4 4 4; |
|||
} |
|||
|
|||
.jfx-text-area, .text-area { |
|||
-fx-font-weight: BOLD; |
|||
} |
|||
|
|||
.jfx-text-field:error, .jfx-password-field:error, .jfx-text-area:error { |
|||
-jfx-focus-color: #D34336; |
|||
-jfx-unfocus-color: #D34336; |
|||
} |
|||
|
|||
.jfx-text-field .error-label, .jfx-password-field .error-label, .jfx-text-area .error-label { |
|||
-fx-text-fill: #D34336; |
|||
-fx-font-size: 0.75em; |
|||
} |
|||
|
|||
.jfx-text-field .error-icon, .jfx-password-field .error-icon, .jfx-text-area .error-icon { |
|||
-fx-text-fill: #D34336; |
|||
-fx-font-size: 1em; |
|||
} |
|||
|
|||
/* Progress Bar Demo */ |
|||
|
|||
.progress-bar > .bar { |
|||
-fx-min-width: 500; |
|||
} |
|||
|
|||
.jfx-progress-bar > .bar { |
|||
-fx-min-width: 500; |
|||
} |
|||
|
|||
.jfx-progress-bar { |
|||
-fx-progress-color: #0F9D58; |
|||
-fx-stroke-width: 3; |
|||
.text-white { |
|||
-fx-text-fill: white; |
|||
} |
|||
|
|||
/* Icons Demo */ |
|||
.icon { |
|||
-fx-text-fill: #FE774D; |
|||
-fx-padding: 10; |
|||
-fx-cursor: hand; |
|||
.clear-list-view .scroll-bar:horizontal .track, |
|||
.clear-list-view .scroll-bar:vertical .track { |
|||
-fx-background-color: transparent; |
|||
-fx-border-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-border-radius: 2em; |
|||
} |
|||
|
|||
.icons-rippler { |
|||
-jfx-rippler-fill: BLUE; |
|||
-jfx-mask-type: CIRCLE; |
|||
.clear-list-view .scroll-bar:horizontal .increment-button, |
|||
.clear-list-view .scroll-bar:horizontal .decrement-button { |
|||
-fx-background-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-padding: 0 0 10 0; |
|||
} |
|||
|
|||
.icons-rippler:hover { |
|||
-fx-cursor: hand; |
|||
.clear-list-view .scroll-bar:vertical .increment-button, |
|||
.clear-list-view .scroll-bar:vertical .decrement-button { |
|||
-fx-background-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-padding: 0 10 0 0; |
|||
} |
|||
|
|||
.jfx-check-box { |
|||
-fx-font-weight: BOLD; |
|||
} |
|||
|
|||
.custom-jfx-check-box { |
|||
-jfx-checked-color: RED; |
|||
-jfx-unchecked-color: BLACK; |
|||
.clear-list-view .scroll-bar .increment-arrow, |
|||
.clear-list-view .scroll-bar .decrement-arrow { |
|||
-fx-shape: " "; |
|||
-fx-padding: 0; |
|||
} |
|||
|
|||
/* Button */ |
|||
.button { |
|||
-fx-padding: 0.7em 0.57em; |
|||
-fx-font-size: 14px; |
|||
.clear-list-view .scroll-bar:horizontal .thumb, |
|||
.clear-list-view .scroll-bar:vertical .thumb { |
|||
-fx-background-color: derive(black, 90%); |
|||
-fx-background-insets: 2, 0, 0; |
|||
-fx-background-radius: 2em; |
|||
} |
|||
|
|||
.button-raised { |
|||
-fx-padding: 0.7em 0.57em; |
|||
-fx-font-size: 14px; |
|||
-jfx-button-type: RAISED; |
|||
-fx-background-color: rgb(77, 102, 204); |
|||
-fx-pref-width: 200; |
|||
-fx-text-fill: WHITE; |
|||
.scroll-pane .scroll-bar:horizontal .track, |
|||
.scroll-pane .scroll-bar:vertical .track { |
|||
-fx-background-color: transparent; |
|||
-fx-border-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-border-radius: 2em; |
|||
} |
|||
|
|||
/* The main scrollbar **track** CSS class */ |
|||
.mylistview .scroll-bar:horizontal .track, |
|||
.mylistview .scroll-bar:vertical .track { |
|||
-fx-background-color: transparent; |
|||
-fx-border-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-border-radius: 2em; |
|||
.scroll-pane .scroll-bar:horizontal .increment-button, |
|||
.scroll-pane .scroll-bar:horizontal .decrement-button { |
|||
-fx-background-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-padding: 0 0 10 0; |
|||
} |
|||
|
|||
/* The increment and decrement button CSS class of scrollbar */ |
|||
.mylistview .scroll-bar:horizontal .increment-button, |
|||
.mylistview .scroll-bar:horizontal .decrement-button { |
|||
-fx-background-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-padding: 0 0 10 0; |
|||
.scroll-pane .scroll-bar:vertical .increment-button, |
|||
.scroll-pane .scroll-bar:vertical .decrement-button { |
|||
-fx-background-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-padding: 0 10 0 0; |
|||
} |
|||
|
|||
/* The increment and decrement button CSS class of scrollbar */ |
|||
|
|||
.mylistview .scroll-bar:vertical .increment-button, |
|||
.mylistview .scroll-bar:vertical .decrement-button { |
|||
-fx-background-color: transparent; |
|||
-fx-background-radius: 0em; |
|||
-fx-padding: 0 10 0 0; |
|||
|
|||
.scroll-pane .scroll-bar .increment-arrow, |
|||
.scroll-pane .scroll-bar .decrement-arrow { |
|||
-fx-shape: " "; |
|||
-fx-padding: 0; |
|||
} |
|||
|
|||
.mylistview .scroll-bar .increment-arrow, |
|||
.mylistview .scroll-bar .decrement-arrow { |
|||
-fx-shape: " "; |
|||
-fx-padding: 0; |
|||
.scroll-pane .scroll-bar:horizontal .thumb, |
|||
.scroll-pane .scroll-bar:vertical .thumb { |
|||
-fx-background-color: derive(black, 90%); |
|||
-fx-background-insets: 2, 0, 0; |
|||
-fx-background-radius: 2em; |
|||
} |
|||
|
|||
/* The main scrollbar **thumb** CSS class which we drag every time (movable) */ |
|||
.mylistview .scroll-bar:horizontal .thumb, |
|||
.mylistview .scroll-bar:vertical .thumb { |
|||
-fx-background-color: derive(black, 90%); |
|||
-fx-background-insets: 2, 0, 0; |
|||
-fx-background-radius: 2em; |
|||
.scroll-pane { |
|||
-fx-background-color: transparent; |
|||
} |
|||
|
|||
.jfx-list-cell-container { |
|||
-fx-alignment: center-left; |
|||
.scroll-pane > .viewport { |
|||
-fx-background-color: transparent; |
|||
} |
|||
|
|||
.jfx-list-cell-container > .label { |
|||
-fx-text-fill: BLACK; |
|||
.clear-list-view { |
|||
-fx-background-color: rgba(28, 28, 28, 28, 0.4); |
|||
-fx-text-fill: white; |
|||
-fx-background-radius: 20px; |
|||
-fx-padding: 5px 10px; |
|||
} |
|||
|
|||
.jfx-list-cell:odd:selected > .jfx-rippler > StackPane, .jfx-list-cell:even:selected > .jfx-rippler > StackPane { |
|||
-fx-background-color: rgba(0, 0, 255, 0.2); |
|||
.clear-list-view .list-cell { |
|||
-fx-background-color: transparent; |
|||
-fx-text-fill: white; |
|||
} |
|||
|
|||
.jfx-list-cell { |
|||
-fx-background-insets: 0.0; |
|||
-fx-text-fill: BLACK; |
|||
.clear-list-view .list-cell:selected { |
|||
-fx-background-color: rgba(0, 0, 0, 0.2); |
|||
-fx-text-fill: white; |
|||
-fx-background-radius: 20px; |
|||
} |
|||
|
|||
.jfx-list-cell:odd, .jfx-list-cell:even { |
|||
-fx-background-color: WHITE; |
|||
.clear-list-view .list-cell:hover { |
|||
-fx-background-color: rgba(0, 0, 0, 0.1); |
|||
-fx-text-fill: white; |
|||
-fx-background-radius: 20px; |
|||
} |
|||
|
|||
.jfx-list-cell:filled:hover { |
|||
-fx-text-fill: black; |
|||
.clear-list-view .list-cell:selected:hover { |
|||
-fx-background-color: rgba(0, 0, 0, 0.3); |
|||
-fx-text-fill: white; |
|||
-fx-background-radius: 20px; |
|||
} |
|||
|
|||
.jfx-list-cell .jfx-rippler { |
|||
-jfx-rippler-fill: BLUE; |
|||
.clear-list-view:focused .list-cell:selected { |
|||
-fx-background-color: rgba(0, 0, 0, 0.3); |
|||
-fx-text-fill: white; |
|||
-fx-background-radius: 20px; |
|||
} |
|||
|
|||
.jfx-list-view { |
|||
-fx-background-insets: 0; |
|||
-jfx-cell-horizontal-margin: 0.0; |
|||
-jfx-cell-vertical-margin: 5.0; |
|||
-jfx-vertical-gap: 10; |
|||
-jfx-expanded: false; |
|||
-fx-pref-width: 200; |
|||
} |
|||
|
|||
.jfx-toggle-button { |
|||
-jfx-toggle-color: RED; |
|||
} |
|||
|
|||
.jfx-tool-bar { |
|||
-fx-font-size: 20; |
|||
-fx-font-weight: BOLD; |
|||
-fx-background-color: #5264AE; |
|||
-fx-pref-width: 100%; |
|||
-fx-pref-height: 64px; |
|||
} |
|||
|
|||
.jfx-tool-bar HBox { |
|||
-fx-alignment: center; |
|||
-fx-spacing: 25; |
|||
-fx-padding: 0 10; |
|||
} |
|||
|
|||
.jfx-tool-bar Label { |
|||
-fx-text-fill: WHITE; |
|||
} |
|||
|
|||
.jfx-popup-container { |
|||
-fx-background-color: WHITE; |
|||
} |
|||
|
|||
.jfx-snackbar-content { |
|||
-fx-background-color: #323232; |
|||
-fx-padding: 5; |
|||
-fx-spacing: 5; |
|||
} |
|||
|
|||
.jfx-snackbar-toast { |
|||
-fx-text-fill: WHITE; |
|||
} |
|||
|
|||
.jfx-snackbar-action { |
|||
-fx-text-fill: #ff4081; |
|||
} |
|||
|
|||
.jfx-list-cell-content-container { |
|||
-fx-alignment: center-left; |
|||
} |
|||
|
|||
.jfx-list-cell-container .label { |
|||
-fx-text-fill: BLACK; |
|||
} |
|||
|
|||
.combo-box-popup .list-view .jfx-list-cell:odd:selected .jfx-list-cell-container, |
|||
.combo-box-popup .list-view .jfx-list-cell:even:selected .jfx-list-cell-container { |
|||
-fx-background-color: rgba(0.0, 0.0, 255.0, 0.2); |
|||
} |
|||
|
|||
.combo-box-popup .list-view .jfx-list-cell { |
|||
-fx-background-insets: 0.0; |
|||
-fx-text-fill: BLACK; |
|||
} |
|||
|
|||
.combo-box-popup .list-view .jfx-list-cell:odd, |
|||
.combo-box-popup .list-view .jfx-list-cell:even { |
|||
-fx-background-color: WHITE; |
|||
} |
|||
|
|||
.combo-box-popup .list-view .jfx-list-cell:filled:hover { |
|||
-fx-text-fill: black; |
|||
} |
|||
|
|||
/*.combo-box .combo-box-button-container{ |
|||
-fx-border-color:BLACK;-fx-border-width: 0 0 1 0; |
|||
} |
|||
.combo-box .combo-box-selected-value-container{ |
|||
-fx-border-color:BLACK; |
|||
} */ |
|||
|
|||
/* |
|||
* TREE TABLE CSS |
|||
*/ |
|||
|
|||
.tree-table-view { |
|||
-fx-tree-table-color: rgba(255, 0, 0, 0.2); |
|||
-fx-tree-table-rippler-color: rgba(255, 0, 0, 0.4); |
|||
} |
|||
|
|||
.tree-table-view .jfx-text-field{ |
|||
-fx-background-color: transparent; |
|||
} |
|||
|
|||
.animated-option-button { |
|||
-fx-background-color: #F1F1F1; |
|||
-fx-background-radius: 50px; |
|||
-fx-pref-height: 50px; |
|||
-fx-pref-width: 50px; |
|||
-fx-min-width: -fx-pref-width; |
|||
-fx-max-width: -fx-pref-width; |
|||
-fx-min-height: -fx-pref-height; |
|||
-fx-max-height: -fx-pref-height; |
|||
-jfx-button-type: RAISED; |
|||
} |
|||
.animated-option-button .jfx-rippler{ |
|||
-jfx-rippler-fill: rgb(113, 118, 114); |
|||
} |
|||
.sub-icon{ |
|||
-fx-fill: rgb(113, 118, 114); |
|||
} |
|||
|
|||
.main-button { |
|||
-fx-pref-width: 60px; |
|||
-fx-background-color: #0F9D58; |
|||
-fx-background-radius: 60px; |
|||
-fx-pref-height: 60px; |
|||
-fx-min-width: -fx-pref-width; |
|||
-fx-max-width: -fx-pref-width; |
|||
-fx-min-height: -fx-pref-height; |
|||
-fx-max-height: -fx-pref-height; |
|||
-jfx-button-type: RAISED; |
|||
} |
|||
.main-button .jfx-rippler{ |
|||
-jfx-rippler-fill: rgba(255,255,255, .87); |
|||
} |
|||
.main-icon{ |
|||
-fx-fill: rgba(255,255,255, .87); |
|||
} |
|||
|
|||
|
|||
.animated-option-sub-button { |
|||
-fx-background-color: #43609C; |
|||
} |
|||
|
|||
.animated-option-sub-button2 { |
|||
-fx-background-color: rgb(203, 104, 96); |
|||
} |
|||
|
|||
.tree-table-view .menu-item:focused { |
|||
-fx-background-color: -fx-tree-table-color; |
|||
|
|||
} |
|||
|
|||
.tree-table-view .menu-item .label { |
|||
-fx-padding: 5 0 5 0; |
|||
} |
|||
|
@ -0,0 +1,71 @@ |
|||
package nova.monadic_sfx.ui |
|||
|
|||
import javafx.{scene => jfxc} |
|||
import monix.bio.Task |
|||
import monix.execution.Cancelable |
|||
import monix.execution.cancelables.CompositeCancelable |
|||
import monix.reactive.subjects.ConcurrentSubject |
|||
import nova.monadic_sfx.implicits._ |
|||
import scalafx.beans.property.ObjectProperty |
|||
import scalafx.scene.Node |
|||
import scalafx.scene.Parent |
|||
import scalafx.scene.control.TextField |
|||
import scalafx.scene.text.Font |
|||
import cats.effect.Resource |
|||
|
|||
final class FXComponent private ( |
|||
val rootNode: Parent, |
|||
val cancelable: Cancelable |
|||
) |
|||
|
|||
object FXComponent { |
|||
def acquire(f: CompositeCancelable => Task[Parent]) = |
|||
for { |
|||
c <- Task(CompositeCancelable()) |
|||
p <- f(c) |
|||
} yield new FXComponent(p, c) |
|||
|
|||
def fxComponent2Node( |
|||
component: FXComponent |
|||
)(implicit c: CompositeCancelable): Node = { |
|||
c += component.cancelable |
|||
component.rootNode |
|||
} |
|||
|
|||
def resource(f: CompositeCancelable => Task[Parent]) = |
|||
Resource.make(acquire(f))(comp => Task(comp.cancelable.cancel())) |
|||
} |
|||
|
|||
object TestFXComp { |
|||
val testComp = |
|||
FXComponent.resource { implicit c => |
|||
Task.deferAction { implicit s => |
|||
Task { |
|||
val sub = ConcurrentSubject.publish[jfxc.text.Font] |
|||
val f = ObjectProperty(Font("hmm")) |
|||
sub.onNext(f()) |
|||
new TextField { |
|||
font <-- sub |
|||
font <== f |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
// val x = for { |
|||
// comp <- testComp |
|||
// res <- FXComponent.make { implicit c => |
|||
// Task.deferAction { implicit s => |
|||
// Task { |
|||
// new BorderPane { |
|||
// val sub = ConcurrentSubject.publish[jfxc.text.Font] |
|||
// center = FXComponent.fxComponent2Node(comp) |
|||
// bottom = new TextField { |
|||
// font <-- sub |
|||
// } |
|||
// } |
|||
// } |
|||
// } |
|||
// } |
|||
// } yield res |
|||
} |
@ -0,0 +1,37 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
final class BoundedStack private ( |
|||
val underlying: List[Int], |
|||
val maxSize: Int, |
|||
val sp: Int |
|||
) { |
|||
def push(v: Int): Either[String, BoundedStack] = |
|||
if (sp < maxSize) Right(new BoundedStack(v :: underlying, maxSize, sp + 1)) |
|||
else Left("overflow") |
|||
|
|||
def pushAll(v: List[Int]) = |
|||
if (sp + v.length < maxSize) |
|||
Right(new BoundedStack(v ::: underlying, maxSize, sp + v.length)) |
|||
else Left("overflow") |
|||
|
|||
def pop = |
|||
if (sp == 0) Left("Underflow") |
|||
else |
|||
Right( |
|||
underlying(sp) -> new BoundedStack( |
|||
underlying.splitAt(sp)._1, |
|||
maxSize, |
|||
sp - 1 |
|||
) |
|||
) |
|||
} |
|||
|
|||
object BoundedStack { |
|||
def apply( |
|||
// defaultValues: List[Int] = List.empty, |
|||
maxSize: Int = 10 |
|||
// sp: Int = 0 |
|||
) = |
|||
new BoundedStack(List.empty, maxSize, 0) |
|||
|
|||
} |
@ -0,0 +1,18 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import os.RelPath |
|||
|
|||
trait CssPath[T] { |
|||
def path(self: T): String |
|||
} |
|||
|
|||
object CssPath { |
|||
implicit val cssPathForString = new CssPath[String] { |
|||
def path(self: String): String = self |
|||
} |
|||
implicit val cssPathForOsRelPath = new CssPath[os.RelPath] { |
|||
def path(self: RelPath): String = self.toString() |
|||
} |
|||
|
|||
implicit def any2CssPath[T](t: T)(implicit C: CssPath[T]): CssPath[T] = C |
|||
} |
@ -0,0 +1,31 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import cats.effect.Resource |
|||
import monix.bio.Task |
|||
import uk.co.caprica.vlcj.factory.MediaPlayerFactory |
|||
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer |
|||
|
|||
class MediaPlayerResource( |
|||
val factory: MediaPlayerFactory, |
|||
val controller: EmbeddedMediaPlayer |
|||
) |
|||
|
|||
object MediaPlayerResource { |
|||
val mediaPlayerFactoryResource = |
|||
Resource.make(Task(new MediaPlayerFactory()))(factory => |
|||
Task(factory.release()) |
|||
) |
|||
|
|||
def mediaPlayerControllerResource(factory: MediaPlayerFactory) = |
|||
Resource.make(Task { |
|||
val players = factory.mediaPlayers() |
|||
players.newEmbeddedMediaPlayer() |
|||
})(controller => Task(controller.release())) |
|||
|
|||
def apply() = |
|||
for { |
|||
factory <- mediaPlayerFactoryResource |
|||
controller <- mediaPlayerControllerResource(factory) |
|||
} yield new MediaPlayerResource(factory, controller) |
|||
|
|||
} |
@ -0,0 +1,47 @@ |
|||
package nova.monadic_sfx.util |
|||
|
|||
import monix.bio.Task |
|||
import nova.monadic_sfx.AppTypes |
|||
import nova.monadic_sfx.implicits._ |
|||
import sttp.client._ |
|||
import sttp.client.httpclient.monix.MonixWebSocketHandler |
|||
|
|||
// class WebSocket()(implicit backend: AppTypes.HttpBackend) { |
|||
|
|||
// // val source = for { |
|||
|
|||
// // } yield () |
|||
// } |
|||
object WebSocket { |
|||
|
|||
def apply()(implicit backend: AppTypes.HttpBackend) = { |
|||
Task |
|||
.deferAction(implicit s => |
|||
IOUtils.toIO( |
|||
basicRequest.get(uri"").openWebsocketF(MonixWebSocketHandler()) |
|||
) |
|||
) |
|||
.flatMap { r => |
|||
val ws = r.result |
|||
// val source = Observable.repeatEvalF(ws.receive) |
|||
val source2 = ws.observableSource |
|||
// ws.send() |
|||
val source3 = source2.map { |
|||
case Left(value) => () |
|||
case Right(value) => value |
|||
} |
|||
// ws.send() |
|||
// val sink = new Observer[WebSocketFrame] { |
|||
|
|||
// override def onNext(elem: WebSocketFrame): Future[Ack] = ws.send(elem) |
|||
|
|||
// override def onError(ex: Throwable): Unit = ex.printStackTrace() |
|||
|
|||
// override def onComplete(): Unit = ws.close |
|||
|
|||
// } |
|||
Task.unit |
|||
} |
|||
} |
|||
|
|||
} |
@ -0,0 +1,3 @@ |
|||
package nova.monadic_sfx.util.controls |
|||
|
|||
|
@ -0,0 +1,22 @@ |
|||
package nova.monadic_sfx.util.controls |
|||
|
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import scalafx.scene.layout.Region |
|||
import scalafx.scene.layout.StackPane |
|||
|
|||
class JFXDialog( |
|||
override val delegate: jfoenixc.JFXDialog = new jfoenixc.JFXDialog |
|||
) extends StackPane(delegate) { |
|||
def show() = delegate.show() |
|||
def show(sp: StackPane) = delegate.show(sp) |
|||
def content = delegate.getContent() |
|||
def content_=(r: Region) = delegate.setContent(r) |
|||
def overlayClose = delegate.overlayCloseProperty() |
|||
def overlayClose_=(v: Boolean) = delegate.setOverlayClose(v) |
|||
def cacheContainer = delegate.cacheContainerProperty() |
|||
def cacheContainer_=(v: Boolean) = delegate.setCacheContainer(v) |
|||
} |
|||
|
|||
object JFXDialog { |
|||
implicit def sfxJfXDialog2Jfx(v: JFXDialog): jfoenixc.JFXDialog = v.delegate |
|||
} |
@ -0,0 +1,37 @@ |
|||
package nova.monadic_sfx.util.controls |
|||
import com.jfoenix.{controls => jfoenixc} |
|||
import scalafx.scene.Node |
|||
import scalafx.scene.layout.StackPane |
|||
import scalafx.scene.paint.Paint |
|||
|
|||
class JFXRippler( |
|||
override val delegate: jfoenixc.JFXRippler = new jfoenixc.JFXRippler |
|||
) extends StackPane(delegate) { |
|||
import JFXRippler._ |
|||
def control = delegate.getControl() |
|||
def control_=(v: Node) = delegate.setControl(v) |
|||
def enabled_=(v: Boolean) = delegate.setEnabled(v) |
|||
def ripplerPos = delegate.getPosition() |
|||
def ripplerPos_=(pos: RipplerPos) = delegate.setPosition(pos) |
|||
def ripplerDisabled = delegate.ripplerDisabledProperty() |
|||
def ripplerDisabled_=(v: Boolean) = delegate.setRipplerDisabled(v) |
|||
def ripplerFill = delegate.ripplerFillProperty() |
|||
def ripplerFill_=(v: Paint) = delegate.setRipplerFill(v) |
|||
def ripplerRecenter = delegate.ripplerRecenterProperty() |
|||
def ripplerRecenter_=(v: Boolean) = delegate.setRipplerRecenter(v) |
|||
def ripplerRadius = delegate.ripplerRadiusProperty() |
|||
def ripplerRadius_=(v: Int) = delegate.setRipplerRadius(v) |
|||
} |
|||
|
|||
object JFXRippler { |
|||
abstract class RipplerPos(val delegate: jfoenixc.JFXRippler.RipplerPos) |
|||
case object Front extends RipplerPos(jfoenixc.JFXRippler.RipplerPos.FRONT) |
|||
case object Back extends RipplerPos(jfoenixc.JFXRippler.RipplerPos.BACK) |
|||
object RipplerPos { |
|||
implicit def sfxRipplerPos2jfxRipplerPos( |
|||
v: RipplerPos |
|||
): jfoenixc.JFXRippler.RipplerPos = v.delegate |
|||
} |
|||
implicit def sfxRippler2jfxRippler(v: JFXRippler): jfoenixc.JFXRippler = |
|||
v.delegate |
|||
} |
@ -0,0 +1,15 @@ |
|||
package nova.monadic_sfx.util.controls |
|||
|
|||
import scalafx.scene.image.ImageView |
|||
import uk.co.caprica.vlcj.javafx.videosurface.ImageViewVideoSurfaceFactory.videoSurfaceForImageView |
|||
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer |
|||
|
|||
class VideoView(val mediaPlayer: EmbeddedMediaPlayer) extends ImageView { |
|||
// private val _mediaPlayer = ObjectProperty[Option[EmbeddedMediaPlayer]](None) |
|||
// def mediaPlayer = _mediaPlayer |
|||
// def mediaPlayer_=(v: EmbeddedMediaPlayer): Unit = { |
|||
// v.videoSurface().set(videoSurfaceForImageView(this)) |
|||
// _mediaPlayer.value = Some(v) |
|||
// } |
|||
mediaPlayer.videoSurface().set(videoSurfaceForImageView(this)) |
|||
} |
@ -0,0 +1,32 @@ |
|||
Vector() |
|||
0 |
|||
//push 1 |
|||
Vector(1) |
|||
0 |
|||
//push 2 |
|||
Vector(1,2) |
|||
1 |
|||
//forward |
|||
Vector(1,2) |
|||
1 |
|||
//backward |
|||
Vector(1,2) |
|||
0 |
|||
//backward |
|||
Vector(1,2) |
|||
0 |
|||
//push 3 |
|||
Vector(1,2,3) |
|||
sp 2 |
|||
//push 4 |
|||
Vector(1,2,3,4) |
|||
sp 3 |
|||
//push 5 |
|||
Vector(1,2,3,4,5) |
|||
sp 4 |
|||
//backward |
|||
Vector(1,2,3,4,5) |
|||
sp 3 |
|||
//push 6 |
|||
Vector(1,2,3,4,6) |
|||
sp 3 |
@ -0,0 +1,107 @@ |
|||
import org.scalatest.funsuite.AnyFunSuite |
|||
import nova.monadic_sfx.util.History |
|||
import monix.execution.atomic.Atomic |
|||
class HistoryTest extends AnyFunSuite { |
|||
val historyRef = Atomic(History(0)) |
|||
|
|||
test("init") { |
|||
val h = historyRef.get() |
|||
assert(h.state.values == Vector(0)) |
|||
assert(h.state.sp == 0) |
|||
assert(h.current == 0) |
|||
} |
|||
|
|||
test("push 1") { |
|||
val h = historyRef.transformAndGet(_.push(1)) |
|||
// logger.debug(mutHistory.ints.toString) |
|||
assert(h.state.values == Vector(0, 1)) |
|||
assert(h.state.values.length - 1 == h.state.sp) |
|||
assert(h.current == 1) |
|||
} |
|||
|
|||
test("push 2") { |
|||
val h = historyRef.transformAndGet(_.push(2)) |
|||
// logger.debug(mutHistory.ints.toString) |
|||
assert(h.state.values == Vector(0, 1, 2)) |
|||
assert(h.state.values.length - 1 == h.state.sp) |
|||
assert(h.current == 2) |
|||
} |
|||
|
|||
test("first forward") { |
|||
// logger.debug(mutHistory.ints.toString) |
|||
// logger.debug(mutHistory.sp.toString) |
|||
val h = historyRef.transformAndGet(_.forward) |
|||
assert(h.state.values == Vector(0, 1, 2)) |
|||
assert(h.state.sp == 2) |
|||
assert(h.current == 2) |
|||
} |
|||
|
|||
test("second forward") { |
|||
// logger.debug(mutHistory.ints.toString) |
|||
// logger.debug(mutHistory.sp.toString) |
|||
val h = historyRef.transformAndGet(_.forward) |
|||
assert(h.state.values == Vector(0, 1, 2)) |
|||
assert(h.state.sp == 2) |
|||
assert(h.current == 2) |
|||
} |
|||
test("first backward") { |
|||
val h = historyRef.transformAndGet(_.backward) |
|||
assert(h.state.values == Vector(0, 1, 2)) |
|||
assert(h.state.sp == 1) |
|||
assert(h.current == 1) |
|||
} |
|||
test("second backward") { |
|||
val h = historyRef.transformAndGet(_.backward) |
|||
assert(h.state.values == Vector(0, 1, 2)) |
|||
assert(h.state.sp == 0) |
|||
assert(h.current == 0) |
|||
} |
|||
test("third backward") { |
|||
val h = historyRef.transformAndGet(_.backward) |
|||
assert(h.state.values == Vector(0, 1, 2)) |
|||
assert(h.state.sp == 0) |
|||
assert(h.current == 0) |
|||
} |
|||
test("push 3") { |
|||
val h = historyRef.transformAndGet(_.push(3)) |
|||
// logger.debug(mutHistory.ints.toString) |
|||
assert(h.state.values == Vector(3)) |
|||
assert(h.state.sp == 0) |
|||
assert(h.current == 3) |
|||
} |
|||
test("fourth backward") { |
|||
val h = historyRef.transformAndGet(_.backward) |
|||
assert(h.state.values == Vector(3)) |
|||
assert(h.state.sp == 0) |
|||
assert(h.current == 3) |
|||
} |
|||
test("lastly") { |
|||
val h1 = historyRef.transformAndGet( |
|||
_.push(4) |
|||
.push(5) |
|||
.push(6) |
|||
.push(7) |
|||
.push(8) |
|||
) |
|||
assert(h1.state.values == Vector(3, 4, 5, 6, 7, 8)) |
|||
assert(h1.state.sp == 5) |
|||
assert(h1.current == 8) |
|||
val h2 = historyRef.transformAndGet(_.backward.backward) |
|||
assert(h2.current == 6) |
|||
assert(h2.state.sp == 3) |
|||
val h3 = historyRef.transformAndGet(_.push(9)) |
|||
assert(h3.state.values == Vector(3, 4, 5, 9)) |
|||
assert(h3.state.sp == 3) |
|||
assert(h3.current == 9) |
|||
for (i <- 1 to 4) historyRef.transform(_.backward) |
|||
// assert(h.current == None) |
|||
// assert(h.ints == Vector.empty) |
|||
val h4 = historyRef.get() |
|||
assert(h4.state.sp == 0) |
|||
assert(h4.current == 3) |
|||
val h5 = historyRef.transformAndGet(_.push(1)) |
|||
assert(h5.current == 1) |
|||
assert(h5.state.sp == 0) |
|||
|
|||
} |
|||
} |
@ -0,0 +1,83 @@ |
|||
import org.scalatest.funsuite.AnyFunSuite |
|||
import monix.eval.Task |
|||
import monix.reactive.subjects.ConcurrentSubject |
|||
import monix.eval.Coeval |
|||
import com.typesafe.scalalogging.LazyLogging |
|||
import monix.execution.Scheduler.global |
|||
import nova.monadic_sfx.util.reactive.store.MonixProSubject |
|||
import scala.concurrent.duration._ |
|||
import scala.concurrent.Await |
|||
import monix.reactive.Observable |
|||
|
|||
class ProSubjectTest extends AnyFunSuite with LazyLogging { |
|||
test("task1") { |
|||
implicit val s = global |
|||
val x = Await.result(task1.runToFuture, 10.seconds) |
|||
assert(x == 1) |
|||
} |
|||
|
|||
test("task2") { |
|||
implicit val s = global |
|||
val x = Await.result(task2.runToFuture, 10.seconds) |
|||
} |
|||
|
|||
def task1 = |
|||
Task |
|||
.deferAction(implicit s => |
|||
Task { |
|||
val sub = ConcurrentSubject.publish[Int] |
|||
// val obs = sub.scan0(0)(_ + _).behavior(2).refCount |
|||
val obs = |
|||
sub |
|||
.scanEval0F(Task.pure(0))((a, b) => Task(a + b)) |
|||
.behavior(2) |
|||
.refCount |
|||
.doOnNextF(i => Coeval(println(s"Emitted1: $i"))) |
|||
// type MonixProSubject[-I, +O] = Observable[O] with Observer[I] |
|||
// def from[I, O]( |
|||
// observer: Observer[I], |
|||
// observable: Observable[O] |
|||
// ): MonixProSubject[I, O] = |
|||
// new Observable[O] with Observer[I] { |
|||
// override def onNext(elem: I): Future[Ack] = observer.onNext(elem) |
|||
// override def onError(ex: Throwable): Unit = observer.onError(ex) |
|||
// override def onComplete(): Unit = observer.onComplete() |
|||
// override def unsafeSubscribeFn( |
|||
// subscriber: Subscriber[O] |
|||
// ): Cancelable = |
|||
// observable.unsafeSubscribeFn(subscriber) |
|||
// } |
|||
MonixProSubject.from(sub, obs) |
|||
}.flatMap { |
|||
case ps => |
|||
Task { |
|||
ps |
|||
.doOnNextF(i => Coeval(println(s"Emitted item 1: $i"))) |
|||
.subscribe() |
|||
|
|||
} >> Task { |
|||
ps |
|||
.doOnNextF(i => Coeval(println(s"Emitted item 2: $i"))) |
|||
.subscribe() |
|||
} >> Task { |
|||
(0 to 5).foreach { i => ps.onNext(i) } |
|||
} >> Task(1) |
|||
} |
|||
) |
|||
|
|||
val task2 = Task.deferAction(implicit s => |
|||
Task( |
|||
Observable(1, 2, 3, 4, 5) |
|||
.scan0(0)(_ + _) |
|||
.behavior(2) |
|||
.refCount |
|||
// .doOnNextF(i => Coeval(println(s"Emitted2: $i"))) |
|||
.delayExecution(10.millis) |
|||
) |
|||
.flatMap(res => |
|||
Task(res.doOnNextF(i => Coeval(println(s"1: $i"))).subscribe()).start >> |
|||
Task(res.doOnNextF(i => Coeval(println(s"2: $i"))).subscribe()).start |
|||
) |
|||
) |
|||
|
|||
} |
@ -0,0 +1,61 @@ |
|||
import org.scalatest.funsuite.AnyFunSuite |
|||
import sttp.client._ |
|||
import sttp.client.httpclient.monix.HttpClientMonixBackend |
|||
import monix.execution.Scheduler |
|||
import sttp.client.httpclient.monix.MonixWebSocketHandler |
|||
import org.scalatest.BeforeAndAfterAll |
|||
import monix.eval.Task |
|||
import sttp.model.ws.WebSocketFrame |
|||
import scala.concurrent.duration._ |
|||
import nova.monadic_sfx.implicits._ |
|||
import monix.catnap.ConcurrentQueue |
|||
class WebSocketTest extends AnyFunSuite with BeforeAndAfterAll { |
|||
implicit val sched = Scheduler.global |
|||
implicit val backend = HttpClientMonixBackend().runSyncUnsafe() |
|||
val ws = basicRequest |
|||
.get(uri"ws://localhost:6789") |
|||
.openWebsocketF(MonixWebSocketHandler()) |
|||
.map(_.result) |
|||
.runSyncUnsafe() |
|||
|
|||
test("open websocket") { |
|||
(for { |
|||
isOpen <- ws.isOpen |
|||
_ <- Task(assert(isOpen == true)) |
|||
_ <- Task(assert(isOpen != false)) |
|||
} yield ()).runSyncUnsafe(10.seconds) |
|||
} |
|||
|
|||
test("send message") { |
|||
(for { |
|||
_ <- ws.send(WebSocketFrame.text("Test Message")) |
|||
// _ <- Task.sleep(1.second) |
|||
// _ <- Task(assert(isOpen == true)) |
|||
} yield ()).runSyncUnsafe(10.seconds) |
|||
} |
|||
|
|||
test("receive messages observable") { |
|||
(for { |
|||
queue <- ConcurrentQueue.bounded[Task, Int](10) |
|||
_ <- |
|||
ws.observableSource |
|||
.filter(_.isRight) |
|||
.map(_.right.get) |
|||
.doOnNext(s => |
|||
Task(println(s"Received item: $s")) >> |
|||
s.toIntOption.fold(Task.unit)(queue.offer) |
|||
) |
|||
.take(5) |
|||
.completedL |
|||
.timeout(5.seconds) |
|||
items <- queue.drain(5, 5).timeout(5.seconds) |
|||
_ <- Task(assert(items == Seq(1, 2, 3, 4, 5))) |
|||
_ <- Task(assert(items != Seq(1, 2, 3, 4, 5, 6))) |
|||
} yield ()).runSyncUnsafe(10.seconds) |
|||
} |
|||
|
|||
override def afterAll() = { |
|||
ws.close.runSyncUnsafe() |
|||
backend.close() |
|||
} |
|||
} |
@ -0,0 +1,25 @@ |
|||
#!/usr/bin/env python3 |
|||
|
|||
import asyncio |
|||
import websockets |
|||
|
|||
|
|||
async def server(websocket, path): |
|||
while True: |
|||
# Get received data from websocket |
|||
data = await websocket.recv() |
|||
|
|||
# Send response back to client to acknowledge receiving message |
|||
print("Received data {}".format(data)) |
|||
# await websocket.send(data) |
|||
# for i in range(1, 6): |
|||
# print("Sending item: {}".format(i)) |
|||
# await websocket.send(str(i)) |
|||
|
|||
# Create websocket server |
|||
start_server = websockets.serve(server, "localhost", 6789) |
|||
|
|||
# Start and run websocket server forever |
|||
asyncio.get_event_loop().run_until_complete(start_server) |
|||
print("Starting websocket server") |
|||
asyncio.get_event_loop().run_forever() |
Write
Preview
Loading…
Cancel
Save
Reference in new issue