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