├── .classpath
├── .gitattributes
├── .gitignore
├── .project
├── .scalafmt.conf
├── README.md
├── build.sbt
├── media
├── earthrise.jpg
└── omgrobots.mp4
├── project
├── build.properties
└── plugin.sbt
└── src
└── proscalafx
├── ch01
├── audioconfig
│ ├── AudioConfigMain.scala
│ └── AudioConfigModel.scala
└── helloearthrise
│ ├── HelloEarthRiseMain.scala
│ └── HelloScrollPaneMain.scala
├── ch02
├── metronome1
│ └── Metronome1Main.scala
├── metronomepathtransition
│ └── MetronomePathTransitionMain.scala
├── metronometransition
│ └── MetronomeTransitionMain.scala
├── onthescene
│ ├── OnTheSceneMain.scala
│ ├── changeOfScene.css
│ └── onTheScene.css
├── stagecoach
│ └── StageCoachMain.scala
└── zenpong
│ ├── ZenPong.scala
│ └── ZenPongMain.scala
├── ch03
├── BidirectionalBindingExample.scala
├── HeronsFormulaDirectExtensionExample.scala
├── HeronsFormulaExample.scala
├── MotivatingExample.scala
├── NumericPropertiesExample.scala
├── RectangleAreaExample.scala
├── TriangleAreaExample.scala
├── TriangleAreaFluentExample.scala
└── scalafxbean
│ ├── ScalaFXBeanControllerExample.scala
│ ├── ScalaFXBeanMainExample.scala
│ ├── ScalaFXBeanModelExample.scala
│ └── ScalaFXBeanViewExample.scala
├── ch04
└── reversi
│ ├── examples
│ ├── AlignUsingStackAndTile.scala
│ ├── BorderLayoutExample.scala
│ ├── CenterUsingBind.scala
│ ├── CenterUsingStack.scala
│ └── PlayerScoreExample.scala
│ ├── model
│ ├── Owner.scala
│ └── ReversiModel.scala
│ ├── reversipieces
│ ├── ReversiPieceTest.scala
│ └── ReversiSquareTest.scala
│ └── ui
│ ├── Reversi.scala
│ ├── ReversiApp.scala
│ ├── ReversiPiece.scala
│ └── ReversiSquare.scala
├── ch05
├── model
│ ├── Person.scala
│ └── StarterAppModel.scala
└── ui
│ ├── StarterAppMain.scala
│ ├── images
│ └── paper.png
│ └── starterApp.css
├── ch06
├── BufferChangeEventExample.scala
├── FXCollectionsExample.scala
├── JavaFXSceneInSwingExample.scala
├── JavaFXThreadsExample.scala
├── MapChangeEventExample.scala
├── ObservableBufferExample.scala
├── ResponsiveUIExample.scala
├── ServiceExample.scala
├── SetChangeEventExample.scala
├── UnresponsiveUIExample.scala
└── WorkerAndTaskExample.scala
├── ch07
├── ChartApp1.scala
├── ChartApp10.scala
├── ChartApp11.scala
├── ChartApp2.scala
├── ChartApp3.scala
├── ChartApp4.scala
├── ChartApp5.scala
├── ChartApp6.scala
├── ChartApp7.scala
├── ChartApp8.scala
└── ChartApp9.scala
├── ch08
├── AudioPlayer1
│ ├── AudioPlayer1.scala
│ └── resources
│ │ └── keeper.mp3
├── AudioPlayer2
│ ├── AudioPlayer2.scala
│ ├── media.css
│ └── resources
│ │ ├── cross.png
│ │ └── defaultAlbum.png
├── AudioPlayer3
│ ├── AbstractView.scala
│ ├── AudioPlayer3.scala
│ ├── MetadataView.scala
│ ├── PlayerControlsView.scala
│ ├── SongModel.scala
│ ├── media.css
│ └── resources
│ │ ├── cross.png
│ │ ├── defaultAlbum.png
│ │ ├── music_note.png
│ │ ├── next.png
│ │ ├── pause.png
│ │ ├── play.png
│ │ ├── prev.png
│ │ ├── volHigh.png
│ │ └── volLow.png
├── AudioPlayer4
│ ├── AbstractView.scala
│ ├── AudioPlayer4.scala
│ ├── EqualizerView.scala
│ ├── MetadataView.scala
│ ├── PlayerControlsView.scala
│ ├── SongModel.scala
│ ├── SpectrumBar.scala
│ ├── SpectrumListener.scala
│ ├── media.css
│ └── resources
│ │ ├── cross.png
│ │ ├── defaultAlbum.png
│ │ ├── music_note.png
│ │ ├── next.png
│ │ ├── pause.png
│ │ ├── play.png
│ │ ├── prev.png
│ │ ├── volHigh.png
│ │ └── volLow.png
├── BasicAudioClip
│ ├── BasicAudioClip.scala
│ ├── media.css
│ └── resources
│ │ ├── beep.wav
│ │ └── cross.png
├── CodeMonkeyToDo
│ ├── CodeMonkeyToDo.scala
│ ├── media.css
│ └── resources
│ │ ├── coffee.mp3
│ │ ├── coffee.wav
│ │ ├── cross.png
│ │ ├── job.mp3
│ │ ├── job.wav
│ │ ├── meeting.mp3
│ │ └── meeting.wav
├── FullScreenVideoPlayer
│ └── FullScreenVideoPlayer.scala
├── VideoPlayer1
│ └── VideoPlayer1.scala
├── VideoPlayer2
│ ├── VideoPlayer2.scala
│ └── media.css
├── VideoPlayer3
│ ├── VideoPlayer3.scala
│ └── media.css
└── VideoPlayer4
│ ├── AbstractView.scala
│ ├── EqualizerView.scala
│ ├── MediaModel.scala
│ ├── PlayerControlsView.scala
│ ├── SpectrumBar.scala
│ ├── SpectrumListener.scala
│ ├── VideoPlayer4.scala
│ ├── VideoPlayer4App.scala
│ ├── VideoView.scala
│ ├── media.css
│ └── resources
│ ├── PlayPause.png
│ ├── cross.png
│ ├── defaultAlbum.png
│ ├── filmstrip.png
│ ├── music_note.png
│ ├── next.png
│ ├── pause.png
│ ├── play.png
│ ├── prev.png
│ ├── thumb.png
│ ├── volHigh.png
│ └── volLow.png
└── ch10
└── fxml
├── AdoptionForm.fxml
├── AdoptionFormController.scala
├── FXMLAdoptionForm.scala
└── cat.jpg
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Set the default line separator behavior, in case people don't have core.autocrlf set.
2 | # [[https://stackoverflow.com/a/42135910]]
3 | * text=auto eol=lf
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #################
2 | ## Eclipse
3 |
4 | *.pydevproject
5 | .project
6 | .metadata
7 | bin/
8 | tmp/
9 | *.tmp
10 | *.bak
11 | *.swp
12 | *~.nib
13 | local.properties
14 | .classpath
15 | .settings/
16 | .loadpath
17 | # External tool builders
18 | .externalToolBuilders/
19 | # Locally stored "Eclipse launch configurations"
20 | *.launch
21 | # CDT-specific
22 | .cproject
23 | # PDT-specific
24 | .buildpath
25 | ## Visual Studio
26 | ## Ignore Visual Studio temporary files, build results, and
27 | ## files generated by popular Visual Studio add-ons.
28 | # User-specific files
29 | *.suo
30 | *.user
31 | *.sln.docstates
32 | # Build results
33 | [Dd]ebug/
34 | [Rr]elease/
35 | *_i.c
36 | *_p.c
37 | *.ilk
38 | *.meta
39 | *.obj
40 | *.pch
41 | *.pdb
42 | *.pgc
43 | *.pgd
44 | *.rsp
45 | *.sbr
46 | *.tlb
47 | *.tli
48 | *.tlh
49 | *.vspscc
50 | .builds
51 | *.dotCover
52 | ## If you have NuGet Package Restore enabled, uncomment this
53 | #packages/
54 | # Visual C++ cache files
55 | ipch/
56 | *.aps
57 | *.ncb
58 | *.opensdf
59 | *.sdf
60 | # Visual Studio profiler
61 | *.psess
62 | *.vsp
63 | # ReSharper is a .NET coding add-in
64 | _ReSharper*
65 | # Installshield output folder
66 | [Ee]xpress
67 | # DocProject is a documentation generator add-in
68 | DocProject/buildhelp/
69 | DocProject/Help/*.HxT
70 | DocProject/Help/*.HxC
71 | DocProject/Help/*.hhc
72 | DocProject/Help/*.hhk
73 | DocProject/Help/*.hhp
74 | DocProject/Help/Html2
75 | DocProject/Help/html
76 | # Click-Once directory
77 | publish
78 | # Others
79 | [Bb]in
80 | [Oo]bj
81 | sql
82 | TestResults
83 | *.Cache
84 | ClientBin
85 | stylecop.*
86 | ~$*
87 | *.dbmdl
88 | Generated_Code #added for RIA/Silverlight projects
89 | # Backup & report files from converting an old project file to a newer
90 | # Visual Studio version. Backup files are not needed, because we have git ;-)
91 | _UpgradeReport_Files/
92 | Backup*/
93 | UpgradeLog*.XML
94 | ############
95 | ## Windows
96 | # Windows image file caches
97 | Thumbs.db
98 | # Folder config file
99 | Desktop.ini
100 | #############
101 | ## Python
102 | *.py[co]
103 | # Packages
104 | *.egg
105 | *.egg-info
106 | dist
107 | build
108 | eggs
109 | parts
110 | bin
111 | var
112 | sdist
113 | develop-eggs
114 | .installed.cfg
115 | # Installer logs
116 | pip-log.txt
117 | # Unit test / coverage reports
118 | .coverage
119 | .tox
120 | #Translations
121 | *.mo
122 | #Mr Developer
123 | .mr.developer.cfg
124 | # Mac crap
125 | .DS_Store
126 | .scala_dependencies
127 | /.idea/
128 | /local/
129 | /out/
130 | /target/
131 | /.idea_modules/
132 | target
133 | /.bsp/
134 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | ProScalaFX
4 |
5 |
6 |
7 |
8 |
9 | org.scala-ide.sdt.core.scalabuilder
10 |
11 |
12 |
13 |
14 | org.eclipse.xtext.ui.shared.xtextBuilder
15 |
16 |
17 |
18 |
19 |
20 | org.scala-ide.sdt.core.scalanature
21 | org.eclipse.jdt.core.javanature
22 | org.eclipse.xtext.ui.shared.xtextNature
23 |
24 |
25 |
--------------------------------------------------------------------------------
/.scalafmt.conf:
--------------------------------------------------------------------------------
1 | version = 3.9.4
2 |
3 | runner.dialect = scala213source3
4 |
5 | preset = IntelliJ
6 | align.preset = more
7 | maxColumn = 120
8 | docstrings.style = Asterisk
9 | docstrings.blankFirstLine = yes
10 | docstrings.wrap = no
11 | importSelectors = singleLine
12 | newlines.source = keep
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ProScalaFX
2 | ==========
3 |
4 | [ScalaFX][1] version of Pro JavaFX books source code.
5 |
6 | Book editions:
7 |
8 | * [Pro JavaFX 9][b9p], book source [code][b9c]
9 | * [Pro JavaFX 8][b8p], book source [code][b8c].
10 | * [Pro JavaFX 2][b2p], book source [code][b2c].
11 |
12 | The original code was created for JavaFX 2.2 (Java 7). Current main branch was updated to work with ScalaFX 11 / JavaFX
13 |
14 | 11. ScalaFX 8 is still available on branch [SFX-8][sfx8]. Code for ScalaFX 2 is on branch [SFX-2][sfx2].
15 |
16 | Please use [ScalaFX Users Group][5] to post questions.
17 |
18 |
19 | [1]: http://scalafx.org
20 |
21 | [5]: https://groups.google.com/forum/#!forum/scalafx-users
22 |
23 | [sfx2]: https://github.com/scalafx/ProScalaFX/tree/SFX-2
24 |
25 | [sfx8]: https://github.com/scalafx/ProScalaFX/tree/SFX-8
26 |
27 | [b2c]: http://www.apress.com/downloadable/download/sample/sample_id/1253
28 |
29 | [b2p]: http://www.apress.com/9781430268727
30 |
31 | [b8c]: http://www.apress.com/downloadable/download/sample/sample_id/1574
32 |
33 | [b8p]: http://www.apress.com/9781430265740
34 |
35 | [b9c]: https://github.com/Apress/pro-javafx-9
36 |
37 | [b9p]: https://www.apress.com/us/book/9781484230411
38 |
--------------------------------------------------------------------------------
/build.sbt:
--------------------------------------------------------------------------------
1 | // Project name
2 | name := "ProScalaFX"
3 |
4 | // Current version
5 | version := "24.0.0-R35"
6 |
7 | // Version of scala to use
8 | val scala2Version = "2.13.16"
9 | val scala3Version = "3.3.5"
10 | // To cross-compile with Scala 2 and Scala 3
11 | crossScalaVersions := Seq(scala2Version, scala3Version)
12 | scalaVersion := scala3Version
13 |
14 | // Set the main Scala source directory to be /src
15 | Compile / scalaSource := baseDirectory(_ / "src").value
16 |
17 | Compile / resourceDirectory := baseDirectory(_ / "src").value
18 |
19 | // Append -deprecation to the options passed to the Scala compiler
20 | scalacOptions ++= Seq("-deprecation", "-feature")
21 | scalacOptions ++= {
22 | CrossVersion.partialVersion(scalaVersion.value) match {
23 | case Some((2, _)) => Seq("-Xcheckinit", "-Xsource:3")
24 | case Some((3, _)) => Seq("-rewrite", "-source:3.3-migration", "-explain", "-explain-types")
25 | case _ => Seq.empty[String]
26 | }
27 | }
28 |
29 | // Point to location of a snapshot repository for ScalaFX
30 | resolvers ++= Opts.resolver.sonatypeOssSnapshots
31 |
32 | //resolvers += Opts.resolver.sonatypeStaging
33 |
34 | // Add ScalaFX dependency, exclude JavaFX transitive dependencies, may not match this OS
35 | libraryDependencies += "org.scalafx" %% "scalafx" % "24.0.0-R35"
36 |
37 | // Fork a new JVM for 'run' and 'test:run'
38 | fork := true
39 |
--------------------------------------------------------------------------------
/media/earthrise.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/media/earthrise.jpg
--------------------------------------------------------------------------------
/media/omgrobots.mp4:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/media/omgrobots.mp4
--------------------------------------------------------------------------------
/project/build.properties:
--------------------------------------------------------------------------------
1 | sbt.version=1.10.11
2 |
3 |
--------------------------------------------------------------------------------
/project/plugin.sbt:
--------------------------------------------------------------------------------
1 | scalacOptions ++= Seq("-unchecked", "-deprecation")
2 |
--------------------------------------------------------------------------------
/src/proscalafx/ch01/audioconfig/AudioConfigMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch01.audioconfig
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.VPos
7 | import scalafx.scene.Scene
8 | import scalafx.scene.control.{CheckBox, ChoiceBox, Slider}
9 | import scalafx.scene.paint.{Color, LinearGradient, Stop}
10 | import scalafx.scene.shape.{Line, Rectangle}
11 | import scalafx.scene.text.{Font, FontWeight, Text}
12 |
13 | object AudioConfigMain extends JFXApp3 {
14 |
15 | override def start(): Unit = {
16 |
17 | val acModel = new AudioConfigModel()
18 |
19 | val genreChoiceBox = new ChoiceBox[String] {
20 | layoutX = 204
21 | layoutY = 154
22 | prefWidth = 93
23 | items = acModel.genres
24 | }
25 |
26 | stage = new PrimaryStage {
27 | title = "Audio Configuration"
28 | scene = new Scene {
29 | content = List(
30 | new Rectangle {
31 | width = 320
32 | height = 45
33 | fill = new LinearGradient(
34 | endX = 0.0,
35 | endY = 1.0,
36 | stops = List(
37 | Stop(0, Color.web("0xAEBBCC")),
38 | Stop(1, Color.web("0x6D84A3"))
39 | )
40 | )
41 | },
42 | new Text {
43 | layoutX = 65
44 | layoutY = 12
45 | textOrigin = VPos.Top
46 | fill = Color.White
47 | text = "Audio Configuration"
48 | font = Font.font("SansSerif", FontWeight.Bold, 20)
49 | },
50 | new Rectangle {
51 | x = 0
52 | y = 43
53 | width = 320
54 | height = 300
55 | fill = Color.rgb(199, 206, 213)
56 | },
57 | new Rectangle {
58 | x = 9
59 | y = 54
60 | width = 300
61 | height = 130
62 | arcWidth = 20
63 | arcHeight = 20
64 | fill = Color.White
65 | stroke = Color.color(0.66, 0.67, 0.69)
66 | },
67 | new Text {
68 | layoutX = 18
69 | layoutY = 69
70 | textOrigin = VPos.Top
71 | fill = Color.web("#131021")
72 | font = Font.font("SansSerif", FontWeight.Bold, 18)
73 | text <== acModel.selectedDBs.asString + " dB"
74 | },
75 | new Slider {
76 | layoutX = 135
77 | layoutY = 69
78 | prefWidth = 162
79 | min = acModel.minDecibels
80 | max = acModel.maxDecibels
81 | value <==> acModel.selectedDBs
82 | disable <== acModel.muting
83 | },
84 | new Line {
85 | startX = 9
86 | startY = 97
87 | endX = 309
88 | endY = 97
89 | stroke = Color.color(0.66, 0.67, 0.69)
90 | },
91 | new Text {
92 | layoutX = 18
93 | layoutY = 113
94 | textOrigin = VPos.Top
95 | fill = Color.web("#131021")
96 | text = "Muting"
97 | font = Font.font("SanSerif", FontWeight.Bold, 18)
98 | },
99 | new CheckBox {
100 | layoutX = 280
101 | layoutY = 113
102 | selected <==> acModel.muting
103 | },
104 | new Line {
105 | startX = 9
106 | startY = 141
107 | endX = 309
108 | endY = 141
109 | stroke = Color.color(0.66, 0.67, 0.69)
110 | },
111 | new Text {
112 | layoutX = 18
113 | layoutY = 154
114 | textOrigin = VPos.Top
115 | fill = Color.web("#131021")
116 | text = "Genre"
117 | font = Font.font("SanSerif", FontWeight.Bold, 18)
118 | },
119 | genreChoiceBox
120 | )
121 | }
122 | }
123 |
124 | acModel.genreSelectionModel = genreChoiceBox.selectionModel()
125 | acModel.addListenerToGenreSelectionModel()
126 | acModel.genreSelectionModel.selectFirst()
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/proscalafx/ch01/audioconfig/AudioConfigModel.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch01.audioconfig
2 |
3 | import scalafx.beans.property.{BooleanProperty, IntegerProperty}
4 | import scalafx.collections.ObservableBuffer
5 | import scalafx.scene.control.SingleSelectionModel
6 |
7 | class AudioConfigModel {
8 |
9 | /** The minimum audio volume in decibels */
10 | val minDecibels = 0.0
11 |
12 | /** The maximum audio volume in decibels */
13 | val maxDecibels = 160.0
14 |
15 | /** The selected audio volume in decibels */
16 | val selectedDBs = IntegerProperty(0)
17 |
18 | /** Indicates whether audio is muted */
19 | val muting = BooleanProperty(false)
20 |
21 | /**
22 | * List of some musical genres
23 | */
24 | val genres = ObservableBuffer("Chamber", "Country", "Cowbell", "Metal", "Polka", "Rock")
25 |
26 | /** A reference to the selection model used by the Slider */
27 | var genreSelectionModel: SingleSelectionModel[String] = _
28 |
29 | /**
30 | * Adds a change listener to the selection model of the ChoiceBox, and contains
31 | * code that executes when the selection in the ChoiceBox changes.
32 | */
33 | def addListenerToGenreSelectionModel(): Unit = {
34 | this.genreSelectionModel.selectedIndex.onChange {
35 | selectedDBs.value = this.genreSelectionModel.selectedIndex() match {
36 | case 0 => 80
37 | case 1 => 100
38 | case 2 => 150
39 | case 3 => 140
40 | case 4 => 120
41 | case 5 => 130
42 | }
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/proscalafx/ch01/helloearthrise/HelloEarthRiseMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch01.helloearthrise
2 |
3 | import scalafx.animation.{Interpolator, Timeline, TranslateTransition}
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.VPos
7 | import scalafx.scene.image.{Image, ImageView}
8 | import scalafx.scene.paint.Color
9 | import scalafx.scene.shape.Rectangle
10 | import scalafx.scene.text.{Font, FontWeight, Text, TextAlignment}
11 | import scalafx.scene.{Group, Scene}
12 | import scalafx.util.Duration
13 |
14 | import java.io.File
15 |
16 | /** Main class for the "Hello World" style example. */
17 | object HelloEarthRiseMain extends JFXApp3 {
18 |
19 | override def start(): Unit = {
20 |
21 | val message =
22 | """Earthrise at Christmas:
23 | |[Forty] years ago this Christmas, a turbulent world
24 | |looked to the heavens for a unique view of our home
25 | |planet. This photo of Earthrise over the lunar horizon
26 | |was taken by the Apollo 8 crew in December 1968, showing
27 | |Earth for the first time as it appears from deep space.
28 | |Astronauts Frank Borman, Jim Lovell and William Anders
29 | |had become the first humans to leave Earth orbit,
30 | |entering lunar orbit on Christmas Eve. In a historic live
31 | |broadcast that night, the crew took turns reading from
32 | |the Book of Genesis, closing with a holiday wish from
33 | |Commander Borman: "We close with good night, good luck,
34 | |a Merry Christmas, and God bless all of you -- all of
35 | |you on the good Earth." """.stripMargin
36 |
37 | val textRef = new Text {
38 | layoutY = 100
39 | textOrigin = VPos.Top
40 | textAlignment = TextAlignment.Justify
41 | wrappingWidth = 400
42 | text = message
43 | fill = Color.rgb(187, 195, 107)
44 | font = Font.font("SansSerif", FontWeight.Bold, 24.0)
45 | }
46 |
47 | stage = new PrimaryStage {
48 | title = "Hello Earthrise"
49 | scene = new Scene(516, 387) {
50 | content = List(
51 | new ImageView(image = new Image(new File("media/earthrise.jpg").toURI.toString)),
52 | new Group {
53 | layoutX = 50
54 | layoutY = 180
55 | children = List(textRef)
56 | clip = Rectangle(430, 85)
57 | }
58 | )
59 | }
60 | }
61 |
62 | new TranslateTransition {
63 | cycleCount = Timeline.Indefinite
64 | duration = Duration(75000)
65 | node = textRef
66 | toY = -820
67 | interpolator = Interpolator.Linear
68 | }.play()
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/proscalafx/ch01/helloearthrise/HelloScrollPaneMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch01.helloearthrise
2 |
3 | import scalafx.animation.{Interpolator, Timeline, TranslateTransition}
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.VPos
7 | import scalafx.scene.Scene
8 | import scalafx.scene.control.ScrollPane
9 | import scalafx.scene.control.ScrollPane.ScrollBarPolicy
10 | import scalafx.scene.image.{Image, ImageView}
11 | import scalafx.scene.paint.Color
12 | import scalafx.scene.text.{Font, FontWeight, Text, TextAlignment}
13 | import scalafx.util.Duration
14 |
15 | import java.io.File
16 |
17 | object HelloScrollPaneMain extends JFXApp3 {
18 |
19 | override def start(): Unit = {
20 | val message =
21 | """Earthrise at Christmas:
22 | [Forty] years ago this Christmas, a turbulent world
23 | looked to the heavens for a unique view of our home
24 | planet. This photo of Earthrise over the lunar horizon
25 | was taken by the Apollo 8 crew in December 1968, showing
26 | Earth for the first time as it appears from deep space.
27 | Astronauts Frank Borman, Jim Lovell and William Anders
28 | had become the first humans to leave Earth orbit,
29 | entering lunar orbit on Christmas Eve. In a historic live
30 | broadcast that night, the crew took turns reading from
31 | the Book of Genesis, closing with a holiday wish from
32 | Commander Borman: "We close with good night, good luck,
33 | a Merry Christmas, and God bless all of you -- all of
34 | you on the good Earth." """.replace("\n", "")
35 |
36 | val textRef = new Text {
37 | layoutY = 100
38 | textOrigin = VPos.Top
39 | textAlignment = TextAlignment.Justify
40 | wrappingWidth = 400
41 | text = message
42 | fill = Color.rgb(187, 195, 107)
43 | font = Font.font("SansSerif", FontWeight.Bold, 24.0)
44 | }
45 |
46 | stage = new PrimaryStage {
47 | title = "Hello Earthrise"
48 | scene = new Scene(516, 387) {
49 | content = List(
50 | new ImageView(new Image(new File("media/earthrise.jpg").toURI.toString)),
51 | new ScrollPane {
52 | layoutX = 50
53 | layoutY = 180
54 | prefWidth = 440
55 | prefHeight = 85
56 | hbarPolicy = ScrollBarPolicy.Never
57 | vbarPolicy = ScrollBarPolicy.Never
58 | pannable = true
59 | content = textRef
60 | style = "-fx-background-color: transparent;"
61 | }
62 | )
63 | }
64 | }
65 |
66 | new TranslateTransition {
67 | cycleCount = Timeline.Indefinite
68 | duration = Duration(75000)
69 | node = textRef
70 | toY = -820
71 | interpolator = Interpolator.Linear
72 | }.play()
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/metronome1/Metronome1Main.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch02.metronome1
2 |
3 | import javafx.animation.Animation.Status
4 | import scalafx.Includes.*
5 | import scalafx.animation.{Interpolator, Timeline}
6 | import scalafx.application.JFXApp3
7 | import scalafx.application.JFXApp3.PrimaryStage
8 | import scalafx.beans.property.DoubleProperty
9 | import scalafx.scene.Scene
10 | import scalafx.scene.control.Button
11 | import scalafx.scene.layout.HBox
12 | import scalafx.scene.paint.Color
13 | import scalafx.scene.shape.Line
14 |
15 | import scala.language.postfixOps
16 |
17 | object Metronome1Main extends JFXApp3 {
18 |
19 | override def start(): Unit = {
20 |
21 | val startXVal = DoubleProperty(100.0)
22 |
23 | val anim = new Timeline {
24 | autoReverse = true
25 | keyFrames = Seq(
26 | at(0 s) {
27 | startXVal -> 100
28 | },
29 | at(1 s) {
30 | startXVal -> 300 tween Interpolator.Linear
31 | }
32 | )
33 | cycleCount = Timeline.Indefinite
34 | }
35 |
36 | stage = new PrimaryStage {
37 | title = "Metronome 1"
38 | scene = new Scene(400, 500) {
39 | content = List(
40 | new Line {
41 | startX <== startXVal
42 | startY = 50
43 | endX = 200
44 | endY = 400
45 | strokeWidth = 4
46 | stroke = Color.Blue
47 | },
48 | new HBox {
49 | layoutX = 60
50 | layoutY = 420
51 | spacing = 10
52 | children = List(
53 | new Button {
54 | text = "Start"
55 | onAction = () => anim.playFromStart()
56 | disable <== anim.status =!= Status.STOPPED
57 | },
58 | new Button {
59 | text = "Pause"
60 | onAction = () => anim.pause()
61 | disable <== anim.status =!= Status.RUNNING
62 | },
63 | new Button {
64 | text = "Resume"
65 | onAction = () => anim.play()
66 | disable <== anim.status =!= Status.PAUSED
67 | },
68 | new Button {
69 | text = "Stop"
70 | onAction = () => anim.stop()
71 | disable <== anim.status === Status.STOPPED
72 | }
73 | )
74 | }
75 | )
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/metronomepathtransition/MetronomePathTransitionMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch02.metronomepathtransition
2 |
3 | import scalafx.Includes.*
4 | import scalafx.animation.Animation.Status
5 | import scalafx.animation.PathTransition.OrientationType
6 | import scalafx.animation.{Interpolator, PathTransition, Timeline}
7 | import scalafx.application.JFXApp3
8 | import scalafx.application.JFXApp3.PrimaryStage
9 | import scalafx.scene.Scene
10 | import scalafx.scene.control.Button
11 | import scalafx.scene.layout.HBox
12 | import scalafx.scene.paint.Color
13 | import scalafx.scene.shape.{ArcTo, Ellipse, MoveTo, Path}
14 | import scalafx.util.Duration
15 |
16 | object MetronomePathTransitionMain extends JFXApp3 {
17 |
18 | override def start(): Unit = {
19 | val ellipse = new Ellipse {
20 | centerX = 100
21 | centerY = 50
22 | radiusX = 4
23 | radiusY = 8
24 | fill = Color.Blue
25 | }
26 |
27 | val anim = new PathTransition {
28 | duration = Duration(1000.0)
29 | node = ellipse
30 | path = new Path {
31 | elements = List(
32 | MoveTo(100, 50),
33 | ArcTo(350, 350, 0, 300, 50, largeArcFlag = false, sweepFlag = true)
34 | )
35 | }
36 | orientation = OrientationType.OrthogonalToTangent
37 | interpolator = Interpolator.Linear
38 | autoReverse = true
39 | cycleCount = Timeline.Indefinite
40 | }
41 |
42 | stage = new PrimaryStage {
43 | title = "Metronome using PathTransition"
44 | scene = new Scene(400, 500) {
45 | content = List(
46 | ellipse,
47 | new HBox {
48 | layoutX = 60
49 | layoutY = 420
50 | spacing = 10
51 | // NOTE: the `disable` bindings below compare value of a property to JavaFX constant
52 | children = List(
53 | new Button {
54 | text = "Start"
55 | onAction = () => anim.playFromStart()
56 | disable <== (anim.status =!= Status.Stopped.delegate)
57 | },
58 | new Button {
59 | text = "Pause"
60 | onAction = () => anim.pause()
61 | disable <== (anim.status =!= Status.Running.delegate)
62 | },
63 | new Button {
64 | text = "Resume"
65 | onAction = () => anim.play()
66 | disable <== (anim.status =!= Status.Paused.delegate)
67 | },
68 | new Button {
69 | text = "Stop"
70 | onAction = () => anim.stop()
71 | disable <== (anim.status === Status.Stopped.delegate)
72 | }
73 | )
74 | }
75 | )
76 | }
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/metronometransition/MetronomeTransitionMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch02.metronometransition
2 |
3 | import javafx.animation.Animation.Status
4 | import scalafx.Includes.*
5 | import scalafx.animation.{Interpolator, Timeline, TranslateTransition}
6 | import scalafx.application.JFXApp3
7 | import scalafx.application.JFXApp3.PrimaryStage
8 | import scalafx.scene.Scene
9 | import scalafx.scene.control.Button
10 | import scalafx.scene.layout.HBox
11 | import scalafx.scene.paint.Color
12 | import scalafx.scene.shape.Circle
13 | import scalafx.util.Duration
14 |
15 | object MetronomeTransitionMain extends JFXApp3 {
16 | override def start(): Unit = {
17 |
18 | val circle = new Circle {
19 | centerX = 100
20 | centerY = 50
21 | radius = 4
22 | fill = Color.Blue
23 | }
24 |
25 | val anim = new TranslateTransition {
26 | duration = Duration(1000.0)
27 | node = circle
28 | fromX = 0
29 | toX = 200
30 | interpolator = Interpolator.Linear
31 | autoReverse = true
32 | cycleCount = Timeline.Indefinite
33 | }
34 |
35 | stage = new PrimaryStage {
36 | width = 400
37 | height = 500
38 | title = "Metronome using TranslateTransition"
39 | scene = new Scene(400, 500) {
40 | content = List(
41 | circle,
42 | new HBox {
43 | layoutX = 60
44 | layoutY = 420
45 | spacing = 10
46 | children = List(
47 | new Button {
48 | text = "Start"
49 | onAction = () => anim.playFromStart()
50 | disable <== anim.status.isNotEqualTo(Status.STOPPED)
51 | },
52 | new Button {
53 | text = "Pause"
54 | onAction = () => anim.pause()
55 | disable <== anim.status.isNotEqualTo(Status.RUNNING)
56 | },
57 | new Button {
58 | text = "Resume"
59 | onAction = () => anim.play()
60 | disable <== anim.status.isNotEqualTo(Status.PAUSED)
61 | },
62 | new Button {
63 | text = "Stop"
64 | onAction = () => anim.stop()
65 | disable <== anim.status.isEqualTo(Status.STOPPED)
66 | }
67 | )
68 | }
69 | )
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/onthescene/changeOfScene.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011, Pro JavaFX Authors
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | * 1. Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * 2. Redistributions in binary form must reproduce the above copyright notice,
10 | * this list of conditions and the following disclaimer in the documentation
11 | * and/or other materials provided with the distribution.
12 | * 3. Neither the name of JFXtras nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | *
28 | * changeOfScene.css - A CSS (Cascading Style Sheet) file the is referred to by
29 | * the OnTheSceneMain.fx program to apply styles to the scene and its nodes.
30 | * Note: Syntax recommendation http://www.w3.org/TR/REC-CSS2/
31 | *
32 | * Developed 2011 by James L. Weaver jim.weaver [at] javafxpert.com
33 | * as a JavaFX SDK 2.0 example for the Pro JavaFX book.
34 | */
35 |
36 | #stageX, #stageY {
37 | -fx-padding: 3;
38 | -fx-border-color: blue;
39 | -fx-stroke-dash-array: 12 2 4 2;
40 | -fx-border-width: 4;
41 | -fx-border-radius: 5;
42 | }
43 |
44 | .emphasized-text {
45 | -fx-font-size: 14pt;
46 | -fx-font-weight: bold;
47 | -fx-font-style: normal;
48 | }
49 |
50 | .radio-button *.radio {
51 | -fx-padding: 10;
52 | -fx-background-color: red, yellow;
53 | -fx-background-insets: 0, 5;
54 | -fx-background-radius: 30, 20;
55 | }
56 |
57 | .radio-button:focused *.radio {
58 | -fx-background-color: blue, red, yellow;
59 | -fx-background-insets: -5, 0, 5;
60 | -fx-background-radius: 40, 30, 20;
61 | }
62 |
63 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/onthescene/onTheScene.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011, Pro JavaFX Authors
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | * 1. Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * 2. Redistributions in binary form must reproduce the above copyright notice,
10 | * this list of conditions and the following disclaimer in the documentation
11 | * and/or other materials provided with the distribution.
12 | * 3. Neither the name of JFXtras nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | *
28 | * onTheScene.css - A CSS (Cascading Style Sheet) file the is referred to by
29 | * the OnTheSceneMain.fx program to apply styles to the scene and its nodes.
30 | * Note: Syntax recommendation http://www.w3.org/TR/REC-CSS2/
31 | *
32 | * Developed 2011 by James L. Weaver jim.weaver [at] javafxpert.com
33 | * as a JavaFX SDK 2.0 example for the Pro JavaFX book.
34 | */
35 |
36 | #stageX, #stageY {
37 | -fx-padding: 1;
38 | -fx-border-color: black;
39 | -fx-border-style: dashed;
40 | -fx-border-width: 2;
41 | -fx-border-radius: 5;
42 | }
43 |
44 | .emphasized-text {
45 | -fx-font-size: 14pt;
46 | -fx-font-weight: normal;
47 | -fx-font-style: italic;
48 | }
49 |
50 | .choice-box:hover {
51 | -fx-scale-x: 1.1;
52 | -fx-scale-y: 1.1;
53 | }
54 |
55 | .radio-button .radio {
56 | -fx-background-color: -fx-shadow-highlight-color, -fx-outer-border,
57 | -fx-inner-border, -fx-body-color;
58 | -fx-background-insets: 0 0 -1 0, 0, 1, 2;
59 | -fx-background-radius: 1.0em;
60 | -fx-padding: 0.333333em;
61 | }
62 |
63 | .radio-button:focused .radio {
64 | -fx-background-color: -fx-focus-color, -fx-outer-border,
65 | -fx-inner-border, -fx-body-color;
66 | -fx-background-radius: 1.0em;
67 | -fx-background-insets: -1.4, 0, 1, 2;
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/stagecoach/StageCoachMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch02.stagecoach
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.beans.property.StringProperty
7 | import scalafx.geometry.VPos
8 | import scalafx.scene.control.{Button, CheckBox, Label, TextField}
9 | import scalafx.scene.layout.{HBox, VBox}
10 | import scalafx.scene.paint.Color
11 | import scalafx.scene.shape.Rectangle
12 | import scalafx.scene.text.Text
13 | import scalafx.scene.{Group, Scene}
14 | import scalafx.stage.{Screen, StageStyle}
15 |
16 | /**
17 | * Stage property example.
18 | *
19 | * Can be run with various command line parameters to control stage style:
20 | * decorated - a solid white background and platform decorations (default).
21 | * transparent - transparent background and no decorations.
22 | * undecorated - a solid white background and no decorations.
23 | * utility - a solid white background and minimal platform decorations used for a utility window.
24 | *
25 | * @author Rafael
26 | */
27 | object StageCoachMain extends JFXApp3 {
28 |
29 | override def start(): Unit = {
30 |
31 | val titleProperty = StringProperty("")
32 |
33 | // Process command line parameters
34 | val stageStyle = parameters.unnamed match {
35 | case Seq("transparent") => StageStyle.Transparent
36 | case Seq("undecorated") => StageStyle.Undecorated
37 | case Seq("utility") => StageStyle.Utility
38 | case _ => StageStyle.Decorated
39 | }
40 |
41 | val textStageX = new Text {
42 | textOrigin = VPos.Top
43 | }
44 | val textStageY = new Text {
45 | textOrigin = VPos.Top
46 | }
47 | val textStageW = new Text {
48 | textOrigin = VPos.Top
49 | }
50 | val textStageH = new Text {
51 | textOrigin = VPos.Top
52 | }
53 | val textStageF = new Text {
54 | textOrigin = VPos.Top
55 | }
56 | val checkBoxResizable = new CheckBox {
57 | text = "resizable"
58 | disable = stageStyle == StageStyle.Transparent || stageStyle == StageStyle.Undecorated
59 | }
60 | val checkBoxFullScreen = new CheckBox {
61 | text = "fullScreen"
62 | }
63 | val titleTextField = new TextField {
64 | text = "Stage Coach"
65 | }
66 |
67 | stage = new PrimaryStage {
68 | resizable = false
69 | title <== titleProperty
70 | scene = new Scene(270, 370) {
71 | fill = Color.Transparent
72 | content = new Group {
73 | children = List(
74 | new Rectangle {
75 | width = 250
76 | height = 350
77 | arcWidth = 50
78 | arcHeight = 50
79 | fill = Color.SkyBlue
80 | },
81 | new VBox {
82 | layoutX = 30
83 | layoutY = 20
84 | spacing = 10
85 | children = List(
86 | textStageX,
87 | textStageY,
88 | textStageW,
89 | textStageH,
90 | textStageF,
91 | checkBoxResizable,
92 | checkBoxFullScreen,
93 | new HBox {
94 | spacing = 10
95 | children = List(
96 | new Label("title:"),
97 | titleTextField
98 | )
99 | },
100 | new Button {
101 | text = "toBack()"
102 | onAction = () => stage.toBack()
103 | },
104 | new Button {
105 | text = "toFront()"
106 | onAction = () => stage.toFront()
107 | },
108 | new Button {
109 | text = "close()"
110 | onAction = () => stage.close()
111 | }
112 | )
113 | }
114 | )
115 | }
116 | }
117 | }
118 |
119 | // when mouse button is pressed, save the initial position of screen
120 | val rootGroup = stage.scene().content(0)
121 | var dragAnchorX = 0.0
122 | var dragAnchorY = 0.0
123 | rootGroup.onMousePressed = me => {
124 | dragAnchorX = me.screenX - stage.x.value
125 | dragAnchorY = me.screenY - stage.y.value
126 | }
127 | rootGroup.onMouseDragged = me => {
128 | stage.x = me.screenX - dragAnchorX
129 | stage.y = me.screenY - dragAnchorY
130 | }
131 |
132 | textStageX.text <== new StringProperty("x: ") + stage.x.asString
133 | textStageY.text <== new StringProperty("y: ") + stage.y.asString
134 | textStageW.text <== new StringProperty("width: ") + stage.width.asString
135 | textStageH.text <== new StringProperty("height: ") + stage.height.asString
136 | textStageF.text <== new StringProperty("focused: ") + stage.focused.asString
137 | stage.resizable = false
138 | stage.resizable <==> checkBoxResizable.selected
139 | checkBoxFullScreen.onAction = () => stage.fullScreen = checkBoxFullScreen.selected()
140 |
141 | stage.title <== titleTextField.text
142 |
143 | stage.initStyle(stageStyle)
144 | stage.onCloseRequest = () => println("Stage is closing")
145 | stage.show()
146 |
147 | val primScreenBounds = Screen.primary.visualBounds
148 | stage.x = (primScreenBounds.width - stage.width()) / 2
149 | stage.y = (primScreenBounds.height - stage.height()) / 2
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/proscalafx/ch02/zenpong/ZenPongMain.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch02.zenpong
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.scene.Scene
6 | import scalafx.scene.paint.{Color, CycleMethod, LinearGradient, Stop}
7 |
8 | import scala.language.postfixOps
9 |
10 | object ZenPongMain extends JFXApp3 {
11 |
12 | override def start(): Unit = {
13 |
14 | val zenPong = new ZenPong()
15 |
16 | stage = new PrimaryStage {
17 | title = "ZenPong Example"
18 | scene = new Scene(500, 500) {
19 | fill = LinearGradient(
20 | startX = 0.0,
21 | startY = 0.0,
22 | endX = 0.0,
23 | endY = 1.0,
24 | proportional = true,
25 | cycleMethod = CycleMethod.NoCycle,
26 | stops = List(Stop(0.0, Color.Black), Stop(0.0, Color.Gray))
27 | )
28 | content = zenPong.pongComponents
29 | }
30 | }
31 |
32 | zenPong.initialize()
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/BidirectionalBindingExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.beans.property.StringProperty
4 |
5 | object BidirectionalBindingExample extends App {
6 | println("Constructing two StringProperty objects.")
7 | val prop1 = new StringProperty("")
8 | val prop2 = new StringProperty("")
9 |
10 | println("Calling bindBidirectional (<==>).")
11 | prop2 <==> prop1
12 |
13 | println("prop1.isBound = " + prop1.isBound)
14 | println("prop2.isBound = " + prop2.isBound)
15 |
16 | println("Calling prop1.set(\"prop1 says: Hi!\")")
17 | prop1() = "prop1 says: Hi!"
18 | println("prop2.get returned:")
19 | println(prop2())
20 |
21 | println("""Calling prop2.set(prop2.get + "\nprop2 says: Bye!")""")
22 | prop2() = prop2() + "\nprop2 says: Bye!"
23 | println("prop1.get returned:")
24 | println(prop1())
25 | }
26 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/HeronsFormulaDirectExtensionExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.Includes.*
4 | import scalafx.beans.binding.Bindings
5 | import scalafx.beans.property.DoubleProperty
6 |
7 | object HeronsFormulaDirectExtensionExample extends App {
8 | val a = DoubleProperty(0)
9 | val b = DoubleProperty(0)
10 | val c = DoubleProperty(0)
11 |
12 | val area = Bindings.createDoubleBinding(
13 | () => {
14 | val a0 = a()
15 | val b0 = b()
16 | val c0 = c()
17 |
18 | if ((a0 + b0 > c0) && (b0 + c0 > a0) && (c0 + a0 > b0)) {
19 | val s = (a0 + b0 + c0) / 2.0d
20 | math.sqrt(s * (s - a0) * (s - b0) * (s - c0))
21 | } else {
22 | 0
23 | }
24 | },
25 | a,
26 | b,
27 | c
28 | )
29 |
30 | // Use braces "()" to access values hold by properties, bindings, like `area`, and numeric expressions.
31 | a() = 3
32 | b() = 4
33 | c() = 5
34 | printf(
35 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
36 | " the area of the triangle is %3.2f\n",
37 | a(),
38 | b(),
39 | c(),
40 | area()
41 | )
42 |
43 | a() = 2
44 | b() = 2
45 | c() = 2
46 | printf(
47 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
48 | " the area of the triangle is %3.2f\n",
49 | a(),
50 | b(),
51 | c(),
52 | area()
53 | )
54 |
55 | }
56 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/HeronsFormulaExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.Includes.*
4 | import scalafx.beans.property.DoubleProperty
5 |
6 | object HeronsFormulaExample extends App {
7 | val a = DoubleProperty(0.0)
8 | val b = DoubleProperty(0)
9 | val c = DoubleProperty(0)
10 |
11 | val s = (a + b + c) / 2.0
12 |
13 | val areaSquared =
14 | when(((a + b) > c) && ((b + c) > a) && ((a + c) > b)) choose (s * (s - a) * (s - b) * (s - c)) otherwise 0.0
15 |
16 | a() = 3
17 | b() = 4
18 | c() = 5
19 |
20 | printf(
21 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
22 | " the area of the triangle is %3.2f\n",
23 | a(),
24 | b(),
25 | c(),
26 | math.sqrt(areaSquared.get)
27 | )
28 |
29 | a() = 2
30 | b() = 2
31 | c() = 2
32 |
33 | printf(
34 | "Given sides a = %1.0f, b = %1.0f, and c = %1.0f," +
35 | " the area of the triangle is %3.2f\n",
36 | a(),
37 | b(),
38 | c(),
39 | math.sqrt(areaSquared.get)
40 | )
41 | }
42 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/MotivatingExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.beans.property.IntegerProperty
4 |
5 | object MotivatingExample extends App {
6 |
7 | var intProperty: IntegerProperty = _
8 |
9 | def createProperty(): Unit = {
10 | println()
11 | intProperty = IntegerProperty(1024)
12 | println("intProperty = " + intProperty)
13 | println("intProperty.get = " + intProperty.get)
14 | println("intProperty.value = " + intProperty.value)
15 | println("intProperty() = " + intProperty())
16 | }
17 |
18 | def addAndRemoveInvalidationListener(): Unit = {
19 | println()
20 | val subscription = intProperty.onInvalidate {
21 | observable => println("The observable has been invalidated: " + observable + ".")
22 | }
23 |
24 | println("Added invalidation listener.")
25 |
26 | println("Calling intProperty.set(2048).")
27 | intProperty() = 2048
28 |
29 | println("Calling intProperty.setValue(3072).")
30 | intProperty() = Integer.valueOf(3072)
31 |
32 | subscription.cancel()
33 | System.out.println("Removed invalidation listener.")
34 |
35 | println("Calling intProperty.set(4096).")
36 | intProperty() = 4096
37 | }
38 |
39 | def addAndRemoveChangeListener(): Unit = {
40 | println()
41 | val subscription = intProperty.onChange {
42 | (_, oldValue, newValue) =>
43 | println("The observableValue has changed: oldValue = " + oldValue + ", newValue = " + newValue)
44 | }
45 | println("Added change listener.")
46 |
47 | println("Calling intProperty.set(5120).")
48 | intProperty() = 5120
49 |
50 | subscription.cancel()
51 | println("Removed change listener.")
52 |
53 | println("Calling intProperty.set(6144).")
54 | intProperty() = 6144
55 | }
56 |
57 | def bindAndUnbindOnePropertyToAnother(): Unit = {
58 | println()
59 | val otherProperty = IntegerProperty(0)
60 | println("otherProperty() = " + otherProperty())
61 |
62 | println("Binding otherProperty to intProperty.")
63 | otherProperty <== intProperty
64 | println("otherProperty() = " + otherProperty())
65 |
66 | println("Calling intProperty.set(7168).")
67 | intProperty() = 7168
68 | println("otherProperty.get = " + otherProperty.get)
69 |
70 | println("Unbinding otherProperty from intProperty.")
71 | otherProperty.unbind()
72 | println("otherProperty() = " + otherProperty())
73 |
74 | println("Calling intProperty.set(8192).")
75 | intProperty() = 8192
76 | println("otherProperty() = " + otherProperty())
77 | }
78 |
79 | createProperty()
80 | addAndRemoveInvalidationListener()
81 | addAndRemoveChangeListener()
82 | bindAndUnbindOnePropertyToAnother()
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/NumericPropertiesExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.beans.property.{DoubleProperty, FloatProperty, IntegerProperty, LongProperty}
4 |
5 | object NumericPropertiesExample extends App {
6 | val i = new IntegerProperty(null, "i", 1024)
7 | val l = new LongProperty(null, "l", 0L)
8 | val f = new FloatProperty(null, "f", 0.0f)
9 | val d = new DoubleProperty(null, "d", 0.0)
10 | println("Constructed numerical properties i, l, f, d.")
11 |
12 | println("i = " + i())
13 | println("l = " + l())
14 | println("f = " + f())
15 | println("d = " + d())
16 |
17 | l <== i
18 | f <== l
19 | d <== f
20 | println("Bound l to i, f to l, d to f.")
21 |
22 | println("i = " + i())
23 | println("l = " + l())
24 | println("f = " + f())
25 | println("d = " + d())
26 |
27 | println("Calling i.set(2048).")
28 | i() = 2048
29 |
30 | println("i = " + i())
31 | println("l = " + l())
32 | println("f = " + f())
33 | println("d = " + d())
34 |
35 | d.unbind()
36 | f.unbind()
37 | l.unbind()
38 | println("Unbound l to i, f to l, d to f.")
39 |
40 | f <== d
41 | l <== f
42 | i <== l
43 | println("Bound f to d, l to f, i to l.")
44 |
45 | println("Calling d.set(10000000000L).")
46 | d() = 10000000000L
47 |
48 | println("d = " + d())
49 | println("f = " + f())
50 | println("l = " + l())
51 | println("i = " + i())
52 | }
53 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/RectangleAreaExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.beans.property.DoubleProperty
4 |
5 | object RectangleAreaExample extends App {
6 | println("Constructing x with initial value of 2.0.")
7 | val x = new DoubleProperty(null, "x", 2.0)
8 | println("Constructing y with initial value of 3.0.")
9 | val y = new DoubleProperty(null, "y", 3.0)
10 | println("Creating binding area with dependencies x and y.")
11 | val area = x * y
12 | println("area = " + area())
13 | println("Setting x to 5")
14 | x() = 5
15 | println("Setting y to 7")
16 | y() = 7
17 | println("area = " + area())
18 | }
19 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/TriangleAreaExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.beans.binding.NumberBinding
4 | import scalafx.beans.property.IntegerProperty
5 |
6 | object TriangleAreaExample extends App {
7 |
8 | def printResult(
9 | x1: IntegerProperty,
10 | y1: IntegerProperty,
11 | x2: IntegerProperty,
12 | y2: IntegerProperty,
13 | x3: IntegerProperty,
14 | y3: IntegerProperty,
15 | area: NumberBinding
16 | ): Unit = {
17 | println("For A(%d,%d), B(%d,%d), C(%d,%d), the area of triangle ABC is %1.1f".format(
18 | x1(),
19 | y1(),
20 | x2(),
21 | y2(),
22 | x3(),
23 | y3(),
24 | area()
25 | ))
26 | }
27 |
28 | val x1 = IntegerProperty(0)
29 | val y1 = IntegerProperty(0)
30 | val x2 = IntegerProperty(0)
31 | val y2 = IntegerProperty(0)
32 | val x3 = IntegerProperty(0)
33 | val y3 = IntegerProperty(0)
34 |
35 | val x1y2 = x1 * y2
36 | val x2y3 = x2 * y3
37 | val x3y1 = x3 * y1
38 | val x1y3 = x1 * y3
39 | val x2y1 = x2 * y1
40 | val x3y2 = x3 * y2
41 |
42 | val sum1 = x1y2 + x2y3
43 | val sum2 = sum1 + x3y1
44 | val sum3 = sum2 + x3y1
45 | val diff1 = sum3 - x1y3
46 | val diff2 = diff1 - x2y1
47 | val determinant = diff2 - x3y2
48 | val area = determinant / 2.0d
49 |
50 | x1() = 0
51 | y1() = 0
52 | x2() = 6
53 | y2() = 0
54 | x3() = 4
55 | y3() = 3
56 |
57 | printResult(x1, y1, x2, y2, x3, y3, area)
58 |
59 | x1() = 1
60 | y1() = 0
61 | x2() = 2
62 | y2() = 2
63 | x3() = 0
64 | y3() = 1
65 |
66 | printResult(x1, y1, x2, y2, x3, y3, area)
67 | }
68 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/TriangleAreaFluentExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03
2 |
3 | import scalafx.beans.binding.Bindings
4 | import scalafx.beans.property.IntegerProperty
5 |
6 | object TriangleAreaFluentExample extends App {
7 | val x1 = IntegerProperty(0)
8 | val y1 = IntegerProperty(0)
9 | val x2 = IntegerProperty(0)
10 | val y2 = IntegerProperty(0)
11 | val x3 = IntegerProperty(0)
12 | val y3 = IntegerProperty(0)
13 |
14 | val area = ((x1 * y2) + (x2 * y3) + (x3 * y1) - (x1 * y3) - (x2 * y1) - (x3 * y2)) / 2.0
15 |
16 | val output = Bindings.createStringBinding(
17 | () =>
18 | f"For A(${x1()},${y1()}), B(${x2()},${y2()}), C(${x3()},${y3()}), the area of triangle ABC is ${area().doubleValue()}%.1f",
19 | x1,
20 | y1,
21 | x2,
22 | y2,
23 | x3,
24 | y3,
25 | area
26 | )
27 |
28 | x1() = 0
29 | y1() = 0
30 | x2() = 6
31 | y2() = 0
32 | x3() = 4
33 | y3() = 3
34 |
35 | println(output())
36 |
37 | x1() = 1
38 | y1() = 0
39 | x2() = 2
40 | y2() = 2
41 | x3() = 0
42 | y3() = 1
43 |
44 | println(output())
45 | }
46 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/scalafxbean/ScalaFXBeanControllerExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03.scalafxbean
2 |
3 | import scalafx.scene.paint.Color
4 |
5 | class ScalaFXBeanControllerExample(model: ScalaFXBeanModelExample, view: ScalaFXBeanViewExample) {
6 |
7 | def incrementIPropertyOnModel(): Unit = {
8 | model.i() = model.i() + 1
9 | }
10 |
11 | def changeStrPropertyOnModel(): Unit = {
12 | val str = model.str()
13 | model.str() = if (str == "Hello") "World" else "Hello"
14 | }
15 |
16 | def switchColorPropertyOnModel(): Unit = {
17 | val color = model.color()
18 | model.color() = if (color == Color.Black) Color.White else Color.Black
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/scalafxbean/ScalaFXBeanMainExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03.scalafxbean
2 |
3 | object ScalaFXBeanMainExample extends App {
4 | val model = new ScalaFXBeanModelExample()
5 | val view = new ScalaFXBeanViewExample(model)
6 | val controller = new ScalaFXBeanControllerExample(model, view)
7 |
8 | controller.incrementIPropertyOnModel()
9 | controller.changeStrPropertyOnModel()
10 | controller.switchColorPropertyOnModel()
11 | controller.incrementIPropertyOnModel()
12 | controller.changeStrPropertyOnModel()
13 | controller.switchColorPropertyOnModel()
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/scalafxbean/ScalaFXBeanModelExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03.scalafxbean
2 |
3 | import scalafx.beans.property.{IntegerProperty, ObjectProperty, StringProperty}
4 | import scalafx.scene.paint.Color
5 |
6 | class ScalaFXBeanModelExample {
7 |
8 | val i = new IntegerProperty(this, "i", 0)
9 |
10 | def i_=(value: Int): Unit = {
11 | i() = value
12 | }
13 |
14 | val str = new StringProperty(this, "str", "Hello")
15 |
16 | def str_=(value: String): Unit = {
17 | str() = value
18 | }
19 |
20 | val color = new ObjectProperty[Color](this, "color", Color.Black)
21 |
22 | def color_=(value: Color): Unit = {
23 | color() = value
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/src/proscalafx/ch03/scalafxbean/ScalaFXBeanViewExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch03.scalafxbean
2 |
3 | class ScalaFXBeanViewExample(val model: ScalaFXBeanModelExample) {
4 |
5 | model.i.onChange((_, oldValue, newValue) => {
6 | println("Property i changed: old value = " + oldValue + ", new value = " + newValue)
7 | })
8 |
9 | model.str.onChange((_, oldValue, newValue) => {
10 | println("Property str changed: old value = " + oldValue + ", new value = " + newValue)
11 | })
12 |
13 | model.color.onChange((_, oldValue, newValue) => {
14 | println("Property color changed: old value = " + oldValue + ", new value = " + newValue)
15 | })
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/examples/AlignUsingStackAndTile.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.examples
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.Pos
7 | import scalafx.scene.Scene
8 | import scalafx.scene.layout.{StackPane, TilePane}
9 | import scalafx.scene.paint.Color
10 | import scalafx.scene.text.{Font, FontWeight, Text}
11 |
12 | object AlignUsingStackAndTile extends JFXApp3 {
13 |
14 | override def start(): Unit = {
15 |
16 | val left = new StackPane {
17 | style = "-fx-background-color: black"
18 | children = new Text {
19 | text = "ScalaFX"
20 | font = Font.font(null, FontWeight.Bold, 18)
21 | fill = Color.White
22 | alignmentInParent = Pos.CenterRight
23 | }
24 | }
25 |
26 | stage = new PrimaryStage {
27 | scene = new Scene(400, 100) {
28 | content = new TilePane {
29 | snapToPixel = false
30 | children = List(
31 | left,
32 | new Text {
33 | text = "Reversi"
34 | font = Font.font(null, FontWeight.Bold, 18)
35 | alignmentInParent = Pos.CenterLeft
36 | }
37 | )
38 | }
39 | }
40 | }
41 |
42 | left.prefWidth <== stage.scene().width / 2
43 | left.prefHeight <== stage.scene().height
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/examples/BorderLayoutExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.examples
2 |
3 | import proscalafx.ch04.reversi.model.{Black, Owner, ReversiModel, White}
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.geometry.Pos
8 | import scalafx.scene.effect.{DropShadow, InnerShadow}
9 | import scalafx.scene.layout.*
10 | import scalafx.scene.paint.Color
11 | import scalafx.scene.shape.Ellipse
12 | import scalafx.scene.text.{Font, FontWeight, Text}
13 | import scalafx.scene.{Node, Scene}
14 |
15 | object BorderLayoutExample extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val borderPane = new BorderPane {
20 | top = createTitle
21 | center = createBackground
22 | bottom = createScoreBoxes
23 | }
24 |
25 | stage = new PrimaryStage {
26 | scene = new Scene(600, 400) {
27 | // Assign borderPane directly to `root` to avoid layout issues.
28 | // If assigned to `content` there will be `Group` node at root that interferes with automatic rescaling.
29 | root = borderPane
30 | }
31 | }
32 | }
33 |
34 | // ---------------------------------------------------------------------------
35 |
36 | private def createTitle = new TilePane {
37 | snapToPixel = false
38 | children = List(
39 | new StackPane {
40 | style = "-fx-background-color: black"
41 | children = new Text("ScalaFX") {
42 | font = Font.font(null, FontWeight.Bold, 18)
43 | fill = Color.White
44 | alignmentInParent = Pos.CenterRight
45 | }
46 | },
47 | new Text("Reversi") {
48 | font = Font.font(null, FontWeight.Bold, 18)
49 | alignmentInParent = Pos.CenterLeft
50 | }
51 | )
52 | prefTileHeight = 40
53 | prefTileWidth <== parent.selectDouble("width") / 2
54 | }
55 |
56 | private def createBackground = new Region {
57 | style = "-fx-background-color: radial-gradient(radius 100%, white, gray)"
58 | }
59 |
60 | private def createScoreBoxes = new TilePane {
61 | snapToPixel = false
62 | prefColumns = 2
63 | children = List(
64 | createScore(Black),
65 | createScore(White)
66 | )
67 | prefTileWidth <== parent.selectDouble("width") / 2
68 | }
69 |
70 | private def createScore(owner: Owner): Node = {
71 |
72 | val innerShadow = new InnerShadow() {
73 | color = Color.DodgerBlue
74 | choke = 0.5
75 | }
76 | val backgroundRegion = new Region {
77 | style = "-fx-background-color: " + owner.opposite.colorStyle
78 | if (Black == owner) {
79 | effect = innerShadow
80 | }
81 | }
82 |
83 | val dropShadow = new DropShadow() {
84 | color = Color.DodgerBlue
85 | spread = 0.2
86 | }
87 |
88 | val piece = new Ellipse {
89 | radiusX = 32
90 | radiusY = 20
91 | fill = owner.color
92 | if (Black == owner) {
93 | effect = dropShadow
94 | }
95 | }
96 |
97 | val score = new Text {
98 | font = Font.font(null, FontWeight.Bold, 100)
99 | fill = owner.color
100 | text <== ReversiModel.score(owner).asString
101 | }
102 |
103 | val remaining = new Text {
104 | font = Font.font(null, FontWeight.Bold, 12)
105 | fill = owner.color
106 | text <== ReversiModel.turnsRemaining(owner).asString() + " turns remaining"
107 | }
108 |
109 | new StackPane {
110 | children = List(
111 | backgroundRegion,
112 | new FlowPane {
113 | hgap = 20
114 | vgap = 10
115 | alignment = Pos.Center
116 | children = List(
117 | score,
118 | new VBox {
119 | alignment = Pos.Center
120 | spacing = 10
121 | children = List(
122 | piece,
123 | remaining
124 | )
125 | }
126 | )
127 | }
128 | )
129 | }
130 | }
131 |
132 | }
133 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/examples/CenterUsingBind.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.examples
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.VPos
7 | import scalafx.scene.Scene
8 | import scalafx.scene.text.{Font, FontWeight, Text}
9 |
10 | object CenterUsingBind extends JFXApp3 {
11 |
12 | override def start(): Unit = {
13 |
14 | val text = new Text("ScalaFX Reversi") {
15 | textOrigin = VPos.Top
16 | font = Font.font(null, FontWeight.Bold, 18)
17 | }
18 |
19 | stage = new PrimaryStage {
20 | scene = new Scene(400, 100) {
21 | content = text
22 | }
23 | }
24 |
25 | text.layoutX <== (stage.scene().width - text.prefWidth(-1)) / 2
26 | text.layoutY <== (stage.scene().height - text.prefHeight(-1)) / 2
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/examples/CenterUsingStack.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.examples
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.scene.Scene
7 | import scalafx.scene.layout.StackPane
8 | import scalafx.scene.paint.Color
9 | import scalafx.scene.shape.Ellipse
10 | import scalafx.scene.text.{Font, FontWeight, Text}
11 |
12 | object CenterUsingStack extends JFXApp3 {
13 |
14 | override def start(): Unit = {
15 |
16 | val ellipse = new Ellipse
17 |
18 | stage = new PrimaryStage {
19 | scene = new Scene(400, 100) {
20 | content = new StackPane {
21 | children = List(
22 | ellipse,
23 | new Text("ScalaFX Reversi") {
24 | font = Font.font(null, FontWeight.Bold, 18)
25 | fill = Color.White
26 | }
27 | )
28 | }
29 | }
30 | }
31 |
32 | ellipse.radiusX <== stage.scene().width / 2
33 | ellipse.radiusY <== stage.scene().height / 2
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/examples/PlayerScoreExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.examples
2 |
3 | import proscalafx.ch04.reversi.model.{Black, Owner, ReversiModel, White}
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.geometry.Pos
8 | import scalafx.scene.Scene
9 | import scalafx.scene.effect.{DropShadow, InnerShadow}
10 | import scalafx.scene.layout.*
11 | import scalafx.scene.paint.Color
12 | import scalafx.scene.shape.Ellipse
13 | import scalafx.scene.text.{Font, FontWeight, Text}
14 |
15 | object PlayerScoreExample extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val tiles = new TilePane() {
20 | snapToPixel = false
21 | children = List(
22 | createScore(Black),
23 | createScore(White)
24 | )
25 | }
26 |
27 | stage = new PrimaryStage() {
28 | scene = new Scene(600, 140) {
29 | content = tiles
30 | }
31 | }
32 |
33 | tiles.prefTileWidth <== stage.scene().width / 2
34 | tiles.prefTileHeight <== stage.scene().height
35 | }
36 |
37 | // ---------------------------------------------------------------------------
38 |
39 | private def createScore(owner: Owner): StackPane = {
40 |
41 | val innerShadow = new InnerShadow() {
42 | color = Color.DodgerBlue
43 | choke = 0.5
44 | }
45 |
46 | val backgroundRegion = new Region() {
47 | style = "-fx-background-color: " + owner.opposite.colorStyle
48 | if (Black == owner) {
49 | effect = innerShadow
50 | }
51 | }
52 |
53 | val dropShadow = new DropShadow() {
54 | color = Color.DodgerBlue
55 | spread = 0.2
56 | }
57 |
58 | val piece = new Ellipse() {
59 | radiusX = 32
60 | radiusY = 20
61 | fill = owner.color
62 | if (Black == owner) {
63 | effect = dropShadow
64 | }
65 | }
66 |
67 | val score = new Text() {
68 | font = Font.font(null, FontWeight.Bold, 100)
69 | fill = owner.color
70 | text <== ReversiModel.score(owner).asString()
71 | }
72 |
73 | val remaining = new Text() {
74 | font = Font.font(null, FontWeight.Bold, 12)
75 | fill = owner.color
76 | text <== ReversiModel.turnsRemaining(owner).asString() + " turns remaining"
77 | }
78 |
79 | new StackPane() {
80 | children = List(
81 | backgroundRegion,
82 | new FlowPane() {
83 | hgap = 20
84 | vgap = 10
85 | alignment = Pos.Center
86 | children = List(
87 | score,
88 | new VBox() {
89 | alignment = Pos.Center
90 | spacing = 10
91 | children = List(
92 | piece,
93 | remaining
94 | )
95 | }
96 | )
97 | }
98 | )
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/model/Owner.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.model
2 |
3 | import scalafx.scene.paint.Color
4 |
5 | sealed case class Owner(color: Color, colorStyle: String) {
6 |
7 | def opposite: Owner = this match {
8 | case White => Black
9 | case Black => White
10 | case _ => NONE
11 | }
12 |
13 | }
14 |
15 | object NONE extends Owner(Color.Transparent, "")
16 |
17 | object White extends Owner(Color.White, "white")
18 |
19 | object Black extends Owner(Color.Black, "black")
20 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/model/ReversiModel.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.model
2 |
3 | import scalafx.Includes.{when, *}
4 | import scalafx.beans.Observable
5 | import scalafx.beans.binding.*
6 | import scalafx.beans.property.ObjectProperty
7 |
8 | import scala.collection.mutable.ListBuffer
9 |
10 | object ReversiModel {
11 |
12 | val BOARD_SIZE = 8
13 |
14 | val turn = ObjectProperty[Owner](Black)
15 |
16 | val board = Array.tabulate(BOARD_SIZE, BOARD_SIZE)((_, _) => ObjectProperty[Owner](NONE))
17 |
18 | initBoard()
19 |
20 | private def initBoard(): Unit = {
21 | val center1 = BOARD_SIZE / 2 - 1
22 | val center2 = BOARD_SIZE / 2
23 | board(center1)(center1)() = White
24 | board(center1)(center2)() = Black
25 | board(center2)(center1)() = Black
26 | board(center2)(center2)() = White
27 | }
28 |
29 | def restart(): Unit = {
30 | board.flatten.foreach(_() = NONE)
31 |
32 | initBoard()
33 | turn() = Black
34 | }
35 |
36 | def score(owner: Owner): NumberExpression = {
37 | board.flatten.map(p => when(p === owner) choose 1 otherwise 0).reduce(_ + _)
38 | }
39 |
40 | def turnsRemaining(owner: Owner): NumberBinding = {
41 | val emptyCellCount = score(NONE)
42 |
43 | when(turn === owner) choose ((emptyCellCount + 1) / 2) otherwise (emptyCellCount / 2)
44 | }
45 |
46 | def legalMove(x: Int, y: Int): BooleanBinding = {
47 | (board(x)(y) === NONE) && (
48 | canFlip(x, y, 0, -1, turn) ||
49 | canFlip(x, y, -1, -1, turn) ||
50 | canFlip(x, y, -1, 0, turn) ||
51 | canFlip(x, y, -1, 1, turn) ||
52 | canFlip(x, y, 0, 1, turn) ||
53 | canFlip(x, y, 1, 1, turn) ||
54 | canFlip(x, y, 1, 0, turn) ||
55 | canFlip(x, y, 1, -1, turn)
56 | )
57 | }
58 |
59 | private def canFlip(
60 | cellX: Int,
61 | cellY: Int,
62 | directionX: Int,
63 | directionY: Int,
64 | turn: ObjectProperty[Owner]
65 | ): BooleanBinding = {
66 | // Build list of dependencies for binding
67 | val dependencies = ListBuffer.empty[Observable]
68 | var x = cellX + directionX
69 | var y = cellY + directionY
70 | while (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE) {
71 | dependencies += board(x)(y)
72 | x += directionX
73 | y += directionY
74 | }
75 | dependencies += turn
76 |
77 | Bindings.createBooleanBinding(
78 | // Flip evaluation
79 | () => {
80 | val turnVal = turn.get
81 | var x = cellX + directionX
82 | var y = cellY + directionY
83 | var first = true
84 | var result: Option[Boolean] = None
85 | while (
86 | result.isEmpty &&
87 | x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board(x)(y).get != NONE
88 | ) {
89 | if (board(x)(y).get == turnVal) {
90 | result = Option(!first)
91 | }
92 | first = false
93 | x += directionX
94 | y += directionY
95 | }
96 | result.getOrElse(false)
97 | },
98 | dependencies.toSeq*
99 | )
100 | }
101 |
102 | def play(cellX: Int, cellY: Int): Unit = {
103 | if (legalMove(cellX, cellY).get) {
104 | board(cellX)(cellY)() = turn()
105 | flip(cellX, cellY, 0, -1, turn)
106 | flip(cellX, cellY, -1, -1, turn)
107 | flip(cellX, cellY, -1, 0, turn)
108 | flip(cellX, cellY, -1, 1, turn)
109 | flip(cellX, cellY, 0, 1, turn)
110 | flip(cellX, cellY, 1, 1, turn)
111 | flip(cellX, cellY, 1, 0, turn)
112 | flip(cellX, cellY, 1, -1, turn)
113 | turn.value = turn.value.opposite
114 | }
115 | }
116 |
117 | def flip(cellX: Int, cellY: Int, directionX: Int, directionY: Int, turn: ObjectProperty[Owner]): Unit = {
118 | if (canFlip(cellX, cellY, directionX, directionY, turn).get) {
119 | var x = cellX + directionX
120 | var y = cellY + directionY
121 | while (x >= 0 && x < BOARD_SIZE && y >= 0 && y < BOARD_SIZE && board(x)(y)() != turn()) {
122 | board(x)(y)() = turn()
123 | x += directionX
124 | y += directionY
125 | }
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/reversipieces/ReversiPieceTest.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.reversipieces
2 |
3 | import proscalafx.ch04.reversi.model.{Black, White}
4 | import proscalafx.ch04.reversi.ui.{ReversiPiece, ReversiSquare}
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.scene.Scene
8 | import scalafx.scene.layout.{HBox, Priority, StackPane}
9 |
10 | object ReversiPieceTest extends JFXApp3 {
11 |
12 | override def start(): Unit = {
13 |
14 | // Unlike in many other examples, here content of the scene is assigned using `root` rather
15 | // than `content` properties.
16 | // `scalafx.scene.Scene` creates by default its root element as `Group`. If we assign using `content`, children
17 | // are added to that group. That may have undesired layout complications. In our case it will prevent automatic
18 | // resizing of the content.
19 | // To work around this, we assign to `root` directly.
20 | stage = new PrimaryStage() {
21 | scene = new Scene() {
22 | root = new HBox {
23 | snapToPixel = false
24 | children = List(
25 | new StackPane {
26 | children = List(
27 | new ReversiSquare(0, 0),
28 | new ReversiPiece(White)
29 | )
30 | hgrow = Priority.Always
31 | },
32 | new StackPane {
33 | children = List(
34 | new ReversiSquare(0, 0),
35 | new ReversiPiece(Black)
36 | )
37 | hgrow = Priority.Always
38 | }
39 | )
40 | }
41 | }
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/reversipieces/ReversiSquareTest.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.reversipieces
2 |
3 | import proscalafx.ch04.reversi.ui.ReversiSquare
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.scene.Scene
7 | import scalafx.scene.layout.StackPane
8 |
9 | object ReversiSquareTest extends JFXApp3 {
10 |
11 | override def start(): Unit = {
12 | // Unlike in many other examples, here content of the scene is assigned using `root` rather
13 | // than `content` properties.
14 | // `scalafx.scene.Scene` creates by default its root element as `Group`. If we assign using `content`, children
15 | // are added to that group. That may have undesired layout complications. In our case it will prevent automatic
16 | // resizing of the content.
17 | // To work around this, we assign to `root` directly.
18 | stage = new PrimaryStage {
19 | scene = new Scene() {
20 | root = new StackPane {
21 | children = new ReversiSquare(0, 0)
22 | }
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/ui/ReversiApp.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.ui
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.scene.Scene
6 | import scalafx.scene.layout.*
7 |
8 | object ReversiApp extends JFXApp3 {
9 |
10 | override def start(): Unit = {
11 |
12 | val reversi = new Reversi()
13 |
14 | stage = new PrimaryStage() {
15 | scene = new Scene(400, 600) {
16 | root = new AnchorPane() {
17 | children = List(
18 | reversi.game,
19 | reversi.restart
20 | )
21 | }
22 | }
23 | }
24 |
25 | AnchorPane.setTopAnchor(reversi.game, 0d)
26 | AnchorPane.setBottomAnchor(reversi.game, 0d)
27 | AnchorPane.setLeftAnchor(reversi.game, 0d)
28 | AnchorPane.setRightAnchor(reversi.game, 0d)
29 | AnchorPane.setRightAnchor(reversi.restart, 10d)
30 | AnchorPane.setTopAnchor(reversi.restart, 10d)
31 |
32 | // Code commended below was only used in the first edition of the book
33 | // if (Platform.isSupported(ConditionalFeature.Scene3D)) {
34 | // stage.scene().camera = new PerspectiveCamera() {
35 | // fieldOfView = 60
36 | // }.delegate
37 | // }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/ui/ReversiPiece.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.ui
2 |
3 | import proscalafx.ch04.reversi.model.{NONE, Owner, White}
4 | import scalafx.Includes.*
5 | import scalafx.beans.property.ObjectProperty
6 | import scalafx.scene.effect.Reflection
7 | import scalafx.scene.layout.Region
8 |
9 | object ReversiPiece {
10 |
11 | private val nonePieceStyle = "radius: 0; "
12 | private val whitePieceStyle = "-fx-background-color: radial-gradient(radius 100%, white .4, gray .9, darkgray 1); "
13 | private val blackPieceStyle = "-fx-background-color: radial-gradient(radius 100%, white .0, black .6); "
14 | private val tileStyle = "-fx-background-radius: 1000em; -fx-background-insets: 5"
15 |
16 | }
17 |
18 | class ReversiPiece(_owner: Owner = NONE) extends Region {
19 |
20 | import ReversiPiece.*
21 |
22 | val owner = new ObjectProperty[Owner](this, "owner", _owner)
23 |
24 | def owner_=(value: Owner): Unit = {
25 | owner() = value
26 | }
27 |
28 | style <== (
29 | when(owner === NONE) choose nonePieceStyle otherwise
30 | (when(owner === White) choose whitePieceStyle otherwise blackPieceStyle)
31 | ) + tileStyle
32 |
33 | effect = new Reflection {
34 | fraction = 1.0
35 | topOffset <== height * (-0.75)
36 | }
37 |
38 | prefWidth = 180
39 | prefHeight = 180
40 | mouseTransparent = true
41 | }
42 |
--------------------------------------------------------------------------------
/src/proscalafx/ch04/reversi/ui/ReversiSquare.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch04.reversi.ui
2 |
3 | import javafx.scene.layout as jfxsl
4 | import proscalafx.ch04.reversi.model.ReversiModel
5 | import scalafx.Includes.*
6 | import scalafx.animation.FadeTransition
7 | import scalafx.geometry.{HPos, VPos}
8 | import scalafx.scene.effect.{Light, Lighting}
9 | import scalafx.scene.layout.Region
10 | import scalafx.util.Duration
11 |
12 | class ReversiSquare(val x: Int, val y: Int) extends Region {
13 |
14 | private val highlight = new Region {
15 | opacity = 0
16 | style = "-fx-border-width: 3; -fx-border-color: dodgerblue"
17 | }
18 |
19 | override val delegate: jfxsl.Region = new jfxsl.Region {
20 | getChildren.add(highlight)
21 |
22 | protected override def layoutChildren(): Unit = {
23 | layoutInArea(highlight, 0, 0, getWidth, getHeight, getBaselineOffset, HPos.Center, VPos.Center)
24 | }
25 | }
26 |
27 | private val highlightTransition = new FadeTransition {
28 | node = highlight
29 | duration = Duration(200)
30 | fromValue = 0
31 | toValue = 1
32 | }
33 |
34 | style <== when(ReversiModel.legalMove(x, y)) choose
35 | "-fx-background-color: derive(dodgerblue, -60%)" otherwise
36 | "-fx-background-color: burlywood"
37 |
38 | effect = new Lighting {
39 | light = new Light.Distant {
40 | azimuth = -135
41 | elevation = 30
42 | }
43 | }
44 |
45 | prefHeight = 200
46 | prefWidth = 200
47 |
48 | onMouseEntered = () => {
49 | if (ReversiModel.legalMove(x, y).get) {
50 | highlightTransition.rate() = 1
51 | highlightTransition.play()
52 | }
53 | }
54 |
55 | onMouseExited = () => {
56 | highlightTransition.rate = -1
57 | highlightTransition.play()
58 | }
59 |
60 | onMouseClicked = () => {
61 | ReversiModel.play(x, y)
62 | highlightTransition.rate() = -1
63 | highlightTransition.play()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/proscalafx/ch05/model/Person.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch05.model
2 |
3 | import scalafx.beans.property.StringProperty
4 |
5 | /**
6 | * @author Jarek Sacha
7 | */
8 | class Person(firstName_ : String, lastName_ : String, phone_ : String) {
9 |
10 | val firstName = new StringProperty(this, "firstName", firstName_)
11 | val lastName = new StringProperty(this, "lastName", lastName_)
12 | val phone = new StringProperty(this, "phone", phone_)
13 |
14 | override def toString: String = {
15 | "Person: " + firstName() + " " + lastName()
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/proscalafx/ch05/model/StarterAppModel.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch05.model
2 |
3 | import scalafx.beans.property.DoubleProperty
4 | import scalafx.collections.ObservableBuffer
5 |
6 | import scala.util.Random
7 |
8 | /**
9 | * @author Jarek Sacha
10 | */
11 | class StarterAppModel {
12 |
13 | val listViewItems = new ObservableBuffer[String]()
14 | val choiceBoxItems = ObservableBuffer("Choice A", "Choice B", "Choice C", "Choice D")
15 | val maxRpm: Double = 8000d
16 | val rpm = new DoubleProperty(this, "rpm", 0)
17 | val maxKph: Double = 300.0
18 | val kph = new DoubleProperty(this, "kph", 0)
19 |
20 | def getTeamMembers: ObservableBuffer[Person] = {
21 |
22 | val teamMembers = new ObservableBuffer[Person]()
23 |
24 | for (i <- 1 to 1000) {
25 | teamMembers += new Person("FirstName" + i, "LastName" + i, "Phone" + i)
26 | }
27 |
28 | teamMembers
29 | }
30 |
31 | def randomWebSite(): String = {
32 |
33 | val webSites: Array[String] = Array(
34 | "https://www.jfx-central.com/",
35 | "https://openjfx.io/",
36 | "https://github.com/mhrimaz/AwesomeJavaFX",
37 | "http://fxexperience.com",
38 | // "http://steveonjava.com",
39 | // "https://javafxpert.com",
40 | "https://pleasingsoftware.blogspot.com",
41 | "https://www.weiqigao.com/blog",
42 | // "https://edencoding.com/category/javafx/",
43 | "https://codingonthestaircase.wordpress.com/",
44 | "https://www.google.com/"
45 | )
46 |
47 | val randomIdx = new Random().nextInt(webSites.length)
48 | webSites(randomIdx)
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/src/proscalafx/ch05/ui/images/paper.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch05/ui/images/paper.png
--------------------------------------------------------------------------------
/src/proscalafx/ch05/ui/starterApp.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011, Pro JavaFX Authors
3 | * All rights reserved.
4 | *
5 | * Redistribution and use in source and binary forms, with or without
6 | * modification, are permitted provided that the following conditions are met:
7 | * 1. Redistributions of source code must retain the above copyright notice,
8 | * this list of conditions and the following disclaimer.
9 | * 2. Redistributions in binary form must reproduce the above copyright notice,
10 | * this list of conditions and the following disclaimer in the documentation
11 | * and/or other materials provided with the distribution.
12 | * 3. Neither the name of JFXtras nor the names of its contributors may be used
13 | * to endorse or promote products derived from this software without
14 | * specific prior written permission.
15 | *
16 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 | * POSSIBILITY OF SUCH DAMAGE.
27 | *
28 | * starterApp.css - A CSS (Cascading Style Sheet) file the is referred to by
29 | * the StarterAppMain.java program to apply styles to the scene and its nodes.
30 | * Note: Syntax recommendation http://www.w3.org/TR/REC-CSS2/
31 | *
32 | * Developed 2011 by James L. Weaver jim.weaver [at] javafxpert.com
33 | * as a JavaFX SDK 2.0 example for the Pro JavaFX book.
34 | */
35 |
36 | #newButton {
37 | -fx-padding: 4 4 4 4;
38 | }
39 |
40 | #editButton {
41 | -fx-padding: 4 4 4 4;
42 | }
43 |
44 | #deleteButton {
45 | -fx-padding: 4 4 4 4;
46 | }
47 |
48 | #boldButton {
49 | -fx-padding: 4 4 4 4;
50 | }
51 |
52 | #italicButton {
53 | -fx-padding: 4 4 4 4;
54 | }
55 |
56 | #leftAlignButton {
57 | -fx-padding: 4 4 4 4;
58 | }
59 |
60 | #centerAlignButton {
61 | -fx-padding: 4 4 4 4;
62 | }
63 |
64 | #rightAlignButton {
65 | -fx-padding: 4 4 4 4;
66 | }
67 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/BufferChangeEventExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.collections.ObservableBuffer
4 | import scalafx.collections.ObservableBuffer.*
5 |
6 | /**
7 | * Example of processing of "change" notifications from ScalaFX `ObservableBuffer`, wrapper for JavaFX `ObservableList`.
8 | *
9 | * This example corresponds to JavaFX example `ListChangeEventExample`.
10 | *
11 | * ScalaFX is using a different way of passing information about modification to `ObservableBuffer`.
12 | * Each modification is represented by a [[scalafx.collections.ObservableBuffer.Change]] object.
13 | */
14 | object BufferChangeEventExample extends App {
15 |
16 | val strings = new ObservableBuffer[String]
17 | strings.onChange((buffer, changes) => {
18 | println("\tbuffer = " + buffer.mkString("[", ", ", "]"))
19 | val log = changes
20 | .zipWithIndex
21 | .foldLeft("\tChange event data:\n")({ case (string, (change, index)) => string + prettyPrint(index, change) })
22 | println(log)
23 | })
24 |
25 | println("""Calling strings ++= Seq("Zero", "One", "Two", "Three"): """)
26 | strings ++= Seq("Zero", "One", "Two", "Three")
27 |
28 | println("Calling strings.sort: ")
29 | strings.sort()
30 |
31 | println("""Calling strings(1) = "Three_1": """)
32 | strings(1) = "Three_1"
33 |
34 | println("""Calling strings.setAll("One_1", "Three_1", "Two_1", "Zero_1"): """)
35 | strings.setAll("One_1", "Three_1", "Two_1", "Zero_1")
36 |
37 | println("""Calling strings --= Seq("One_1", "Two_1", "Zero_1"): """)
38 | strings --= Seq("One_1", "Two_1", "Zero_1")
39 |
40 | private def prettyPrint(index: Int, change: Change[String]): String = {
41 | val sb = new StringBuffer("\t\tcursor = " + index + "\n")
42 | sb.append("\t\tKind of change: ")
43 |
44 | change match {
45 | case Add(position, added) =>
46 | sb.append("added\n")
47 | sb.append("\t\tPosition: " + position + "\n")
48 | sb.append("\t\tElement : " + added + "\n")
49 | case Remove(position, removed) =>
50 | sb.append("removed\n")
51 | sb.append("\t\tPosition: " + position + "\n")
52 | sb.append("\t\tElement : " + removed + "\n")
53 | case Reorder(start, end, permutation) =>
54 | sb.append("reordered\n")
55 | sb.append("\t\tAffected Range: [%d, %d]\n".format(start, end))
56 | val strLog = (start until end)
57 | .map(i => "%d->%s".format(i, permutation(i)))
58 | .mkString("\t\tPermutation: [", ", ", "]\n")
59 | sb.append(strLog)
60 | case Update(from, to) =>
61 | sb.append("updated\n")
62 | sb.append("\t\tfrom: " + from + "\n")
63 | sb.append("\t\tto : " + to + "\n")
64 | }
65 |
66 | sb.toString
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/FXCollectionsExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import javafx.collections as jfxc
4 | import scalafx.collections.ObservableBuffer
5 | import scalafx.collections.ObservableBuffer.*
6 |
7 | import java.util as ju
8 | import scala.jdk.CollectionConverters.*
9 |
10 | /**
11 | * ScalaFX version of `FXCollectionsExample` from "Pro JavaFX 2" book.
12 | *
13 | * ScalaFX does not have a direct equivalent of JavaFX `FXCollections`.
14 | * Some of static methods from `FXCollections` are members of relevant observable collection in ScalaFX,
15 | * for instance `replaceAll`. Some are in companion objects, for instance, `shuffle`.
16 | * Some are not implemented and require use of `FXCollections`.
17 | *
18 | * @author Jarek Sacha
19 | */
20 | object FXCollectionsExample extends App {
21 |
22 | // `println` statements show JavaFX API, for easier comparison to ScalaFX API used in the code.
23 |
24 | val strings = new ObservableBuffer[String]()
25 | strings.onChange((source, changes) => {
26 | println("\tlist = " + source.mkString("[", ", ", "]"))
27 | changes.foreach(prettyPrint)
28 | println()
29 | })
30 |
31 | println("""Calling addAll("Zero", "One", "Two", "Three"): """)
32 | strings.addAll("Zero", "One", "Two", "Three")
33 |
34 | println("Calling copy: ")
35 | jfxc.FXCollections.copy(strings, List("Four", "Five").asJava)
36 |
37 | println("Calling replaceAll: ")
38 | strings.replaceAll("Two", "Two_1")
39 |
40 | println("Calling reverse: ")
41 | ObservableBuffer.revertBuffer(strings)
42 |
43 | println("Calling rotate(strings, 2: ")
44 | jfxc.FXCollections.rotate(strings.delegate, 2)
45 |
46 | println("Calling shuffle(strings): ")
47 | ObservableBuffer.shuffle(strings)
48 |
49 | println("Calling shuffle(strings, new Random(0L)): ")
50 | ObservableBuffer.shuffle(strings, new ju.Random(0L))
51 |
52 | println("Calling sort(strings): ")
53 | strings.sort()
54 |
55 | println("Calling sort(strings, c) with custom comparator: ")
56 | strings.sort((lhs, rhs) => lhs > rhs)
57 |
58 | println("""Calling fill(strings, "Ten"): """)
59 | ObservableBuffer.fillAll(strings, "Ten")
60 |
61 | def prettyPrint(change: Change[String]): Unit = {
62 | change match {
63 | case Add(_, added) =>
64 | println("\t\tKind of change: added")
65 | println("\t\tAdded size : " + added.size)
66 | println("\t\tAdded sublist : " + mkString(added))
67 | case Remove(_, removed) =>
68 | println("\t\tKind of change: removed")
69 | println("\t\tRemoved size : " + removed.size)
70 | println("\t\tRemoved : " + mkString(removed))
71 | case Reorder(start, end, permutation) =>
72 | val permutations = for (i <- start until end) yield s"$i -> ${permutation(i)}"
73 | println("\t\tKind of change: permuted")
74 | println("\t\tPermutation : " + mkString(permutations))
75 | case Update(from, to) =>
76 | println("\t\tKind of change: updated\n")
77 | println("\t\t\tfrom: " + from + "\n")
78 | println("\t\t\tto : " + to + "\n")
79 |
80 | }
81 | }
82 |
83 | def mkString[T](seq: IterableOnce[T]): String =
84 | if (seq.iterator.isEmpty) "[]" else seq.iterator.mkString("[", ", ", "]")
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/JavaFXSceneInSwingExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import javafx.embed.swing as jfxes
4 | import scalafx.application.Platform
5 | import scalafx.beans.property.ObjectProperty
6 | import scalafx.scene.Scene
7 | import scalafx.scene.layout.VBox
8 | import scalafx.scene.paint.Color
9 | import scalafx.scene.shape.Rectangle
10 |
11 | import java.awt.event.ActionEvent
12 | import java.awt.{BorderLayout, Dimension, FlowLayout}
13 | import javax.swing.{JButton, JFrame, JPanel, WindowConstants}
14 |
15 | /**
16 | * @author Jarek Sacha
17 | */
18 | object JavaFXSceneInSwingExample extends App {
19 |
20 | private val model = new Model()
21 | new Controller(model, new View(model)).mainLoop()
22 |
23 | private class Model {
24 | // `fill` and `stroke` are created using ObjectProperty factory method to ensure proper type parameter
25 | // to ObjectProperty. We use here, implicitly, JavaFX Paint as the type for `ObjectProperty`.
26 | val fill = ObjectProperty(this, "fillPaint", Color.LightGray)
27 | val stroke = ObjectProperty(this, "strokePaint", Color.DarkGray)
28 | }
29 |
30 | private class View(model: Model) {
31 | val frame = new JFrame("ScalaFX in Swing Example")
32 | // NOTE: ScalaFX does not currently implement JFXPanel, so we use JavaFX/Swing directly.
33 | val canvas = new jfxes.JFXPanel() {
34 | setPreferredSize(new Dimension(210, 210))
35 | }
36 | // Execute JavaFX code on JavaFX Application Thread.
37 | Platform.runLater {
38 | val rectangle = new Rectangle {
39 | width = 200
40 | height = 200
41 | strokeWidth = 10
42 | fill <== model.fill
43 | stroke <== model.stroke
44 | }
45 |
46 | canvas.setScene(new Scene {
47 | root = new VBox {
48 | children = List(rectangle)
49 | }
50 | }.delegate)
51 | }
52 |
53 | val canvasPanelLayout = new FlowLayout(FlowLayout.CENTER, 10, 10)
54 | val canvasPanel = new JPanel(canvasPanelLayout)
55 | canvasPanel.add(canvas)
56 | val changeFillButton = new JButton("Change Fill")
57 | val changeStrokeButton = new JButton("Change Stroke")
58 | val buttonPanelLayout = new FlowLayout(FlowLayout.CENTER, 10, 10)
59 | val buttonPanel = new JPanel(buttonPanelLayout)
60 | buttonPanel.add(changeFillButton)
61 | buttonPanel.add(changeStrokeButton)
62 | frame.add(canvasPanel, BorderLayout.CENTER)
63 | frame.add(buttonPanel, BorderLayout.SOUTH)
64 | frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE)
65 | frame.setLocationByPlatform(true)
66 | frame.pack()
67 | }
68 |
69 | private class Controller(model: Model, view: View) {
70 | view.changeFillButton.addActionListener((_: ActionEvent) => {
71 | Platform.runLater {
72 | model.fill() = if (model.fill() == Color.LightGray) Color.Gray else Color.LightGray
73 | }
74 | })
75 | view.changeStrokeButton.addActionListener((_: ActionEvent) => {
76 | Platform.runLater {
77 | model.stroke() = if (model.stroke() == Color.DarkGray) Color.Black else Color.DarkGray
78 | }
79 | })
80 |
81 | def mainLoop(): Unit = {
82 | view.frame.setVisible(true)
83 | }
84 | }
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/JavaFXThreadsExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.collections.ObservableBuffer
7 | import scalafx.geometry.Insets
8 | import scalafx.scene.Scene
9 | import scalafx.scene.control.{Button, ListView, TextArea}
10 | import scalafx.scene.layout.VBox
11 |
12 | import scala.jdk.CollectionConverters.*
13 |
14 | /**
15 | * ScalaFX version of `JavaFXThreadsExample` from "Pro JavaFX 2" book.
16 | *
17 | * @author Jarek Sacha
18 | */
19 | object JavaFXThreadsExample extends JFXApp3 {
20 |
21 | class Model {
22 | val threadNames = new ObservableBuffer[String]()
23 | val stackTraces = new ObservableBuffer[String]()
24 | update()
25 |
26 | def update(): Unit = {
27 | threadNames.clear()
28 | stackTraces.clear()
29 | val map = Thread.getAllStackTraces.asScala
30 | for ((k, v) <- map) {
31 | threadNames += "\"" + k.getName + "\""
32 | stackTraces += formatStackTrace(v)
33 | }
34 | }
35 |
36 | private def formatStackTrace(v: Array[StackTraceElement]): String = {
37 | val sb = new StringBuilder("StackTrace: \n")
38 | for (stackTraceElement <- v) {
39 | sb ++= " at " ++= stackTraceElement.toString ++= "\n"
40 | }
41 | sb.toString()
42 | }
43 | }
44 |
45 | class View(model: Model) {
46 | val threadNames = new ListView(model.threadNames)
47 | val stackTrace = new TextArea()
48 | val updateButton = new Button("Update")
49 | val scene = new Scene(440, 640) {
50 | root = new VBox {
51 | spacing = 10
52 | padding = Insets(10)
53 | children = List(
54 | threadNames,
55 | stackTrace,
56 | updateButton
57 | )
58 | }
59 | }
60 | }
61 |
62 | override def start(): Unit = {
63 |
64 | val model = new Model()
65 | val view = new View(model)
66 |
67 | hookupEvents()
68 | stage = new PrimaryStage {
69 | title = "JavaFX Threads Information"
70 | scene = view.scene
71 | }
72 |
73 | def hookupEvents(): Unit = {
74 | view.updateButton.onAction = () => model.update()
75 | view.threadNames.selectionModel().selectedItem.onChange {
76 | val index = view.threadNames.selectionModel().getSelectedIndex
77 | if (index >= 0) {
78 | view.stackTrace.text = model.stackTraces(index)
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/MapChangeEventExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.collections.ObservableMap
4 | import scalafx.collections.ObservableMap.*
5 |
6 | /**
7 | * Example of processing of "change" notifications from ScalaFX `ObservableMap`, wrapper for JavaFX `ObservableMap`.
8 | *
9 | * ScalaFX used a different way of passing information about modification to `ObservableMap`.
10 | * Each modification is represented by a [[scalafx.collections.ObservableMap.Change]] object.
11 | */
12 | object MapChangeEventExample extends App {
13 |
14 | val map = ObservableMap.empty[String, Int]
15 | map.onChange((map, change) => {
16 | println("\tmap = " + map.mkString("[", ", ", "]"))
17 | println(prettyChange(change))
18 | })
19 |
20 | println("""Calling map("First") = 1: """)
21 | map("First") = 1
22 |
23 | println("""Calling map(First") = 100: """)
24 | map("First") = 100
25 |
26 | val anotherMap = Map("Second" -> 2, "Third" -> 3)
27 | println("""Calling map ++= anotherMap: """)
28 | map ++= anotherMap
29 |
30 | println("""Removing by key: Calling map -= "Second"""")
31 | map -= "Second"
32 |
33 | println("Removing by value: Calling map retain({case (k, v) => v != 3})")
34 | map filterInPlace { case (_, v) => v != 3 }
35 |
36 | def prettyChange(change: Change[_, _]): String = {
37 | val sb = new StringBuffer("\tChange event data:\n")
38 |
39 | change match {
40 | case Add(key, added) =>
41 | sb.append("\t\tWas added\n")
42 | sb.append("\t\tKey : %s\n".format(key))
43 | sb.append("\t\tValue added : %s\n".format(added))
44 | case Remove(key, removed) =>
45 | sb.append("\t\tWas removed\n")
46 | sb.append("\t\tKey : %s\n".format(key))
47 | sb.append("\t\tValue removed: %s\n".format(removed))
48 | case Replace(key, added, removed) =>
49 | sb.append("\t\tWas replaced\n")
50 | sb.append("\t\tKey : %s\n".format(key))
51 | sb.append("\t\tValue added : %s\n".format(added))
52 | sb.append("\t\tValue removed: %s\n".format(removed))
53 | }
54 |
55 | sb.toString
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/ObservableBufferExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.collections.ObservableBuffer
4 |
5 | /**
6 | * This example corresponds to JavaFX example `ObservableListExample`.
7 | *
8 | * In ScalaFX `ObservableBuffer` is a wrapper for JavaFX `ObservableList`.
9 | */
10 | object ObservableBufferExample extends App {
11 |
12 | val strings = new ObservableBuffer[String]()
13 | strings.onInvalidate(println("\tlist invalidated"))
14 | strings.onChange((source, change) => println("\tstrings = " + source.mkString("[", ", ", "]")))
15 |
16 | println("""Calling += "First": """)
17 | strings += "First"
18 |
19 | println("""Calling insert(0, "Zeroth"): """)
20 | strings.insert(0, "Zeroth")
21 |
22 | println(""""Calling ++= Seq("Second", "Third"): """)
23 | strings ++= Seq("Second", "Third")
24 |
25 | println("""Calling (1) = "New First"): """)
26 | strings(1) = "New First"
27 |
28 | val list = List("Second_1", "Second_2")
29 | println("Calling addAll(3, list): ")
30 | strings.insertAll(3, list)
31 |
32 | // There is a difference between meaning of the second argument of `remove` in JavaFX and ScalaFX.
33 | // ScalaFX `remove` corresponds to `scala.collection.mutable.Buffer.remove(n:Int, count:Int)`.
34 | // Equivalent of JavaFX `remove` is renamed in ScalaFX to `removeRange`.
35 | println("Calling removeRange(2, 4): ")
36 | strings.removeRange(2, 4)
37 |
38 | println("""Remove elements that contain letter "t"""")
39 | strings --= strings.filter(_.contains("t"))
40 |
41 | println("""Calling --= Seq("Third", "Fourth"): """)
42 | strings --= Seq("Third", "Fourth")
43 | }
44 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/ResponsiveUIExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.application.{JFXApp3, Platform}
6 | import scalafx.beans.property.ObjectProperty
7 | import scalafx.geometry.{Insets, Pos}
8 | import scalafx.scene.Scene
9 | import scalafx.scene.control.Button
10 | import scalafx.scene.layout.{BorderPane, HBox}
11 | import scalafx.scene.paint.Color
12 | import scalafx.scene.shape.Rectangle
13 |
14 | /**
15 | * @author Jarek Sacha
16 | */
17 | object ResponsiveUIExample extends JFXApp3 {
18 |
19 | override def start(): Unit = {
20 |
21 | hookupEvents()
22 |
23 | stage = new PrimaryStage {
24 | title = "Unresponsive UI Example"
25 | scene = View.scene
26 | }
27 | }
28 |
29 | def hookupEvents(): Unit = {
30 | View.changeFillButton.onAction = () => {
31 | val fillPaint = Model.fillPaint()
32 | Model.fillPaint() = if (Color.LightGray.delegate == fillPaint) Color.Gray else Color.LightGray
33 |
34 | val task = new Runnable {
35 | def run(): Unit = {
36 | try {
37 | Thread.sleep(3000)
38 | Platform.runLater {
39 | val rect = View.rectangle
40 | val newArcSize = if (rect.arcHeight() < 20) 30 else 0
41 | rect.arcWidth() = newArcSize
42 | rect.arcHeight() = newArcSize
43 | }
44 | } catch {
45 | case e: InterruptedException => /* Properly handle exception */
46 | }
47 | }
48 | }
49 | new Thread(task).start()
50 | }
51 |
52 | View.changeStrokeButton.onAction = () => {
53 | val strokePaint = Model.strokePaint()
54 | Model.strokePaint() = if (strokePaint == Color.DarkGray.delegate) Color.Black else Color.DarkGray
55 | }
56 | }
57 |
58 | private object Model {
59 | // `fill` and `stroke` are created using ObjectProperty factory method to ensure proper type parameter
60 | // to ObjectProperty. We use here, implicitly, JavaFX Paint as the type for `ObjectProperty`.
61 | val fillPaint = ObjectProperty(this, "fillPaint", Color.LightGray)
62 | val strokePaint = ObjectProperty(this, "strokePaint", Color.DarkGray)
63 | }
64 |
65 | private object View {
66 | val rectangle = new Rectangle {
67 | width = 200
68 | height = 200
69 | strokeWidth = 10
70 | fill <== Model.fillPaint
71 | stroke <== Model.strokePaint
72 | }
73 |
74 | val changeFillButton = new Button("Change Fill")
75 | val changeStrokeButton = new Button("Chang Stroke")
76 |
77 | val buttonHBox = new HBox {
78 | padding = Insets(10)
79 | spacing = 10
80 | alignment = Pos.Center
81 | children = List(
82 | changeFillButton,
83 | changeStrokeButton
84 | )
85 | }
86 |
87 | val scene = new Scene {
88 | root = new BorderPane {
89 | padding = Insets(10)
90 | center = rectangle
91 | bottom = buttonHBox
92 | }
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/SetChangeEventExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.collections.ObservableSet
4 | import scalafx.collections.ObservableSet.*
5 |
6 | object SetChangeEventExample extends App {
7 |
8 | def prettyChange(change: Change[_]): String = {
9 | val sb = new StringBuffer("\tChange event data:\n")
10 |
11 | change match {
12 | case Add(added) =>
13 | sb.append("\t\tWas added\n")
14 | sb.append("\t\tValue added : %s\n".format(added))
15 | case Remove(removed) =>
16 | sb.append("\t\tWas removed\n")
17 | sb.append("\t\tValue removed: %s\n".format(removed))
18 | }
19 |
20 | sb.toString
21 | }
22 |
23 | def onChange[T](set: ObservableSet[T], change: Change[T]): Unit = {
24 | println("\tset = " + set)
25 | println(prettyChange(change))
26 | }
27 |
28 | val set = ObservableSet.empty[String]
29 | set.onChange(onChange(_, _))
30 |
31 | println("Calling set += \"First\": ")
32 | set += "First"
33 |
34 | println("Calling set ++= Seq(\"Second\", \"Third\"): ")
35 | set ++= Seq("Second", "Third")
36 |
37 | println("Calling set -= \"Second\": ")
38 | set -= "Second"
39 |
40 | println("Calling set --= Seq(\"First\", \"Third\"): ")
41 | set --= Seq("First", "Third")
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/src/proscalafx/ch06/UnresponsiveUIExample.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch06
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.beans.property.ObjectProperty
7 | import scalafx.geometry.{Insets, Pos}
8 | import scalafx.scene.Scene
9 | import scalafx.scene.control.Button
10 | import scalafx.scene.layout.{BorderPane, HBox}
11 | import scalafx.scene.paint.Color
12 | import scalafx.scene.shape.Rectangle
13 |
14 | /**
15 | * @author Jarek Sacha
16 | */
17 | object UnresponsiveUIExample extends JFXApp3 {
18 |
19 | override def start(): Unit = {
20 |
21 | hookupEvents()
22 |
23 | stage = new PrimaryStage {
24 | title = "Unresponsive UI Example"
25 | scene = View.scene
26 | }
27 | }
28 |
29 | def hookupEvents(): Unit = {
30 | View.changeFillButton.onAction = () => {
31 | val fillPaint = Model.fillPaint()
32 | Model.fillPaint() = if (fillPaint == Color.LightGray.delegate) Color.Gray else Color.LightGray
33 | // Bad code that will cause the UI to be unresponsive
34 | try {
35 | Thread.sleep(Long.MaxValue)
36 | } catch {
37 | case _: InterruptedException => /* Properly handle exception */
38 | }
39 | }
40 |
41 | View.changeStrokeButton.onAction = () => {
42 | val strokePaint = Model.strokePaint()
43 | Model.strokePaint() = if (strokePaint == Color.DarkGray.delegate) Color.Black else Color.DarkGray
44 | }
45 | }
46 |
47 | private object Model {
48 | // `fill` and `stroke` are created using ObjectProperty factory method to ensure proper type parameter
49 | // to ObjectProperty. We use here, implicitly, JavaFX Paint as the type for `ObjectProperty`.
50 | val fillPaint = ObjectProperty(this, "fillPaint", Color.LightGray)
51 | val strokePaint = ObjectProperty(this, "strokePaint", Color.DarkGray)
52 | }
53 |
54 | private object View {
55 | val rectangle = new Rectangle {
56 | width = 200
57 | height = 200
58 | strokeWidth = 10
59 | fill <== Model.fillPaint
60 | stroke <== Model.strokePaint
61 | }
62 |
63 | val changeFillButton = new Button("Change Fill")
64 | val changeStrokeButton = new Button("Chang Stroke")
65 |
66 | val buttonHBox = new HBox {
67 | padding = Insets(10)
68 | spacing = 10
69 | alignment = Pos.Center
70 | children = List(
71 | changeFillButton,
72 | changeStrokeButton
73 | )
74 | }
75 |
76 | val scene = new Scene {
77 | root = new BorderPane {
78 | padding = Insets(10)
79 | center = rectangle
80 | bottom = buttonHBox
81 | }
82 | }
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp1.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.collections.ObservableBuffer
6 | import scalafx.scene.Scene
7 | import scalafx.scene.chart.PieChart
8 | import scalafx.scene.layout.StackPane
9 |
10 | /**
11 | * @author Jarek Sacha
12 | */
13 | object ChartApp1 extends JFXApp3 {
14 |
15 | override def start(): Unit = {
16 |
17 | stage = new PrimaryStage {
18 | title = "PieChart"
19 | scene = new Scene(400, 250) {
20 | root = new StackPane {
21 | children = new PieChart() {
22 | data = chartData()
23 | }
24 | }
25 | }
26 | }
27 | }
28 |
29 | private def chartData() = ObservableBuffer(
30 | PieChart.Data("java", 17.56),
31 | PieChart.Data("C", 17.06),
32 | PieChart.Data("C++", 8.25),
33 | PieChart.Data("C#", 8.20),
34 | PieChart.Data("ObjectiveC", 6.8),
35 | PieChart.Data("PHP", 6.0),
36 | PieChart.Data("(Visual)Basic", 4.76),
37 | PieChart.Data("Other", 31.37)
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp10.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.*
10 | import scalafx.scene.layout.StackPane
11 | import scalafx.util.StringConverter
12 |
13 | /**
14 | * We use here the version of the example that is described in the book, it scales axis so bubbles appear as spheres
15 | * rather than ellipses (in the downloadable Java code).
16 | *
17 | * @author Jarek Sacha
18 | */
19 | object ChartApp10 extends JFXApp3 {
20 |
21 | private val xStep = 10
22 | private val xMin = 2010 * xStep
23 | private val xMax = 2016 * xStep
24 |
25 | override def start(): Unit = {
26 |
27 | val xAxis = new NumberAxis {
28 | autoRanging = false
29 | lowerBound = xMin - xStep
30 | upperBound = xMax + xStep
31 | tickUnit = xStep
32 | // The xAxis ranges from 20110 till 20210, but of course we want to show the years at the axis. This can
33 | // be achieved by calling
34 | tickLabelFormatter = new StringConverter[Number] {
35 | // Here we do not need to convert from string.
36 | def fromString(string: String): Number = throw new UnsupportedOperationException("Not implemented.")
37 |
38 | def toString(t: Number): String = (t.intValue() / xStep).toString
39 | }
40 | }
41 | val yAxis = new NumberAxis()
42 | val bubbleChart = BubbleChart(xAxis, yAxis)
43 | bubbleChart.title = "Speculations"
44 | bubbleChart.data = createChartData()
45 |
46 | stage = new PrimaryStage {
47 | title = "BubbleChart example"
48 | scene = new Scene(400, 250) {
49 | root = new StackPane {
50 | children = bubbleChart
51 | }
52 | }
53 | }
54 | }
55 |
56 | // NOTE: explicit type signature using Number instead Int and Double
57 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
58 | // signature for scalafx.scene.chart.XYChart.data used above.
59 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = {
60 | var javaValue = 17.56
61 | var cValue = 17.06
62 | var cppValue = 8.25
63 | val scale = 10
64 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]()
65 | val java = new XYChart.Series[Number, Number] {
66 | name = "Java"
67 | }
68 | val c = new XYChart.Series[Number, Number] {
69 | name = "C"
70 | }
71 | val cpp = new XYChart.Series[Number, Number] {
72 | name = "C++"
73 | }
74 | for (i <- xMin to xMax by xStep) {
75 | java.data() += XYChart.Data[Number, Number](i, javaValue)
76 | javaValue += +scale * math.random() - scale / 2
77 |
78 | c.data() += XYChart.Data[Number, Number](i, cValue)
79 | cValue += scale * math.random() - scale / 2
80 |
81 | cpp.data() += XYChart.Data[Number, Number](i, cppValue)
82 | cppValue += scale * math.random() - scale / 2
83 | }
84 | answer.addAll(java, c, cpp)
85 | answer
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp11.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.*
10 | import scalafx.scene.layout.StackPane
11 | import scalafx.util.StringConverter
12 |
13 | /**
14 | * We use here the version of the example that is described in the book, it scales axis so bubbles appear as spheres
15 | * rather than ellipses (in the downloadable Java code).
16 | *
17 | * @author Jarek Sacha
18 | */
19 | object ChartApp11 extends JFXApp3 {
20 |
21 | val xStep = 10
22 | val xMin = 2010 * xStep
23 | val xMax = 2016 * xStep
24 |
25 | override def start(): Unit = {
26 |
27 | val xAxis = new NumberAxis() {
28 | autoRanging = false
29 | lowerBound = xMin - xStep
30 | upperBound = xMax + xStep
31 | tickUnit = xStep
32 | // The xAxis ranges from 20110 till 20210, but of course we want to show the years at the axis. This can
33 | // be achieved by calling
34 | tickLabelFormatter = new StringConverter[Number] {
35 | // Here we do not need to convert from string.
36 | def fromString(string: String): Number = throw new UnsupportedOperationException("Not implemented.")
37 |
38 | def toString(t: Number): String = (t.intValue() / xStep).toString
39 | }
40 | }
41 | val yAxis = new NumberAxis()
42 | val bubbleChart = BubbleChart(xAxis, yAxis)
43 | bubbleChart.title = "Speculations"
44 | bubbleChart.data = createChartData()
45 |
46 | stage = new PrimaryStage {
47 | title = "BubbleChart example"
48 | scene = new Scene(400, 250) {
49 | root = new StackPane {
50 | children = bubbleChart
51 | }
52 | }
53 | }
54 | }
55 |
56 | // NOTE: explicit type signature using Number instead Int and Double
57 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
58 | // signature for scalafx.scene.chart.XYChart.data used above.
59 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = {
60 | var javaValue = 17.56
61 | var cValue = 17.06
62 | var cppValue = 8.25
63 | val scale = 10
64 |
65 | def dif: Double = scale * math.random() / 4
66 |
67 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]()
68 | val java = new XYChart.Series[Number, Number] {
69 | name = "Java"
70 | }
71 | val c = new XYChart.Series[Number, Number] {
72 | name = "C"
73 | }
74 | val cpp = new XYChart.Series[Number, Number] {
75 | name = "C++"
76 | }
77 | for (i <- xMin to xMax by xStep) {
78 | // NOTE: XYChart.Data declares its third parameter as a `AnyRef`. A Double returned by `math.rand`
79 | // is not a subclass of `AnyRef`, so we need to explicitly convert it to a `java.lang.Number` to make it work.
80 | java.data() += XYChart.Data[Number, Number](i, javaValue, dif.asInstanceOf[Number])
81 | javaValue += +scale * math.random() - scale / 2
82 |
83 | c.data() += XYChart.Data[Number, Number](i, cValue, dif.asInstanceOf[Number])
84 | cValue += scale * math.random() - scale / 2
85 |
86 | cpp.data() += XYChart.Data[Number, Number](i, cppValue, dif.asInstanceOf[Number])
87 | cppValue += scale * math.random() - scale / 2
88 | }
89 | answer.addAll(java, c, cpp)
90 | answer
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp2.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.collections.ObservableBuffer
6 | import scalafx.geometry.Side
7 | import scalafx.scene.Scene
8 | import scalafx.scene.chart.PieChart
9 | import scalafx.scene.layout.StackPane
10 |
11 | /**
12 | * @author Jarek Sacha
13 | */
14 | object ChartApp2 extends JFXApp3 {
15 |
16 | override def start(): Unit = {
17 |
18 | stage = new PrimaryStage {
19 | title = "Chart App 2"
20 | scene = new Scene(400, 350) {
21 | root = new StackPane {
22 | children = new PieChart() {
23 | data = chartData()
24 | title = "Tiobe index"
25 | legendSide = Side.Left
26 | clockwise = false
27 | labelsVisible = false
28 | }
29 | }
30 | }
31 | }
32 | }
33 |
34 | private def chartData() = ObservableBuffer(
35 | PieChart.Data("java", 17.56),
36 | PieChart.Data("C", 17.06),
37 | PieChart.Data("C++", 8.25),
38 | PieChart.Data("C#", 8.20),
39 | PieChart.Data("ObjectiveC", 6.8),
40 | PieChart.Data("PHP", 6.0),
41 | PieChart.Data("(Visual)Basic", 4.76),
42 | PieChart.Data("Other", 31.37)
43 | )
44 | }
45 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp3.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.{NumberAxis, ScatterChart, XYChart}
10 | import scalafx.scene.layout.StackPane
11 |
12 | /**
13 | * @author Jarek Sacha
14 | */
15 | object ChartApp3 extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val xAxis = new NumberAxis()
20 | val yAxis = new NumberAxis()
21 | val scatterChart = ScatterChart(xAxis, yAxis)
22 |
23 | scatterChart.data = createChartData()
24 |
25 | stage = new PrimaryStage {
26 | title = "Chart App 3"
27 | scene = new Scene(400, 250) {
28 | root = new StackPane {
29 | children = scatterChart
30 | }
31 | }
32 | }
33 | }
34 |
35 | // NOTE: explicit type signature using Number instead Int and Double
36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
37 | // signature for scalafx.scene.chart.XYChart.data used above.
38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = {
39 | var javaValue = 17.56
40 | var cValue = 17.06
41 | var cppValue = 8.25
42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]()
43 | val java = new XYChart.Series[Number, Number] {
44 | name = "Java"
45 | }
46 | val c = new XYChart.Series[Number, Number] {
47 | name = "C"
48 | }
49 | val cpp = new XYChart.Series[Number, Number] {
50 | name = "C++"
51 | }
52 | for (i <- 2011 to 2020) {
53 | java.data() += XYChart.Data[Number, Number](i, javaValue)
54 | javaValue += math.random() - .5
55 |
56 | c.data() += XYChart.Data[Number, Number](i, cValue)
57 | cValue += math.random() - .5
58 |
59 | cpp.data() += XYChart.Data[Number, Number](i, cppValue)
60 | cppValue += math.random() - .5
61 | }
62 | answer.addAll(java, c, cpp)
63 | answer
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp4.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.collections.ObservableBuffer
6 | import scalafx.scene.Scene
7 | import scalafx.scene.chart.{NumberAxis, ScatterChart, XYChart}
8 | import scalafx.scene.layout.StackPane
9 |
10 | /**
11 | * @author Jarek Sacha
12 | */
13 | object ChartApp4 extends JFXApp3 {
14 |
15 | override def start(): Unit = {
16 |
17 | val xAxis = new NumberAxis {
18 | autoRanging = false
19 | lowerBound = 2011
20 | upperBound = 2021
21 | }
22 | val yAxis = new NumberAxis()
23 | val scatterChart = ScatterChart[Number, Number](xAxis, yAxis, createChartData())
24 |
25 | stage = new PrimaryStage {
26 | title = "Chart App 4"
27 | scene = new Scene(400, 250) {
28 | root = new StackPane {
29 | children = scatterChart
30 | }
31 | }
32 | }
33 | }
34 |
35 | private def createChartData() = {
36 |
37 | val years = 2011 to 2020
38 |
39 | // Generate trend by creating a cumulative sum of random values
40 | def generateTrend(startValue: Double) = years.map(_ => math.random() - .5).scanLeft(startValue)(_ + _)
41 |
42 | val javaTrend = generateTrend(17.56)
43 | val cTrend = generateTrend(17.06)
44 | val cppTrend = generateTrend(8.25)
45 |
46 | // NOTE: explicit type signature using Number instead Int and Double
47 | // We are deliberately using here factory methods, instead of "new", to create instances of
48 | // javafx.scene.chart.* types.
49 | val javaData = years zip javaTrend map { case (y, d) => XYChart.Data[Number, Number](y, d) }
50 | val cData = years zip cTrend map { case (y, d) => XYChart.Data[Number, Number](y, d) }
51 | val cppData = years zip cppTrend map { case (y, d) => XYChart.Data[Number, Number](y, d) }
52 |
53 | ObservableBuffer(
54 | XYChart.Series[Number, Number](name = "Java", data = ObservableBuffer.from(javaData)),
55 | XYChart.Series[Number, Number](name = "C", data = ObservableBuffer.from(cData)),
56 | XYChart.Series[Number, Number](name = "C++", data = ObservableBuffer.from(cppData))
57 | )
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp5.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.{NumberAxis, ScatterChart, XYChart}
10 | import scalafx.scene.layout.StackPane
11 |
12 | /**
13 | * @author Jarek Sacha
14 | */
15 | object ChartApp5 extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val xAxis = new NumberAxis {
20 | autoRanging = false
21 | lowerBound = 2011
22 | upperBound = 2021
23 | }
24 | val yAxis = new NumberAxis()
25 |
26 | val scatterChart = ScatterChart(xAxis, yAxis)
27 | scatterChart.title = "Speculations"
28 | scatterChart.data = createChartData()
29 |
30 | stage = new PrimaryStage {
31 | title = "ScatterChart example"
32 | scene = new Scene(400, 250) {
33 | root = new StackPane {
34 | children = scatterChart
35 | }
36 | }
37 | }
38 | }
39 |
40 | // NOTE: explicit type signature using Number instead Int and Double
41 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
42 | // signature for scalafx.scene.chart.XYChart.data used above.
43 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[Number, Number]] = {
44 | var javaValue: Double = 17.56
45 | var cValue: Double = 17.06
46 | var cppValue: Double = 8.25
47 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[Number, Number]]()
48 | val java = new XYChart.Series[Number, Number] {
49 | name = "Java"
50 | }
51 | val c = new XYChart.Series[Number, Number] {
52 | name = "C"
53 | }
54 | val cpp = new XYChart.Series[Number, Number] {
55 | name = "C++"
56 | }
57 | for (i <- 2011 to 2021) {
58 | java.data() += XYChart.Data[Number, Number](i, javaValue)
59 | javaValue += math.random() - .5
60 |
61 | c.data() += XYChart.Data[Number, Number](i, cValue)
62 | cValue += math.random() - .5
63 |
64 | cpp.data() += XYChart.Data[Number, Number](i, cppValue)
65 | cppValue += math.random() - .5
66 | }
67 | answer.addAll(java, c, cpp)
68 | answer
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp6.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.*
10 | import scalafx.scene.layout.StackPane
11 |
12 | /**
13 | * @author Jarek Sacha
14 | */
15 | object ChartApp6 extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val xAxis = new CategoryAxis()
20 | val yAxis = new NumberAxis()
21 | val scatterChart = ScatterChart(xAxis, yAxis)
22 | scatterChart.title = "Speculations"
23 | scatterChart.data = createChartData()
24 |
25 | stage = new PrimaryStage {
26 | title = "ScatterChart example"
27 | scene = new Scene(400, 250) {
28 | root = new StackPane {
29 | children = scatterChart
30 | }
31 | }
32 | }
33 | }
34 |
35 | // NOTE: explicit type signature using Number instead Int and Double
36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
37 | // signature for scalafx.scene.chart.XYChart.data used above.
38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = {
39 | var javaValue = 17.56
40 | var cValue = 17.06
41 | var cppValue = 8.25
42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]()
43 | val java = new XYChart.Series[String, Number] {
44 | name = "Java"
45 | }
46 | val c = new XYChart.Series[String, Number] {
47 | name = "C"
48 | }
49 | val cpp = new XYChart.Series[String, Number] {
50 | name = "C++"
51 | }
52 | for (i <- 2011 to 2021) {
53 | java.data() += XYChart.Data[String, Number](i.toString, javaValue)
54 | javaValue += math.random() - .5
55 |
56 | c.data() += XYChart.Data[String, Number](i.toString, cValue)
57 | cValue += math.random() - .5
58 |
59 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue)
60 | cppValue += math.random() - .5
61 | }
62 | answer.addAll(java, c, cpp)
63 | answer
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp7.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.*
10 | import scalafx.scene.layout.StackPane
11 |
12 | /**
13 | * @author Jarek Sacha
14 | */
15 | object ChartApp7 extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val xAxis = new CategoryAxis()
20 | val yAxis = new NumberAxis()
21 | val lineChart = LineChart(xAxis, yAxis)
22 | lineChart.title = "Speculations"
23 | lineChart.data = createChartData()
24 |
25 | stage = new PrimaryStage {
26 | title = "LineChart example"
27 | scene = new Scene(400, 250) {
28 | root = new StackPane {
29 | children = lineChart
30 | }
31 | }
32 | }
33 | }
34 |
35 | // NOTE: explicit type signature using Number instead Int and Double
36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
37 | // signature for scalafx.scene.chart.XYChart.data used above.
38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = {
39 | var javaValue = 17.56
40 | var cValue = 17.06
41 | var cppValue = 8.25
42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]()
43 | val java = new XYChart.Series[String, Number] {
44 | name = "Java"
45 | }
46 | val c = new XYChart.Series[String, Number] {
47 | name = "C"
48 | }
49 | val cpp = new XYChart.Series[String, Number] {
50 | name = "C++"
51 | }
52 | for (i <- 2011 to 2021) {
53 | java.data() += XYChart.Data[String, Number](i.toString, javaValue)
54 | javaValue += math.random() - .5
55 |
56 | c.data() += XYChart.Data[String, Number](i.toString, cValue)
57 | cValue += math.random() - .5
58 |
59 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue)
60 | cppValue += math.random() - .5
61 | }
62 | answer.addAll(java, c, cpp)
63 | answer
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp8.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.*
10 | import scalafx.scene.layout.StackPane
11 |
12 | /**
13 | * @author Jarek Sacha
14 | */
15 | object ChartApp8 extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val xAxis = new CategoryAxis()
20 | val yAxis = new NumberAxis()
21 | val barChart = BarChart(xAxis, yAxis)
22 | barChart.title = "Speculations"
23 | barChart.data = createChartData()
24 | barChart.barGap = 1
25 |
26 | stage = new PrimaryStage {
27 | title = "BarChart example"
28 | scene = new Scene(400, 250) {
29 | root = new StackPane {
30 | children = barChart
31 | }
32 | }
33 | }
34 | }
35 |
36 | // NOTE: explicit type signature using Number instead Int and Double
37 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
38 | // signature for scalafx.scene.chart.XYChart.data used above.
39 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = {
40 | var javaValue = 17.56
41 | var cValue = 17.06
42 | var cppValue = 8.25
43 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]()
44 | val java = new XYChart.Series[String, Number] {
45 | name = "Java"
46 | }
47 | val c = new XYChart.Series[String, Number] {
48 | name = "C"
49 | }
50 | val cpp = new XYChart.Series[String, Number] {
51 | name = "C++"
52 | }
53 | for (i <- 2011 to 2016) {
54 | java.data() += XYChart.Data[String, Number](i.toString, javaValue)
55 | javaValue += math.random() - .5
56 |
57 | c.data() += XYChart.Data[String, Number](i.toString, cValue)
58 | cValue += math.random() - .5
59 |
60 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue)
61 | cppValue += math.random() - .5
62 | }
63 | answer.addAll(java, c, cpp)
64 | answer
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/proscalafx/ch07/ChartApp9.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch07
2 |
3 | import javafx.scene.chart as jfxsc
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.collections.ObservableBuffer
8 | import scalafx.scene.Scene
9 | import scalafx.scene.chart.*
10 | import scalafx.scene.layout.StackPane
11 |
12 | /**
13 | * @author Jarek Sacha
14 | */
15 | object ChartApp9 extends JFXApp3 {
16 |
17 | override def start(): Unit = {
18 |
19 | val xAxis = new CategoryAxis()
20 | val yAxis = new NumberAxis()
21 | val areaChart = AreaChart(xAxis, yAxis)
22 | areaChart.title = "Speculations"
23 | areaChart.data = createChartData()
24 |
25 | stage = new PrimaryStage {
26 | title = "AreaChart example"
27 | scene = new Scene(400, 250) {
28 | root = new StackPane {
29 | children = areaChart
30 | }
31 | }
32 | }
33 | }
34 |
35 | // NOTE: explicit type signature using Number instead Int and Double
36 | // NOTE: use of jfxsc.XYChart.Series as type for ObservableBuffer, this the same as
37 | // signature for scalafx.scene.chart.XYChart.data used above.
38 | private def createChartData(): ObservableBuffer[jfxsc.XYChart.Series[String, Number]] = {
39 | var javaValue = 17.56
40 | var cValue = 17.06
41 | var cppValue = 8.25
42 | val answer = new ObservableBuffer[jfxsc.XYChart.Series[String, Number]]()
43 | val java = new XYChart.Series[String, Number] {
44 | name = "Java"
45 | }
46 | val c = new XYChart.Series[String, Number] {
47 | name = "C"
48 | }
49 | val cpp = new XYChart.Series[String, Number] {
50 | name = "C++"
51 | }
52 | for (i <- 2011 to 2020) {
53 | java.data() += XYChart.Data[String, Number](i.toString, javaValue)
54 | javaValue += math.random() - .5
55 |
56 | c.data() += XYChart.Data[String, Number](i.toString, cValue)
57 | cValue += math.random() - .5
58 |
59 | cpp.data() += XYChart.Data[String, Number](i.toString, cppValue)
60 | cppValue += math.random() - .5
61 | }
62 | answer.addAll(java, c, cpp)
63 | answer
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer1/AudioPlayer1.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer1
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.scene.Scene
6 | import scalafx.scene.media.{Media, MediaPlayer}
7 |
8 | /**
9 | * A very simple application that plays an audio file.
10 | *
11 | * @author Jarek Sacha
12 | */
13 | object AudioPlayer1 extends JFXApp3 {
14 |
15 | override def start(): Unit = {
16 |
17 | val resource = getClass.getResource("resources/keeper.mp3")
18 | val media = new Media(resource.toString)
19 | val mediaPlayer = new MediaPlayer(media)
20 | mediaPlayer.play()
21 |
22 | stage = new PrimaryStage {
23 | title = "Audio Player 1"
24 | scene = new Scene(200, 200)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer1/resources/keeper.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer1/resources/keeper.mp3
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer2/AudioPlayer2.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer2
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.collections.ObservableMap.Add
7 | import scalafx.geometry.{Insets, VPos}
8 | import scalafx.scene.Scene
9 | import scalafx.scene.control.Label
10 | import scalafx.scene.effect.Reflection
11 | import scalafx.scene.image.{Image, ImageView}
12 | import scalafx.scene.layout.{ColumnConstraints, GridPane, Priority, RowConstraints}
13 | import scalafx.scene.media.{Media, MediaPlayer}
14 |
15 | /**
16 | * Displaying the metadata information in the scene graph.
17 | *
18 | * @author Jarek Sacha
19 | */
20 | object AudioPlayer2 extends JFXApp3 {
21 |
22 | private var media: Media = _
23 | private var mediaPlayer: MediaPlayer = _
24 | private var artist: Label = _
25 | private var album: Label = _
26 | private var title: Label = _
27 | private var year: Label = _
28 | private var albumCover: ImageView = _
29 |
30 | override def start(): Unit = {
31 |
32 | createControls()
33 | createMedia()
34 |
35 | stage = new PrimaryStage {
36 | self =>
37 | self.title = "Audio Player 2"
38 | scene = new Scene(createGridPane(), 800, 400) {
39 | val stylesheet = getClass.getResource("media.css")
40 | stylesheets += stylesheet.toString
41 | }
42 | }
43 | }
44 |
45 | private def createGridPane(): GridPane = new GridPane {
46 | padding = Insets(10)
47 | hgap = 20
48 | add(albumCover, 0, 0, 1, GridPane.Remaining)
49 | add(title, 1, 0)
50 | add(artist, 1, 1)
51 | add(album, 1, 2)
52 | add(year, 1, 3)
53 | columnConstraints ++= Seq(
54 | new ColumnConstraints(),
55 | // NOTE: the call to delegate to avoid compilation error.
56 | // Should `scalafx.scene.layout.GridPane.columnConstraints_=()` be fixed to work without call to delegate?
57 | new ColumnConstraints {
58 | hgrow = Priority.Always
59 | }.delegate
60 | )
61 | val r0 = new RowConstraints {
62 | valignment = VPos.Top
63 | }
64 | rowConstraints ++= Seq(r0, r0, r0, r0)
65 | }
66 |
67 | private def createControls(): Unit = {
68 | artist = new Label {
69 | id = "artist"
70 | }
71 | album = new Label {
72 | id = "album"
73 | }
74 | title = new Label {
75 | id = "title"
76 | }
77 | year = new Label {
78 | id = "year"
79 | }
80 | val url = getClass.getResource("resources/defaultAlbum.png")
81 | albumCover = new ImageView {
82 | image = new Image(url.toString)
83 | fitWidth = 240
84 | preserveRatio = true
85 | smooth = true
86 | effect = new Reflection {
87 | fraction = 0.2
88 | }
89 | }
90 | }
91 |
92 | private def createMedia(): Unit = {
93 | try {
94 | media = new Media("https://traffic.libsyn.com/dickwall/JavaPosse373.mp3") {
95 | metadata.onChange((_, change) => {
96 | change match {
97 | case Add(key, added) => handleMetadata(key, added)
98 | case _ =>
99 | }
100 | })
101 | }
102 |
103 | mediaPlayer = new MediaPlayer(media) {
104 | self =>
105 | onError = {
106 | val errorMessage: String = self.media.getError.getMessage
107 | System.out.println("MediaPlayer Error: " + errorMessage)
108 | }
109 | }
110 |
111 | mediaPlayer.play()
112 | } catch {
113 | // Handle construction errors
114 | case re: RuntimeException => println("Caught Exception: " + re.getMessage)
115 | }
116 | }
117 |
118 | private def handleMetadata(key: String, value: AnyRef): Unit = {
119 | key match {
120 | case "album" => album.text = value.toString
121 | case "artist" => artist.text = value.toString
122 | case "title" => title.text = value.toString
123 | case "year" => year.text = value.toString
124 | case "image" => albumCover.image = value.asInstanceOf[javafx.scene.image.Image]
125 | case _ => println("Unhandled metadata key: " + key + ", value: " + value)
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer2/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: #aaa;
3 | -fx-background-color: black;
4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000);
5 | -fx-background-image: url("resources/cross.png");
6 | }
7 |
8 | .button {
9 | -fx-text-fill: #E0E0E0;
10 | -fx-font-size: 20pt;
11 | -fx-pref-width: 300px;
12 | }
13 |
14 | .label {
15 | -fx-font-size: 18pt;
16 | -fx-text-fill: #aaa;
17 | }
18 |
19 | #title {
20 | -fx-font-size: 24pt;
21 | -fx-text-fill: #ddd;
22 | }
23 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer2/resources/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer2/resources/cross.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer2/resources/defaultAlbum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer2/resources/defaultAlbum.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/AbstractView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer3
2 |
3 | import scalafx.scene.Node
4 |
5 | /**
6 | * @author Jarek Sacha
7 | */
8 | abstract class AbstractView(protected val songMadel: SongModel) {
9 | private val _viewNode: Node = initView()
10 |
11 | def viewNode: Node = _viewNode
12 |
13 | protected def initView(): Node
14 | }
15 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/AudioPlayer3.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer3
2 |
3 | import com.sun.javafx.runtime.VersionInfo
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.scene.Scene
8 | import scalafx.scene.input.TransferMode
9 | import scalafx.scene.layout.BorderPane
10 |
11 | /**
12 | * @author Jarek Sacha
13 | */
14 | object AudioPlayer3 extends JFXApp3 {
15 |
16 | override def start(): Unit = {
17 |
18 | val songModel = new SongModel() {
19 | url = "https://traffic.libsyn.com/dickwall/JavaPosse373.mp3"
20 | }
21 |
22 | println("JavaFX version: " + VersionInfo.getRuntimeVersion)
23 |
24 | val metaDataView = new MetadataView(songModel)
25 | val playerControlsView = new PlayerControlsView(songModel)
26 |
27 | val root = new BorderPane {
28 | center = metaDataView.viewNode
29 | bottom = playerControlsView.viewNode
30 | }
31 |
32 | stage = new PrimaryStage {
33 | title = "Audio Player 3"
34 | scene = new Scene(root, 800, 400) {
35 | val stylesheet = getClass.getResource("media.css")
36 | stylesheets += stylesheet.toString
37 | }
38 | initSceneDragAndDrop(scene(), songModel)
39 | }
40 | }
41 |
42 | private def initSceneDragAndDrop(scene: Scene, songModel: SongModel): Unit = {
43 | scene.onDragOver = event => {
44 | val db = event.dragboard
45 | if (db.hasFiles || db.hasUrl) {
46 | // NOTE: We need to pass in `TransferMode` as Java vararg, since we use `javafx.scene.input.DragEvent`
47 | event.acceptTransferModes(TransferMode.Any: _*)
48 | }
49 | event.consume()
50 | }
51 |
52 | scene.onDragDropped = event => {
53 | val db = event.dragboard
54 | val url =
55 | if (db.hasFiles) {
56 | db.getFiles.get(0).toURI.toString
57 | } else if (db.hasUrl) {
58 | db.getUrl
59 | } else {
60 | null
61 | }
62 |
63 | if (url != null) {
64 | songModel.url = url
65 | songModel.mediaPlayer().play()
66 | }
67 | event.dropCompleted = url != null
68 | event.consume()
69 | }
70 | }
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/MetadataView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer3
2 |
3 | import scalafx.Includes.*
4 | import scalafx.geometry.{Insets, VPos}
5 | import scalafx.scene.Node
6 | import scalafx.scene.control.Label
7 | import scalafx.scene.effect.Reflection
8 | import scalafx.scene.image.ImageView
9 | import scalafx.scene.layout.{ColumnConstraints, GridPane, Priority, RowConstraints}
10 |
11 | /**
12 | * @author Jarek Sacha
13 | */
14 | class MetadataView(songModel: SongModel) extends AbstractView(songModel) {
15 |
16 | def initView(): Node = {
17 | val title = new Label {
18 | text <== songModel.title
19 | id = "title"
20 | }
21 | val artist = new Label {
22 | text <== songModel.artist
23 | id = "artist"
24 | }
25 | val album = new Label {
26 | text <== songModel.album
27 | id = "album"
28 | }
29 | val year = new Label {
30 | text <== songModel.year
31 | id = "year"
32 | }
33 | val albumCover = new ImageView {
34 | image <== songModel.albumCover
35 | fitWidth = 240
36 | preserveRatio = true
37 | smooth = true
38 | effect = new Reflection {
39 | fraction = 0.2
40 | }
41 | }
42 |
43 | new GridPane {
44 | padding = Insets(10)
45 | hgap = 20
46 | add(albumCover, 0, 0, 1, GridPane.Remaining)
47 | add(title, 1, 0)
48 | add(artist, 1, 1)
49 | add(album, 1, 2)
50 | add(year, 1, 3)
51 | columnConstraints ++= Seq(
52 | new ColumnConstraints(),
53 | // NOTE: the call to delegate to avoid compilation error.
54 | // Should `scalafx.scene.layout.GridPane.columnConstraints_=()` be fixed to work without call to delegate?
55 | new ColumnConstraints {
56 | hgrow = Priority.Always
57 | }.delegate
58 | )
59 | val r0 = new RowConstraints {
60 | valignment = VPos.Top
61 | }
62 | rowConstraints ++= Seq(r0, r0, r0, r0)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/SongModel.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer3
2 |
3 | import javafx.scene.image as jfxsi
4 | import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectWrapper, StringProperty}
5 | import scalafx.collections.ObservableMap.Add
6 | import scalafx.scene.image.Image
7 | import scalafx.scene.media.{Media, MediaPlayer}
8 |
9 | /**
10 | * @author Jarek Sacha
11 | */
12 | class SongModel {
13 | private val DefaultImageURL = classOf[SongModel].getResource("resources/defaultAlbum.png").toString
14 | private val DefaultImageCover = new Image(DefaultImageURL)
15 | val album = new StringProperty(this, "album")
16 | val artist = new StringProperty(this, "artist")
17 | val title = new StringProperty(this, "title")
18 | val year = new StringProperty(this, "year")
19 | // NOTE: use of `javafx.scene.image.Image` instead of `scalafx.scene.image.Image`, this is required for binding in
20 | // MetadataView to compile.
21 | val albumCover = new ObjectProperty[jfxsi.Image](this, "albumCover")
22 | private val _mediaPlayer = new ReadOnlyObjectWrapper[MediaPlayer](this, "mediaPlayer")
23 |
24 | resetProperties()
25 |
26 | def mediaPlayer = _mediaPlayer.readOnlyProperty
27 |
28 | def url: String = if (mediaPlayer() != null) mediaPlayer().media.source else null
29 |
30 | def url_=(url: String): Unit = {
31 | if (mediaPlayer() != null) mediaPlayer().stop()
32 |
33 | initializeMedia(url)
34 | }
35 |
36 | private def resetProperties(): Unit = {
37 | artist() = ""
38 | album() = ""
39 | title() = ""
40 | year() = ""
41 | albumCover() = DefaultImageCover
42 | }
43 |
44 | private def initializeMedia(url: String): Unit = {
45 | resetProperties()
46 |
47 | try {
48 | val media = new Media(url) {
49 | metadata.onChange((_, change) => {
50 | change match {
51 | case Add(key, added) => handleMetadata(key, added)
52 | case _ =>
53 | }
54 | })
55 | }
56 |
57 | _mediaPlayer() = new MediaPlayer(media) {
58 | self =>
59 | // Handle errors during playback
60 | onError = {
61 | val errorMessage = self.media.error().getMessage
62 | println("MediaPlayer Error: " + errorMessage)
63 | }
64 | }
65 | } catch {
66 | // Handle construction errors
67 | case re: RuntimeException => println("Caught Exception: " + re.getMessage)
68 | }
69 | }
70 |
71 | private def handleMetadata(key: String, value: AnyRef): Unit = {
72 | key match {
73 | case "album" => album() = value.toString
74 | case "artist" => artist() = value.toString
75 | case "title" => title() = value.toString
76 | case "year" => year() = value.toString
77 | case "image" => albumCover() = value.asInstanceOf[javafx.scene.image.Image]
78 | case _ => println("Unhandled metadata key: " + key + ", value: " + value)
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: #222;
3 | -fx-background-color: black;
4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000);
5 | -fx-background-image: url("resources/cross.png");
6 | }
7 |
8 | .button {
9 | -fx-text-fill: #E0E0E0;
10 | }
11 |
12 | .label {
13 | -fx-font-size: 18pt;
14 | -fx-text-fill: #aaa;
15 | }
16 |
17 | #title {
18 | -fx-font-size: 24pt;
19 | -fx-text-fill: #ddd;
20 | }
21 |
22 | #openButton {
23 | -fx-graphic: url("resources/music_note.png");
24 | }
25 |
26 | #playPauseButton, #seekEndButton, #seekStartButton {
27 | -fx-background-color: transparent;
28 | }
29 |
30 | #playPauseButton:hover, #seekEndButton:hover, #seekStartButton:hover {
31 | -fx-background-color: rgb(255, 255, 255, 0.1);
32 | }
33 |
34 | #seekEndButton {
35 | -fx-graphic: url("resources/next.png");
36 | }
37 |
38 | #seekStartButton {
39 | -fx-graphic: url("resources/prev.png");
40 | }
41 |
42 | #volumeHigh {
43 | -fx-image: url("resources/volHigh.png");
44 | }
45 |
46 | #volumeLow {
47 | -fx-image: url("resources/volLow.png");
48 | }
49 |
50 | .statusDisplay {
51 | -fx-border-color: white;
52 | -fx-border-radius: 2;
53 | -fx-border-width: 1;
54 | -fx-text-fill: white;
55 | -fx-font-size: 10pt;
56 | }
57 |
58 | .mediaText {
59 | -fx-text-fill: white;
60 | -fx-font-size: 11pt;
61 | }
62 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/cross.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/defaultAlbum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/defaultAlbum.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/music_note.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/next.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/pause.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/play.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/prev.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/volHigh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/volHigh.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer3/resources/volLow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer3/resources/volLow.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/AbstractView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import scalafx.event.ActionEvent
4 | import scalafx.scene.Node
5 |
6 | /**
7 | * @author Jarek Sacha
8 | */
9 | abstract class AbstractView[T <: Node](protected val songMadel: SongModel) {
10 |
11 | private val _viewNode: T = initView()
12 |
13 | def viewNode: T = _viewNode
14 |
15 | def onNextPageAction(nextHandler: ActionEvent => Unit): Unit = {}
16 |
17 | protected def initView(): T
18 | }
19 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/AudioPlayer4.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import com.sun.javafx.runtime as csjfxr
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.scene.input.TransferMode
8 | import scalafx.scene.layout.{BorderPane, StackPane}
9 | import scalafx.scene.{Node, Scene}
10 |
11 | /**
12 | * @author Jarek Sacha
13 | */
14 | object AudioPlayer4 extends JFXApp3 {
15 | println("JavaFX version: " + csjfxr.VersionInfo.getRuntimeVersion)
16 |
17 | private var songModel: SongModel = _
18 | private var playerControlsView: PlayerControlsView = _
19 | private var metaDataView: MetadataView = _
20 | private var equalizerView: EqualizerView = _
21 | private var page1: Node = _
22 | private var page2: Node = _
23 | private var rootNode: StackPane = _
24 |
25 | override def start(): Unit = {
26 |
27 | songModel = new SongModel() {
28 | url = "https://traffic.libsyn.com/dickwall/JavaPosse373.mp3"
29 | }
30 |
31 | page1 = createPageOne()
32 | page2 = createPageTwo()
33 | rootNode = new StackPane {
34 | children = page1
35 | }
36 |
37 | stage = new PrimaryStage {
38 | title = "Audio Player 4"
39 | scene = new Scene(rootNode, 800, 400) {
40 | val stylesheet = getClass.getResource("media.css")
41 | stylesheets += stylesheet.toString
42 | }
43 | initSceneDragAndDrop(scene())
44 | }
45 |
46 | songModel.mediaPlayer().play()
47 | }
48 |
49 | private def createPageOne(): Node = {
50 | metaDataView = new MetadataView(songModel)
51 | playerControlsView = new PlayerControlsView(songModel)
52 | playerControlsView.onNextPageAction { _ => rootNode.children = page2 }
53 | new BorderPane {
54 | center = metaDataView.viewNode
55 | bottom = playerControlsView.viewNode
56 | }
57 | }
58 |
59 | private def createPageTwo(): Node = {
60 | equalizerView = new EqualizerView(songModel)
61 | equalizerView.onNextPageAction { _ =>
62 | println("Got back button click")
63 | rootNode.children = page1
64 | }
65 | equalizerView.viewNode
66 | }
67 |
68 | private def initSceneDragAndDrop(scene: Scene): Unit = {
69 | scene.onDragOver = event => {
70 | val db = event.dragboard
71 | if (db.hasFiles || db.hasUrl) {
72 | // NOTE: We need to pass in `TransferMode` as Java vararg, since we use `javafx.scene.input.DragEvent`
73 | event.acceptTransferModes(TransferMode.Any: _*)
74 | }
75 | event.consume()
76 | }
77 |
78 | scene.onDragDropped = event => {
79 | val db = event.dragboard
80 | val url =
81 | if (db.hasFiles) {
82 | db.getFiles.get(0).toURI.toString
83 | } else if (db.hasUrl) {
84 | db.getUrl
85 | } else {
86 | null
87 | }
88 |
89 | if (url != null) {
90 | songModel.url = url
91 | songModel.mediaPlayer().play()
92 | }
93 | event.dropCompleted = (url != null)
94 | event.consume()
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/EqualizerView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import scalafx.Includes.*
4 | import scalafx.event.ActionEvent
5 | import scalafx.geometry.{HPos, Insets, Orientation, VPos}
6 | import scalafx.scene.control.{Button, Label, Slider}
7 | import scalafx.scene.layout.{GridPane, Priority, RowConstraints}
8 | import scalafx.scene.media.{EqualizerBand, MediaPlayer}
9 |
10 | /**
11 | * @author Jarek Sacha
12 | */
13 | class EqualizerView(songModel: SongModel) extends AbstractView[GridPane](songModel) {
14 | private final val StartFreq: Double = 250.0
15 | private final val BandCount: Int = 7
16 | private var spectrumBars: Array[SpectrumBar] = _
17 | private var spectrumListener: SpectrumListener = _
18 | private val backButton = new Button {
19 | text = "Back"
20 | id = "backButton"
21 | prefWidth = 50
22 | prefHeight = 32
23 | }
24 |
25 | songModel.mediaPlayer.onChange {
26 | (_, oldValue, _) =>
27 | if (oldValue != null) {
28 | oldValue.setAudioSpectrumListener(null)
29 | clearGridPane()
30 | }
31 |
32 | createEQInterface()
33 | }
34 |
35 | createEQInterface()
36 |
37 | viewNode.scene.onChange {
38 | (_, _, newValue) =>
39 | val mp = songModel.mediaPlayer()
40 | mp.audioSpectrumListener = if (newValue != null) spectrumListener else null
41 | }
42 |
43 | override def onNextPageAction(nextHandler: ActionEvent => Unit): Unit = {
44 | backButton.onAction = nextHandler
45 | }
46 |
47 | protected def initView(): GridPane = {
48 | val middle = new RowConstraints()
49 | val outside = new RowConstraints() {
50 | vgrow = Priority.Always
51 | }
52 | new GridPane {
53 | padding = Insets(10)
54 | hgap = 20
55 | rowConstraints ++= Seq(outside, middle, outside)
56 | }
57 | }
58 |
59 | private def createEQInterface(): Unit = {
60 | val gridPane = viewNode
61 | val mediaPlayer = songModel.mediaPlayer()
62 | createEQBands(gridPane, mediaPlayer)
63 | createSpectrumBars(gridPane)
64 | spectrumListener = new SpectrumListener(StartFreq, mediaPlayer, spectrumBars)
65 | GridPane.setValignment(backButton, VPos.Bottom)
66 | GridPane.setHalignment(backButton, HPos.Center)
67 | GridPane.setMargin(backButton, Insets(20, 0, 0, 0))
68 | gridPane.add(backButton, 0, 3)
69 | }
70 |
71 | private def createEQBands(gp: GridPane, mp: MediaPlayer): Unit = {
72 | val bands = mp.getAudioEqualizer.getBands
73 | bands.clear()
74 | val min = EqualizerBand.MinGain
75 | val max = EqualizerBand.MaxGain
76 | val mid = (max - min) / 2
77 | var freq = StartFreq
78 |
79 | for (j <- 0 until BandCount) {
80 | val theta = j.toDouble / (BandCount - 1).toDouble * (2 * math.Pi)
81 | val scale = 0.4 * (1 + math.cos(theta))
82 | val gain = min + mid + (mid * scale)
83 | bands.add(new EqualizerBand(freq, freq / 2, gain))
84 | freq *= 2
85 | }
86 |
87 | for (i <- 0 until bands.size) {
88 | val band = bands.get(i)
89 | val slider = createEQSlider(band, min, max)
90 | val label = new Label {
91 | text = formatFrequency(band.getCenterFrequency)
92 | styleClass ++= Seq("mediaText", "eqLabel")
93 | }
94 | GridPane.setHalignment(label, HPos.Center)
95 | GridPane.setHalignment(slider, HPos.Center)
96 | GridPane.setHgrow(slider, Priority.Always)
97 | gp.add(label, i, 1)
98 | gp.add(slider, i, 2)
99 | }
100 | }
101 |
102 | private def createEQSlider(eb: EqualizerBand, minValue: Double, maxValue: Double) = new Slider {
103 | min = minValue
104 | max = maxValue
105 | value = eb.gain()
106 | styleClass += "eqSlider"
107 | orientation = Orientation.Vertical
108 | value <==> eb.gain
109 | prefWidth = 44
110 | }
111 |
112 | private def createSpectrumBars(gridPane: GridPane): Unit = {
113 | spectrumBars = new Array[SpectrumBar](BandCount)
114 | for (i <- spectrumBars.indices) {
115 | spectrumBars(i) = new SpectrumBar(100, 20)
116 | spectrumBars(i).setMaxWidth(44)
117 | GridPane.setHalignment(spectrumBars(i), HPos.Center)
118 | gridPane.add(spectrumBars(i), i, 0)
119 | }
120 | }
121 |
122 | private def formatFrequency(centerFrequency: Double): String =
123 | if (centerFrequency < 1000) {
124 | "%.0f Hz".format(centerFrequency)
125 | } else {
126 | "%.1f kHz".format(centerFrequency / 1000)
127 | }
128 |
129 | private def clearGridPane(): Unit = {
130 | viewNode.children.foreach(GridPane.clearConstraints)
131 | viewNode.children.clear()
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/MetadataView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import scalafx.Includes.*
4 | import scalafx.geometry.{Insets, VPos}
5 | import scalafx.scene.control.Label
6 | import scalafx.scene.effect.Reflection
7 | import scalafx.scene.image.ImageView
8 | import scalafx.scene.layout.{ColumnConstraints, GridPane, Priority, RowConstraints}
9 |
10 | /**
11 | * @author Jarek Sacha
12 | */
13 | class MetadataView(songModel: SongModel) extends AbstractView[GridPane](songModel) {
14 |
15 | def initView(): GridPane = {
16 | val title = new Label {
17 | text <== songModel.title
18 | id = "title"
19 | }
20 | val artist = new Label {
21 | text <== songModel.artist
22 | id = "artist"
23 | }
24 | val album = new Label {
25 | text <== songModel.album
26 | id = "album"
27 | }
28 | val year = new Label {
29 | text <== songModel.year
30 | id = "year"
31 | }
32 | val albumCover = new ImageView {
33 | image <== songModel.albumCover
34 | fitWidth = 240
35 | preserveRatio = true
36 | smooth = true
37 | effect = new Reflection {
38 | fraction = 0.2
39 | }
40 | }
41 |
42 | new GridPane {
43 | padding = Insets(10)
44 | hgap = 20
45 | add(albumCover, 0, 0, 1, GridPane.Remaining)
46 | add(title, 1, 0)
47 | add(artist, 1, 1)
48 | add(album, 1, 2)
49 | add(year, 1, 3)
50 | columnConstraints ++= Seq(
51 | new ColumnConstraints(),
52 | // NOTE: the call to delegate to avoid compilation error.
53 | // Should `scalafx.scene.layout.GridPane.columnConstraints_=()` be fixed to work without call to delegate?
54 | new ColumnConstraints {
55 | hgrow = Priority.Always
56 | }.delegate
57 | )
58 | val r0 = new RowConstraints {
59 | valignment = VPos.Top
60 | }
61 | rowConstraints ++= Seq(r0, r0, r0, r0)
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/SongModel.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import javafx.scene.image as jfxsi
4 | import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectWrapper, StringProperty}
5 | import scalafx.collections.ObservableMap.Add
6 | import scalafx.scene.image.Image
7 | import scalafx.scene.media.{Media, MediaPlayer}
8 |
9 | /**
10 | * @author Jarek Sacha
11 | */
12 | class SongModel {
13 | private val DefaultImageURL = classOf[SongModel].getResource("resources/defaultAlbum.png").toString
14 | private val DefaultImageCover = new Image(DefaultImageURL)
15 | val album = new StringProperty(this, "album")
16 | val artist = new StringProperty(this, "artist")
17 | val title = new StringProperty(this, "title")
18 | val year = new StringProperty(this, "year")
19 | // NOTE: use of `javafx.scene.image.Image` instead of `scalafx.scene.image.Image`, this is required for binding in
20 | // MetadataView to compile.
21 | val albumCover = new ObjectProperty[jfxsi.Image](this, "albumCover")
22 | private val _mediaPlayer = new ReadOnlyObjectWrapper[MediaPlayer](this, "mediaPlayer")
23 |
24 | resetProperties()
25 |
26 | def mediaPlayer = _mediaPlayer.readOnlyProperty
27 |
28 | def url: String = if (mediaPlayer() != null) mediaPlayer().media.source else null
29 |
30 | def url_=(url: String): Unit = {
31 | if (mediaPlayer() != null) mediaPlayer().stop()
32 |
33 | initializeMedia(url)
34 | }
35 |
36 | private def resetProperties(): Unit = {
37 | artist() = ""
38 | album() = ""
39 | title() = ""
40 | year() = ""
41 | albumCover() = DefaultImageCover
42 | }
43 |
44 | private def initializeMedia(url: String): Unit = {
45 | resetProperties()
46 |
47 | try {
48 | val media = new Media(url) {
49 | metadata.onChange((_, change) => {
50 | change match {
51 | case Add(key, added) => handleMetadata(key, added)
52 | case _ =>
53 | }
54 | })
55 | }
56 |
57 | _mediaPlayer() = new MediaPlayer(media) {
58 | self =>
59 | // Handle errors during playback
60 | onError = {
61 | val errorMessage = self.media.error().getMessage
62 | println("MediaPlayer Error: " + errorMessage)
63 | }
64 | }
65 | } catch {
66 | // Handle construction errors
67 | case re: RuntimeException => println("Caught Exception: " + re.getMessage)
68 | }
69 | }
70 |
71 | private def handleMetadata(key: String, value: AnyRef): Unit = {
72 | key match {
73 | case "album" => album() = value.toString
74 | case "artist" => artist() = value.toString
75 | case "title" => title() = value.toString
76 | case "year" => year() = value.toString
77 | case "image" => albumCover() = value.asInstanceOf[javafx.scene.image.Image]
78 | case _ => println("Unhandled metadata key: " + key + ", value: " + value)
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/SpectrumBar.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import javafx.scene.layout as jfxsl
4 | import scalafx.Includes.*
5 | import scalafx.geometry.Pos
6 | import scalafx.scene.shape.Rectangle
7 |
8 | /**
9 | * Custom component for displaying a spectrum bar.
10 | *
11 | * @author Jarek Sacha
12 | */
13 | class SpectrumBar(maxValue: Int, barCount: Int) extends jfxsl.VBox {
14 | private val SPACING = 1.0
15 | private val ASPECT_RATIO = 3.0
16 | private val MIN_BAR_HEIGHT = 3.0
17 | private var lastWidth = 0.0
18 | private var lastHeight = 0.0
19 | getStyleClass.add("spectrumBar")
20 |
21 | setSpacing(SPACING)
22 | setAlignment(Pos.BottomCenter)
23 |
24 | for (i <- 0 until barCount) {
25 | val c = (i.toDouble / barCount.toDouble * 255.0).toInt
26 | val r = new Rectangle {
27 | visible = false
28 | style = s"-fx-fill: ladder( rgb($c, $c, $c), red 30%, yellow 70%, #56F32B 90%);"
29 | arcWidth = 2
30 | arcHeight = 2
31 | }
32 |
33 | getChildren.add(r)
34 | }
35 |
36 | protected override def computeMinHeight(width: Double) = computeHeight(MIN_BAR_HEIGHT)
37 |
38 | protected override def computeMinWidth(height: Double) = computeWidthForHeight(MIN_BAR_HEIGHT)
39 |
40 | protected override def computePrefHeight(width: Double) = computeHeight(5)
41 |
42 | protected override def computePrefWidth(height: Double) = computeWidthForHeight(5)
43 |
44 | def setValue(value: Double): Unit = {
45 | val barsLit = math.min(barCount, math.round(value / maxValue * barCount).toInt)
46 | val childList = getChildren
47 | for (i <- 0 until childList.size) {
48 | childList.get(i).setVisible(i > barCount - barsLit)
49 | }
50 | }
51 |
52 | protected override def layoutChildren(): Unit = {
53 | if (lastWidth != getWidth || lastHeight != getHeight) {
54 | val spacing = SPACING * (barCount - 1)
55 | val barHeight = (getHeight - getVerticalPadding - spacing) / barCount
56 | val barWidth = math.min(barHeight * ASPECT_RATIO, getWidth - getHorizontalPadding)
57 |
58 | for (node <- getChildren) {
59 | val r = node.asInstanceOf[javafx.scene.shape.Rectangle]
60 | r.width = barWidth
61 | r.height = barHeight
62 | }
63 |
64 | lastWidth = getWidth
65 | lastHeight = getHeight
66 | }
67 |
68 | super.layoutChildren()
69 | }
70 |
71 | private def computeWidthForHeight(barHeight: Double) = barHeight * ASPECT_RATIO + getHorizontalPadding
72 |
73 | private def computeHeight(barHeight: Double): Double = {
74 | val vPadding = getVerticalPadding
75 | val barHeights = barHeight * barCount
76 | val spacing = SPACING * (barCount - 1)
77 | barHeights + spacing + vPadding
78 | }
79 |
80 | private def getVerticalPadding: Double = {
81 | val padding = getPadding
82 | padding.top + padding.bottom
83 | }
84 |
85 | private def getHorizontalPadding: Double = {
86 | val padding = getPadding
87 | padding.left + padding.right
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/SpectrumListener.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.AudioPlayer4
2 |
3 | import javafx.scene.media as jfxcm
4 | import scalafx.scene.media.MediaPlayer
5 |
6 | /**
7 | * @author Jarek Sacha
8 | */
9 | class SpectrumListener(startFreq: Double, mp: MediaPlayer, bars: Array[SpectrumBar])
10 | extends jfxcm.AudioSpectrumListener {
11 | private val minValue = mp.audioSpectrumThreshold()
12 | private val norms = createNormArray()
13 | private val bandCount = mp.audioSpectrumNumBands()
14 | private val spectrumBucketCounts = createBucketCounts(startFreq, bandCount)
15 |
16 | def spectrumDataUpdate(timestamp: Double, duration: Double, magnitudes: Array[Float], phases: Array[Float]): Unit = {
17 | var index = 0
18 | var bucketIndex = 0
19 | var currentBucketCount = 0
20 | var sum = 0.0
21 |
22 | while (index < magnitudes.length) {
23 | sum += magnitudes(index) - minValue
24 | currentBucketCount += 1
25 |
26 | if (currentBucketCount >= spectrumBucketCounts(bucketIndex)) {
27 | bars(bucketIndex).setValue(sum / norms(bucketIndex))
28 | currentBucketCount = 0
29 | sum = 0.0
30 | bucketIndex += 1
31 | }
32 |
33 | index += 1
34 | }
35 | }
36 |
37 | private def createNormArray(): Array[Double] = {
38 | val normArray = new Array[Double](bars.length)
39 | var currentNorm = 0.05
40 | for (i <- normArray.indices) {
41 | normArray(i) = 1 + currentNorm
42 | currentNorm *= 2
43 | }
44 | normArray
45 | }
46 |
47 | private def createBucketCounts(startFreq: Double, bandCount: Int): Array[Int] = {
48 | val bucketCounts = new Array[Int](bars.length)
49 |
50 | val bandwidth = 22050.0 / bandCount
51 | val centerFreq = bandwidth / 2
52 | var currentSpectrumFreq = centerFreq
53 | var currentEQFreq = startFreq / 2
54 | var currentCutoff = 0d
55 | var currentBucketIndex = -1
56 |
57 | for (_ <- 0 until bandCount) {
58 | if (currentSpectrumFreq > currentCutoff) {
59 | currentEQFreq *= 2
60 | currentCutoff = currentEQFreq + currentEQFreq / 2
61 | currentBucketIndex += 1
62 | }
63 |
64 | if (currentBucketIndex < bucketCounts.length) {
65 | bucketCounts(currentBucketIndex) += 1
66 | currentSpectrumFreq += bandwidth
67 | }
68 | }
69 |
70 | bucketCounts
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: #222;
3 | -fx-background-color: black;
4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000);
5 | -fx-background-image: url("resources/cross.png");
6 | }
7 |
8 | .button {
9 | -fx-text-fill: #E0E0E0;
10 | }
11 |
12 | .label {
13 | -fx-font-size: 18pt;
14 | -fx-text-fill: #aaa;
15 | }
16 |
17 | #title {
18 | -fx-font-size: 24pt;
19 | -fx-text-fill: #ddd;
20 | }
21 |
22 | #openButton {
23 | -fx-graphic: url("resources/music_note.png");
24 | }
25 |
26 | #eqButton {
27 | -fx-shape: "M 1,0 L 10,0 13,5 10,10 1,10 Q 0 10 0 9 L 0,1 Q 0 0 1 0";
28 | -fx-font-size: 12pt;
29 | -fx-alignment: center-left;
30 | }
31 |
32 | .eqSlider {
33 | -fx-background-radius: 5;
34 | -fx-background-color: #222, #888, black;
35 | -fx-background-insets: 0, 1 0 0 1, 1;
36 | -fx-padding: 2 10;
37 | }
38 |
39 | .eqSlider .thumb {
40 | -fx-background-image: url("resources/thumb.png");
41 | -fx-padding: 12;
42 | }
43 |
44 | .eqSlider:vertical .track {
45 | -fx-padding: 5;
46 | }
47 |
48 | .spectrumBar {
49 | -fx-background-radius: 5;
50 | -fx-background-color: #222, #888, black;
51 | -fx-background-insets: 0, 1 0 0 1, 1;
52 | -fx-padding: 2 10;
53 | }
54 |
55 | #backButton {
56 | -fx-shape: "M 3,0 L 12,0 Q 13,0 13,1 L 13,9 Q 13,10 12,10 L 3,10 1,5";
57 | -fx-font-size: 12pt;
58 | -fx-alignment: center-right;
59 | }
60 |
61 | #playPauseButton, #seekEndButton, #seekStartButton {
62 | -fx-background-color: transparent;
63 | }
64 |
65 | #playPauseButton:hover, #seekEndButton:hover, #seekStartButton:hover {
66 | -fx-background-color: rgb(255, 255, 255, 0.1);
67 | }
68 |
69 | #seekEndButton {
70 | -fx-graphic: url("resources/next.png");
71 | }
72 |
73 | #seekStartButton {
74 | -fx-graphic: url("resources/prev.png");
75 | }
76 |
77 | #volumeHigh {
78 | -fx-image: url("resources/volHigh.png");
79 | }
80 |
81 | #volumeLow {
82 | -fx-image: url("resources/volLow.png");
83 | }
84 |
85 | .statusDisplay {
86 | -fx-border-color: white;
87 | -fx-border-radius: 2;
88 | -fx-border-width: 1;
89 | -fx-text-fill: white;
90 | -fx-font-size: 10pt;
91 | }
92 |
93 | .mediaText {
94 | -fx-text-fill: white;
95 | -fx-font-size: 11pt;
96 | }
97 |
98 | .eqLabel {
99 | -fx-padding: 8 3;
100 | }
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/cross.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/defaultAlbum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/defaultAlbum.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/music_note.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/next.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/pause.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/play.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/prev.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/volHigh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/volHigh.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/AudioPlayer4/resources/volLow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/AudioPlayer4/resources/volLow.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/BasicAudioClip/BasicAudioClip.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.BasicAudioClip
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.Insets
7 | import scalafx.scene.Scene
8 | import scalafx.scene.control.Button
9 | import scalafx.scene.layout.StackPane
10 | import scalafx.scene.media.AudioClip
11 |
12 | /**
13 | * Playing an audio clip.
14 | *
15 | * @author Jarek Sacha
16 | */
17 | object BasicAudioClip extends JFXApp3 {
18 |
19 | override def start(): Unit = {
20 |
21 | val resource = getClass.getResource("resources/beep.wav")
22 | val audioClip = new AudioClip(resource.toString)
23 | val stackPane = new StackPane {
24 | padding = Insets(10)
25 | children = new Button {
26 | text = "Bing Zzzzt!"
27 | onAction = () => audioClip.play(1.0)
28 | }
29 | }
30 |
31 | stage = new PrimaryStage {
32 | title = "Basic AudioClip Example"
33 | scene = new Scene(stackPane, 200, 200) {
34 | stylesheets += getClass.getResource("media.css").toString
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/BasicAudioClip/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: #606060;
3 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #666a6b, #2b2f32);
4 | -fx-background-image: url("resources/cross.png");
5 | /* -fx-background-repeat: repeat; */
6 | -fx-background-position: left top;
7 | }
8 |
9 | .button {
10 | -fx-text-fill: #E0E0E0;
11 | -fx-font-size: 20pt;
12 | -fx-pref-width: 300px;
13 | }
14 |
15 | .label {
16 | -fx-text-fill: #E0E0E0;
17 | }
18 |
19 | #clipLabel {
20 | -fx-font-size: 24pt;
21 | }
22 |
23 | .hyperlink {
24 | -fx-text-fill: #808080;
25 | }
--------------------------------------------------------------------------------
/src/proscalafx/ch08/BasicAudioClip/resources/beep.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/BasicAudioClip/resources/beep.wav
--------------------------------------------------------------------------------
/src/proscalafx/ch08/BasicAudioClip/resources/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/BasicAudioClip/resources/cross.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/CodeMonkeyToDo.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.CodeMonkeyToDo
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.geometry.{HPos, Insets, Pos}
7 | import scalafx.scene.Scene
8 | import scalafx.scene.control.{Button, Hyperlink, Label, Slider}
9 | import scalafx.scene.layout.{GridPane, Priority, VBox}
10 | import scalafx.scene.media.AudioClip
11 | import scalafx.scene.web.WebView
12 |
13 | /**
14 | * Controlling the playback parameters of an AudioClip.
15 | *
16 | * @author Jarek Sacha
17 | */
18 | object CodeMonkeyToDo extends JFXApp3 {
19 |
20 | override def start(): Unit = {
21 | val coffeeClip = new AudioClip(clipResourceString("resources/coffee.mp3"))
22 | val jobClip = new AudioClip(clipResourceString("resources/job.mp3"))
23 | val meetingClip = new AudioClip(clipResourceString("resources/meeting.mp3"))
24 |
25 | val grid = new GridPane {
26 | padding = Insets(10)
27 | hgap = 10
28 | vgap = 5
29 | }
30 |
31 | createClipList(grid, coffeeClip, jobClip, meetingClip)
32 |
33 | stage = new PrimaryStage {
34 | scene = new Scene(grid, 640, 380) {
35 | title = "AudioClip Example"
36 | stylesheets += getClass.getResource("media.css").toString
37 | }
38 | }
39 | }
40 |
41 | private def clipResourceString(clipName: String): String = {
42 | getClass.getResource(clipName).toString
43 | }
44 |
45 | private def createControls(grid: GridPane): (Slider, Slider, Slider) = {
46 | val volumeLabel = new Label {
47 | text = "Volume"
48 | }
49 | val rateLabel = new Label {
50 | text = "Rate"
51 | }
52 | val balanceLabel = new Label {
53 | text = "Balance"
54 | }
55 |
56 | GridPane.setHalignment(volumeLabel, HPos.Center)
57 | GridPane.setHalignment(rateLabel, HPos.Center)
58 | GridPane.setHalignment(balanceLabel, HPos.Center)
59 |
60 | val volumeSlider = new Slider {
61 | min = 0.0
62 | max = 1.0
63 | value = 1.0
64 | hgrow = Priority.Always
65 | }
66 | val rateSlider = new Slider {
67 | min = 0.25
68 | max = 2.5
69 | value = 1.0
70 | hgrow = Priority.Always
71 | }
72 | val balanceSlider = new Slider {
73 | min = -1.0
74 | max = 1.0
75 | value = 0.0
76 | hgrow = Priority.Always
77 | }
78 |
79 | grid.add(volumeLabel, 0, 2)
80 | grid.add(volumeSlider, 0, 3)
81 | grid.add(rateLabel, 1, 2)
82 | grid.add(rateSlider, 1, 3)
83 | grid.add(balanceLabel, 2, 2)
84 | grid.add(balanceSlider, 2, 3)
85 |
86 | (volumeSlider, rateSlider, balanceSlider)
87 | }
88 |
89 | private def createClipList(
90 | grid: GridPane,
91 | coffeeClip: AudioClip,
92 | jobClip: AudioClip,
93 | meetingClip: AudioClip
94 | ): Unit = {
95 |
96 | val (volumeSlider, rateSlider, balanceSlider) = createControls(grid)
97 |
98 | val clipLabel = new Label {
99 | text = "Code Monkey To-Do List:"
100 | id = "clipLabel"
101 | }
102 | val getUpButton = new Button {
103 | text = "Get Up, Get Coffee"
104 | prefWidth = 300
105 | onAction = play(coffeeClip, volumeSlider, rateSlider, balanceSlider)
106 | }
107 | val goToJobButton: Button = new Button {
108 | text = "Go to Job"
109 | prefWidth = 300
110 | onAction = play(jobClip, volumeSlider, rateSlider, balanceSlider)
111 | }
112 | val meetingButton: Button = new Button {
113 | text = "Have Boring Meeting"
114 | prefWidth = 300
115 | onAction = play(meetingClip, volumeSlider, rateSlider, balanceSlider)
116 | }
117 | val link = new Hyperlink {
118 | text = "About Code Monkey..."
119 | onAction = () => {
120 | val webView = new WebView {
121 | engine.load("https://www.jonathancoulton.com/2006/04/14/" + "thing-a-week-29-code-monkey/")
122 | }
123 | val stage = new PrimaryStage {
124 | title = "Code Monkey"
125 | scene = new Scene(webView, 720, 480)
126 | }
127 | stage.show()
128 | }
129 | }
130 |
131 | val vbox = new VBox {
132 | spacing = 30
133 | alignment = Pos.TopCenter
134 | children ++= Seq(clipLabel, getUpButton, goToJobButton, meetingButton, link)
135 | }
136 |
137 | GridPane.setHalignment(vbox, HPos.Center)
138 |
139 | GridPane.setHgrow(vbox, Priority.Always)
140 | GridPane.setVgrow(vbox, Priority.Always)
141 | grid.add(vbox, 0, 0, GridPane.Remaining, 1)
142 | }
143 |
144 | /** Returns a function that can be assigned to `oAction` */
145 | private def play(audioClip: AudioClip, volumeSlider: Slider, rateSlider: Slider, balanceSlider: Slider): () => Unit =
146 | () => audioClip.play(volumeSlider.value(), balanceSlider.value(), rateSlider.value(), 0.0, 0)
147 | }
148 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: #606060;
3 | -fx-background-color: radial-gradient(center 50% 25%, radius 60%, #666a6b, #2b2f32);
4 | -fx-background-image: url("resources/cross.png");
5 | /* -fx-background-repeat: repeat;*/
6 | -fx-background-position: left top;
7 | }
8 |
9 | .button {
10 | -fx-text-fill: #E0E0E0;
11 | -fx-font-size: 20pt;
12 | -fx-pref-width: 300px;
13 | }
14 |
15 | .label {
16 | -fx-text-fill: #E0E0E0;
17 | }
18 |
19 | #clipLabel {
20 | -fx-font-size: 24pt;
21 | }
22 |
23 | .hyperlink {
24 | -fx-text-fill: #808080;
25 | }
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.mp3
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/coffee.wav
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/cross.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/job.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/job.mp3
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/job.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/job.wav
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.mp3:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.mp3
--------------------------------------------------------------------------------
/src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.wav:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/CodeMonkeyToDo/resources/meeting.wav
--------------------------------------------------------------------------------
/src/proscalafx/ch08/FullScreenVideoPlayer/FullScreenVideoPlayer.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.FullScreenVideoPlayer
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.scene.Scene
6 | import scalafx.scene.layout.StackPane
7 | import scalafx.scene.media.{Media, MediaPlayer, MediaView}
8 |
9 | import java.io.File
10 |
11 | /**
12 | * @author Jarek Sacha
13 | */
14 | object FullScreenVideoPlayer extends JFXApp3 {
15 |
16 | override def start(): Unit = {
17 |
18 | val file = new File("media/omgrobots.mp4")
19 | val media = new Media(file.toURI.toString)
20 | val mediaPlayer = new MediaPlayer(media)
21 | val mediaView = new MediaView(mediaPlayer) {
22 | fitWidth <== scene.selectDouble("width")
23 | fitHeight <== scene.selectDouble("height")
24 | preserveRatio = true
25 | }
26 |
27 | val root = new StackPane {
28 | children = mediaView
29 | }
30 |
31 | stage = new PrimaryStage {
32 | title = "Video Player 1"
33 | fullScreen = true
34 | scene = new Scene(root, 960, 540)
35 | }
36 |
37 | mediaPlayer.play()
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer1/VideoPlayer1.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer1
2 |
3 | import scalafx.application.JFXApp3
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.scene.Scene
6 | import scalafx.scene.layout.StackPane
7 | import scalafx.scene.media.{Media, MediaPlayer, MediaView}
8 |
9 | import java.io.File
10 |
11 | /**
12 | * @author Jarek Sacha
13 | */
14 | object VideoPlayer1 extends JFXApp3 {
15 |
16 | override def start(): Unit = {
17 | val file = new File("media/omgrobots.mp4")
18 |
19 | val media = new Media(file.toURI.toString)
20 | val mediaPlayer = new MediaPlayer(media)
21 | val mediaView = new MediaView(mediaPlayer)
22 | val root = new StackPane {
23 | children = mediaView
24 | }
25 |
26 | stage = new PrimaryStage {
27 | title = "Video Player 1"
28 | scene = new Scene(root, 960, 540)
29 | }
30 |
31 | mediaPlayer.play()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer2/VideoPlayer2.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer2
2 |
3 | import scalafx.Includes.*
4 | import scalafx.application.JFXApp3.PrimaryStage
5 | import scalafx.application.{JFXApp3, Platform}
6 | import scalafx.geometry.Pos
7 | import scalafx.scene.Scene
8 | import scalafx.scene.control.Label
9 | import scalafx.scene.layout.StackPane
10 | import scalafx.scene.media.{Media, MediaPlayer, MediaView}
11 | import scalafx.util.Duration
12 |
13 | import java.io.File
14 | import java.net.URL
15 | import scala.language.postfixOps
16 |
17 | /**
18 | * @author Jarek Sacha
19 | */
20 | object VideoPlayer2 extends JFXApp3 {
21 |
22 | override def start(): Unit = {
23 |
24 | val markerText = new Label {
25 | alignmentInParent = Pos.TopCenter
26 | }
27 |
28 | val file = new File("media/omgrobots.mp4")
29 | val media = new Media(file.toURI.toString) {
30 | markers ++= Map(
31 | "Robot Finds Wall" -> (3100 ms),
32 | "Then Finds the Green Line" -> (5600 ms),
33 | "Robot Grabs Sled" -> (8000 ms),
34 | "And Heads for Home" -> (11500 ms)
35 | )
36 | }
37 |
38 | val mediaPlayer = new MediaPlayer(media) {
39 | onMarker = event =>
40 | Platform.runLater {
41 | markerText.text = event.marker.getKey
42 | }
43 | }
44 |
45 | val mediaView = new MediaView(mediaPlayer)
46 | val root = new StackPane {
47 | children ++= Seq(mediaView, markerText)
48 | onMouseClicked = () => {
49 | mediaPlayer.seek(Duration.Zero)
50 | markerText.text = ""
51 | }
52 | }
53 |
54 | stage = new PrimaryStage {
55 | title = "Video Player 2"
56 | scene = new Scene(root, 960, 540) {
57 | val stylesheet: URL = getClass.getResource("media.css")
58 | stylesheets += stylesheet.toString
59 | }
60 | }
61 |
62 | mediaPlayer.play()
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer2/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-background-color: black;
3 | }
4 |
5 | .label {
6 | -fx-padding: 20;
7 | -fx-font-size: 36pt;
8 | -fx-text-fill: white;
9 | }
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer3/VideoPlayer3.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer3
2 |
3 | import scalafx.Includes.*
4 | import scalafx.animation.{ParallelTransition, TranslateTransition}
5 | import scalafx.application.JFXApp3.PrimaryStage
6 | import scalafx.application.{JFXApp3, Platform}
7 | import scalafx.geometry.{Pos, Rectangle2D}
8 | import scalafx.scene.control.Label
9 | import scalafx.scene.layout.StackPane
10 | import scalafx.scene.media.{Media, MediaPlayer, MediaView}
11 | import scalafx.scene.{Node, Scene}
12 | import scalafx.util.Duration
13 |
14 | import java.io.File
15 | import java.net.URL
16 | import scala.language.postfixOps
17 |
18 | /**
19 | * @author Jarek Sacha
20 | */
21 | object VideoPlayer3 extends JFXApp3 {
22 |
23 | override def start(): Unit = {
24 |
25 | val message = new Label {
26 | text = "I \u2764 Robots"
27 | visible = false
28 | }
29 |
30 | val file = new File("media/omgrobots.mp4")
31 | val media = new Media(file.toURI.toString) {
32 | markers ++= Map(
33 | "Split" -> (3000 ms),
34 | "Join" -> (9000 ms)
35 | )
36 | }
37 |
38 | val mediaPlayer = new MediaPlayer(media)
39 |
40 | val mediaView1 = new MediaView(mediaPlayer) {
41 | viewport = new Rectangle2D(0, 0, 960 / 2, 540)
42 | alignmentInParent = Pos.CenterLeft
43 | }
44 |
45 | val mediaView2 = new MediaView(mediaPlayer) {
46 | viewport = new Rectangle2D(960 / 2, 0, 960 / 2, 540)
47 | alignmentInParent = Pos.CenterRight
48 | }
49 |
50 | val root = new StackPane {
51 | children ++= Seq(message, mediaView1, mediaView2)
52 | onMouseClicked = () => {
53 | mediaPlayer.seek(Duration.Zero)
54 | message.visible = false
55 | }
56 | }
57 |
58 | stage = new PrimaryStage {
59 | title = "Video Player 3"
60 | scene = new Scene(root, 960, 540) {
61 | val stylesheet: URL = getClass.getResource("media.css")
62 | stylesheets += stylesheet.toString
63 | }
64 | }
65 |
66 | mediaPlayer.onMarker = event =>
67 | Platform.runLater {
68 | event.marker.getKey match {
69 | case "Split" =>
70 | message.visible = true
71 | buildSplitTransition(mediaView1, mediaView2).play()
72 | case _ => buildJoinTransition(mediaView1, mediaView2).play()
73 | }
74 | }
75 |
76 | mediaPlayer.play()
77 | }
78 |
79 | private def buildJoinTransition(one: Node, two: Node) = new ParallelTransition {
80 | children = List(
81 | new TranslateTransition {
82 | duration = (1000 ms)
83 | node = one
84 | byX = 200
85 | },
86 | new TranslateTransition {
87 | duration = (1000 ms)
88 | node = two
89 | byX = -200
90 | }
91 | )
92 | }
93 |
94 | private def buildSplitTransition(one: Node, two: Node) = new ParallelTransition {
95 | children = List(
96 | new TranslateTransition {
97 | duration = (1000 ms)
98 | node = one
99 | byX = -200
100 | },
101 | new TranslateTransition {
102 | duration = (1000 ms)
103 | node = two
104 | byX = 200
105 | }
106 | )
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer3/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-background-color: black;
3 | }
4 |
5 | .label {
6 | -fx-padding: 20;
7 | -fx-font-size: 48pt;
8 | -fx-text-fill: darkred;
9 | }
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/AbstractView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import scalafx.event.ActionEvent
4 | import scalafx.scene.Node
5 |
6 | /**
7 | * @author Jarek Sacha
8 | */
9 | abstract class AbstractView[T <: Node](protected val mediaModel: MediaModel) {
10 |
11 | private val _viewNode: T = initView()
12 |
13 | def viewNode: T = _viewNode
14 |
15 | def onNextPageAction(nextHandler: ActionEvent => Unit): Unit = {}
16 |
17 | protected def initView(): T
18 | }
19 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/EqualizerView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import scalafx.Includes.*
4 | import scalafx.event.ActionEvent
5 | import scalafx.geometry.{HPos, Insets, Orientation, VPos}
6 | import scalafx.scene.control.{Button, Label, Slider}
7 | import scalafx.scene.layout.{GridPane, Priority, RowConstraints}
8 | import scalafx.scene.media.{EqualizerBand, MediaPlayer}
9 |
10 | /**
11 | * @author Jarek Sacha
12 | */
13 | class EqualizerView(mediaModel: MediaModel) extends AbstractView[GridPane](mediaModel) {
14 |
15 | private final val StartFrequency: Double = 250.0
16 | private final val BandCount: Int = 7
17 | private var spectrumBars: Array[SpectrumBar] = _
18 | private var spectrumListener: SpectrumListener = _
19 | private val backButton = new Button {
20 | text = "Back"
21 | id = "backButton"
22 | prefWidth = 50
23 | prefHeight = 32
24 | }
25 |
26 | mediaModel.mediaPlayer.onChange {
27 | (_, oldValue, _) =>
28 | if (oldValue != null) {
29 | oldValue.setAudioSpectrumListener(null)
30 | clearGridPane()
31 | }
32 |
33 | createEQInterface()
34 | }
35 |
36 | createEQInterface()
37 |
38 | viewNode.scene.onChange {
39 | (_, _, newValue) =>
40 | val mp = mediaModel.mediaPlayer()
41 | mp.audioSpectrumListener = if (newValue != null) spectrumListener else null
42 | }
43 |
44 | override def onNextPageAction(nextHandler: ActionEvent => Unit): Unit = {
45 | backButton.onAction = nextHandler
46 | }
47 |
48 | protected def initView(): GridPane = {
49 | val middle = new RowConstraints()
50 | val outside = new RowConstraints() {
51 | vgrow = Priority.Always
52 | }
53 | new GridPane {
54 | padding = Insets(10)
55 | hgap = 20
56 | rowConstraints ++= Seq(outside, middle, outside)
57 | }
58 | }
59 |
60 | private def createEQInterface(): Unit = {
61 | val gridPane = viewNode
62 | val mediaPlayer = mediaModel.mediaPlayer()
63 |
64 | createEQBands(gridPane, mediaPlayer)
65 | createSpectrumBars(gridPane)
66 | spectrumListener = new SpectrumListener(StartFrequency, mediaPlayer, spectrumBars)
67 |
68 | GridPane.setValignment(backButton, VPos.Bottom)
69 | GridPane.setHalignment(backButton, HPos.Center)
70 | GridPane.setMargin(backButton, Insets(20, 0, 0, 0))
71 | gridPane.add(backButton, 0, 3)
72 | }
73 |
74 | private def createEQBands(gp: GridPane, mp: MediaPlayer): Unit = {
75 | val bands = mp.getAudioEqualizer.getBands
76 |
77 | bands.clear()
78 |
79 | val min = EqualizerBand.MinGain
80 | val max = EqualizerBand.MaxGain
81 | val mid = (max - min) / 2
82 | var freq = StartFrequency
83 |
84 | // Create the equalizer bands with the gains preset to
85 | // a nice cosine wave pattern.
86 | for (j <- 0 until BandCount) {
87 | // Use j and BandCount to calculate a value between 0 and 2*pi
88 | val theta = j.toDouble / (BandCount - 1).toDouble * (2 * math.Pi)
89 |
90 | // The cos function calculates a scale value between 0 and 0.4
91 | val scale = 0.4 * (1 + math.cos(theta))
92 |
93 | // Set the gain to be a value between the midpoint and 0.9*max.
94 | val gain = min + mid + (mid * scale)
95 |
96 | bands.add(new EqualizerBand(freq, freq / 2, gain))
97 | freq *= 2
98 | }
99 |
100 | for (i <- 0 until bands.size) {
101 | val band = bands.get(i)
102 | val slider = createEQSlider(band, min, max)
103 |
104 | val label = new Label {
105 | text = formatFrequency(band.getCenterFrequency)
106 | styleClass ++= Seq("mediaText", "eqLabel")
107 | }
108 |
109 | GridPane.setHalignment(label, HPos.Center)
110 | GridPane.setHalignment(slider, HPos.Center)
111 | GridPane.setHgrow(slider, Priority.Always)
112 |
113 | gp.add(label, i, 1)
114 | gp.add(slider, i, 2)
115 | }
116 | }
117 |
118 | private def createEQSlider(eb: EqualizerBand, minValue: Double, maxValue: Double) = new Slider {
119 | min = minValue
120 | max = maxValue
121 | value = eb.gain()
122 | styleClass += "eqSlider"
123 | orientation = Orientation.Vertical
124 | value <==> eb.gain
125 | prefWidth = 44
126 | }
127 |
128 | private def createSpectrumBars(gridPane: GridPane): Unit = {
129 | spectrumBars = new Array[SpectrumBar](BandCount)
130 |
131 | for (i <- spectrumBars.indices) {
132 | spectrumBars(i) = new SpectrumBar(100, 20)
133 | spectrumBars(i).setMaxWidth(44)
134 | GridPane.setHalignment(spectrumBars(i), HPos.Center)
135 | gridPane.add(spectrumBars(i), i, 0)
136 | }
137 | }
138 |
139 | private def formatFrequency(centerFrequency: Double): String =
140 | if (centerFrequency < 1000) {
141 | "%.0f Hz".format(centerFrequency)
142 | } else {
143 | "%.1f kHz".format(centerFrequency / 1000)
144 | }
145 |
146 | private def clearGridPane(): Unit = {
147 | viewNode.children.foreach(GridPane.clearConstraints)
148 | viewNode.children.clear()
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/MediaModel.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import javafx.scene.{image as jfxsi, media as jfxsm}
4 | import scalafx.Includes.*
5 | import scalafx.beans.property.{ObjectProperty, ReadOnlyObjectWrapper, StringProperty}
6 | import scalafx.collections.ObservableMap.Add
7 | import scalafx.scene.image.Image
8 | import scalafx.scene.media.{Media, MediaPlayer}
9 |
10 | /**
11 | * @author Jarek Sacha
12 | */
13 | class MediaModel {
14 | private val DefaultImageURL = classOf[MediaModel].getResource("resources/defaultAlbum.png").toString
15 | private val DefaultImageCover = new Image(DefaultImageURL)
16 |
17 | val album = new StringProperty(this, "album")
18 | val artist = new StringProperty(this, "artist")
19 | val title = new StringProperty(this, "title")
20 | val year = new StringProperty(this, "year")
21 |
22 | // NOTE: use of `javafx.scene.image.Image` instead of `scalafx.scene.image.Image`, this is required for binding in
23 | // MetadataView to compile.
24 | val albumCover = new ObjectProperty[jfxsi.Image](this, "albumCover")
25 |
26 | // NOTE We use jfxsm.MediaPlayer as type parameter to ReadOnlyObjectWrapper so we can bind to MediaView.mediaPlayer
27 | // see proscalafx.ch08.VideoPlayer4.VideoView.initView
28 | private val _mediaPlayer = new ReadOnlyObjectWrapper[jfxsm.MediaPlayer](this, "mediaPlayer")
29 |
30 | resetProperties()
31 |
32 | def mediaPlayer = _mediaPlayer.readOnlyProperty
33 |
34 | def url: String = if (mediaPlayer() != null) mediaPlayer().media.source else null
35 |
36 | def url_=(url: String): Unit = {
37 | if (mediaPlayer() != null) mediaPlayer().stop()
38 |
39 | initializeMedia(url)
40 | }
41 |
42 | private def resetProperties(): Unit = {
43 | artist() = ""
44 | album() = ""
45 | title() = ""
46 | year() = ""
47 | albumCover() = DefaultImageCover
48 | }
49 |
50 | private def initializeMedia(url: String): Unit = {
51 | resetProperties()
52 |
53 | try {
54 | val media = new Media(url) {
55 | metadata.onChange((_, change) => {
56 | change match {
57 | case Add(key, added) => handleMetadata(key, added)
58 | case _ =>
59 | }
60 | })
61 | }
62 |
63 | _mediaPlayer() = new MediaPlayer(media) {
64 | self =>
65 | // Handle errors during playback
66 | onError = {
67 | val errorMessage = self.media.error().getMessage
68 | println("MediaPlayer Error: " + errorMessage)
69 | }
70 | }.delegate
71 | } catch {
72 | // Handle construction errors
73 | case re: RuntimeException => println("Caught Exception: " + re.getMessage)
74 | }
75 | }
76 |
77 | private def handleMetadata(key: String, value: AnyRef): Unit = {
78 | key match {
79 | case "album" => album() = value.toString
80 | case "artist" => artist() = value.toString
81 | case "title" => title() = value.toString
82 | case "year" => year() = value.toString
83 | case "image" => albumCover() = value.asInstanceOf[javafx.scene.image.Image]
84 | case _ => println("Unhandled metadata key: " + key + ", value: " + value)
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/SpectrumBar.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import javafx.scene.layout as jfxsl
4 | import scalafx.Includes.*
5 | import scalafx.geometry.Pos
6 | import scalafx.scene.shape.Rectangle
7 |
8 | /**
9 | * Custom component for displaying a spectrum bar.
10 | *
11 | * @author Jarek Sacha
12 | */
13 | class SpectrumBar(maxValue: Int, barCount: Int) extends jfxsl.VBox {
14 | private val SPACING = 1.0
15 | private val ASPECT_RATIO = 3.0
16 | private val MIN_BAR_HEIGHT = 3.0
17 | private var lastWidth = 0.0
18 | private var lastHeight = 0.0
19 | getStyleClass.add("spectrumBar")
20 |
21 | setSpacing(SPACING)
22 | setAlignment(Pos.BottomCenter)
23 |
24 | for (i <- 0 until barCount) {
25 | val c = (i.toDouble / barCount.toDouble * 255.0).toInt
26 | val r = new Rectangle {
27 | visible = false
28 | style = s"-fx-fill: ladder( rgb($c, $c, $c), red 30%, yellow 70%, #56F32B 90%);"
29 | arcWidth = 2
30 | arcHeight = 2
31 | }
32 |
33 | getChildren.add(r)
34 | }
35 |
36 | protected override def computeMinHeight(width: Double) = computeHeight(MIN_BAR_HEIGHT)
37 |
38 | protected override def computeMinWidth(height: Double) = computeWidthForHeight(MIN_BAR_HEIGHT)
39 |
40 | protected override def computePrefHeight(width: Double) = computeHeight(5)
41 |
42 | protected override def computePrefWidth(height: Double) = computeWidthForHeight(5)
43 |
44 | def setValue(value: Double): Unit = {
45 | val barsLit = math.min(barCount, math.round(value / maxValue * barCount).toInt)
46 | val childList = getChildren
47 | for (i <- 0 until childList.size) {
48 | childList.get(i).visible = (i > barCount - barsLit)
49 | }
50 | }
51 |
52 | protected override def layoutChildren(): Unit = {
53 | if (lastWidth != getWidth || lastHeight != getHeight) {
54 | val spacing = SPACING * (barCount - 1)
55 | val barHeight = (getHeight - getVerticalPadding - spacing) / barCount
56 | val barWidth = math.min(barHeight * ASPECT_RATIO, getWidth - getHorizontalPadding)
57 |
58 | for (node <- getChildren) {
59 | val r = node.asInstanceOf[javafx.scene.shape.Rectangle]
60 | r.width = barWidth
61 | r.height = barHeight
62 | }
63 |
64 | lastWidth = getWidth
65 | lastHeight = getHeight
66 | }
67 |
68 | super.layoutChildren()
69 | }
70 |
71 | private def computeWidthForHeight(barHeight: Double) = barHeight * ASPECT_RATIO + getHorizontalPadding
72 |
73 | private def computeHeight(barHeight: Double): Double = {
74 | val vPadding = getVerticalPadding
75 | val barHeights = barHeight * barCount
76 | val spacing = SPACING * (barCount - 1)
77 | barHeights + spacing + vPadding
78 | }
79 |
80 | private def getVerticalPadding: Double = {
81 | val padding = getPadding
82 | padding.top + padding.bottom
83 | }
84 |
85 | private def getHorizontalPadding: Double = {
86 | val padding = getPadding
87 | padding.left + padding.right
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/SpectrumListener.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import javafx.scene.media as jfxcm
4 | import scalafx.scene.media.MediaPlayer
5 |
6 | /**
7 | * @author Jarek Sacha
8 | */
9 | class SpectrumListener(startFreq: Double, mp: MediaPlayer, bars: Array[SpectrumBar])
10 | extends jfxcm.AudioSpectrumListener {
11 | private val minValue = mp.audioSpectrumThreshold()
12 | private val norms = createNormArray()
13 | private val bandCount = mp.audioSpectrumNumBands()
14 | private val spectrumBucketCounts = createBucketCounts(startFreq, bandCount)
15 |
16 | def spectrumDataUpdate(timestamp: Double, duration: Double, magnitudes: Array[Float], phases: Array[Float]): Unit = {
17 | var index = 0
18 | var bucketIndex = 0
19 | var currentBucketCount = 0
20 | var sum = 0.0
21 |
22 | while (index < magnitudes.length) {
23 | sum += magnitudes(index) - minValue
24 | currentBucketCount += 1
25 |
26 | if (currentBucketCount >= spectrumBucketCounts(bucketIndex)) {
27 | bars(bucketIndex).setValue(sum / norms(bucketIndex))
28 | currentBucketCount = 0
29 | sum = 0.0
30 | bucketIndex += 1
31 | }
32 |
33 | index += 1
34 | }
35 | }
36 |
37 | private def createNormArray(): Array[Double] = {
38 | val normArray = new Array[Double](bars.length)
39 | var currentNorm = 0.05
40 | for (i <- normArray.indices) {
41 | normArray(i) = 1 + currentNorm
42 | currentNorm *= 2
43 | }
44 | normArray
45 | }
46 |
47 | private def createBucketCounts(startFreq: Double, bandCount: Int): Array[Int] = {
48 | val bucketCounts = new Array[Int](bars.length)
49 |
50 | val bandwidth = 22050.0 / bandCount
51 | val centerFreq = bandwidth / 2
52 | var currentSpectrumFreq = centerFreq
53 | var currentEQFreq = startFreq / 2
54 | var currentCutoff = 0d
55 | var currentBucketIndex = -1
56 |
57 | for (_ <- 0 until bandCount) {
58 | if (currentSpectrumFreq > currentCutoff) {
59 | currentEQFreq *= 2
60 | currentCutoff = currentEQFreq + currentEQFreq / 2
61 | currentBucketIndex += 1
62 | }
63 |
64 | if (currentBucketIndex < bucketCounts.length) {
65 | bucketCounts(currentBucketIndex) += 1
66 | currentSpectrumFreq += bandwidth
67 | }
68 | }
69 |
70 | bucketCounts
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/VideoPlayer4.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import scalafx.Includes.*
4 | import scalafx.scene.input.TransferMode
5 | import scalafx.scene.layout.{BorderPane, StackPane}
6 | import scalafx.scene.{Node, Scene}
7 |
8 | /**
9 | * @author Jarek Sacha
10 | */
11 | class VideoPlayer4 {
12 |
13 | private var playerControlsView: PlayerControlsView = _
14 | private var videoView: VideoView = _
15 | private var equalizerView: EqualizerView = _
16 |
17 | val mediaModel: MediaModel = new MediaModel() {
18 | url = "https://download.oracle.com/otndocs/products/javafx/oow2010-2.mp4"
19 | }
20 |
21 | private val page1 = createPageOne()
22 | private val page2 = createPageTwo()
23 | val rootNode: StackPane = new StackPane {
24 | children = page1
25 | }
26 |
27 | private def createPageOne(): Node = {
28 | videoView = new VideoView(mediaModel)
29 | playerControlsView = new PlayerControlsView(mediaModel)
30 | playerControlsView.onNextPageAction { _ => rootNode.children = page2 }
31 | new BorderPane {
32 | center = videoView.viewNode
33 | bottom = playerControlsView.viewNode
34 | }
35 | }
36 |
37 | private def createPageTwo(): Node = {
38 | equalizerView = new EqualizerView(mediaModel)
39 | equalizerView.onNextPageAction { _ => rootNode.children = page1 }
40 | equalizerView.viewNode
41 | }
42 |
43 | def initSceneDragAndDrop(scene: Scene): Unit = {
44 | scene.onDragOver = event => {
45 | val db = event.dragboard
46 | if (db.hasFiles || db.hasUrl) {
47 | // NOTE: We need to pass in `TransferMode` as Java vararg, since we use `javafx.scene.input.DragEvent`
48 | event.acceptTransferModes(TransferMode.Any: _*)
49 | }
50 | event.consume()
51 | }
52 |
53 | scene.onDragDropped = event => {
54 | val db = event.dragboard
55 | val url =
56 | if (db.hasFiles) {
57 | db.getFiles.get(0).toURI.toString
58 | } else if (db.hasUrl) {
59 | db.getUrl
60 | } else {
61 | null
62 | }
63 |
64 | if (url != null) {
65 | mediaModel.url = url
66 | mediaModel.mediaPlayer().play()
67 | }
68 | event.dropCompleted = (url != null)
69 | event.consume()
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/VideoPlayer4App.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import com.sun.javafx.runtime as csjfxr
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.scene.Scene
8 |
9 | /**
10 | * @author Jarek Sacha
11 | */
12 | object VideoPlayer4App extends JFXApp3 {
13 |
14 | override def start(): Unit = {
15 |
16 | println("JavaFX version: " + csjfxr.VersionInfo.getRuntimeVersion)
17 |
18 | val videoPlayer = new VideoPlayer4()
19 |
20 | val stylesheet = getClass.getResource("media.css")
21 |
22 | stage = new PrimaryStage {
23 | title = "Video Player 4"
24 | scene = new Scene(videoPlayer.rootNode, 1024, 680) {
25 | stylesheets += stylesheet.toString
26 | }
27 | videoPlayer.initSceneDragAndDrop(scene())
28 | }
29 |
30 | videoPlayer.mediaModel.mediaPlayer().play()
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/VideoView.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch08.VideoPlayer4
2 |
3 | import scalafx.scene.media.MediaView
4 |
5 | /**
6 | * @author Jarek Sacha
7 | */
8 | class VideoView(mediaModel: MediaModel) extends AbstractView[MediaView](mediaModel) {
9 |
10 | protected def initView(): MediaView = {
11 | val mediaView = new MediaView
12 | mediaView.mediaPlayer <== mediaModel.mediaPlayer
13 | mediaView
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/media.css:
--------------------------------------------------------------------------------
1 | .root {
2 | -fx-base: #222;
3 | -fx-background-color: black;
4 | -fx-background-color: radial-gradient(center 50% 50%, radius 60%, #111, #000);
5 | -fx-background-image: url("resources/cross.png");
6 | }
7 |
8 | .button {
9 | -fx-text-fill: #E0E0E0;
10 | }
11 |
12 | .label {
13 | -fx-font-size: 18pt;
14 | -fx-text-fill: #aaa;
15 | }
16 |
17 | #title {
18 | -fx-font-size: 24pt;
19 | -fx-text-fill: #ddd;
20 | }
21 |
22 | #openButton {
23 | -fx-graphic: url("resources/filmstrip.png");
24 | }
25 |
26 | #eqButton {
27 | -fx-shape: "M 1,0 L 10,0 13,5 10,10 1,10 Q 0 10 0 9 L 0,1 Q 0 0 1 0";
28 | -fx-font-size: 12pt;
29 | -fx-alignment: center-left;
30 | }
31 |
32 | .eqSlider {
33 | -fx-background-radius: 5;
34 | -fx-background-color: #222, #888, black;
35 | -fx-background-insets: 0, 1 0 0 1, 1;
36 | -fx-padding: 2 10;
37 | }
38 |
39 | .eqSlider .thumb {
40 | -fx-background-image: url("resources/thumb.png");
41 | -fx-padding: 12;
42 | }
43 |
44 | .eqSlider:vertical .track {
45 | -fx-padding: 5;
46 | }
47 |
48 | .spectrumBar {
49 | -fx-background-radius: 5;
50 | -fx-background-color: #222, #888, black;
51 | -fx-background-insets: 0, 1 0 0 1, 1;
52 | -fx-padding: 2 10;
53 | }
54 |
55 | #backButton {
56 | -fx-shape: "M 3,0 L 12,0 Q 13,0 13,1 L 13,9 Q 13,10 12,10 L 3,10 1,5";
57 | -fx-font-size: 12pt;
58 | -fx-alignment: center-right;
59 | }
60 |
61 | #playPauseButton, #seekEndButton, #seekStartButton {
62 | -fx-background-color: transparent;
63 | }
64 |
65 | #playPauseButton:hover, #seekEndButton:hover, #seekStartButton:hover {
66 | -fx-background-color: rgb(255, 255, 255, 0.1);
67 | }
68 |
69 | #seekEndButton {
70 | -fx-graphic: url("resources/next.png");
71 | }
72 |
73 | #seekStartButton {
74 | -fx-graphic: url("resources/prev.png");
75 | }
76 |
77 | #volumeHigh {
78 | -fx-image: url("resources/volHigh.png");
79 | }
80 |
81 | #volumeLow {
82 | -fx-image: url("resources/volLow.png");
83 | }
84 |
85 | .statusDisplay {
86 | -fx-border-color: white;
87 | -fx-border-radius: 2;
88 | -fx-border-width: 1;
89 | -fx-text-fill: white;
90 | -fx-font-size: 10pt;
91 | }
92 |
93 | .mediaText {
94 | -fx-text-fill: white;
95 | -fx-font-size: 11pt;
96 | }
97 |
98 | .eqLabel {
99 | -fx-padding: 8 3;
100 | }
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/PlayPause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/PlayPause.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/cross.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/cross.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/defaultAlbum.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/defaultAlbum.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/filmstrip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/filmstrip.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/music_note.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/music_note.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/next.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/next.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/pause.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/pause.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/play.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/play.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/prev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/prev.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/thumb.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/thumb.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/volHigh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/volHigh.png
--------------------------------------------------------------------------------
/src/proscalafx/ch08/VideoPlayer4/resources/volLow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch08/VideoPlayer4/resources/volLow.png
--------------------------------------------------------------------------------
/src/proscalafx/ch10/fxml/AdoptionForm.fxml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
32 |
34 |
35 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
49 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/proscalafx/ch10/fxml/AdoptionFormController.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch10.fxml
2 |
3 | import javafx.scene.{control as jfxsc, layout as jfxsl}
4 | import javafx.{event as jfxe, fxml as jfxf}
5 | import scalafx.Includes.*
6 | import scalafx.scene.layout.GridPane
7 |
8 | import java.net.URL
9 | import java.util
10 |
11 | /**
12 | * Example of a controlled initialized through FXML.
13 | *
14 | * When working with FXML, due to the nature of JavaFX FXMLLoader, we need to expose variables and methods that
15 | * FXMLLoader will be using with JavaFX signatures.
16 | *
17 | * The FXMLLoader injects JavaFX objects as values of member variables marked with annotation `@jfxf.FXML`.
18 | * We need to declare those variables using JavaFX types (not ScalaFX types).
19 | * We can use those variables directly or wrap them in ScalaFX objects.
20 | * Here, for the sake of illustration, we only wrap one variable `gridDelegate` (it is not strictly necessary).
21 | * The most convenient place to do wrapping is in the overloaded method `initialize`. It is executed after
22 | * FXMLLoader injects its objects.
23 | *
24 | * We can rely on ScalaFX "magic" to use ScalaFX methods on variables that were not explicitly wrapped.
25 | * All we need to do is to "summon the magic" using "import scalafx.Includes._".
26 | * This is demonstrated in method "handleClear" where we access properties on
27 | * JavaFX objects using ScalaFX way, no `get` or `set` involved.
28 | *
29 | * Methods annotated with `@jfxf.FXML`, that will be wired to event handlers by FLXMLoader.
30 | * They need to use JavaFX method signatures. This is illustrated in methods: `handleSubmit` and `handleClear`.
31 | *
32 | * In the rest of the code we can use ScalaFX, for instance, to create more in the event handlers or bind
33 | * properties.
34 | *
35 | * @author Jarek Sacha
36 | */
37 | class AdoptionFormController extends jfxf.Initializable {
38 |
39 | @jfxf.FXML
40 | private var sizeTextField: jfxsc.TextField = _
41 | @jfxf.FXML
42 | private var breedTextField: jfxsc.TextField = _
43 | @jfxf.FXML
44 | private var sexChoiceBox: jfxsc.ChoiceBox[String] = _
45 | @jfxf.FXML
46 | private var additionalInfoTextArea: jfxsc.TextArea = _
47 |
48 | @jfxf.FXML
49 | private var gridDelegate: jfxsl.GridPane = _
50 | private var grid: GridPane = _
51 |
52 | @jfxf.FXML
53 | private def handleSubmit(event: jfxe.ActionEvent): Unit = {
54 | grid.gridLinesVisible() = !grid.gridLinesVisible()
55 | }
56 |
57 | @jfxf.FXML
58 | private def handleClear(event: jfxe.ActionEvent): Unit = {
59 | sizeTextField.text = ""
60 | breedTextField.text = ""
61 | sexChoiceBox.selectionModel().clearSelection()
62 | additionalInfoTextArea.text = ""
63 | }
64 |
65 | override def initialize(url: URL, rb: util.ResourceBundle): Unit = {
66 | grid = new GridPane(gridDelegate)
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/proscalafx/ch10/fxml/FXMLAdoptionForm.scala:
--------------------------------------------------------------------------------
1 | package proscalafx.ch10.fxml
2 |
3 | import javafx.{fxml as jfxf, scene as jfxs}
4 | import scalafx.Includes.*
5 | import scalafx.application.JFXApp3
6 | import scalafx.application.JFXApp3.PrimaryStage
7 | import scalafx.scene.Scene
8 |
9 | import java.io.IOException
10 |
11 | /**
12 | * Example of using FXMLLoader from ScalaFX.
13 | *
14 | * @author Jarek Sacha
15 | */
16 | object FXMLAdoptionForm extends JFXApp3 {
17 |
18 | def start(): Unit = {
19 |
20 | val resource = getClass.getResource("AdoptionForm.fxml")
21 | if (resource == null) {
22 | throw new IOException("Cannot load resource: AdoptionForm.fxml")
23 | }
24 |
25 | // NOTE: ScalaFX doe not yet provide a wrapper fro FXMLLoader (2012.11.12)
26 | // We load here FXML content using JavaFX directly.
27 | // It is important to provide type for the element loaded,
28 | // though it can be a generic, here use `javafx.scene.parent`.
29 | val root: jfxs.Parent = jfxf.FXMLLoader.load(resource)
30 |
31 | stage = new PrimaryStage() {
32 | title = "FXML GridPane Demo"
33 | scene = new Scene(root)
34 | }
35 | }
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/src/proscalafx/ch10/fxml/cat.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/scalafx/ProScalaFX/5fd9c6559a9f07d46bdae6a97fe001bd6bc20d42/src/proscalafx/ch10/fxml/cat.jpg
--------------------------------------------------------------------------------