diff --git a/README.md b/README.md index 0cceda1..1a09650 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,3 @@ # TrackerV2 -Literally just Tracker, but better. +Literally just Tracker, but better. Hack and roll submission is in hack and roll branch. diff --git a/build.gradle.kts b/build.gradle.kts index ca228c6..7794906 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,60 +1,26 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +import org.jetbrains.compose.compose plugins { - kotlin("jvm") version "1.5.10" - - id("org.openjfx.javafxplugin") version "0.0.10" - id("org.beryx.jlink") version "2.24.0" -} - -group = "org.tracker" -version = "1.0-SNAPSHOT" - -application { - mainClass.set("application.Main") + kotlin("jvm") version "1.6.10" + id("org.jetbrains.compose") version "1.0.1-rc2" } repositories { mavenCentral() + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") + google() } dependencies { + implementation(compose.desktop.currentOs) + implementation("com.github.ajalt.colormath:colormath:3.2.0") implementation("org.bytedeco:javacv-platform:1.5.5") implementation("com.github.holgerbrandl:krangl:0.17.1") testImplementation(kotlin("test")) } -javafx { - modules("javafx.controls", "javafx.fxml", "javafx.swing", "javafx.web") -} - -sourceSets { - main { - java { - srcDirs("src/main/kotlin") - } - resources { - srcDirs("src/main/resources") - } - } - test { - java { - srcDirs("src/test/kotlin") - } - resources { - srcDirs("src/test/resources") - } +compose.desktop { + application { + mainClass = "MainKt" } -} - -tasks.test { - useJUnitPlatform() -} - -tasks.withType() { - kotlinOptions.jvmTarget = "11" -} - -tasks.withType { - duplicatesStrategy = DuplicatesStrategy.EXCLUDE } \ No newline at end of file diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt new file mode 100644 index 0000000..3502c5b --- /dev/null +++ b/src/main/kotlin/Main.kt @@ -0,0 +1,40 @@ +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.material.Divider +import androidx.compose.material.MaterialTheme +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application +import androidx.compose.ui.window.rememberWindowState +import backend.Video +import gui.NodesPane +import gui.VideoPlayer + +fun main() { + val video = Video("C:\\Users\\jedli\\OneDrive - NUS High School\\Documents\\Physics\\SYPT 2022" + + "\\16. Saving Honey\\Experimental Data\\IMG_1911.MOV") + val preprocessor = video.preprocesser + + return application { + Window( + onCloseRequest = ::exitApplication, + title = "Compose for Desktop", + state = rememberWindowState(width = 320.dp, height = 300.dp) + ) { + MaterialTheme { + Row(modifier = Modifier.padding(10.dp)) { + VideoPlayer(video) + Divider( + color = Color.Gray, + modifier = Modifier.fillMaxHeight().width(1.dp) + ) + NodesPane(preprocessor) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/application/Main.kt b/src/main/kotlin/application/Main.kt deleted file mode 100644 index 4f06b9d..0000000 --- a/src/main/kotlin/application/Main.kt +++ /dev/null @@ -1,104 +0,0 @@ -package application - -import application.splash.Splash -import javafx.animation.KeyFrame -import javafx.animation.KeyValue -import javafx.animation.Timeline -import javafx.application.Application -import javafx.application.Application.launch -import javafx.event.ActionEvent -import javafx.event.EventHandler -import javafx.fxml.FXMLLoader -import javafx.geometry.Rectangle2D -import javafx.scene.Parent -import javafx.scene.Scene -import javafx.scene.image.Image -import javafx.scene.input.MouseEvent -import javafx.scene.layout.AnchorPane -import javafx.scene.layout.VBox -import javafx.stage.Screen -import javafx.stage.Stage -import javafx.stage.StageStyle -import javafx.util.Duration -import java.io.IOException -import java.util.* -import com.thepyprogrammer.fxtools.resizable.* - -fun main(args: Array) { - launch(Main::class.java) -} - -class Main : Application() { - private lateinit var splash: Splash - private var xOffset = 0.0 - private var yOffset = 0.0 - - override fun start(stage: Stage) { - Companion.stage = stage - stage.initStyle(StageStyle.UNDECORATED) - icon = Image( - Objects.requireNonNull(Main::class.java.getResource("/images/icons/phyton.png")).toExternalForm() - ) - stage.icons.add(icon) - splash() - stage.show() - } - fun splash() { - splash = Splash() - splash.show() - stage.scene = Splash.splashScene - Splash.splashScene?.stylesheets?.add( - Objects.requireNonNull(Main::class.java.getResource("/stylesheets/splash.css")).toExternalForm() - ) - splash.progresser.onFinished = EventHandler { ex: ActionEvent? -> endSplash(ex) } - } - - fun endSplash(ex: ActionEvent?) { - val timeline = Timeline() - val key = KeyFrame( - Duration.millis(1600.0), - KeyValue(Splash.splashScene?.root?.opacityProperty(), 0) - ) - timeline.keyFrames.add(key) - timeline.onFinished = EventHandler { e: ActionEvent? -> loadFXML() } - timeline.play() - } - - fun loadFXML() { - val root = FXMLLoader.load(Objects.requireNonNull(javaClass.getResource("/mainframe.fxml"))) - root.onMousePressed = EventHandler { event: MouseEvent -> - xOffset = event.sceneX - yOffset = event.sceneY - } - root.onMouseDragged = EventHandler { event: MouseEvent -> - stage.x = event.screenX - xOffset - stage.y = event.screenY - yOffset - } - val scene = Scene(root) - scene.stylesheets.add(Objects.requireNonNull(Main::class.java.getResource("/stylesheets/style.css")).toExternalForm()) - stage.scene = scene - stage.minHeight = (root as VBox).minHeight - stage.minWidth = root.minWidth - stage.title = "tracker2.workspace" - fullScreen() - addResizeListener(stage) - } - - companion object { - @JvmStatic - lateinit var stage: Stage - private set - @JvmStatic - var icon: Image? = null - private set - - fun fullScreen() { - val screen: Screen = Screen.getPrimary() - val bounds: Rectangle2D = screen.visualBounds - stage.x = bounds.minX - stage.y = bounds.minY - stage.width = bounds.width - stage.height = bounds.height - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/Test.kt b/src/main/kotlin/application/Test.kt deleted file mode 100644 index 55952ce..0000000 --- a/src/main/kotlin/application/Test.kt +++ /dev/null @@ -1,24 +0,0 @@ -package application - -import application.backend.Preprocessor -import org.bytedeco.opencv.global.opencv_imgcodecs.imwrite -import org.bytedeco.opencv.opencv_core.Mat -import org.bytedeco.opencv.opencv_videoio.VideoCapture - -class Test { - companion object { - @JvmStatic - fun main(args: Array) { - val preprocessor = Preprocessor() - //preprocessor.nodes.add(ThresholdingNode(100.0, 255.0, true)) - - val video = VideoCapture( - "C:\\Users\\jedli\\OneDrive - NUS High School\\Documents\\Physics\\SYPT 2022\\" + - "16. Saving Honey\\Experimental Data\\Anim2.mp4") - - val img = Mat() - video.read(img) - imwrite("test.png", preprocessor.process(img)) - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/OpenCVUtils.kt b/src/main/kotlin/application/backend/OpenCVUtils.kt deleted file mode 100644 index 3476691..0000000 --- a/src/main/kotlin/application/backend/OpenCVUtils.kt +++ /dev/null @@ -1,6 +0,0 @@ -package application.backend - -import org.bytedeco.javacv.* -import org.bytedeco.opencv.opencv_core.Mat - -fun convertToImage(mat: Mat) = JavaFXFrameConverter().convert(OpenCVFrameConverter.ToMat().convert(mat)) diff --git a/src/main/kotlin/application/backend/Preprocessor.kt b/src/main/kotlin/application/backend/Preprocessor.kt deleted file mode 100644 index 3557568..0000000 --- a/src/main/kotlin/application/backend/Preprocessor.kt +++ /dev/null @@ -1,52 +0,0 @@ -package application.backend - -import application.backend.preprocess.PreprocessingNode -import org.bytedeco.opencv.global.opencv_imgproc.* -import org.bytedeco.opencv.opencv_core.Mat - -class Preprocessor { - val nodes: ArrayList = arrayListOf() - - fun process(img: Mat): Mat { - var newImg = img.clone() - cvtColor(img, newImg, COLOR_BGR2RGB) - - var currentSpace = Colourspace.RGB - - nodes.forEach { - if (it.inputColourspace != currentSpace) { - when (currentSpace) { - Colourspace.RGB -> when (it.inputColourspace) { - Colourspace.HSV -> cvtColor(newImg, newImg, COLOR_RGB2HSV) - Colourspace.GRAYSCALE -> cvtColor(newImg, newImg, COLOR_RGB2GRAY) - else -> {} - } - Colourspace.HSV -> when (it.inputColourspace) { - Colourspace.RGB -> cvtColor(newImg, newImg, COLOR_HSV2RGB) - Colourspace.GRAYSCALE -> { - cvtColor(newImg, newImg, COLOR_HSV2RGB) - cvtColor(newImg, newImg, COLOR_RGB2GRAY) - } - else -> {} - } - Colourspace.GRAYSCALE -> when (it.inputColourspace) { - Colourspace.RGB -> cvtColor(newImg, newImg, COLOR_GRAY2RGB) - Colourspace.HSV -> { - cvtColor(newImg, newImg, COLOR_GRAY2RGB) - cvtColor(newImg, newImg, COLOR_RGB2HSV) - } - else -> {} - } - } - } - - newImg = it.process(newImg) - currentSpace = it.outputColourspace - } - - val finalImg = Mat() - cvtColor(newImg, finalImg, COLOR_RGB2BGR) - - return finalImg - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/Processing.kt b/src/main/kotlin/application/backend/Processing.kt deleted file mode 100644 index 9f954ab..0000000 --- a/src/main/kotlin/application/backend/Processing.kt +++ /dev/null @@ -1,9 +0,0 @@ -package application.backend - -abstract class Processing { - abstract val name: String - abstract val help: String - - open var inputColourspace: Colourspace = Colourspace.RGB - abstract val inputColourspaces: List -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/preprocess/PreprocessingNode.kt b/src/main/kotlin/application/backend/preprocess/PreprocessingNode.kt deleted file mode 100644 index 3291503..0000000 --- a/src/main/kotlin/application/backend/preprocess/PreprocessingNode.kt +++ /dev/null @@ -1,11 +0,0 @@ -package application.backend.preprocess - -import application.backend.Colourspace -import application.backend.Processing -import org.bytedeco.opencv.opencv_core.Mat - -abstract class PreprocessingNode: Processing() { - abstract val outputColourspace: Colourspace - - abstract fun process(img: Mat): Mat -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/preprocess/blurring/BlurringNode.kt b/src/main/kotlin/application/backend/preprocess/blurring/BlurringNode.kt deleted file mode 100644 index 3c5a89d..0000000 --- a/src/main/kotlin/application/backend/preprocess/blurring/BlurringNode.kt +++ /dev/null @@ -1,35 +0,0 @@ -package application.backend.preprocess.blurring - -import application.backend.ALL_SPACES -import application.backend.Colourspace -import application.backend.preprocess.PreprocessingNode -import org.bytedeco.opencv.global.opencv_imgproc.* -import org.bytedeco.opencv.opencv_core.Mat -import org.bytedeco.opencv.opencv_core.Size - -enum class Blurring { - GAUSSIAN, - MEDIAN, - BOX_FILTER -} - -class BlurringNode(var blurType: Blurring = Blurring.GAUSSIAN, var kernelSize: Int = 3): PreprocessingNode() { - override val name: String = "Blurring" - override val help: String = "This node blurs the video to remove noise. The kernel size controls the extent of blurring and " + - "can only be odd." - - override val inputColourspaces: List = ALL_SPACES - override val outputColourspace: Colourspace get() = inputColourspace - - override fun process(img: Mat): Mat { - val newImg = img.clone() - - when (blurType) { - Blurring.GAUSSIAN -> GaussianBlur(img, newImg, Size(kernelSize, kernelSize), 0.0) - Blurring.MEDIAN -> medianBlur(img, newImg, kernelSize) - else -> blur(img, newImg, Size(kernelSize, kernelSize)) - } - - return newImg - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/preprocess/edge_detection/CannyEdgeNode.kt b/src/main/kotlin/application/backend/preprocess/edge_detection/CannyEdgeNode.kt deleted file mode 100644 index dcde125..0000000 --- a/src/main/kotlin/application/backend/preprocess/edge_detection/CannyEdgeNode.kt +++ /dev/null @@ -1,35 +0,0 @@ -package application.backend.preprocess.edge_detection - -import application.backend.ALL_SPACES -import application.backend.Colourspace -import application.backend.preprocess.PreprocessingNode -import org.bytedeco.opencv.global.opencv_imgproc -import org.bytedeco.opencv.global.opencv_imgproc.Canny -import org.bytedeco.opencv.opencv_core.Mat - -class CannyEdgeNode(var threshold: Double = 200.0, var kernelSize: Int = 3): PreprocessingNode() { - override val name: String = "Canny Edge \nDetection" - override val help: String = "Detects edges in the image. Blurring first in recommended." - - override val inputColourspaces: List = ALL_SPACES - override val outputColourspace: Colourspace = Colourspace.GRAYSCALE - - override fun process(img: Mat): Mat { - val newImg = img.clone() - - var gray = Mat() - when (inputColourspace) { - Colourspace.RGB -> opencv_imgproc.cvtColor(newImg, gray, opencv_imgproc.COLOR_RGB2GRAY) - Colourspace.HSV -> { - opencv_imgproc.cvtColor(newImg, gray, opencv_imgproc.COLOR_HSV2RGB) - opencv_imgproc.cvtColor(gray, gray, opencv_imgproc.COLOR_RGB2GRAY) - } - else -> gray = newImg - } - - val edges = Mat() - Canny(gray, edges, threshold, 2 * threshold, kernelSize, false) - - return edges - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/preprocess/masking/ColourRangeNode.kt b/src/main/kotlin/application/backend/preprocess/masking/ColourRangeNode.kt deleted file mode 100644 index c54593c..0000000 --- a/src/main/kotlin/application/backend/preprocess/masking/ColourRangeNode.kt +++ /dev/null @@ -1,35 +0,0 @@ -package application.backend.preprocess.masking - -import application.backend.Colourspace -import application.backend.preprocess.PreprocessingNode -import javafx.scene.paint.Color -import org.bytedeco.opencv.global.opencv_core.inRange -import org.bytedeco.opencv.opencv_core.Mat - -data class ColourRangeNode(var colours: ArrayList>, var binarise: Boolean): PreprocessingNode() { - override val name: String = "Filter Colours" - override val help: String = "Filters out parts of the images within the given colour range." - - override val inputColourspaces: List = listOf(Colourspace.RGB, Colourspace.HSV) - override val outputColourspace: Colourspace get() = inputColourspace - - override fun process(img: Mat): Mat { - val mask = Mat() - var newImg = img.clone() - colours.forEach { (start, end) -> - when (inputColourspace) { - Colourspace.RGB -> inRange(newImg, Mat(start.red * 255, start.green * 255, start.blue * 255), - Mat(end.red * 255, end.green * 255, end.blue * 255), mask) - Colourspace.HSV -> inRange(newImg, Mat(start.hue / 360 * 255, start.saturation / 360 * 255, start.brightness / 360 * 255), - Mat(end.hue / 360 * 255, end.saturation / 360 * 255, end.brightness / 360 * 255), mask) - else -> {} - } - - val newerImg = Mat() - newImg.copyTo(newerImg, mask) - newImg = newerImg - } - - return if (binarise) mask else newImg - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/preprocess/masking/ThresholdingNode.kt b/src/main/kotlin/application/backend/preprocess/masking/ThresholdingNode.kt deleted file mode 100644 index 6bead4f..0000000 --- a/src/main/kotlin/application/backend/preprocess/masking/ThresholdingNode.kt +++ /dev/null @@ -1,38 +0,0 @@ -package application.backend.preprocess.masking - -import application.backend.ALL_SPACES -import application.backend.Colourspace -import application.backend.preprocess.PreprocessingNode -import org.bytedeco.opencv.global.opencv_core.bitwise_and -import org.bytedeco.opencv.global.opencv_core.bitwise_or -import org.bytedeco.opencv.global.opencv_imgproc.* -import org.bytedeco.opencv.opencv_core.Mat - -class ThresholdingNode(var minThreshold: Double, var maxThreshold: Double, var binarise: Boolean): PreprocessingNode() { - override val name: String = "Thresholding" - override val help: String = "Performs a black and white threshold on the image." - - override val inputColourspaces: List = ALL_SPACES - override val outputColourspace: Colourspace get() = if (binarise) inputColourspace else Colourspace.GRAYSCALE - - override fun process(img: Mat): Mat { - val mask = Mat() - val newImg = img.clone() - - var gray = Mat() - when (inputColourspace) { - Colourspace.RGB -> cvtColor(newImg, gray, COLOR_RGB2GRAY) - Colourspace.HSV -> { - cvtColor(newImg, gray, COLOR_HSV2RGB) - cvtColor(gray, gray, COLOR_RGB2GRAY) - } - else -> gray = newImg - } - - threshold(gray, mask, minThreshold, maxThreshold, 1) - - val newerImg = Mat() - if (!binarise) bitwise_and(img, img, newerImg, mask) - return if (binarise) mask else newerImg - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/controller/MainframeController.kt b/src/main/kotlin/application/controller/MainframeController.kt deleted file mode 100644 index 0e0c87d..0000000 --- a/src/main/kotlin/application/controller/MainframeController.kt +++ /dev/null @@ -1,201 +0,0 @@ -package application.controller - -import application.Main -import com.thepyprogrammer.fxtools.draggable.DraggableTab -import com.thepyprogrammer.fxtools.io.File -import javafx.beans.property.ObjectProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.beans.value.ObservableValue -import javafx.event.ActionEvent -import javafx.fxml.FXML -import javafx.fxml.FXMLLoader -import javafx.fxml.Initializable -import javafx.scene.Parent -import javafx.scene.control.* -import javafx.scene.layout.VBox -import javafx.stage.Stage -import java.io.IOException -import java.net.URL -import java.util.* - -class MainframeController: Initializable { - private val files = HashMap() - - @FXML - lateinit var win: VBox - - @FXML - lateinit var menubar: MenuBar - - @FXML - lateinit var file: Menu - - @FXML - lateinit var help: Menu - - @FXML - lateinit var demoMenu: MenuItem - - @FXML - lateinit var openMenu: MenuItem - - @FXML - lateinit var notebook: TabPane - - @FXML - lateinit var title: Label - - @FXML - lateinit var minimize: Hyperlink - - @FXML - lateinit var maximize: Hyperlink - - @FXML - lateinit var close: Hyperlink - - /** - * @param event Closes window - */ - @FXML - fun closeWin(event: ActionEvent) { - for (tab in notebook.tabs) { - tab.onCloseRequest.handle(null) - } - val stage = (event.source as Hyperlink).scene.window as Stage - stage.close() - } - - /** - * @param event Minimizes window - */ - @FXML - fun minimizeWin(event: ActionEvent) { - val stage = (event.source as Hyperlink).scene.window as Stage - stage.isIconified = true - } - - @FXML - fun runVideo(event: ActionEvent?) { - tabController?.play() - } - - @FXML - fun stopVideo(event: ActionEvent?) { - tabController?.stop() - } - - @FXML - private fun setFullScreen(event: ActionEvent) { - Main.fullScreen() - } - - @FXML - @Throws(IOException::class) - fun open(event: ActionEvent?) { - val file = File.getMP4() - var tab = getByFile(file.absolutePath) - if (tab != null) { - notebook.selectionModel.select(tab) - return - } - val path = file.absolutePath.split("\\\\".toRegex()).toTypedArray() - - val node = FXMLLoader.load(Main::class.java.getResource("/tab.fxml")) - TabController.getController(node)?.setFile(file.absolutePath) - tab = DraggableTab(" " + path[path.size - 1] + " ") - tab.isClosable = true - tab.detachable = true - tab.label.style = "-fx-background-color: #ffffbf;" - tab.style = "-fx-background-color: #ffffbf;" - tab.label.styleClass.add("tablabel") - tab.content = node - - notebook.tabs.add(tab) - notebook.selectionModel.select(tab) - files[file.absolutePath] = tab - - val controller = TabController.getController(node) - - tab.setOnCloseRequest { - controller?.stop() - } - - file.close() - } - - @FXML - fun demo(event: ActionEvent?) { - val node = FXMLLoader.load(Main::class.java.getResource("/tab.fxml")) - val tab = DraggableTab(" demo ") - tab.isClosable = true - tab.detachable = true - tab.label.style = "-fx-background-color: #ffffbf;" - tab.style = "-fx-background-color: #ffffbf;" - tab.label.styleClass.add("tablabel") - tab.content = node - - notebook.tabs.add(tab) - notebook.selectionModel.select(tab) - - val controller = TabController.getController(node) - - tab.setOnCloseRequest { - controller?.stop() - } - } - - val nameOfTab: String - get() { - val tab = notebook.selectionModel.selectedItem as DraggableTab - val text = tab.label.text.trim { it <= ' ' } - return text.substring(0, text.length - 5) - } - - override fun initialize(location: URL?, resources: ResourceBundle?) { - notebook.selectionModel.selectedItemProperty() - .addListener { _: ObservableValue?, oldValue: Tab?, newValue: Tab? -> - if (oldValue != null) { - val old = oldValue as DraggableTab - old.setStyles("-fx-background-color: #ffffbf;") - } - if (newValue != null) { - val newTab = newValue as DraggableTab - newTab.setStyleClasses("focusedTab") - newTab.setStyles("-fx-background-color: #add8e6;") - } - } - - currentOccurrence = this - demo(ActionEvent()) - - } - - val tabController: TabController? - get() { - val node = notebook.selectionModel.selectedItem.content - return TabController.getController(node) - } - val tabControllers: ArrayList - get() { - val controllers = ArrayList() - for (tab in notebook.tabs) { - val controller = TabController.getController(tab.content) - if(controller != null) controllers.add(controller) - } - return controllers - } - - fun getByFile(filename: String): DraggableTab? { - val selTab: ObjectProperty = SimpleObjectProperty(null) - files.forEach { (file: String, tab: DraggableTab?) -> if (file == filename) selTab.set(tab) } - return selTab.get() - } - - - companion object { - var currentOccurrence: MainframeController? = null - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/application/controller/TabController.kt b/src/main/kotlin/application/controller/TabController.kt deleted file mode 100644 index 6706082..0000000 --- a/src/main/kotlin/application/controller/TabController.kt +++ /dev/null @@ -1,116 +0,0 @@ -package application.controller - -import application.Main -import application.backend.convertToImage -import application.gui.NodesPane -import application.model.MediaControl -import javafx.application.Platform -import javafx.fxml.FXML -import javafx.fxml.Initializable -import javafx.scene.Node -import javafx.scene.Parent -import javafx.scene.image.Image -import javafx.scene.image.ImageView -import javafx.scene.layout.Pane -import javafx.scene.layout.VBox -import javafx.scene.media.Media -import javafx.scene.media.MediaPlayer -import org.apache.poi.ss.formula.functions.NumericFunction.LOG -import org.bytedeco.javacv.FFmpegFrameGrabber -import org.bytedeco.javacv.JavaFXFrameConverter -import org.bytedeco.opencv.opencv_core.Mat -import org.bytedeco.opencv.opencv_videoio.VideoCapture -import java.net.URL -import java.nio.ByteBuffer -import java.nio.ShortBuffer -import java.util.* -import java.util.concurrent.Executors -import java.util.concurrent.TimeUnit -import javax.sound.sampled.AudioFormat -import javax.sound.sampled.AudioSystem -import javax.sound.sampled.DataLine -import javax.sound.sampled.SourceDataLine - - -class TabController: Initializable { - companion object { - var panes = HashMap() - - fun getController(node: Node?): TabController? { - return if (panes.containsKey(node)) panes[node] else null - } - } - - @FXML lateinit var pane: Pane - @FXML lateinit var nodesPane: NodesPane - @FXML lateinit var imageView: ImageView - @FXML lateinit var imageViewOrig: ImageView - @FXML lateinit var parent: VBox - - lateinit var resource: String - - // lateinit var media: Parent - - var playThread: Thread? = null - - override fun initialize(location: URL?, resources: ResourceBundle?) { - panes[parent] = this - resource = Objects.requireNonNull(Main::class.java.getResource("/video/Untitledd.mp4")).toExternalForm() - // play() - // media = createContent() - // pane.children.add(media) - } - - fun setFile(filename: String) { - resource = filename -// Platform.runLater { -// //pane.children.remove(media) -// //media = createContent() -// //pane.children.add(media) -// } - - playThread?.interrupt() - - } - - fun play() { - println("Button has been pressed!") - stop() - playThread = Thread { - val video = VideoCapture(resource) - val img = Mat() - - val postprocessor = nodesPane.postprocessor - if(postprocessor != null) { - println(postprocessor.process(sequence { while (video.read(img)) yield(nodesPane.preprocessor.process(img)) })) - } else { - whileLoop@ while (video.read(img) && !Thread.interrupted()) { - println("An Image Loaded") - Platform.runLater { - try { - imageViewOrig.image = convertToImage(img) - imageView.image = convertToImage(nodesPane.preprocessor.process(img)) - } catch(e: RuntimeException) {} - } - Thread.sleep(1000) - } - } - Platform.exit() - - } - playThread?.start() - } - - fun createContent(): Parent { - val mediaPlayer = MediaPlayer(Media(resource)).apply { isAutoPlay = true } - val mediaControl = MediaControl(mediaPlayer) - mediaControl.setMinSize(800.0, 467.0) - mediaControl.setPrefSize(800.0, 467.0) - mediaControl.setMaxSize(800.0, 467.0) - return mediaControl - } - - fun stop() { - if(playThread != null && playThread!!.isAlive) playThread!!.interrupt() - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/NodesPane.kt b/src/main/kotlin/application/gui/NodesPane.kt deleted file mode 100644 index bbd3090..0000000 --- a/src/main/kotlin/application/gui/NodesPane.kt +++ /dev/null @@ -1,111 +0,0 @@ -package application.gui - -import application.backend.Postprocessor -import application.backend.Preprocessor -import application.backend.postprocess.PostprocessingNode -import application.backend.postprocess.fitting.EllipseFittingNode -import application.backend.preprocess.PreprocessingNode -import application.gui.postprocessing.CirclePane -import application.gui.postprocessing.EllipsePane -import application.gui.preprocessing.BlurringPane -import application.gui.preprocessing.CannyEdgePane -import application.gui.preprocessing.ColourRangePane -import application.gui.preprocessing.ThresholdingPane -import javafx.application.Platform -import javafx.event.EventHandler -import javafx.scene.control.Alert -import javafx.scene.control.ContextMenu -import javafx.scene.control.MenuItem -import javafx.scene.control.ScrollPane -import javafx.scene.layout.HBox -import java.awt.Dialog - -class NodesPane: ScrollPane() { - val hbox: HBox = HBox(20.0) - val nodes: ArrayList = arrayListOf() - var outputNode: PostprocessingPane? = null - - val preprocessor: Preprocessor - get() { - val processor = Preprocessor() - processor.nodes.addAll(nodes.map { it.node as PreprocessingNode }) - return processor - } - - val postprocessor: Postprocessor? - get() { - if(outputNode != null) return Postprocessor(outputNode!!.node as PostprocessingNode) - else return null - } - - init { - content = hbox - hbox.prefHeightProperty().bind(heightProperty()) - - contextMenu = ContextMenu().apply { - items.add(MenuItem("Add Blur").apply { - onAction = EventHandler { addNode(BlurringPane().apply { - deleteButton.onAction = EventHandler { deleteNode(this) } - }) } - }) - items.add(MenuItem("Add Thresholding").apply { - onAction = EventHandler { addNode(ThresholdingPane().apply { - deleteButton.onAction = EventHandler { deleteNode(this) } - }) } - }) - items.add(MenuItem("Add Colour Filter").apply { - onAction = EventHandler { addNode(ColourRangePane().apply { - deleteButton.onAction = EventHandler { deleteNode(this) } - }) } - }) - items.add(MenuItem("Add Edge Detection").apply { - onAction = EventHandler { addNode(CannyEdgePane().apply { - deleteButton.onAction = EventHandler { deleteNode(this) } - }) } - }) - items.add(MenuItem("Add Ellipse Fitting").apply { - onAction = EventHandler { setOutputPane(EllipsePane().apply { - deleteButton.onAction = EventHandler { deleteOutputNode(this) } - }) } - }) - items.add(MenuItem("Add Circle Fitting").apply { - onAction = EventHandler { setOutputPane(CirclePane().apply { - deleteButton.onAction = EventHandler { deleteOutputNode(this) } - }) } - }) - } - } - - private fun deleteNode(node: PreprocessingPane) { - val index = nodes.find { it === node }!! - nodes.remove(index) - Platform.runLater { hbox.children.remove(index) } - } - - private fun addNode(node: PreprocessingPane) { - Platform.runLater { - if (outputNode == null) hbox.children.add(node) - else hbox.children.add(hbox.children.size - 2, node) - } - - nodes.add(node) - } - - private fun setOutputPane(node: PostprocessingPane) { - if (outputNode == null) { - Platform.runLater { hbox.children.add(node) } - outputNode = node - } else { - Alert(Alert.AlertType.ERROR).apply { - title = "Error!" - headerText = "Cannot have more an 1 output node!" - contentText = "Cannot have more an 1 output node!" - }.show() - } - } - - private fun deleteOutputNode(node: PostprocessingPane) { - Platform.runLater { hbox.children.remove(node) } - outputNode = null - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/PostprocessingPane.kt b/src/main/kotlin/application/gui/PostprocessingPane.kt deleted file mode 100644 index ba23e8a..0000000 --- a/src/main/kotlin/application/gui/PostprocessingPane.kt +++ /dev/null @@ -1,6 +0,0 @@ -package application.gui - -import application.backend.postprocess.PostprocessingNode -import application.wrappers.generic.ProcessingNode - -abstract class PostprocessingPane(node: PostprocessingNode) : ProcessingNode(node) {} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/PreprocessingPane.kt b/src/main/kotlin/application/gui/PreprocessingPane.kt deleted file mode 100644 index cd1f3ba..0000000 --- a/src/main/kotlin/application/gui/PreprocessingPane.kt +++ /dev/null @@ -1,6 +0,0 @@ -package application.gui - -import application.backend.preprocess.PreprocessingNode -import application.wrappers.generic.ProcessingNode - -abstract class PreprocessingPane(node: PreprocessingNode) : ProcessingNode(node) {} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/calibration/Axes.kt b/src/main/kotlin/application/gui/calibration/Axes.kt deleted file mode 100644 index 3b6da95..0000000 --- a/src/main/kotlin/application/gui/calibration/Axes.kt +++ /dev/null @@ -1,24 +0,0 @@ -package application.gui.calibration - -import com.thepyprogrammer.fxtools.draggable.DraggableNode -import javafx.scene.Node -import javafx.scene.image.ImageView -import javafx.scene.layout.Pane -import javafx.scene.paint.Color -import javafx.scene.shape.Rectangle - -class Axes(val x: Double = 0.0, val y: Double = 0.0): DraggableNode() { - override fun createWidget(): Node { - val rect = Rectangle(3.0, 10000.0) - rect.x = 0.0 - rect.y = -5000.0 - rect.fill = Color.MAGENTA - - val rect2 = Rectangle(10000.0, 3.0) - rect2.x = -5000.0 - rect2.y = 0.0 - rect2.fill = Color.MAGENTA - - return Pane().apply { children.addAll(rect, rect2) } - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/calibration/CalibrationPoint.kt b/src/main/kotlin/application/gui/calibration/CalibrationPoint.kt deleted file mode 100644 index b21f5a9..0000000 --- a/src/main/kotlin/application/gui/calibration/CalibrationPoint.kt +++ /dev/null @@ -1,16 +0,0 @@ -package application.gui.calibration - -import com.thepyprogrammer.fxtools.draggable.DraggableNode -import javafx.scene.Node -import javafx.scene.image.ImageView - -class CalibrationPoint(val x: Double = 0.0, val y: Double = 0.0): DraggableNode() { - lateinit var image: ImageView - - override fun createWidget(): Node { - image = ImageView("/image/outline_close_black_24dp.png") - image.x = x - image.y = y - return image - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/calibration/CalibrationRuler.kt b/src/main/kotlin/application/gui/calibration/CalibrationRuler.kt deleted file mode 100644 index 16b2f8e..0000000 --- a/src/main/kotlin/application/gui/calibration/CalibrationRuler.kt +++ /dev/null @@ -1,37 +0,0 @@ -package application.gui.calibration - -import javafx.event.EventHandler -import javafx.scene.Node -import javafx.scene.input.DragEvent -import javafx.scene.input.MouseEvent -import javafx.scene.layout.Pane -import javafx.scene.paint.Color -import javafx.scene.shape.Line - -class CalibrationRuler(val pane: Pane) { - val pointOne = CalibrationPoint() - val pointTwo = CalibrationPoint() - val line = Line() - - init { - line.apply { - startXProperty().bind(pointOne.image.translateXProperty()) - startYProperty().bind(pointOne.image.translateYProperty()) - endXProperty().bind(pointTwo.image.translateXProperty()) - endYProperty().bind(pointTwo.image.translateYProperty()) - - strokeWidth = 5.0 - stroke = Color.BLUE - } - - pane.children.addAll(pointOne, pointTwo, line) - } - - // Returns the number of metres that 1 pixel represents - fun updateScale(actualDist: Double): Double { - val dist = (pointTwo.nodeX - pointOne.nodeX) * (pointTwo.nodeX - pointOne.nodeX) + - (pointTwo.nodeY - pointOne.nodeY) * (pointTwo.nodeY - pointOne.nodeY) - - return actualDist / dist // TODO (Account for canvas zoom) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/postprocessing/CirclePane.kt b/src/main/kotlin/application/gui/postprocessing/CirclePane.kt deleted file mode 100644 index a26d027..0000000 --- a/src/main/kotlin/application/gui/postprocessing/CirclePane.kt +++ /dev/null @@ -1,91 +0,0 @@ -package application.gui.postprocessing - -import application.backend.postprocess.fitting.CircleFittingNode -import application.backend.preprocess.blurring.Blurring -import application.backend.preprocess.blurring.BlurringNode -import application.gui.PostprocessingPane -import javafx.collections.FXCollections -import javafx.scene.control.Label -import javafx.scene.control.Slider -import javafx.scene.control.TextField -import javafx.scene.control.Tooltip -import javafx.scene.layout.HBox -import javafx.scene.layout.VBox - -class CirclePane: PostprocessingPane(CircleFittingNode()) { - val minRadiusField = TextField() - val maxRadiusField = TextField() - val minDistSlider = Slider(0.0, 100.0, 20.0) - val param1Slider = Slider(0.0, 400.0, 100.0) - val param2Slider = Slider(0.0, 400.0, 100.0) - - init { - children.add(VBox(10.0).apply { - children.add(HBox(10.0).apply { - children.add(Label("Minimum Radius:")) - children.add(minRadiusField.apply { - tooltip = Tooltip("Minimum radius of the circle") - textProperty().addListener { _, _, new -> - try { - (node as CircleFittingNode).minRadius = new.toInt() - } catch (_: NumberFormatException) { - } - } - }) - }) - - children.add(HBox(10.0).apply { - children.add(Label("Maximum Radius:")) - children.add(maxRadiusField.apply { - tooltip = Tooltip("Maximum radius of the circle") - textProperty().addListener { _, _, new -> - try { - (node as CircleFittingNode).maxRadius = new.toInt() - } catch (_: NumberFormatException) { - } - } - }) - }) - - children.add(HBox(10.0).apply { - children.add(Label("Minimum Distance:")) - children.add(minDistSlider.apply { - tooltip = Tooltip("Controls the minimum distance between 2 circles") - valueProperty().addListener { _, _, new -> (node as CircleFittingNode).minDist = new.toDouble() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 20.0 - }) - }) - - children.add(HBox(10.0).apply { - children.add(Label("Edge Detection:")) - children.add(param1Slider.apply { - tooltip = Tooltip("For edge detection") - valueProperty().addListener { _, _, new -> (node as CircleFittingNode).param1 = new.toDouble() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 100.0 - }) - }) - - children.add(HBox(10.0).apply { - children.add(Label("Circle Standards:")) - children.add(param2Slider.apply { - tooltip = Tooltip("Controls how circular a circle has to be to be a circle") - valueProperty().addListener { _, _, new -> (node as CircleFittingNode).param2 = new.toDouble() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 100.0 - }) - }) - - // Set AnchorPane location - setBottomAnchor(this, 0.0) - setLeftAnchor(this, 0.0) - }) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/postprocessing/EllipsePane.kt b/src/main/kotlin/application/gui/postprocessing/EllipsePane.kt deleted file mode 100644 index 87ef1fa..0000000 --- a/src/main/kotlin/application/gui/postprocessing/EllipsePane.kt +++ /dev/null @@ -1,6 +0,0 @@ -package application.gui.postprocessing - -import application.backend.postprocess.fitting.EllipseFittingNode -import application.gui.PostprocessingPane - -class EllipsePane: PostprocessingPane(EllipseFittingNode()) {} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/preprocessing/BlurringPane.kt b/src/main/kotlin/application/gui/preprocessing/BlurringPane.kt deleted file mode 100644 index 2fb1832..0000000 --- a/src/main/kotlin/application/gui/preprocessing/BlurringPane.kt +++ /dev/null @@ -1,51 +0,0 @@ -package application.gui.preprocessing - -import application.backend.preprocess.blurring.Blurring -import application.backend.preprocess.blurring.BlurringNode -import application.gui.PreprocessingPane -import javafx.collections.FXCollections -import javafx.scene.control.ComboBox -import javafx.scene.control.Label -import javafx.scene.control.Slider -import javafx.scene.control.Tooltip -import javafx.scene.layout.AnchorPane -import javafx.scene.layout.HBox -import javafx.scene.layout.VBox - -class BlurringPane: PreprocessingPane(BlurringNode()) { - val kernelSizeSlider = Slider(3.0, 53.0, 3.0) - val blurringComboBox = ComboBox() - - init { - children.add(VBox(10.0).apply { - // Setup slider - children.add(HBox(10.0).apply { - children.add(Label("Kernel Size:")) - children.add(kernelSizeSlider.apply { - tooltip = Tooltip("Controls the extent of blurring") - valueProperty().addListener { _, _, new -> (node as BlurringNode).kernelSize = new.toInt() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 10.0 - blockIncrement = 2.0 - }) - }) - - // Setup combobox - children.add(HBox(10.0).apply { - children.add(Label("Blur Type:")) - children.add(blurringComboBox.apply { - items = FXCollections.observableList(listOf(Blurring.GAUSSIAN, Blurring.MEDIAN, Blurring.BOX_FILTER)) - valueProperty().addListener { _, _, new -> (node as BlurringNode).blurType = new } - selectionModel.selectFirst(); - tooltip = Tooltip("Controls the type of blurring") - }) - }) - - // Set AnchorPane location - setBottomAnchor(this, 0.0) - setLeftAnchor(this, 0.0) - }) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/preprocessing/CannyEdgePane.kt b/src/main/kotlin/application/gui/preprocessing/CannyEdgePane.kt deleted file mode 100644 index f9a903e..0000000 --- a/src/main/kotlin/application/gui/preprocessing/CannyEdgePane.kt +++ /dev/null @@ -1,52 +0,0 @@ -package application.gui.preprocessing - -import application.backend.preprocess.edge_detection.CannyEdgeNode -import application.gui.PreprocessingPane -import application.wrappers.generic.ProcessingNode -import javafx.scene.control.Label -import javafx.scene.control.Slider -import javafx.scene.control.Tooltip -import javafx.scene.layout.AnchorPane -import javafx.scene.layout.HBox -import javafx.scene.layout.VBox - -class CannyEdgePane: PreprocessingPane(CannyEdgeNode()) { - val kernelSizeSlider = Slider(3.0, 53.0, 3.0) - val thresholdSlider = Slider(0.0, 255.0, 1.0) - - init { - children.add(VBox(10.0).apply { - // Setup kernel size slider - children.add(HBox(10.0).apply { - children.add(Label("Kernel Size:")) - children.add(kernelSizeSlider.apply { - tooltip = Tooltip("Controls the kernel size of the sorbel kernel used for edge detection") - valueProperty().addListener { _, _, new -> (node as CannyEdgeNode).kernelSize = new.toInt() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 10.0 - blockIncrement = 2.0 - }) - }) - - // Setup threshold slider - children.add(HBox(10.0).apply { - children.add(Label("Minimum threshold:")) - children.add(thresholdSlider.apply { - tooltip = Tooltip("The minimum threshold") - valueProperty().addListener { _, _, new -> (node as CannyEdgeNode).threshold = new.toDouble() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 10.0 - blockIncrement = 2.0 - }) - }) - - // Set AnchorPane location - setBottomAnchor(this, 0.0) - setLeftAnchor(this, 0.0) - }) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/preprocessing/ColourRangePane.kt b/src/main/kotlin/application/gui/preprocessing/ColourRangePane.kt deleted file mode 100644 index d1be320..0000000 --- a/src/main/kotlin/application/gui/preprocessing/ColourRangePane.kt +++ /dev/null @@ -1,54 +0,0 @@ -package application.gui.preprocessing - -import application.backend.preprocess.masking.ColourRangeNode -import application.gui.PreprocessingPane -import javafx.scene.control.CheckBox -import javafx.scene.control.ColorPicker -import javafx.scene.control.Label -import javafx.scene.control.Tooltip -import javafx.scene.layout.AnchorPane -import javafx.scene.layout.HBox -import javafx.scene.layout.VBox -import javafx.scene.paint.Color - -class ColourRangePane: PreprocessingPane(ColourRangeNode(arrayListOf(Pair(Color.rgb(0, 0, 0), - Color.rgb(255, 255, 255))), true)) { - val minColour = ColorPicker() - val maxColour = ColorPicker() - val binariseCheckbox = CheckBox("Binarise") - - init { - children.add(VBox(10.0).apply { - // Checkbox - children.add(binariseCheckbox.apply { - selectedProperty().addListener { _, _, new -> (node as ColourRangeNode).binarise = new } - }) - - // Setup first picker - children.add(HBox(10.0).apply { - children.add(Label("Minimum colour:")) - children.add(minColour.apply { - tooltip = Tooltip("The lower bound of the colour range for filtering") - valueProperty().addListener { _, _, new -> - (node as ColourRangeNode).colours[0] = Pair(new, node.colours[0].second) - } - }) - }) - - // Setup second picker - children.add(HBox(10.0).apply { - children.add(Label("Maximum threshold:")) - children.add(maxColour.apply { - tooltip = Tooltip("The higher bound of the colour range for filtering") - valueProperty().addListener { _, _, new -> - (node as ColourRangeNode).colours[0] = Pair(node.colours[0].first, new) - } - }) - }) - - // Set AnchorPane location - AnchorPane.setBottomAnchor(this, 0.0) - AnchorPane.setLeftAnchor(this, 0.0) - }) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/gui/preprocessing/ThresholdingPane.kt b/src/main/kotlin/application/gui/preprocessing/ThresholdingPane.kt deleted file mode 100644 index 9745564..0000000 --- a/src/main/kotlin/application/gui/preprocessing/ThresholdingPane.kt +++ /dev/null @@ -1,58 +0,0 @@ -package application.gui.preprocessing - -import application.backend.preprocess.masking.ThresholdingNode -import application.gui.PreprocessingPane -import javafx.scene.control.CheckBox -import javafx.scene.control.Label -import javafx.scene.control.Slider -import javafx.scene.control.Tooltip -import javafx.scene.layout.AnchorPane -import javafx.scene.layout.HBox -import javafx.scene.layout.VBox - -class ThresholdingPane: PreprocessingPane(ThresholdingNode(0.0, 255.0, false)) { - val minThresholdSlider = Slider(0.0, 255.0, 1.0) - val maxThresholdSlider = Slider(0.0, 255.0, 1.0) - val binariseCheckbox = CheckBox("Binarise") - - init { - children.add(VBox(10.0).apply { - // Checkbox - children.add(binariseCheckbox.apply { - selectedProperty().addListener { _, _, new -> (node as ThresholdingNode).binarise = new } - }) - - // Setup first slider - children.add(HBox(10.0).apply { - children.add(Label("Minimum threshold:")) - children.add(minThresholdSlider.apply { - tooltip = Tooltip("The minimum threshold") - valueProperty().addListener { _, _, new -> (node as ThresholdingNode).minThreshold = new.toDouble() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 10.0 - blockIncrement = 2.0 - }) - }) - - // Setup second slider - children.add(HBox(10.0).apply { - children.add(Label("Maximum threshold:")) - children.add(maxThresholdSlider.apply { - tooltip = Tooltip("The maximum threshold") - valueProperty().addListener { _, _, new -> (node as ThresholdingNode).maxThreshold = new.toDouble() } - - isShowTickMarks = true - isShowTickLabels = true - majorTickUnit = 10.0 - blockIncrement = 2.0 - }) - }) - - // Set AnchorPane location - AnchorPane.setBottomAnchor(this, 0.0) - AnchorPane.setLeftAnchor(this, 0.0) - }) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/model/Address.kt b/src/main/kotlin/application/model/Address.kt deleted file mode 100644 index 1d3acb1..0000000 --- a/src/main/kotlin/application/model/Address.kt +++ /dev/null @@ -1,21 +0,0 @@ -package application.model - -import javafx.beans.property.SimpleStringProperty - -class Address(firstName: String = "", lastName: String = "", email: String = "") { - private val firstNameProperty = SimpleStringProperty(firstName) - private val lastNameProperty = SimpleStringProperty(lastName) - private val emailProperty = SimpleStringProperty(email) - - var firstName: String - get() = firstNameProperty.get() - set(value) { firstNameProperty.set(value) } - - var lastName: String - get() = lastNameProperty.get() - set(value) { lastNameProperty.set(value) } - - var email: String - get() = emailProperty.get() - set(value) { emailProperty.set(value) } -} diff --git a/src/main/kotlin/application/model/MediaControl.kt b/src/main/kotlin/application/model/MediaControl.kt deleted file mode 100644 index af3648b..0000000 --- a/src/main/kotlin/application/model/MediaControl.kt +++ /dev/null @@ -1,285 +0,0 @@ -package application.model - -import javafx.application.Platform -import javafx.beans.value.ObservableValue -import javafx.event.ActionEvent -import javafx.event.EventHandler -import javafx.geometry.Insets -import javafx.geometry.Pos -import javafx.scene.Scene -import javafx.scene.control.Button -import javafx.scene.control.Control -import javafx.scene.control.Label -import javafx.scene.control.Slider -import javafx.scene.image.Image -import javafx.scene.image.ImageView -import javafx.scene.layout.BorderPane -import javafx.scene.layout.HBox -import javafx.scene.layout.Pane -import javafx.scene.layout.Priority -import javafx.scene.media.MediaPlayer -import javafx.scene.media.MediaView -import javafx.stage.Stage -import javafx.util.Duration -import kotlin.math.abs -import kotlin.math.roundToInt - - -open class MediaControl(val mp: MediaPlayer): BorderPane() { - var mediaView: MediaView = MediaView(mp) - val repeat = false - var stopRequested = false - var atEndOfMedia = false - var duration: Duration? = null - var timeSlider = Slider().apply { - minWidth = 30.0 - maxWidth = Double.MAX_VALUE - HBox.setHgrow(this, Priority.ALWAYS) - valueProperty().addListener { _: ObservableValue?, old: Number, now: Number -> - if (isValueChanging) { - if (duration != null) { - mp.seek(duration!!.multiply(value / 100.0)) - } - updateValues() - } else if (abs(now.toDouble() - old.toDouble()) > 1.5) { - if (duration != null) { - mp.seek(duration!!.multiply(value / 100.0)) - } - } - } - } - - var playTime = Label().apply { - minWidth = Control.USE_PREF_SIZE - } - - val volumeSlider: Slider? = null - val mediaBar = HBox(5.0).apply { - padding = Insets(5.0, 10.0, 5.0, 10.0) - alignment = Pos.CENTER_LEFT - setAlignment(this, Pos.CENTER) - } - - val mvPane = Pane().apply { - children.add(mediaView) - style = "-fx-background-color: black;" - this@MediaControl.center = this - } - var newStage: Stage? = null - var fullScreen = false - - - override fun layoutChildren() { - if (bottom != null) { - mediaView.fitWidth = width - mediaView.fitHeight = height - bottom.prefHeight(-1.0) - } - super.layoutChildren() - if (center != null) { - mediaView.translateX = ((center as Pane).width - - mediaView.prefWidth(-1.0)) / 2 - mediaView.translateY = ((center as Pane).height - - mediaView.prefHeight(-1.0)) / 2 - } - } - - override fun computeMinWidth(height: Double): Double { - return mediaBar.prefWidth(-1.0) - } - - override fun computeMinHeight(width: Double): Double { - return 200.0 - } - - override fun computePrefWidth(height: Double): Double { - return Math.max(mp.media.width.toDouble(), mediaBar.prefWidth(height)) - } - - override fun computePrefHeight(width: Double): Double { - return mp.media.height + mediaBar.prefHeight(width) - } - - override fun computeMaxWidth(height: Double): Double { - return Double.MAX_VALUE - } - - override fun computeMaxHeight(width: Double): Double { - return Double.MAX_VALUE - } - - init { - style = "-fx-background-color: #bfc2c7;" - - val imageViewPlay = ImageView(Image("file:src/main/resources/image/playbutton.png")) - val imageViewPause = ImageView(Image("file:src/main/resources/image/pausebutton.png")) - - val playButton = Button().apply { - minWidth = Control.USE_PREF_SIZE - graphic = imageViewPlay - onAction = EventHandler { _: ActionEvent? -> - updateValues() - val status = mp.status - if (status == MediaPlayer.Status.UNKNOWN - || status == MediaPlayer.Status.HALTED - ) { - return@EventHandler - } - if (status == MediaPlayer.Status.PAUSED || status == MediaPlayer.Status.READY || status == MediaPlayer.Status.STOPPED - ) { - if (atEndOfMedia) { - mp.seek(mp.startTime) - atEndOfMedia = false - graphic = imageViewPlay - updateValues() - } - mp.play() - graphic = imageViewPause - } else mp.pause() - } - } - - mp.apply { - currentTimeProperty().addListener { _: ObservableValue?, _: Duration?, _: Duration? -> updateValues() } - onPlaying = Runnable { - if (stopRequested) { - pause() - stopRequested = false - } else playButton.graphic = imageViewPause - } - - onPaused = Runnable { playButton.graphic = imageViewPlay } - - onReady = Runnable { - duration = media.duration - updateValues() - } - - cycleCount = if (repeat) MediaPlayer.INDEFINITE else 1 - - onEndOfMedia = Runnable { - if (!repeat) { - playButton.graphic = imageViewPlay - stopRequested = true - atEndOfMedia = true - } - } - } - - - bottom = mediaBar.apply { - children.addAll( - playButton, - Label("Time").apply { minWidth = Control.USE_PREF_SIZE }, - timeSlider, playTime, - Button("Full Screen").apply { - minWidth = Control.USE_PREF_SIZE - onAction = EventHandler { - if (!fullScreen) { - newStage = Stage() - val full = newStage!!.fullScreenProperty() - full.addListener { _: ObservableValue?, _: Boolean?, _: Boolean? -> onFullScreen() } - val borderPane: BorderPane = object : BorderPane() { - override fun layoutChildren() { - if (bottom != null) { - mediaView.fitWidth = width - mediaView.fitHeight = height - bottom.prefHeight(-1.0) - } - super.layoutChildren() - (center as Pane).apply { - mediaView.translateX = (this.width - mediaView.prefWidth(-1.0)) / 2.0 - mediaView.translateY = (this.height - mediaView.prefHeight(-1.0)) / 2.0 - } - - } - } - center = null - bottom = null - borderPane.apply { center = mvPane; bottom = mediaBar } - val newScene = Scene(borderPane) - newStage!!.apply { - scene = newScene - x = -100000.0 - y = -100000.0 - isFullScreen = true - show() - } - } else { - newStage!!.isFullScreen = false - } - fullScreen = !fullScreen - } - } - ) - } - } - - fun onFullScreen() { - if (!newStage!!.isFullScreen) { - fullScreen = false - val smallBP = newStage!!.scene.root as BorderPane - smallBP.center = null - center = mvPane - smallBP.bottom = null - bottom = mediaBar - Platform.runLater { newStage!!.close() } - } - } - - fun updateValues() { - if (volumeSlider != null && duration != null) { - Platform.runLater { - val now = mp.currentTime - playTime.text = formatTime(now, duration!!) - timeSlider.isDisable = duration!!.isUnknown - if (!timeSlider.isDisabled && duration!!.greaterThan(Duration.ZERO) && !timeSlider.isValueChanging) timeSlider.value = - now.divide(duration).toMillis() * 100.0 - if (!volumeSlider.isValueChanging) volumeSlider.value = (mp.volume * 100).roundToInt().toDouble() - } - } - } - - open fun formatTime(elapsed: Duration, duration: Duration): String? { - var intElapsed = Math.floor(elapsed.toSeconds()).toInt() - val elapsedHours = intElapsed / (60 * 60) - if (elapsedHours > 0) { - intElapsed -= elapsedHours * 60 * 60 - } - val elapsedMinutes = intElapsed / 60 - val elapsedSeconds = intElapsed - elapsedHours * 60 * 60 - elapsedMinutes * 60 - return if (duration.greaterThan(Duration.ZERO)) { - var intDuration = Math.floor(duration.toSeconds()).toInt() - val durationHours = intDuration / (60 * 60) - if (durationHours > 0) { - intDuration -= durationHours * 60 * 60 - } - val durationMinutes = intDuration / 60 - val durationSeconds = intDuration - durationHours * 60 * 60 - durationMinutes * 60 - if (durationHours > 0) { - String.format( - "%d:%02d:%02d/%d:%02d:%02d", - elapsedHours, elapsedMinutes, elapsedSeconds, - durationHours, durationMinutes, durationSeconds - ) - } else { - String.format( - "%02d:%02d/%02d:%02d", - elapsedMinutes, elapsedSeconds, - durationMinutes, durationSeconds - ) - } - } else { - if (elapsedHours > 0) { - String.format( - "%d:%02d:%02d", - elapsedHours, elapsedMinutes, elapsedSeconds - ) - } else { - String.format( - "%02d:%02d", - elapsedMinutes, elapsedSeconds - ) - } - } - } -} - diff --git a/src/main/kotlin/application/splash/Splash.kt b/src/main/kotlin/application/splash/Splash.kt deleted file mode 100644 index bb962a7..0000000 --- a/src/main/kotlin/application/splash/Splash.kt +++ /dev/null @@ -1,183 +0,0 @@ -package application.splash - -import application.Main -import java.util.Objects -import javafx.scene.layout.Pane -import javafx.scene.control.ProgressBar -import kotlin.Throws -import java.io.FileNotFoundException -import javafx.animation.* -import javafx.scene.layout.VBox -import javafx.scene.Scene -import javafx.collections.FXCollections -import javafx.geometry.Insets -import javafx.geometry.Pos -import javafx.scene.control.Label -import javafx.scene.effect.DropShadow -import javafx.scene.image.Image -import javafx.scene.image.ImageView -import javafx.scene.paint.Color -import javafx.scene.shape.Rectangle -import javafx.scene.text.Font -import javafx.util.Duration -import kotlin.math.abs - -class Splash { - val IMAGE_URL = - Objects.requireNonNull(Main::class.java.getResource("/images/lightning.gif")).toExternalForm() - private val seqT = SequentialTransition() - val progresser = SequentialTransition() - private val fillerT = SequentialTransition() - val iv = ImageView(Image(IMAGE_URL)).apply { - fitWidth = 400.0 - fitHeight = 300.0 - x = 300.0 - y = 0.0 - } - var scale = 30 - var dur = 800 - private var pane = Pane().apply { style = "-fx-background-color:black" } - private val loadProgress = ProgressBar().apply { prefWidth = 800.0 } - private var progressText = Label("Generating tracker.workspace...").apply { font = Font("System", 13.0) } - private var splashLayout = VBox(loadProgress, progressText).apply { - effect = DropShadow() - style = "-fx-border-radius: 5" - layoutX = 0.0 - layoutY = 240.0 - opaqueInsets = Insets(10.0) - } - private val label = Label("Tracker").apply { - font = Font("Verdana", 40.0) - style = "-fx-text-fill:white" - layoutX = 140.0 - layoutY = 70.0 - } - - - @Throws(FileNotFoundException::class) - fun init() { - rect = Rectangle((100 - 2 * scale).toDouble(), 20.0, scale.toDouble(), scale.toDouble()).apply { fill = Color.BLACK } - progressText.alignment = Pos.CENTER - pane.children.addAll(rect, label, iv, splashLayout) - } - - @Throws(FileNotFoundException::class) - fun show() { - init() - fillerT.children.addAll( - FillTransition(Duration.millis(2000.0), rect, Color.BLACK, Color.CORNFLOWERBLUE), - FillTransition(Duration.millis(2000.0), rect, Color.CORNFLOWERBLUE, Color.DEEPSKYBLUE), - FillTransition(Duration.millis(2000.0), rect, Color.DEEPSKYBLUE, Color.MIDNIGHTBLUE), - FillTransition(Duration.millis(2000.0), rect, Color.MIDNIGHTBLUE, Color.BLACK) - ) - fillerT.play() - progresser.children.add(object : Transition() { - override fun interpolate(frac: Double) { - loadProgress.progress = frac - progressText.text = texts[(frac * (texts.size - 1)).toInt()] - } - - init { - cycleDuration = Duration.millis(8000.0) - } - }) - progresser.play() - val rotins = intArrayOf( - scale, - 2 * scale, - 3 * scale, - 4 * scale, - 5 * scale, - -6 * scale, - -5 * scale, - -4 * scale, - -3 * scale, - -2 * scale - ) - var x: Int - var y: Int - for (i in rotins) { - val rt = RotateTransition(Duration.millis(dur.toDouble()), rect) - rt.byAngle = (i / abs(i) * 90).toDouble() - rt.cycleCount = 1 - val pt = TranslateTransition(Duration.millis(dur.toDouble()), rect) - x = (rect.x + abs(i)).toInt() - y = (rect.x + abs(i) + abs(i) / i * scale).toInt() - pt.fromX = x.toDouble() - pt.toX = y.toDouble() - val pat = ParallelTransition() - pat.children.addAll(pt, rt) - pat.cycleCount = 1 - seqT.children.add(pat) - } - seqT.node = rect - seqT.play() - } - - companion object { - var splashScene: Scene? = null; - var rect = Rectangle() - private val texts = FXCollections.observableArrayList( - "Implementing MVC Framework...", - "Creating 100+ Files...", - "Reading Java API...", - "Importing *...", - "Generating classes...", - "Generating interfaces...", - "Generating enumerations...", - "Extending classes...", - "Implementing interfaces...", - "Setting preferences...", - "Initializing Current Flow...", - "Introducing models to environment...", - "Creating power sources...", - "Creating components...", - "Creating practical components...", - "Catching all Pokemon...", - "Parsing i18N.java...", - "Translating into multiple languages...", - "Seeking damages...", - "Formatting Menubar...", - "Creating JavaFX Controls...", - "Initializing Drag and Drop...", - "Making tracker.canvas...", - "Deriving images...", - "Finding logo...", - "Generating logo...", - "Loading FXML Files...", - "Generating mainframe.fxml...", - "Searching for JavaFX Widgets...", - "Generating GUI...", - "Generating About The Programmer page...", - "Generating About tracker page...", - "Generating autocomplete combobox...", - "Generating sidebar...", - "Styling the window...", - "Initializing MainframeController...", - "Reading tracker source...", - "Calculating resistance...", - "Restarting tracker...", - "Parsing .exml files...", - "Generating electrons...", - "Annihilating fake electrons...", - "Searching games...", - "Failing in generating games...", - "Finding other possible components...", - "Threading everything together...", - "Forming simulator...", - "Reading everything...", - "Ignoring magnetism...", - "Excusing Quantum Physics for this session...", - "Trying to understand PO...", - "Failing to understand PO...", - "Ignoring PO...", - "Preparing Environment...", - "Finally loading tracker.workspace...", - "Completed." - ) - } - - init { - splashScene = Scene(pane) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/wrappers/generic/HelpButton.kt b/src/main/kotlin/application/wrappers/generic/HelpButton.kt deleted file mode 100644 index 6fb06c4..0000000 --- a/src/main/kotlin/application/wrappers/generic/HelpButton.kt +++ /dev/null @@ -1,32 +0,0 @@ -package application.wrappers.generic - -import application.backend.Processing -import javafx.event.ActionEvent -import javafx.event.EventHandler -import javafx.scene.control.Button -import javafx.scene.control.ButtonBar.ButtonData -import javafx.scene.control.ButtonType -import javafx.scene.control.Dialog -import javafx.scene.control.Tooltip -import javafx.scene.shape.Circle - - -class HelpButton(processing: Processing): Button("?") { - val dialog = Dialog().apply { - title = "Help with ${processing.name}" - contentText = processing.help - dialogPane.buttonTypes.add(ButtonType("OK", ButtonData.OK_DONE)) - } - - init { - val r = 13.0 - shape = Circle(r) - setMinSize(2 * r, 2 * r) - setMaxSize(2 * r, 2 * r) - - style = "-fx-font-weight: bold" - tooltip = Tooltip(processing.help) - - onAction = EventHandler { dialog.showAndWait() } - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/wrappers/generic/ProcessingNode.kt b/src/main/kotlin/application/wrappers/generic/ProcessingNode.kt deleted file mode 100644 index 8366832..0000000 --- a/src/main/kotlin/application/wrappers/generic/ProcessingNode.kt +++ /dev/null @@ -1,55 +0,0 @@ -package application.wrappers.generic - -import application.backend.Colourspace -import application.backend.Processing -import javafx.collections.FXCollections -import javafx.scene.Node -import javafx.scene.control.Button -import javafx.scene.control.ComboBox -import javafx.scene.control.Label -import javafx.scene.layout.AnchorPane -import javafx.scene.layout.HBox -import javafx.scene.layout.VBox -import javafx.scene.text.Font - -abstract class ProcessingNode(val node: Processing): AnchorPane() { - val helpButton = HelpButton(node) - val deleteButton = Button("Delete") - val colourspaceCombobox = ComboBox().apply { - items = FXCollections.observableList(node.inputColourspaces) - valueProperty().addListener { _, _, new -> node.inputColourspace = new } - // promptText = "Select Colourspace" - selectionModel.selectFirst(); - } - - init { - // Set style :) - style = "-fx-hgap: 20px; -fx-margin: 30px; -fx-padding: 10px; -fx-background-radius: 5px;" + - "-fx-background-color: #eeeeee; " + - "-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 10, 0, 0, 0);" - - // application.Main anchor pane - // val menu = this - // HBox for top-left - children.addAll( - HBox(8.0).apply { - children.add(Label(node.name).apply { - font = Font(15.0) - style = "-fx-font-weight: bold" - }) - children.add(helpButton) - - setTopAnchor(this, 0.0) - setLeftAnchor(this, 0.0) - }, - - // ComboxBox for top-right - VBox(8.0).apply { - children.addAll(colourspaceCombobox, deleteButton) - - setTopAnchor(this, 0.0) - setRightAnchor(this, 0.0) - } - ) - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/wrappers/generic/RoundedPane.kt b/src/main/kotlin/application/wrappers/generic/RoundedPane.kt deleted file mode 100644 index 88441c4..0000000 --- a/src/main/kotlin/application/wrappers/generic/RoundedPane.kt +++ /dev/null @@ -1,12 +0,0 @@ -package application.wrappers.generic - -import javafx.scene.Node -import javafx.scene.layout.Pane - -class RoundedPane(vararg children: Node, radius: Int = 30, padding: Int = 30): Pane(*children) { - init { - this.style = "-fx-hgap: 20px; -fx-padding: ${padding}px; -fx-background-radius: ${radius}px; " + - "-fx-border-radius: ${radius}px; -fx-border-width: 5px; -fx-border-color: black; " + - "-fx-effect: dropshadow(three-pass-box, rgba(0, 0, 0, 0.8), 10, 0, 0, 0);" - } -} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/Colourspace.kt b/src/main/kotlin/backend/Colourspace.kt similarity index 83% rename from src/main/kotlin/application/backend/Colourspace.kt rename to src/main/kotlin/backend/Colourspace.kt index 8fd149d..6a3d2fb 100644 --- a/src/main/kotlin/application/backend/Colourspace.kt +++ b/src/main/kotlin/backend/Colourspace.kt @@ -1,4 +1,4 @@ -package application.backend +package backend val ALL_SPACES = listOf(Colourspace.RGB, Colourspace.HSV, Colourspace.GRAYSCALE) enum class Colourspace { diff --git a/src/main/kotlin/backend/Image.kt b/src/main/kotlin/backend/Image.kt new file mode 100644 index 0000000..e4c1e5d --- /dev/null +++ b/src/main/kotlin/backend/Image.kt @@ -0,0 +1,346 @@ +package backend + +import com.github.ajalt.colormath.Color +import com.github.ajalt.colormath.model.HSV +import com.github.ajalt.colormath.model.RGB +import org.bytedeco.javacpp.indexer.UByteIndexer +import org.bytedeco.opencv.global.opencv_core.* +import org.bytedeco.opencv.global.opencv_imgcodecs.imread +import org.bytedeco.opencv.global.opencv_imgcodecs.imwrite +import org.bytedeco.opencv.global.opencv_imgproc.* +import org.bytedeco.opencv.opencv_core.Mat +import org.bytedeco.opencv.opencv_core.Size +import org.bytedeco.opencv.opencv_core.Point as cvPoint + + +/** + * Represents an image + */ +class Image(colourspace: Colourspace, img: Mat) { + /** + * The scale of the image (1 pixel is [scale] metres) + */ + var scale = 1.0 + + /** + * The angle that the x axis makes with the horizontal. Take anti-clockwise to be positive. + */ + var angle = 0.0 + + /** + * The position of the origin + */ + var origin = Point(0.0, 0.0) + + /** + * The colourspace of the image + */ + var colourspace: Colourspace = colourspace + set(value) { + when (field) { + Colourspace.RGB -> when (value) { + Colourspace.HSV -> cvtColor(img, img, COLOR_RGB2HSV) + Colourspace.GRAYSCALE -> cvtColor(img, img, COLOR_RGB2GRAY) + else -> {} + } + Colourspace.HSV -> when (value) { + Colourspace.RGB -> cvtColor(img, img, COLOR_HSV2RGB) + Colourspace.GRAYSCALE -> { + cvtColor(img, img, COLOR_HSV2RGB) + cvtColor(img, img, COLOR_RGB2GRAY) + } + else -> {} + } + Colourspace.GRAYSCALE -> when (value) { + Colourspace.RGB -> cvtColor(img, img, COLOR_GRAY2RGB) + Colourspace.HSV -> { + cvtColor(img, img, COLOR_GRAY2RGB) + cvtColor(img, img, COLOR_RGB2HSV) + } + else -> {} + } + } + + field = value + } + + /** + * The OpenCV image + */ + var img: Mat = img + private set(value) { + field = value + indexer = field.createIndexer() + } + + private var indexer: UByteIndexer = img.createIndexer() + + constructor(path: String) : this(Colourspace.RGB, with(null) { + val img = imread(path) + cvtColor(img, img, COLOR_BGR2RGB) + + img + }) + + /* Pixel Manipulation */ + + /** + * Returns the colour of the pixel at the given [point] + */ + operator fun get(point: Point): Color { + val point2 = fromScaled(point) + + return when (colourspace) { + Colourspace.RGB -> RGB.from255(indexer.get(point2.y.toLong(), point2.x.toLong(), 0), + indexer.get(point2.y.toLong(), point2.x.toLong(), 1), + indexer.get(point2.y.toLong(), point2.x.toLong(), 2)) + Colourspace.HSV -> HSV(indexer.get(point2.y.toLong(), point2.x.toLong(), 0) / 255.0 * 360, + indexer.get(point2.y.toLong(), point2.x.toLong(), 1) / 255.0, + indexer.get(point2.y.toLong(), point2.x.toLong(), 2) / 255.0) + Colourspace.GRAYSCALE -> RGB.from255(indexer.get(point2.y.toLong(), point2.x.toLong()), + indexer.get(point2.y.toLong(), point2.x.toLong()), + indexer.get(point2.y.toLong(), point2.x.toLong())) + } + } + + /** + * Returns the colour at the given [x] and [y] coordinates + */ + operator fun get(x: Double, y: Double) = this[Point(x, y)] + + /** + * Returns the colour of the pixel at the given [point] + */ + operator fun set(point: Point, colour: Color) { + val point2 = fromScaled(point) + when (colourspace) { + Colourspace.RGB -> { + val rgb = colour.toSRGB() + indexer.put(point2.x.toLong(), point2.y.toLong(), 0, (rgb.r * 255).toInt()) + indexer.put(point2.x.toLong(), point2.y.toLong(), 1, (rgb.g * 255).toInt()) + indexer.put(point2.x.toLong(), point2.y.toLong(), 2, (rgb.b * 255).toInt()) + } + Colourspace.HSV -> { + val hsv = colour.toHSV() + indexer.put(point2.x.toLong(), point2.y.toLong(), 0, (hsv.h / 360.0 * 255).toInt()) + indexer.put(point2.x.toLong(), point2.y.toLong(), 1, (hsv.s * 255).toInt()) + indexer.put(point2.x.toLong(), point2.y.toLong(), 2, (hsv.v * 255).toInt()) + } + else -> { + val rgb = colour.toSRGB() + indexer.put(point2.x.toLong(), point2.y.toLong(), (rgb.r * 255).toInt()) + } + } + + indexer = img.createIndexer() + } + + /** + * Returns the colour at the given [x] and [y] coordinates + */ + operator fun set(x: Double, y: Double, colour: Color) { + this[Point(x, y)] = colour + } + + /* Blurring */ + + /** + * Performs a gaussian blur on the image + * @param kernelSize The kernel size of the gaussian kernel + * @param sigma The standard deviation of the gaussian kernel. + * If set to 0, it is automatically calculated. + */ + fun gaussianBlur(kernelSize: Int, sigma: Double = 0.0): Image { + require(kernelSize >= 3 && kernelSize % 2 == 1) { "Kernel size must be odd and larger than or equal to 3" } + + GaussianBlur(img, img, Size(kernelSize, kernelSize), sigma) + indexer = img.createIndexer() + + return this + } + + /** + * Blurs the image using a box filter convolution of the given [kernelSize] + */ + fun boxFilter(kernelSize: Int): Image { + blur(img, img, Size(kernelSize, kernelSize)) + indexer = img.createIndexer() + + return this + } + + /** + * Performs a median blur on the image + */ + fun medianBlur(kernelSize: Int): Image { + medianBlur(img, img, kernelSize) + indexer = img.createIndexer() + + return this + } + + /* Masking */ + + /** + * Applies the given [mask] to the image + */ + fun applyMask(mask: Image): Image = applyMask(mask.img) + + /** + * Applies the given [mask] to the image + */ + private fun applyMask(mask: Mat): Image { + val newImg = Mat() + bitwise_and(img, img, newImg, mask) + + img = newImg + indexer = img.createIndexer() + + return this + } + + /** + * Filters out all pixels dimmer than [minThreshold] and brighter than [maxThreshold]. + * If [binarise] is true, the image will be converted to a binary mask. + * If not the resulting binary mask is applied to the original image. + */ + fun threshold(minThreshold: Double, maxThreshold: Double = 255.0, binarise: Boolean = true): Image { + val mask = Mat() + val mask2 = Mat() + val newImg = img.clone() + + // Converting to grayscale + var gray = Mat() + when (colourspace) { + Colourspace.RGB -> cvtColor(newImg, gray, COLOR_RGB2GRAY) + Colourspace.HSV -> { + cvtColor(newImg, gray, COLOR_HSV2RGB) + cvtColor(gray, gray, COLOR_RGB2GRAY) + } + else -> gray = newImg + } + + // Perform thresholding + threshold(gray, mask, minThreshold, 255.0, THRESH_BINARY) + threshold(gray, mask2, maxThreshold, 255.0, THRESH_BINARY) + + // Combine the masks + bitwise_not(mask2, mask2) + bitwise_and(mask, mask2, mask) + + // Apply mask on the image if necessary + if (binarise) img = mask else applyMask(mask) + + indexer = img.createIndexer() + return this + } + + /** + * Filters out all colours not within the ranges specified in [colours] + * If [binarise] is true, the image will be converted to a binary mask. + * If not the resulting binary mask is applied to the original image. + */ + fun colourFilter(colours: List>, binarise: Boolean = true): Image { + val mask = Mat() + var newImg = img.clone() + colours.forEach { (start, end) -> + when (colourspace) { + Colourspace.RGB -> inRange(newImg, Mat(start.toSRGB().r * 255, start.toSRGB().g * 255, start.toSRGB().b * 255), + Mat(end.toSRGB().r * 255, end.toSRGB().g * 255, end.toSRGB().b * 255), mask) + Colourspace.HSV -> { + inRange(newImg, Mat(start.toHSV().h / 360 * 255, start.toHSV().s * 255, start.toHSV().v * 255), + Mat(end.toHSV().h / 360 * 255, end.toHSV().s / 360 * 255, end.toHSV().v / 360 * 255), mask) + } + else -> {} + } + + val newerImg = Mat() + newImg.copyTo(newerImg, mask) + newImg = newerImg + } + + if (binarise) img = mask else applyMask(mask) + + indexer = img.createIndexer() + return this + } + + /* Morphological Transformations */ + + /** + * Performs erosion with the specified [kernelSize] [iterations] times. + */ + fun erode(kernelSize: Int, iterations: Int = 1): Image { + erode(img, img, getStructuringElement( + CV_SHAPE_RECT, Size(2 * kernelSize + 1, 2 * kernelSize + 1), + cvPoint(kernelSize, kernelSize) + ), cvPoint(-1, -1), iterations, BORDER_CONSTANT, null) + + indexer = img.createIndexer() + return this + } + + /** + * Performs dilation with the specified [kernelSize] [iterations] times. + */ + fun dilate(kernelSize: Int, iterations: Int = 1): Image { + dilate(img, img, getStructuringElement( + CV_SHAPE_RECT, Size(2 * kernelSize + 1, 2 * kernelSize + 1), + cvPoint(kernelSize, kernelSize) + ), cvPoint(-1, -1), iterations, BORDER_CONSTANT, null) + + indexer = img.createIndexer() + return this + } + + /* Edge Detection */ + + /** + * Performs canny edge detection on the image with given [lowerThreshold] and [upperThreshold]. + * @param kernelSize The size of the kernel used for the edge detection + */ + fun cannyEdge(lowerThreshold: Double, upperThreshold: Double = lowerThreshold * 2, kernelSize: Int = 3): Image { + var gray = Mat() + when (colourspace) { + Colourspace.RGB -> cvtColor(img, gray, COLOR_RGB2GRAY) + Colourspace.HSV -> { + cvtColor(img, gray, COLOR_HSV2RGB) + cvtColor(gray, gray, COLOR_RGB2GRAY) + } + else -> gray = img + } + + Canny(gray, img, lowerThreshold, upperThreshold, kernelSize, false) + + return this + } + + /* Fitting Shapes */ + + /* Misc */ + + /** + * Writes the image to the given [path] + */ + fun write(path: String) { + val newImg = Mat() + cvtColor(img, newImg, COLOR_RGB2BGR) + + imwrite(path, newImg) + } + + /** + * Returns a deep copy of the image + */ + fun clone() = Image(colourspace, img.clone()) + + /** + * Returns the unscaled point given the scaled [point] + */ + private fun fromScaled(point: Point) = (point - origin) * scale + + /** + * Returns the unscaled point given the scaled [x] and [y] coordinates + */ + private fun fromScaled(x: Double, y: Double) = fromScaled(Point(x, y)) +} \ No newline at end of file diff --git a/src/main/kotlin/application/backend/Point.kt b/src/main/kotlin/backend/Point.kt similarity index 93% rename from src/main/kotlin/application/backend/Point.kt rename to src/main/kotlin/backend/Point.kt index ae42492..dc3e7e8 100644 --- a/src/main/kotlin/application/backend/Point.kt +++ b/src/main/kotlin/backend/Point.kt @@ -1,4 +1,4 @@ -package application.backend +package backend data class Point(val x: Double, val y: Double) { operator fun plus(other: Point) = Point(x + other.x, y + other.y) diff --git a/src/main/kotlin/backend/Video.kt b/src/main/kotlin/backend/Video.kt new file mode 100644 index 0000000..9aaf530 --- /dev/null +++ b/src/main/kotlin/backend/Video.kt @@ -0,0 +1,63 @@ +package backend + +import backend.image_processing.preprocess.Preprocessor +import org.bytedeco.opencv.global.opencv_imgproc.COLOR_BGR2RGB +import org.bytedeco.opencv.global.opencv_imgproc.cvtColor +import org.bytedeco.opencv.global.opencv_videoio.CAP_PROP_FRAME_COUNT +import org.bytedeco.opencv.opencv_core.Mat +import org.bytedeco.opencv.opencv_videoio.VideoCapture + +/** + * Represents a video + */ +class Video(val videoCapture: VideoCapture) : Iterator { + private var nextImage: Mat = Mat() + + /** + * The current image shown in the video + */ + lateinit var currentImage: Image + private set + + /** + * The total number of frames of the video + */ + val totalFrames: Int = videoCapture.get(CAP_PROP_FRAME_COUNT).toInt() + + /** + * The preprocessor that preprocesses the video frames + */ + val preprocesser: Preprocessor = Preprocessor() + + /** + * The current frame number of the video + */ + var currentFrame: Int = 0 + private set + + /** + * Imports a video from a [file] + */ + constructor(file: String) : this(VideoCapture(file)) + + /** + * Moves the video to the specified [frameNumber] + */ + fun seek(frameNumber: Int) { + videoCapture.set(1, (frameNumber - 1).toDouble()) + currentFrame = frameNumber - 1 + } + + override fun hasNext(): Boolean = videoCapture.read(nextImage) + + override fun next(): Image { + currentFrame++ + + // Convert from BGR to RGB + cvtColor(nextImage, nextImage, COLOR_BGR2RGB) + currentImage = Image(Colourspace.RGB, nextImage) + currentImage = preprocesser.process(currentImage) + + return currentImage + } +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/Processing.kt b/src/main/kotlin/backend/image_processing/Processing.kt new file mode 100644 index 0000000..afcbd72 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/Processing.kt @@ -0,0 +1,28 @@ +package backend.image_processing + +import backend.Colourspace + +/** + * The base class for all types of processing nodes + */ +abstract class Processing { + /** + * Name of the node + */ + abstract val name: String + + /** + * The help string to display + */ + abstract val help: String + + /** + * The input colourspace of the node + */ + open var inputColourspace: Colourspace = Colourspace.RGB + + /** + * The list of possible input colourspaces of the node + */ + abstract val inputColourspaces: List +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/Shapes.kt b/src/main/kotlin/backend/image_processing/Shapes.kt new file mode 100644 index 0000000..3902bd6 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/Shapes.kt @@ -0,0 +1,7 @@ +package backend.image_processing + +import backend.Point + +data class Circle(val centre: Point, val radius: Double) + +data class Ellipse(val centre: Point, val angle: Double, val semimajor: Double, val semiminor: Double) diff --git a/src/main/kotlin/application/backend/postprocess/PostprocessingNode.kt b/src/main/kotlin/backend/image_processing/postprocess/PostprocessingNode.kt similarity index 73% rename from src/main/kotlin/application/backend/postprocess/PostprocessingNode.kt rename to src/main/kotlin/backend/image_processing/postprocess/PostprocessingNode.kt index ce7aed9..9a7c2bc 100644 --- a/src/main/kotlin/application/backend/postprocess/PostprocessingNode.kt +++ b/src/main/kotlin/backend/image_processing/postprocess/PostprocessingNode.kt @@ -1,8 +1,7 @@ -package application.backend.postprocess +package backend.image_processing.postprocess -import application.backend.Colourspace -import application.backend.Point -import application.backend.Processing +import backend.Point +import backend.image_processing.Processing import org.bytedeco.opencv.opencv_core.Mat abstract class PostprocessingNode: Processing() { diff --git a/src/main/kotlin/application/backend/Postprocessor.kt b/src/main/kotlin/backend/image_processing/postprocess/Postprocessor.kt similarity index 82% rename from src/main/kotlin/application/backend/Postprocessor.kt rename to src/main/kotlin/backend/image_processing/postprocess/Postprocessor.kt index 130dea8..71f3ec1 100644 --- a/src/main/kotlin/application/backend/Postprocessor.kt +++ b/src/main/kotlin/backend/image_processing/postprocess/Postprocessor.kt @@ -1,12 +1,10 @@ -package application.backend +package backend.image_processing.postprocess -import application.backend.postprocess.PostprocessingNode +import backend.Colourspace import krangl.DataFrame import krangl.dataFrameOf -import org.bytedeco.opencv.global.opencv_imgproc import org.bytedeco.opencv.global.opencv_imgproc.* import org.bytedeco.opencv.opencv_core.Mat -import org.bytedeco.opencv.opencv_videoio.VideoCapture class Postprocessor(val node: PostprocessingNode) { fun process(videoCapture: Sequence): DataFrame { diff --git a/src/main/kotlin/application/backend/postprocess/fitting/CircleFittingNode.kt b/src/main/kotlin/backend/image_processing/postprocess/fitting/CircleFittingNode.kt similarity index 89% rename from src/main/kotlin/application/backend/postprocess/fitting/CircleFittingNode.kt rename to src/main/kotlin/backend/image_processing/postprocess/fitting/CircleFittingNode.kt index 5286999..735b6e5 100644 --- a/src/main/kotlin/application/backend/postprocess/fitting/CircleFittingNode.kt +++ b/src/main/kotlin/backend/image_processing/postprocess/fitting/CircleFittingNode.kt @@ -1,8 +1,8 @@ -package application.backend.postprocess.fitting +package backend.image_processing.postprocess.fitting -import application.backend.Colourspace -import application.backend.Point -import application.backend.postprocess.PostprocessingNode +import backend.Colourspace +import backend.Point +import backend.image_processing.postprocess.PostprocessingNode import org.bytedeco.opencv.global.opencv_imgproc.CV_HOUGH_GRADIENT import org.bytedeco.opencv.global.opencv_imgproc.HoughCircles import org.bytedeco.opencv.opencv_core.Mat diff --git a/src/main/kotlin/application/backend/postprocess/fitting/EllipseFittingNode.kt b/src/main/kotlin/backend/image_processing/postprocess/fitting/EllipseFittingNode.kt similarity index 87% rename from src/main/kotlin/application/backend/postprocess/fitting/EllipseFittingNode.kt rename to src/main/kotlin/backend/image_processing/postprocess/fitting/EllipseFittingNode.kt index cf607e0..1d38706 100644 --- a/src/main/kotlin/application/backend/postprocess/fitting/EllipseFittingNode.kt +++ b/src/main/kotlin/backend/image_processing/postprocess/fitting/EllipseFittingNode.kt @@ -1,8 +1,8 @@ -package application.backend.postprocess.fitting +package backend.image_processing.postprocess.fitting -import application.backend.Colourspace -import application.backend.Point -import application.backend.postprocess.PostprocessingNode +import backend.Colourspace +import backend.Point +import backend.image_processing.postprocess.PostprocessingNode import org.bytedeco.opencv.global.opencv_imgproc.* import org.bytedeco.opencv.opencv_core.Mat import org.bytedeco.opencv.opencv_core.MatVector diff --git a/src/main/kotlin/backend/image_processing/preprocess/PreprocessingNode.kt b/src/main/kotlin/backend/image_processing/preprocess/PreprocessingNode.kt new file mode 100644 index 0000000..70985d9 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/preprocess/PreprocessingNode.kt @@ -0,0 +1,25 @@ +package backend.image_processing.preprocess + +import backend.Colourspace +import backend.Image +import backend.image_processing.Processing + +/** + * The base class for all preprocessing nodes + */ +abstract class PreprocessingNode: Processing() { + /** + * The output colourspace of the node + */ + abstract val outputColourspace: Colourspace + + /** + * Processes the given [img] and outputs the processed image + */ + abstract fun process(img: Image): Image + + /** + * Returns a deep copy of the preprocessing node + */ + abstract fun clone(): PreprocessingNode +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/preprocess/Preprocessor.kt b/src/main/kotlin/backend/image_processing/preprocess/Preprocessor.kt new file mode 100644 index 0000000..7a85970 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/preprocess/Preprocessor.kt @@ -0,0 +1,27 @@ +package backend.image_processing.preprocess + +import backend.Image + +/** + * Preprocesses the inputted image + */ +class Preprocessor { + /** + * The nodes to use in the pre-processing + */ + val nodes: ArrayList = arrayListOf() + + /** + * Preprocesses the image with the [nodes] + */ + fun process(img: Image): Image { + var newImg = img.clone() + + nodes.forEach { + newImg.colourspace = it.inputColourspace + newImg = it.process(newImg) + } + + return newImg + } +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/preprocess/blurring/BlurringNode.kt b/src/main/kotlin/backend/image_processing/preprocess/blurring/BlurringNode.kt new file mode 100644 index 0000000..969bf0c --- /dev/null +++ b/src/main/kotlin/backend/image_processing/preprocess/blurring/BlurringNode.kt @@ -0,0 +1,41 @@ +package backend.image_processing.preprocess.blurring + +import backend.ALL_SPACES +import backend.Colourspace +import backend.Image +import backend.image_processing.preprocess.PreprocessingNode + +/** + * All the types of blurring supported + */ +enum class Blurring { + GAUSSIAN, + MEDIAN, + BOX_FILTER +} + +/** + * The node for blurring images + * @param blurType The type of blurring to use + * @property blurType The type of blurring to use + * @param kernelSize The kernel size to use for blurring + * @property kernelSize The kernel size to use for blurring + */ +class BlurringNode(var blurType: Blurring = Blurring.GAUSSIAN, var kernelSize: Int = 3): PreprocessingNode() { + override val name: String = "Blurring" + override val help: String = "This node blurs the video to remove noise. The kernel size controls the extent of blurring and " + + "can only be odd." + + override val inputColourspaces: List = ALL_SPACES + override val outputColourspace: Colourspace get() = inputColourspace + + override fun process(img: Image): Image = img.clone().apply { + when (blurType) { + Blurring.GAUSSIAN -> gaussianBlur(kernelSize) + Blurring.MEDIAN -> medianBlur(kernelSize) + else -> boxFilter(kernelSize) + } + } + + override fun clone(): BlurringNode = BlurringNode(blurType, kernelSize) +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/preprocess/edge_detection/CannyEdgeNode.kt b/src/main/kotlin/backend/image_processing/preprocess/edge_detection/CannyEdgeNode.kt new file mode 100644 index 0000000..c8eed41 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/preprocess/edge_detection/CannyEdgeNode.kt @@ -0,0 +1,25 @@ +package backend.image_processing.preprocess.edge_detection + +import backend.ALL_SPACES +import backend.Colourspace +import backend.Image +import backend.image_processing.preprocess.PreprocessingNode + +/** + * The node that performs canny edge detection + * @param threshold The threshold to use for the edge detection + * @property threshold The threshold to use for the edge detection + * @param kernelSize The size of the kernel used for edge detection + * @property kernelSize The size of the kernel used for edge detection + */ +class CannyEdgeNode(var threshold: Double = 200.0, var kernelSize: Int = 3): PreprocessingNode() { + override val name: String = "Edge\nDetection" + override val help: String = "Detects edges in the image. Blurring first in recommended." + + override val inputColourspaces: List = ALL_SPACES + override val outputColourspace: Colourspace = Colourspace.GRAYSCALE + + override fun process(img: Image): Image = img.clone().apply { cannyEdge(threshold, kernelSize = kernelSize) } + + override fun clone(): PreprocessingNode = CannyEdgeNode(threshold, kernelSize) +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/preprocess/masking/ColourRangeNode.kt b/src/main/kotlin/backend/image_processing/preprocess/masking/ColourRangeNode.kt new file mode 100644 index 0000000..c5f9261 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/preprocess/masking/ColourRangeNode.kt @@ -0,0 +1,25 @@ +package backend.image_processing.preprocess.masking + +import backend.Colourspace +import backend.Image +import backend.image_processing.preprocess.PreprocessingNode +import com.github.ajalt.colormath.Color + +/** + * The node for filter colours within the specified colour ranges + * @param colours A list of colour ranges used to filter the image + * @property colours A list of colour ranges used to filter the image + * @param binarise Should the image outputted be a binary mask? + * @property binarise Should the image outputted be a binary mask? + */ +data class ColourRangeNode(var colours: ArrayList>, var binarise: Boolean): PreprocessingNode() { + override val name: String = "Filter Colours" + override val help: String = "Filters out parts of the images within the given colour range." + + override val inputColourspaces: List = listOf(Colourspace.RGB, Colourspace.HSV) + override val outputColourspace: Colourspace get() = inputColourspace + + override fun process(img: Image): Image = img.clone().apply { colourFilter(colours, binarise) } + + override fun clone(): PreprocessingNode = ColourRangeNode(colours, binarise) +} \ No newline at end of file diff --git a/src/main/kotlin/backend/image_processing/preprocess/masking/ThresholdingNode.kt b/src/main/kotlin/backend/image_processing/preprocess/masking/ThresholdingNode.kt new file mode 100644 index 0000000..4205924 --- /dev/null +++ b/src/main/kotlin/backend/image_processing/preprocess/masking/ThresholdingNode.kt @@ -0,0 +1,27 @@ +package backend.image_processing.preprocess.masking + +import backend.ALL_SPACES +import backend.Colourspace +import backend.Image +import backend.image_processing.preprocess.PreprocessingNode + +/** + * The node used for thresholding + * @param minThreshold The minimum threshold to use + * @property minThreshold The minimum threshold to use + * @param maxThreshold The maximum threshold to use + * @property maxThreshold The minimum threshold to use + * @param binarise Should the image be converted to a binary mask? + * @property binarise Should the image be converted to a binary mask? + */ +class ThresholdingNode(var minThreshold: Double = 0.0, var maxThreshold: Double = 255.0, var binarise: Boolean = true): PreprocessingNode() { + override val name: String = "Binarise" + override val help: String = "Performs a black and white threshold on the image." + + override val inputColourspaces: List = ALL_SPACES + override val outputColourspace: Colourspace get() = if (binarise) inputColourspace else Colourspace.GRAYSCALE + + override fun process(img: Image): Image = img.clone().apply { threshold(minThreshold, maxThreshold, binarise) } + + override fun clone(): ThresholdingNode = ThresholdingNode(minThreshold, maxThreshold, binarise) +} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/angles/AngleChooser.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/angles/AngleChooser.kt deleted file mode 100644 index 67cf7b0..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/angles/AngleChooser.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.thepyprogrammer.fxtools.angles - -import javafx.scene.layout.Pane -import javafx.scene.paint.* - - - -class AngleChooser : AnglePicker() { - private var node: Pane? = null - fun bind(node: Pane) { - this.node?.rotateProperty()?.unbindBidirectional(angleProperty()) - this.node = node.apply { - setAngle(rotate) - if(rotate == 0.0) setAngle(360.0) - rotateProperty().bindBidirectional(angleProperty()) - } - } - - init { - setForegroundPaint(Color.WHITE as Paint) - setBackgroundPaint( - LinearGradient( - .0, .0, .0, 1.0, true, CycleMethod.NO_CYCLE, - Stop(0.0, Color.rgb(214, 214, 214)), - Stop(0.5, Color.rgb(206, 206, 206)), - Stop(1.0, Color.rgb(195, 195, 195)) - ) - ) - setIndicatorPaint(Color.rgb(97, 97, 97)) - setTextPaint(Color.rgb(26, 26, 26)) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/angles/AnglePicker.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/angles/AnglePicker.kt deleted file mode 100644 index da51eba..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/angles/AnglePicker.kt +++ /dev/null @@ -1,461 +0,0 @@ -package com.thepyprogrammer.fxtools.angles - -import javafx.beans.DefaultProperty -import javafx.beans.InvalidationListener -import javafx.beans.property.DoubleProperty -import javafx.beans.property.DoublePropertyBase -import javafx.beans.property.ObjectProperty -import javafx.beans.property.ObjectPropertyBase -import javafx.beans.value.ObservableValue -import javafx.collections.ObservableList -import javafx.event.EventHandler -import javafx.geometry.Insets -import javafx.geometry.Pos -import javafx.geometry.VPos -import javafx.scene.Node -import javafx.scene.control.TextField -import javafx.scene.control.TextFormatter -import javafx.scene.effect.BlurType -import javafx.scene.effect.InnerShadow -import javafx.scene.input.KeyCode -import javafx.scene.input.KeyEvent -import javafx.scene.input.MouseEvent -import javafx.scene.layout.Pane -import javafx.scene.layout.Region -import javafx.scene.paint.* -import javafx.scene.shape.Circle -import javafx.scene.shape.Rectangle -import javafx.scene.text.Font -import javafx.scene.text.Text -import javafx.scene.transform.Rotate -import javafx.util.StringConverter -import java.util.* -import kotlin.math.atan2 -import kotlin.math.sqrt - -/* Copyright (c) 2018 by Gerrit Grunwald - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - - - -/** - * User: hansolo - * Date: 07.11.18 - * Time: 12:30 - */ -@DefaultProperty("children") -open class AnglePicker : Region() { - private var size = 0.0 - private var widthProp = 0.0 - private var heightProp = 0.0 - private var background: Circle? = null - private var foreground: Circle? = null - private var indicator: Rectangle? = null - private var text: Text? = null - private var pane: Pane? = null - private val rotate: Rotate - private var _angle: Double - private var angle: DoubleProperty? = null - private var _backgroundPaint: Paint? - private var backgroundPaint: ObjectProperty? = null - private var _foregroundPaint: Paint? - private var foregroundPaint: ObjectProperty? = null - private var indicatorPaint: ObjectProperty? = null - private var _indicatorPaint: Paint? - private var _textPaint: Paint? - private var textPaint: ObjectProperty? = null - private var innerShadow: InnerShadow? = null - private var textField: TextField? = null - private val converter: StringConverter - private val mouseFilter: EventHandler - - // ******************** Initialization ************************************ - private fun initGraphics() { - if (prefWidth.compareTo(0.0) <= 0 || prefHeight.compareTo(0.0) <= 0 || getWidth().compareTo(0.0) <= 0 || getHeight().compareTo(0.0) <= 0) { - if (prefWidth > 0 && prefHeight > 0) { - setPrefSize(prefWidth, prefHeight) - } else { - setPrefSize(PREFERRED_WIDTH, PREFERRED_HEIGHT) - } - } - styleClass.add("angle-picker") - rotate.angle = 0.0 - innerShadow = InnerShadow(BlurType.TWO_PASS_BOX, Color.rgb(255, 255, 255, 0.3), 1.0, 0.0, 0.0, 0.5) - background = Circle() - background!!.fill = _backgroundPaint - background!!.isMouseTransparent = true - foreground = Circle() - foreground!!.fill = _foregroundPaint - foreground!!.effect = innerShadow - indicator = Rectangle() - indicator!!.transforms.add(rotate) - indicator!!.isMouseTransparent = true - text = Text(String.format(Locale.US, "%.0f\u00b0", _angle)) - text!!.textOrigin = VPos.CENTER - text!!.isMouseTransparent = true - textField = TextField(String.format(Locale.US, "%.0f\u00b0", _angle)) - //textField.setRegex("\\d{0,3}([\\.]\\d{0,1})?"); - textField!!.textFormatterProperty().value = TextFormatter(converter, getAngle()) - textField!!.padding = Insets(2.0) - textField!!.alignment = Pos.CENTER - textField!!.isVisible = false - textField!!.isManaged = false - pane = Pane(background, foreground, indicator, text, textField) - children.setAll(pane) - } - - private fun registerListeners() { - widthProperty().addListener( InvalidationListener{ resize() }) - heightProperty().addListener( InvalidationListener{ resize() }) - foreground!!.addEventFilter(MouseEvent.MOUSE_DRAGGED, mouseFilter) - foreground!!.addEventFilter(MouseEvent.MOUSE_CLICKED, mouseFilter) - textField!!.onKeyPressed = EventHandler { e: KeyEvent -> - if (e.code == KeyCode.ENTER) { - updateTextField() - } - } - textField!!.focusedProperty().addListener { o: ObservableValue?, _: Boolean?, nv: Boolean? -> - if (!nv!!) { - updateTextField() - } - } - focusedProperty().addListener { o: ObservableValue?, _: Boolean?, nv: Boolean? -> - if (!nv!!) { - updateTextField() - } - } - } - - // ******************** Methods ******************************************* - public override fun layoutChildren() { - super.layoutChildren() - } - - override fun computeMinWidth(HEIGHT: Double): Double { - return MINIMUM_WIDTH - } - - override fun computeMinHeight(WIDTH: Double): Double { - return MINIMUM_HEIGHT - } - - override fun computeMaxWidth(HEIGHT: Double): Double { - return MAXIMUM_WIDTH - } - - override fun computeMaxHeight(WIDTH: Double): Double { - return MAXIMUM_HEIGHT - } - - public override fun getChildren(): ObservableList { - return super.getChildren() - } - - fun getAngle(): Double { - return if (null == angle) _angle else angle!!.get() - } - - fun setAngle(angle: Double) { - if (null == this.angle) { - _angle = angle % 360.0 - rotate.angle = _angle - text!!.text = String.format(Locale.US, "%.0f\u00b0", 360 - _angle) - text!!.relocate((size - text!!.layoutBounds.width) * 0.5, (size - text!!.layoutBounds.height) * 0.5) - textField!!.text = String.format(Locale.US, "%.0f\u00b0", 360 - _angle) - } else { - this.angle!!.set(angle) - } - } - - fun angleProperty(): DoubleProperty { - if (null == angle) { - angle = object : DoublePropertyBase(_angle) { - override fun invalidated() { - rotate.angle = get() % 360.0 - text!!.text = String.format(Locale.US, "%.0f\u00b0", 360 - get()) - text!!.relocate((size - text!!.layoutBounds.width) * 0.5, (size - text!!.layoutBounds.height) * 0.5) - textField!!.text = String.format(Locale.US, "%.0f\u00b0", 360 - get()) - } - - override fun getBean(): Any { - return this@AnglePicker - } - - override fun getName(): String { - return "angle" - } - } - } - return angle!! - } - - fun getBackgroundPaint(): Paint? { - return if (null == backgroundPaint) _backgroundPaint else backgroundPaint!!.get() - } - - fun setBackgroundPaint(backgroundPaint: Paint) { - if (null == this.backgroundPaint) { - _backgroundPaint = backgroundPaint - redraw() - } else { - this.backgroundPaint!!.set(backgroundPaint) - } - } - - fun backgroundPaintProperty(): ObjectProperty? { - if (null == backgroundPaint) { - backgroundPaint = object : ObjectPropertyBase(_backgroundPaint) { - override fun invalidated() { - redraw() - } - - override fun getBean(): Any { - return this@AnglePicker - } - - override fun getName(): String { - return "backgroundPaint" - } - } - _backgroundPaint = null - } - return backgroundPaint - } - - fun getForegroundPaint(): Paint? { - return if (null == foregroundPaint) _foregroundPaint else foregroundPaint!!.get() - } - - fun setForegroundPaint(foregroundPaint: Paint) { - if (null == this.foregroundPaint) { - _foregroundPaint = foregroundPaint - redraw() - } else { - this.foregroundPaint!!.set(foregroundPaint) - } - } - - fun foregroundPaintProperty(): ObjectProperty? { - if (null == foregroundPaint) { - foregroundPaint = object : ObjectPropertyBase(_foregroundPaint) { - override fun invalidated() { - redraw() - } - - override fun getBean(): Any { - return this@AnglePicker - } - - override fun getName(): String { - return "foregroundPaint" - } - } - _foregroundPaint = null - } - return foregroundPaint - } - - fun getIndicatorPaint(): Paint? { - return if (null == indicatorPaint) _indicatorPaint else indicatorPaint!!.get() - } - - fun setIndicatorPaint(indicatorPaint: Paint) { - if (null == this.indicatorPaint) { - _indicatorPaint = indicatorPaint - redraw() - } else { - this.indicatorPaint!!.set(indicatorPaint) - } - } - - fun indicatorPaintProperty(): ObjectProperty? { - if (null == indicatorPaint) { - indicatorPaint = object : ObjectPropertyBase(_indicatorPaint) { - override fun invalidated() { - redraw() - } - - override fun getBean(): Any { - return this@AnglePicker - } - - override fun getName(): String { - return "indicatorPaint" - } - } - _indicatorPaint = null - } - return indicatorPaint - } - - fun getTextPaint(): Paint? { - return if (null == textPaint) _textPaint else textPaint!!.get() - } - - fun setTextPaint(textPaint: Paint) { - if (null == this.textPaint) { - _textPaint = textPaint - redraw() - } else { - this.textPaint!!.set(textPaint) - } - } - - fun textPaintProperty(): ObjectProperty? { - if (null == textPaint) { - textPaint = object : ObjectPropertyBase(_textPaint) { - override fun invalidated() { - redraw() - } - - override fun getBean(): Any { - return this@AnglePicker - } - - override fun getName(): String { - return "textPaint" - } - } - _textPaint = null - } - return textPaint - } - - private fun updateTextField() { - val text = textField!!.text.replace("\\n", "").replace("\u00b0", "") - if (!text.matches(Regex("\\d{0,3}([.]\\d?)?"))) { - return - } - if (text.isNotEmpty()) { - setAngle(textField!!.text.toDouble()) - } - textField!!.isVisible = false - textField!!.isManaged = false - } - - private fun getAngleFromXY( - x: Double, - y: Double, - centerX: Double, - centerY: Double, - angleOffset: Double - ): Double { - // For ANGLE_OFFSET = 0 -> Angle of 0 is at 3 o'clock - // For ANGLE_OFFSET = 90 ->Angle of 0 is at 12 o'clock - val deltaX = x - centerX - val deltaY = y - centerY - val radius = sqrt(deltaX * deltaX + deltaY * deltaY) - val nx = deltaX / radius - val ny = deltaY / radius - var theta = atan2(ny, nx) - theta = if (theta.compareTo(0.0) >= 0) Math.toDegrees(theta) else Math.toDegrees(theta) + 360.0 - return (theta + angleOffset) % 360 - } - - // ******************** Resizing ****************************************** - private fun resize() { - widthProp = getWidth() - insets.left - insets.right - heightProp = getHeight() - insets.top - insets.bottom - size = if (widthProp < heightProp) widthProp else heightProp - if (widthProp > 0 && heightProp > 0) { - pane!!.setMaxSize(size, size) - pane!!.setPrefSize(size, size) - pane!!.relocate((getWidth() - size) * 0.5, (getHeight() - size) * 0.5) - rotate.pivotX = indicator!!.x - size * 0.27777778 - rotate.pivotY = indicator!!.height * 0.5 - innerShadow!!.radius = size * 0.0212766 - innerShadow!!.offsetY = size * 0.0106383 - background!!.radius = size * 0.5 - background!!.relocate(0.0, 0.0) - foreground!!.radius = size * 0.4787234 - foreground!!.relocate(size * 0.0212766, size * 0.0212766) - indicator!!.width = size * 0.20 - indicator!!.height = size * 0.01587302 - indicator!!.relocate(size * 0.77777778, (size - indicator!!.height) * 0.5) - text!!.font = Font.font(size * 0.19148936) - text!!.relocate((size - text!!.layoutBounds.width) * 0.5, (size - text!!.layoutBounds.height) * 0.5) - textField!!.prefWidth = size * 0.6 - textField!!.prefHeight = size * 0.22 - textField!!.font = Font.font(size * 0.19148936) - textField!!.relocate((size - textField!!.prefWidth) * 0.5, (size - textField!!.prefHeight) * 0.5) - redraw() - } - } - - private fun redraw() { - background!!.fill = getBackgroundPaint() - foreground!!.fill = getForegroundPaint() - indicator!!.fill = getIndicatorPaint() - text!!.fill = getTextPaint() - } - - companion object { - private const val PREFERRED_WIDTH = 63.0 - private const val PREFERRED_HEIGHT = 63.0 - private const val MINIMUM_WIDTH = 20.0 - private const val MINIMUM_HEIGHT = 20.0 - private const val MAXIMUM_WIDTH = 1024.0 - private const val MAXIMUM_HEIGHT = 1024.0 - } - - // ******************** Constructors ************************************** - init { - - stylesheets.add(Thread.currentThread().contextClassLoader.getResource("style/angle-picker.css")?.toExternalForm()) - rotate = Rotate() - _angle = 0.0 - _backgroundPaint = Color.rgb(32, 32, 32) - _foregroundPaint = LinearGradient( - 0.0, .0, .0, 1.0, true, CycleMethod.NO_CYCLE, - Stop(0.0, Color.rgb(61, 61, 61)), - Stop(0.5, Color.rgb(50, 50, 50)), - Stop(1.0, Color.rgb(42, 42, 42)) - ) - _indicatorPaint = Color.rgb(159, 159, 159) - _textPaint = Color.rgb(230, 230, 230) - converter = object : StringConverter() { - override fun toString(number: Double?): String { - return String.format(Locale.US, "%.1f", 360 - number!!) - } - - override fun fromString(string: String?): Double? { - if (string.isNullOrBlank()) return null - val numberString = string.replace("\\n", "").replace("\u00b0", "") - return if (numberString.matches(Regex("\\d{0,3}([.]\\d?)?"))) { - 360 - java.lang.Double.valueOf(numberString) - } else { - 0.0 - } - } - } - mouseFilter = EventHandler { evt: MouseEvent -> - val type = evt.eventType - if (type == MouseEvent.MOUSE_DRAGGED) { - val angle = getAngleFromXY(evt.x + size * 0.5, evt.y + size * 0.5, size * 0.5, size * 0.5, 0.0) - setAngle(angle) - } else if (type == MouseEvent.MOUSE_CLICKED) { - val clicks = evt.clickCount - if (clicks == 2) { - textField!!.isManaged = true - textField!!.isVisible = true - textField!!.isFocusTraversable = true - } - } - } - initGraphics() - registerListeners() - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/angles/Rotation.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/angles/Rotation.kt deleted file mode 100644 index da9b8df..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/angles/Rotation.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.thepyprogrammer.fxtools.angles - -import javafx.beans.property.DoubleProperty -import javafx.beans.property.SimpleDoubleProperty - - -data class Rotation( - var rotateProperty: DoubleProperty = SimpleDoubleProperty(0.0) -) : Cloneable { - - constructor(angle: Double): this() { - rotate = angle % 360 - } - - private var rotate: Double - get() = rotateProperty.get() - set(other) = rotateProperty.set(360 - other) - - fun copy(): Rotation { - return Rotation(360 - rotate) - } - - public override fun clone(): Rotation { - return copy() - } - - fun get(): Double { - return rotate - } - - fun set(rotate: Double) { - this.rotate = rotate - } - - fun bind(rotate: Rotation) { - bind(rotate.rotateProperty) - } - - fun bind(property: DoubleProperty) { - rotateProperty.bind(property) - } - - fun bindBidirectional(rotate: Rotation) { - bindBidirectional(rotate.rotateProperty) - } - - fun bindBidirectional(property: DoubleProperty?) { - rotateProperty.bindBidirectional(property) - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/anim/TypingTransition.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/anim/TypingTransition.kt deleted file mode 100644 index 5f2d044..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/anim/TypingTransition.kt +++ /dev/null @@ -1,24 +0,0 @@ -package com.thepyprogrammer.fxtools.anim - -import javafx.animation.Interpolator -import javafx.animation.Transition -import javafx.scene.control.Label -import javafx.util.Duration -import kotlin.math.roundToInt - - -class TypingTransition(var label: Label, var text: String, var speed: Double = 50.0) : Transition() { - constructor(label: Label, speed: Double = 50.0) : this(label, label.text, speed) - - override fun interpolate(frac: Double) { - val length = text.length - val n = (length * frac.toFloat()).roundToInt() - label.text = text.substring(0, n) - } - - init { - label.text = "" - cycleDuration = Duration.millis(speed * text.length) - interpolator = Interpolator.LINEAR - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/autocomplete/AutoCompleteComboBoxListener.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/autocomplete/AutoCompleteComboBoxListener.kt deleted file mode 100644 index 97bcc6c..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/autocomplete/AutoCompleteComboBoxListener.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.thepyprogrammer.fxtools.autocomplete - -import javafx.collections.FXCollections -import javafx.collections.ObservableList -import javafx.event.EventHandler -import javafx.scene.control.ComboBox -import javafx.scene.input.KeyCode -import javafx.scene.input.KeyEvent -import java.util.* - - -class AutoCompleteComboBoxListener(private val comboBox: ComboBox) : EventHandler { - private val sb: StringBuilder = StringBuilder() - private val data: ObservableList = comboBox.items - private var moveCaretToPos = false - private var caretPos = 0 - - override fun handle(event: KeyEvent) { - when (event.code) { - KeyCode.UP -> { - caretPos = -1 - moveCaret(comboBox.editor.text.length) - return - } - KeyCode.DOWN -> { - if (!comboBox.isShowing) { - comboBox.show() - } - caretPos = -1 - moveCaret(comboBox.editor.text.length) - return - } - KeyCode.BACK_SPACE -> { - moveCaretToPos = true - caretPos = comboBox.editor.caretPosition - } - KeyCode.DELETE -> { - moveCaretToPos = true - caretPos = comboBox.editor.caretPosition - } - else -> {} - } - if (event.code == KeyCode.RIGHT || event.code == KeyCode.LEFT || event.isControlDown || event.code == KeyCode.HOME || event.code == KeyCode.END || event.code == KeyCode.TAB) { - return - } - val list: ObservableList = FXCollections.observableArrayList() - for (i in data.indices) { - if (data[i].toString().lowercase(Locale.getDefault()).startsWith( - comboBox - .editor.text.lowercase(Locale.getDefault()) - ) - ) list.add(data[i]) - } - val t = comboBox.editor.text - comboBox.items = list - comboBox.editor.text = t - if (!moveCaretToPos) { - caretPos = -1 - } - moveCaret(t.length) - if (!list.isEmpty()) comboBox.show() - } - - private fun moveCaret(textLength: Int) { - if (caretPos == -1) comboBox.editor.positionCaret(textLength) - else comboBox.editor.positionCaret(caretPos) - moveCaretToPos = false - } - - init { - comboBox.apply { - isEditable = true - onKeyPressed = EventHandler { hide() } - onKeyReleased = this@AutoCompleteComboBoxListener - isFocusTraversable = false - } - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/draggable/DraggableNode.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/draggable/DraggableNode.kt deleted file mode 100644 index 87bd029..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/draggable/DraggableNode.kt +++ /dev/null @@ -1,233 +0,0 @@ -package com.thepyprogrammer.fxtools.draggable - -import com.thepyprogrammer.fxtools.util.given -import com.thepyprogrammer.fxtools.resizable.ResizeMode -import javafx.event.EventHandler -import javafx.scene.Cursor -import javafx.scene.Node -import javafx.scene.input.MouseEvent -import javafx.scene.input.ScrollEvent -import javafx.scene.layout.Pane -import javafx.scene.transform.Scale -import kotlin.math.abs - -abstract class DraggableNode : Pane() { - val view: Node - - // node position - private var x = 0.0 - private var y = 0.0 - - val nodeX get() = x - val nodeY get() = y - - // mouse position - private var mousex = 0.0 - private var mousey = 0.0 - - protected var isDragging = false - private set - - var isMoveToFront = true - private var scaleTransform: Scale? = null - - var isZoomable = false - private var resizable = false - set(resizable) { - field = resizable.given { initScale() } - } - - fun initScale() { - scaleTransform = Scale(1.0, 1.0).apply { - pivotX = 0.0 - pivotY = 0.0 - pivotZ = 0.0 - transforms.add(this) - } - } - - var minScale = 0.1 - var maxScale = 10.0 - var scaleIncrement = 0.001 - private var resizeMode: ResizeMode? = null - private var RESIZE_TOP = false - private var RESIZE_LEFT = false - private var RESIZE_BOTTOM = false - private var RESIZE_RIGHT = false - abstract fun createWidget(): Node - private fun init() { - resizable.given { initScale() } - onMousePressedProperty().set(EventHandler { event: MouseEvent -> - val n: Node = this@DraggableNode - val parentScaleX = n.parent.localToSceneTransformProperty().value.mxx - val parentScaleY = n.parent.localToSceneTransformProperty().value.myy - - // record the current mouse X and Y position on Node - mousex = event.sceneX - mousey = event.sceneY - x = n.layoutX * parentScaleX - y = n.layoutY * parentScaleY - if (isMoveToFront) { - toFront() - } - }) - - //Event Listener for MouseDragged - onMouseDraggedProperty().set(EventHandler { event: MouseEvent -> - val n: Node = this@DraggableNode - val parentScaleX = n.parent.localToSceneTransformProperty().value.mxx - val parentScaleY = n.parent.localToSceneTransformProperty().value.myy - val scaleX = n.localToSceneTransformProperty().value.mxx - val scaleY = n.localToSceneTransformProperty().value.myy - - // Get the exact moved X and Y - val offsetX = event.sceneX - mousex - val offsetY = event.sceneY - mousey - if (resizeMode === ResizeMode.NONE) { - x += offsetX - y += offsetY - val scaledX = x / parentScaleX - val scaledY = y / parentScaleY - layoutX = scaledX - layoutY = scaledY - isDragging = true - } else { - if (RESIZE_TOP) { - val newHeight = (boundsInLocal.height - - offsetY / scaleY - insets.top) - y += offsetY - val scaledY = y / parentScaleY - layoutY = scaledY - prefHeight = newHeight - } - if (RESIZE_LEFT) { - val newWidth = (boundsInLocal.width - - offsetX / scaleX - insets.left) - x += offsetX - val scaledX = x / parentScaleX - layoutX = scaledX - prefWidth = newWidth - } - if (RESIZE_BOTTOM) { - val newHeight = (boundsInLocal.height - + offsetY / scaleY - - insets.bottom) - prefHeight = newHeight - } - if (RESIZE_RIGHT) { - val newWidth = (boundsInLocal.width - + offsetX / scaleX - - insets.right) - prefWidth = newWidth - } - } - - // again set current Mouse x AND y position - mousex = event.sceneX - mousey = event.sceneY - event.consume() - }) - onMouseClickedProperty().set(EventHandler { - isDragging = false - }) - onMouseMovedProperty().set(EventHandler { t: MouseEvent -> - val n: Node = this@DraggableNode - val scaleX = n.localToSceneTransformProperty().value.mxx - val scaleY = n.localToSceneTransformProperty().value.myy - val border = 10.0 - val diffMinX = abs(n.boundsInLocal.minX - t.x) - val diffMinY = abs(n.boundsInLocal.minY - t.y) - val diffMaxX = abs(n.boundsInLocal.maxX - t.x) - val diffMaxY = abs(n.boundsInLocal.maxY - t.y) - val left = diffMinX * scaleX < border - val top = diffMinY * scaleY < border - val right = diffMaxX * scaleX < border - val bottom = diffMaxY * scaleY < border - RESIZE_TOP = false - RESIZE_LEFT = false - RESIZE_BOTTOM = false - RESIZE_RIGHT = false - if (left && !top && !bottom) { - n.cursor = Cursor.W_RESIZE - resizeMode = ResizeMode.LEFT - RESIZE_LEFT = true - } else if (left && top && !bottom) { - n.cursor = Cursor.NW_RESIZE - resizeMode = ResizeMode.TOP_LEFT - RESIZE_LEFT = true - RESIZE_TOP = true - } else if (left && !top && bottom) { - n.cursor = Cursor.SW_RESIZE - resizeMode = ResizeMode.BOTTOM_LEFT - RESIZE_LEFT = true - RESIZE_BOTTOM = true - } else if (right && !top && !bottom) { - n.cursor = Cursor.E_RESIZE - resizeMode = ResizeMode.RIGHT - RESIZE_RIGHT = true - } else if (right && top && !bottom) { - n.cursor = Cursor.NE_RESIZE - resizeMode = ResizeMode.TOP_RIGHT - RESIZE_RIGHT = true - RESIZE_TOP = true - } else if (right && !top && bottom) { - n.cursor = Cursor.SE_RESIZE - resizeMode = ResizeMode.BOTTOM_RIGHT - RESIZE_RIGHT = true - RESIZE_BOTTOM = true - } else if (top && !left && !right) { - n.cursor = Cursor.N_RESIZE - resizeMode = ResizeMode.TOP - RESIZE_TOP = true - } else if (bottom && !left && !right) { - n.cursor = Cursor.S_RESIZE - resizeMode = ResizeMode.BOTTOM - RESIZE_BOTTOM = true - } else { - n.cursor = Cursor.DEFAULT - resizeMode = ResizeMode.NONE - } - if (!resizable) { - n.cursor = Cursor.DEFAULT - resizeMode = ResizeMode.NONE - RESIZE_TOP = false - RESIZE_LEFT = false - RESIZE_BOTTOM = false - RESIZE_RIGHT = false - } - }) - onScroll = EventHandler { event: ScrollEvent -> - if (!isZoomable) return@EventHandler - var scaleValue = scaleTransform!!.y + event.deltaY * scaleIncrement - scaleValue = scaleValue.coerceAtLeast(minScale) - scaleValue = scaleValue.coerceAtMost(maxScale) - scaleTransform!!.x = scaleValue - scaleTransform!!.y = scaleValue - scaleTransform!!.pivotX = 0.0 - scaleTransform!!.pivotX = 0.0 - scaleTransform!!.pivotZ = 0.0 - event.consume() - } - } - - /** - * @return the resizable - */ - override fun isResizable(): Boolean { - return resizable - } - - fun removeNode(n: Node?) = children.remove(n) - - companion object { - const val CSS_STYLE = " -fx-alignment: center; -fx-font-size: 20;" - } - - init { - view = createWidget() - children.add(view) - - init() - style = CSS_STYLE - } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/draggable/DraggableTab.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/draggable/DraggableTab.kt deleted file mode 100644 index 27a2a63..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/draggable/DraggableTab.kt +++ /dev/null @@ -1,215 +0,0 @@ -package com.thepyprogrammer.fxtools.draggable - -import com.thepyprogrammer.fxtools.point.Point -import com.thepyprogrammer.fxtools.methods.absoluteRect -import com.thepyprogrammer.fxtools.methods.hide -import com.thepyprogrammer.fxtools.methods.point -import com.thepyprogrammer.fxtools.methods.screenPoint -import com.thepyprogrammer.fxtools.resizable.addResizeListener -import com.thepyprogrammer.fxtools.util.toInt -import javafx.collections.ListChangeListener -import javafx.geometry.Point2D -import javafx.geometry.Pos -import javafx.geometry.Rectangle2D -import javafx.scene.Scene -import javafx.scene.control.Control -import javafx.scene.control.Label -import javafx.scene.control.Tab -import javafx.scene.control.TabPane -import javafx.scene.layout.StackPane -import javafx.scene.layout.VBox -import javafx.scene.paint.Color -import javafx.scene.shape.Rectangle -import javafx.scene.text.Text -import javafx.stage.Stage -import javafx.stage.StageStyle - - -/** - * A draggable tab that can optionally be detached from its tab pane and shown - * in a separate window. This can be added to any normal TabPane, however a - * TabPane with draggable tabs must *only* have DraggableTabs, normal tabs and - * DrragableTabs mixed will cause issues! - * - * - * - * @author Michael Berry - * edited by: Prannaya Gupta - */ -class DraggableTab(text: String? = "") : Tab() { - companion object { - private val tabPanes: MutableSet = HashSet() - private var markerStage: Stage = Stage() - - init { - with(markerStage) { - initStyle(StageStyle.UNDECORATED) - markerStage.scene = Scene( StackPane().apply { children.add(Rectangle(3.0, 10.0, Color.web("#555555"))) }) - } - } - } - - val label: Label = Label(text) - private val dragText: Text - private val dragStage: Stage - var detachable: Boolean = true - - var labelText: String? - get() = label.text - set(text) { - label.text = text - dragText.text = text - } - - private fun getInsertData(screenPoint: Point2D): InsertData? { - for (tabPane in tabPanes) { - val tabAbsolute = tabPane.absoluteRect - if (tabAbsolute.contains(screenPoint)) { - var tabInsertIndex = 0 - if (!tabPane.tabs.isEmpty()) { - val firstTabRect = getAbsoluteRect(tabPane.tabs[0]) - if (firstTabRect.maxY + 60 < screenPoint.y || firstTabRect.minY > screenPoint.y) return null - val lastTabRect = getAbsoluteRect(tabPane.tabs[tabPane.tabs.size - 1]) - when { - screenPoint.x < firstTabRect.minX + firstTabRect.width / 2 -> tabInsertIndex = 0 - screenPoint.x > lastTabRect.maxX - lastTabRect.width / 2 -> tabInsertIndex = tabPane.tabs.size - else -> { - for (i in 0 until tabPane.tabs.size - 1) { - val leftTab = tabPane.tabs[i] - val rightTab = tabPane.tabs[i + 1] - if (leftTab is DraggableTab && rightTab is DraggableTab) { - val leftTabRect = getAbsoluteRect(leftTab) - val rightTabRect = getAbsoluteRect(rightTab) - if (betweenX(leftTabRect, rightTabRect, screenPoint.x)) { - tabInsertIndex = i + 1 - break - } - } - } - } - } - } - return InsertData(tabInsertIndex, tabPane) - } - } - return null - } - - private fun getInsertData(screenPoint: Point): InsertData? { - return getInsertData(screenPoint.toPoint2d()) - } - - private fun getAbsoluteRect(tab: Tab): Rectangle2D { - val node: Control = (tab as DraggableTab).label - return node.absoluteRect - } - - private fun betweenX(r1: Rectangle2D, r2: Rectangle2D, xPoint: Double): Boolean { - val lowerBound = r1.minX + r1.width / 2 - val upperBound = r2.maxX - r2.width / 2 - return xPoint in lowerBound..upperBound - } - - fun setStyles(style: String?) { - setStyle(style) - label.style = style - } - - fun setStyleClasses(styleclass: String?) { - styleClass.add(styleclass) - label.styleClass.add(styleclass) - } - - private data class InsertData(val index: Int, val insertPane: TabPane) - - /** - * Create a new draggable tab. This can be added to any normal TabPane, - * however a TabPane with draggable tabs must *only* have DraggableTabs, - * normal tabs and DraggableTabs mixed will cause issues! - * - * - * - * @param text the text to appear on the tag label. - */ - init { - graphic = label - detachable = true - dragStage = Stage().apply { - initStyle(StageStyle.UNDECORATED) - val dragStagePane = StackPane().apply { - style = "-fx-background-color:#DDDDDD;" - } - dragText = Text(text) - - StackPane.setAlignment(dragText, Pos.CENTER) - dragStagePane.children.add(dragText) - scene = Scene(dragStagePane) - label.setOnMouseDragged { - width = label.width + 10 - height = label.height + 10 - point = it.screenPoint - show() - tabPanes.add(tabPane) - getInsertData(point).apply { - if(this == null || insertPane.tabs.isEmpty()) markerStage.hide() - else { - val rect = getAbsoluteRect(insertPane.tabs[index - (index == insertPane.tabs.size).toInt()]) - markerStage.apply { - point = Point( - if(index == insertPane.tabs.size) rect.maxX + 13 else rect.minX, - rect.maxY + 10 - ) - show() - } - } - - } - - } - - label.setOnMouseReleased EventHandler@ { - listOf(markerStage, this).hide() - if(!it.isStillSincePress) { - val pt = it.screenPoint - tabPane.apply oldTabPane@ { - val oldIndex = tabs.indexOf(this@DraggableTab) - tabPanes.add(this) - val insertData = getInsertData(pt).apply insertData@ { - if(this != null) { - if(this@oldTabPane !== this.insertPane && this@oldTabPane.tabs.size != 1) { - this@oldTabPane.tabs.remove(this@DraggableTab) - var addIndex = index - addIndex = maxOf(addIndex - (oldIndex < addIndex && this@oldTabPane === this@insertData.insertPane).toInt(), insertPane.tabs.size) - insertPane.tabs.add(addIndex, this@DraggableTab) - insertPane.selectionModelProperty().get().select(addIndex) - } - return@EventHandler - } - if(!detachable) return@EventHandler - - val newStage = Stage().apply newStage@ { - val pane = TabPane() - tabPanes.add(pane) - setOnHiding { tabPanes.remove(pane) } - tabPane.tabs.remove(this@DraggableTab) - pane.tabs.add(this@DraggableTab) - pane.tabs.addListener(ListChangeListener { - if (pane.tabs.isEmpty()) this@newStage.hide() - }) - scene = Scene(pane) - minHeight = (content as VBox).minHeight - minWidth = (content as VBox).minWidth - addResizeListener(this) - point = pt - title = label.text - show() - pane.requestLayout() - pane.requestFocus() - } - } - } - } - } - } - } -} diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/io/File.java b/src/main/kotlin/com/thepyprogrammer/fxtools/io/File.java deleted file mode 100644 index 1d7b8bc..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/io/File.java +++ /dev/null @@ -1,1098 +0,0 @@ -package com.thepyprogrammer.fxtools.io; - -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.PrintWriter; -import java.net.*; -import java.nio.charset.StandardCharsets; -import java.nio.file.*; -import java.util.*; - -import javafx.stage.*; - -public class File extends java.io.File implements Cloneable, AutoCloseable { - public PrintWriter out = null; - public Scanner in = null; - public FileOutputStream outstream = null; - private char type; - - // JavaFX Integration - private static FileChooser fc = new FileChooser(); - private static DirectoryChooser dc = new DirectoryChooser(); - - // Ensuring only a singular in, out exists per file - private static HashMap scanners = new HashMap<>(); - private static HashMap appendwriters = new HashMap<>(); - private static HashMap overwriters = new HashMap<>(); - private static HashMap writeoutstreams = new HashMap<>(); - private static HashMap appendoutstreams = new HashMap<>(); - - // Extension Filters - public static FileChooser.ExtensionFilter PNG = new FileChooser.ExtensionFilter("Portable Newtwork Graphics (PNG) Files", "*.png"), - ICO = new FileChooser.ExtensionFilter("ICO Files", "*.ico"), - JPG = new FileChooser.ExtensionFilter("JPEG Files", "*.jpg", "*.jpeg"), - GIF = new FileChooser.ExtensionFilter("Graphics Interchange Format (GIF) Files", "*.gif"), - JFIF = new FileChooser.ExtensionFilter("JPEG File Interchange Format (JFIF) Files", "*.jfif"), - PDF = new FileChooser.ExtensionFilter("Portable Document Format (PDF) Files", "*.pdf"), - HTML = new FileChooser.ExtensionFilter("HyperText Markup Language (HTML) Files", "*.htm", "*.html"), - XHTML = new FileChooser.ExtensionFilter("Extensible HyperText Markup Language (XHTML) Files", "*.xhtml"), - PHTML = new FileChooser.ExtensionFilter("PHP HyperText Markup Language (PHTML) Files", "*.phtml"), - XML = new FileChooser.ExtensionFilter("Extensible Markup Language (XML) Files", "*.xml"), - FXML = new FileChooser.ExtensionFilter("JavaFX XML Files", "*.fxml"), - PHP = new FileChooser.ExtensionFilter("PHP Files", "*.php", "*.php5"), - CSS = new FileChooser.ExtensionFilter("Cascading Style Sheets (CSS) Files", "*.css"), - JS = new FileChooser.ExtensionFilter("JavaScript (JS) Files", "*.js", "*.jsm"), - JSON = new FileChooser.ExtensionFilter("JSON Files", "*.json"), - JAVA = new FileChooser.ExtensionFilter("Java Files", "*.java"), - KOTLIN = new FileChooser.ExtensionFilter("Kotlin Files", "*.kt"), - SCALA = new FileChooser.ExtensionFilter("Scala Files", "*.scala"), - PYTHON = new FileChooser.ExtensionFilter("Python Files", "*.py", "*.pyc", "*.pyw"), - RUBY = new FileChooser.ExtensionFilter("Ruby Files", "*.rb", "*.rbw", "*.rake", "*.rbx"), - C = new FileChooser.ExtensionFilter("C Files", "*.c", "*.h", "*.idc"), - CPP = new FileChooser.ExtensionFilter("C++ Files", "*.cpp", "*.hpp", "*.c++", "*.h++", "*.cc", "*.hh", "*.cxx", "*.hxx", "*.C", "*.H", "*.cp", "*.cpp"), - CSHARP = new FileChooser.ExtensionFilter("C# Files", "*.cs"), - INO = new FileChooser.ExtensionFilter("Arduino Files", "*.ino"), - VB = new FileChooser.ExtensionFilter("Visual Basic Files", "*.vbs"), - GO = new FileChooser.ExtensionFilter("Golang Files", "*.go"), - RUST = new FileChooser.ExtensionFilter("Rust Files", "*.rs"), - SWIFT = new FileChooser.ExtensionFilter("Swift Files", "*.swift"), - TEXT = new FileChooser.ExtensionFilter("Text Files", "*.txt", "*.text"), - MD = new FileChooser.ExtensionFilter("Markdown (MD) Files", "*.md"), - README = new FileChooser.ExtensionFilter("README Files", "*.README"), - BIBTEX = new FileChooser.ExtensionFilter("BibTeX Files", "*.bib"), - LATEX = new FileChooser.ExtensionFilter("LaTeX Files", "*.tex", "*.aux", "*.toc"), - SQL = new FileChooser.ExtensionFilter("Structured Query Language (SQL) Files", "*.sql"), - MP4 = new FileChooser.ExtensionFilter("MP4 Files", "*.mp4"); - - // Setting Initial Directory - static { - fc.setInitialDirectory(new java.io.File(System.getProperty("user.dir"))); - dc.setInitialDirectory(new java.io.File(System.getProperty("user.dir"))); - } - - - // Constructors - public File(String filename) { - this(filename, 'r'); - } - - public File(String filename, char type) { - super(filename); - try { - if (!exists()) createNewFile(); - } catch(IOException ex) {} - this.type = type; - if(!isDirectory()) { - if ((type == 'r' || type == 'w' || type == 'a') && Files.isReadable(toPath())) { - if(scanners.containsKey(getAbsolutePath())) in = scanners.get(getAbsolutePath()); - else { - try { in = new Scanner(this); scanners.put(getAbsolutePath(), in); } catch (IOException e) {} - } - } - if (type == 'w' && Files.isWritable(toPath())) { - if(writeoutstreams.containsKey(getAbsolutePath()) && overwriters.containsKey(getAbsolutePath())) { - outstream = writeoutstreams.get(getAbsolutePath()); - out = overwriters.get(getAbsolutePath()); - } else { - try { - outstream = new FileOutputStream(filename); - out = new PrintWriter(outstream); - writeoutstreams.put(getAbsolutePath(), outstream); - overwriters.put(getAbsolutePath(), out); - } catch (IOException e) {} - } - } else if (type == 'a' && Files.isWritable(toPath())) { - if(appendoutstreams.containsKey(getAbsolutePath()) && appendwriters.containsKey(getAbsolutePath())) { - outstream = appendoutstreams.get(getAbsolutePath()); - out = appendwriters.get(getAbsolutePath()); - } else { - try { - outstream = new FileOutputStream(filename, true); - out = new PrintWriter(outstream); - appendoutstreams.put(getAbsolutePath(), outstream); - appendwriters.put(getAbsolutePath(), out); - } catch (IOException e) {} - } - } - } - } - - public File() { this('r'); } - public File(char type) { this(getFile(), type); } - - public File(java.io.File file) { this(file.getAbsolutePath()); } - public File(java.io.File file, char type) { this(file.getAbsolutePath(), type); } - - public File(URL url) { this(url.getFile()); } - public File(URL url, char type) { this(url.getFile(), type); } - - public File(URI uri) throws MalformedURLException { this(uri.toURL().getFile()); } - public File(URI uri, char type) throws MalformedURLException { this(uri.toURL().getFile(), type); } - - public static File read(String filename) { return new File(filename); } - public static File read(File file) { return new File(file); } - public static File read(java.io.File file) { return new File(file); } - public static File read(URI uri) throws MalformedURLException { return new File(uri); } - public static File read(URL url) { return new File(url); } - - public static File write(String filename) { return new File(filename, 'w'); } - public static File write(File file) { return new File(file, 'w'); } - public static File write(java.io.File file) { return new File(file, 'w'); } - public static File write(URI uri) throws MalformedURLException { return new File(uri, 'w'); } - public static File write(URL url) { return new File(url, 'w'); } - - public static void writeTo(java.io.File file, String text) throws IOException { - PrintWriter pw = new PrintWriter(file); - pw.println(text); - pw.close(); - } - - public static File append(String filename) { return new File(filename, 'a'); } - public static File append(File file) { return new File(file, 'a'); } - public static File append(java.io.File file) { return new File(file, 'a'); } - public static File append(URI uri) throws MalformedURLException { return new File(uri, 'a'); } - public static File append(URL url) { return new File(url, 'a'); } - - - // Methods - public String getName() { return getAbsolutePath(); } - public File getAbsoluteFile() { return new File(super.getAbsoluteFile()); } - public File getParentFile() { return new File(super.getParentFile()); } - public File getCanonicalFile() { - try { return new File(super.getCanonicalFile()); } catch (IOException e) { return null; } - } - - public boolean rename(File dest) { return renameTo(dest); } - - public File[] listFiles() { return convert(listFiles()); } - - public String getRelativePath() { return new File(System.getProperty("user.dir")).relativize(this); } - public String getRelativePath(String directory) { return new File(directory).relativize(this); } - public String getRelativePath(File directory) { return directory.relativize(this); } - public String getRelativePath(java.io.File directory) { return directory.toURI().relativize(this.toURI()).getPath(); } - public String getRelativePath(URI directory) { return directory.relativize(this.toURI()).getPath(); } - public String getRelativePath(URL directory) throws URISyntaxException { return directory.toURI().relativize(this.toURI()).getPath(); } - - public static File[] convert(java.io.File ...files) { return convert(Arrays.asList(files)); } - - public static File[] convert(Collection files) { - File[] rearr = new File[files.size()]; - int i = 0; - for(java.io.File file: files) { - rearr[i] = new File(file); - i++; - } - return rearr; - } - - public static File getFile(Stage stage) { return convert(fc.showOpenDialog(stage))[0]; } - public static File[] getFiles(Stage stage) { return convert(fc.showOpenMultipleDialog(stage)); } - - public static File getFile() { return getFile(new Stage()); } - public static File[] getFiles() { return getFiles(new Stage()); } - - public static File getFile(Stage stage, FileChooser.ExtensionFilter...extensions) { return getFile(stage, Arrays.asList(extensions)); } - - public static File getFile(Stage stage, Collection extensions) { - fc.getExtensionFilters().addAll(extensions); - File file = getFile(stage); - fc.getExtensionFilters().clear(); - return file; - } - - public static File getFile(FileChooser.ExtensionFilter...extensions) { return getFile(new Stage(), extensions); } - public static File getFile(Collection extensions) { return getFile(new Stage(), extensions); } - - public static File[] getFiles(Stage stage, FileChooser.ExtensionFilter...extensions) { return getFiles(stage, Arrays.asList(extensions)); } - - public static File[] getFiles(Stage stage, Collection extensions) { - fc.getExtensionFilters().addAll(extensions); - File[] files = getFiles(stage); - fc.getExtensionFilters().clear(); - return files; - } - - public static File[] getFiles(FileChooser.ExtensionFilter...extensions) { return getFiles(new Stage(), extensions); } - public static File[] getFiles(Collection extensions) { return getFiles(new Stage(), extensions); } - - - public static File getPNG(Stage stage) { return getFile(stage, PNG); } - public static File[] getPNGs(Stage stage) { return getFiles(stage, PNG); } - public static File getPNG() { return getFile(PNG); } - public static File[] getPNGs() { return getFiles(PNG); } - - - public static File getMP4(Stage stage) { return getFile(stage, MP4); } - public static File[] getMP4s(Stage stage) { return getFiles(stage, MP4); } - public static File getMP4() { return getFile(MP4); } - public static File[] getMP4s() { return getFiles(MP4); } - - - public static File getJPG(Stage stage) { return getFile(stage, JPG); } - public static File[] getJPGs(Stage stage) { return getFiles(stage, JPG); } - public static File getJPG() { return getFile(JPG); } - public static File[] getJPGs() { return getFiles(JPG); } - - - public static File getICO(Stage stage) { return getFile(stage, ICO); } - public static File[] getICOs(Stage stage) { return getFiles(stage, ICO); } - public static File getICO() { return getFile(ICO); } - public static File[] getICOs() { return getFiles(ICO); } - - - public static File getGIF(Stage stage) { return getFile(stage, GIF); } - public static File[] getGIFs(Stage stage) { return getFiles(stage, GIF); } - public static File getGIF() { return getFile(GIF); } - public static File[] getGIFs() { return getFiles(GIF); } - - - public static File getJFIF(Stage stage) { return getFile(stage, JFIF); } - public static File[] getJFIFs(Stage stage) { return getFiles(stage, JFIF); } - public static File getJFIF() { return getFile(JFIF); } - public static File[] getJFIFs() { return getFiles(JFIF); } - - - public static File getPDF(Stage stage) { return getFile(stage, PDF); } - public static File[] getPDFs(Stage stage) { return getFiles(stage, PDF);} - public static File getPDF() { return getFile(PDF); } - public static File[] getPDFs() { return getFiles(PDF);} - - - public static File getHTML(Stage stage) { return getFile(stage, HTML); } - public static File[] getHTMLs(Stage stage) { return getFiles(stage, HTML);} - public static File getHTML() { return getFile(HTML); } - public static File[] getHTMLs() { return getFiles(HTML);} - - - public static File getXHTML(Stage stage) { return getFile(stage, XHTML); } - public static File[] getXHTMLs(Stage stage) { return getFiles(stage, XHTML);} - public static File getXHTML() { return getFile(XHTML); } - public static File[] getXHTMLs() { return getFiles(XHTML);} - - - public static File getPHTML(Stage stage) { return getFile(stage, PHTML); } - public static File[] getPHTMLs(Stage stage) { return getFiles(stage, PHTML);} - public static File getPHTML() { return getFile(PHTML); } - public static File[] getPHTMLs() { return getFiles(PHTML);} - - - public static File getXML(Stage stage) { return getFile(stage, XML); } - public static File[] getXMLs(Stage stage) { return getFiles(stage, XML);} - public static File getXML() { return getFile(XML); } - public static File[] getXMLs() { return getFiles(XML);} - - - public static File getFXML(Stage stage) { return getFile(stage, FXML); } - public static File[] getFXMLs(Stage stage) { return getFiles(stage, FXML);} - public static File getFXML() { return getFile(FXML); } - public static File[] getFXMLs() { return getFiles(FXML);} - - - public static File getPHP(Stage stage) { return getFile(stage, PHP); } - public static File[] getPHPs(Stage stage) { return getFiles(stage, PHP);} - - - public static File getCSS(Stage stage) { return getFile(stage, CSS); } - public static File[] getCSSs(Stage stage) { return getFiles(stage, CSS);} - - - public static File getJS(Stage stage) { return getFile(stage, JS); } - public static File[] getJSs(Stage stage) { return getFiles(stage, JS);} - - - public static File getJSON(Stage stage) { return getFile(stage, JSON); } - public static File[] getJSONs(Stage stage) { return getFiles(stage, JSON);} - - - public static File getJAVA(Stage stage) { return getFile(stage, JAVA); } - public static File[] getJAVAs(Stage stage) { return getFiles(stage, JAVA);} - - - public static File getKOTLIN(Stage stage) { return getFile(stage, KOTLIN); } - public static File[] getKOTLINs(Stage stage) { return getFiles(stage, KOTLIN);} - - - public static File getSCALA(Stage stage) { return getFile(stage, SCALA); } - public static File[] getSCALAs(Stage stage) { return getFiles(stage, SCALA);} - - - public static File getPYTHON(Stage stage) { return getFile(stage, PYTHON); } - public static File[] getPYTHONs(Stage stage) { return getFiles(stage, PYTHON);} - - - public static File getRUBY(Stage stage) { return getFile(stage, RUBY); } - public static File[] getRUBYs(Stage stage) { return getFiles(stage, RUBY);} - - - public static File getC(Stage stage) { return getFile(stage, C); } - public static File[] getCs(Stage stage) { return getFiles(stage, C);} - - - public static File getCPP(Stage stage) { return getFile(stage, CPP); } - public static File[] getCPPs(Stage stage) { return getFiles(stage, CPP);} - - - public static File getCSHARP(Stage stage) { return getFile(stage, CSHARP); } - public static File[] getCSHARPs(Stage stage) { return getFiles(stage, CSHARP);} - - - public static File getINO(Stage stage) { return getFile(stage, INO); } - public static File[] getINOs(Stage stage) { return getFiles(stage, INO);} - - - public static File getVB(Stage stage) { return getFile(stage, VB); } - public static File[] getVBs(Stage stage) { return getFiles(stage, VB);} - - - public static File getGO(Stage stage) { return getFile(stage, GO); } - public static File[] getGOs(Stage stage) { return getFiles(stage, GO);} - - - public static File getRUST(Stage stage) { return getFile(stage, RUST); } - public static File[] getRUSTs(Stage stage) { return getFiles(stage, RUST);} - - - public static File getSWIFT(Stage stage) { return getFile(stage, SWIFT); } - public static File[] getSWIFTs(Stage stage) { return getFiles(stage, SWIFT);} - - - public static File getTEXT(Stage stage) { return getFile(stage, TEXT); } - public static File[] getTEXTs(Stage stage) { return getFiles(stage, TEXT);} - - - public static File getMD(Stage stage) { return getFile(stage, MD); } - public static File[] getMDs(Stage stage) { return getFiles(stage, MD);} - - - public static File getREADME(Stage stage) { return getFile(stage, README); } - public static File[] getREADMEs(Stage stage) { return getFiles(stage, README);} - - - public static File getBIBTEX(Stage stage) { return getFile(stage, BIBTEX); } - public static File[] getBIBTEXs(Stage stage) { return getFiles(stage, BIBTEX);} - - - public static File getLATEX(Stage stage) { return getFile(stage, LATEX); } - public static File[] getLATEXs(Stage stage) { return getFiles(stage, LATEX);} - - - public static File getSQL(Stage stage) { return getFile(stage, SQL); } - public static File[] getSQLs(Stage stage) { return getFiles(stage, SQL);} - - public static File getPHP() { return getFile(PHP); } - public static File[] getPHPs() { return getFiles(PHP);} - - - public static File getCSS() { return getFile(CSS); } - public static File[] getCSSs() { return getFiles(CSS);} - - - public static File getJS() { return getFile(JS); } - public static File[] getJSs() { return getFiles(JS);} - - - public static File getJSON() { return getFile(JSON); } - public static File[] getJSONs() { return getFiles(JSON);} - - - public static File getJAVA() { return getFile(JAVA); } - public static File[] getJAVAs() { return getFiles(JAVA);} - - - public static File getKOTLIN() { return getFile(KOTLIN); } - public static File[] getKOTLINs() { return getFiles(KOTLIN);} - - - public static File getSCALA() { return getFile(SCALA); } - public static File[] getSCALAs() { return getFiles(SCALA);} - - - public static File getPYTHON() { return getFile(PYTHON); } - public static File[] getPYTHONs() { return getFiles(PYTHON);} - - - public static File getRUBY() { return getFile(RUBY); } - public static File[] getRUBYs() { return getFiles(RUBY);} - - - public static File getC() { return getFile(C); } - public static File[] getCs() { return getFiles(C);} - - - public static File getCPP() { return getFile(CPP); } - public static File[] getCPPs() { return getFiles(CPP);} - - - public static File getCSHARP() { return getFile(CSHARP); } - public static File[] getCSHARPs() { return getFiles(CSHARP);} - - - public static File getINO() { return getFile(INO); } - public static File[] getINOs() { return getFiles(INO);} - - - public static File getVB() { return getFile(VB); } - public static File[] getVBs() { return getFiles(VB);} - - - public static File getGO() { return getFile(GO); } - public static File[] getGOs() { return getFiles(GO);} - - - public static File getRUST() { return getFile(RUST); } - public static File[] getRUSTs() { return getFiles(RUST);} - - - public static File getSWIFT() { return getFile(SWIFT); } - public static File[] getSWIFTs() { return getFiles(SWIFT);} - - - public static File getTEXT() { return getFile(TEXT); } - public static File[] getTEXTs() { return getFiles(TEXT);} - - - public static File getMD() { return getFile(MD); } - public static File[] getMDs() { return getFiles(MD);} - - - public static File getREADME() { return getFile(README); } - public static File[] getREADMEs() { return getFiles(README);} - - - public static File getBIBTEX() { return getFile(BIBTEX); } - public static File[] getBIBTEXs() { return getFiles(BIBTEX);} - - - public static File getLATEX() { return getFile(LATEX); } - public static File[] getLATEXs() { return getFiles(LATEX);} - - - public static File getSQL() { return getFile(SQL); } - public static File[] getSQLs() { return getFiles(SQL);} - - public static File openFile(Stage stage) { return convert(fc.showOpenDialog(stage))[0]; } - public static File[] openFiles(Stage stage) { return convert(fc.showOpenMultipleDialog(stage)); } - - public static File openFile() { return openFile(new Stage()); } - public static File[] openFiles() { return openFiles(new Stage()); } - - public static File openFile(Stage stage, FileChooser.ExtensionFilter...extensions) { return openFile(stage, Arrays.asList(extensions)); } - - public static File openFile(Stage stage, Collection extensions) { - fc.getExtensionFilters().addAll(extensions); - File file = openFile(stage); - fc.getExtensionFilters().clear(); - return file; - } - - public static File openFile(FileChooser.ExtensionFilter...extensions) { return openFile(new Stage(), extensions); } - public static File openFile(Collection extensions) { return openFile(new Stage(), extensions); } - - public static File[] openFiles(Stage stage, FileChooser.ExtensionFilter...extensions) { return openFiles(stage, Arrays.asList(extensions)); } - - public static File[] openFiles(Stage stage, Collection extensions) { - fc.getExtensionFilters().addAll(extensions); - File[] files = openFiles(stage); - fc.getExtensionFilters().clear(); - return files; - } - - public static File[] openFiles(FileChooser.ExtensionFilter...extensions) { return openFiles(new Stage(), extensions); } - public static File[] openFiles(Collection extensions) { return openFiles(new Stage(), extensions); } - - - public static File openPNG(Stage stage) { return openFile(stage, PNG); } - public static File[] openPNGs(Stage stage) { return openFiles(stage, PNG); } - public static File openPNG() { return openFile(PNG); } - public static File[] openPNGs() { return openFiles(PNG); } - - - public static File openJPG(Stage stage) { return openFile(stage, JPG); } - public static File[] openJPGs(Stage stage) { return openFiles(stage, JPG); } - public static File openJPG() { return openFile(JPG); } - public static File[] openJPGs() { return openFiles(JPG); } - - - public static File openICO(Stage stage) { return openFile(stage, ICO); } - public static File[] openICOs(Stage stage) { return openFiles(stage, ICO); } - public static File openICO() { return openFile(ICO); } - public static File[] openICOs() { return openFiles(ICO); } - - - public static File openGIF(Stage stage) { return openFile(stage, GIF); } - public static File[] openGIFs(Stage stage) { return openFiles(stage, GIF); } - public static File openGIF() { return openFile(GIF); } - public static File[] openGIFs() { return openFiles(GIF); } - - - public static File openJFIF(Stage stage) { return openFile(stage, JFIF); } - public static File[] openJFIFs(Stage stage) { return openFiles(stage, JFIF); } - public static File openJFIF() { return openFile(JFIF); } - public static File[] openJFIFs() { return openFiles(JFIF); } - - - public static File openPDF(Stage stage) { return openFile(stage, PDF); } - public static File[] openPDFs(Stage stage) { return openFiles(stage, PDF);} - public static File openPDF() { return openFile(PDF); } - public static File[] openPDFs() { return openFiles(PDF);} - - - public static File openHTML(Stage stage) { return openFile(stage, HTML); } - public static File[] openHTMLs(Stage stage) { return openFiles(stage, HTML);} - public static File openHTML() { return openFile(HTML); } - public static File[] openHTMLs() { return openFiles(HTML);} - - - public static File openXHTML(Stage stage) { return openFile(stage, XHTML); } - public static File[] openXHTMLs(Stage stage) { return openFiles(stage, XHTML);} - public static File openXHTML() { return openFile(XHTML); } - public static File[] openXHTMLs() { return openFiles(XHTML);} - - - public static File openPHTML(Stage stage) { return openFile(stage, PHTML); } - public static File[] openPHTMLs(Stage stage) { return openFiles(stage, PHTML);} - public static File openPHTML() { return openFile(PHTML); } - public static File[] openPHTMLs() { return openFiles(PHTML);} - - - public static File openXML(Stage stage) { return openFile(stage, XML); } - public static File[] openXMLs(Stage stage) { return openFiles(stage, XML);} - public static File openXML() { return openFile(XML); } - public static File[] openXMLs() { return openFiles(XML);} - - - public static File openFXML(Stage stage) { return openFile(stage, FXML); } - public static File[] openFXMLs(Stage stage) { return openFiles(stage, FXML);} - public static File openFXML() { return openFile(FXML); } - public static File[] openFXMLs() { return openFiles(FXML);} - - - public static File openPHP(Stage stage) { return openFile(stage, PHP); } - public static File[] openPHPs(Stage stage) { return openFiles(stage, PHP);} - - - public static File openCSS(Stage stage) { return openFile(stage, CSS); } - public static File[] openCSSs(Stage stage) { return openFiles(stage, CSS);} - - - public static File openJS(Stage stage) { return openFile(stage, JS); } - public static File[] openJSs(Stage stage) { return openFiles(stage, JS);} - - - public static File openJSON(Stage stage) { return openFile(stage, JSON); } - public static File[] openJSONs(Stage stage) { return openFiles(stage, JSON);} - - - public static File openJAVA(Stage stage) { return openFile(stage, JAVA); } - public static File[] openJAVAs(Stage stage) { return openFiles(stage, JAVA);} - - - public static File openKOTLIN(Stage stage) { return openFile(stage, KOTLIN); } - public static File[] openKOTLINs(Stage stage) { return openFiles(stage, KOTLIN);} - - - public static File openSCALA(Stage stage) { return openFile(stage, SCALA); } - public static File[] openSCALAs(Stage stage) { return openFiles(stage, SCALA);} - - - public static File openPYTHON(Stage stage) { return openFile(stage, PYTHON); } - public static File[] openPYTHONs(Stage stage) { return openFiles(stage, PYTHON);} - - - public static File openRUBY(Stage stage) { return openFile(stage, RUBY); } - public static File[] openRUBYs(Stage stage) { return openFiles(stage, RUBY);} - - - public static File openC(Stage stage) { return openFile(stage, C); } - public static File[] openCs(Stage stage) { return openFiles(stage, C);} - - - public static File openCPP(Stage stage) { return openFile(stage, CPP); } - public static File[] openCPPs(Stage stage) { return openFiles(stage, CPP);} - - - public static File openCSHARP(Stage stage) { return openFile(stage, CSHARP); } - public static File[] openCSHARPs(Stage stage) { return openFiles(stage, CSHARP);} - - - public static File openINO(Stage stage) { return openFile(stage, INO); } - public static File[] openINOs(Stage stage) { return openFiles(stage, INO);} - - - public static File openVB(Stage stage) { return openFile(stage, VB); } - public static File[] openVBs(Stage stage) { return openFiles(stage, VB);} - - - public static File openGO(Stage stage) { return openFile(stage, GO); } - public static File[] openGOs(Stage stage) { return openFiles(stage, GO);} - - - public static File openRUST(Stage stage) { return openFile(stage, RUST); } - public static File[] openRUSTs(Stage stage) { return openFiles(stage, RUST);} - - - public static File openSWIFT(Stage stage) { return openFile(stage, SWIFT); } - public static File[] openSWIFTs(Stage stage) { return openFiles(stage, SWIFT);} - - - public static File openTEXT(Stage stage) { return openFile(stage, TEXT); } - public static File[] openTEXTs(Stage stage) { return openFiles(stage, TEXT);} - - - public static File openMD(Stage stage) { return openFile(stage, MD); } - public static File[] openMDs(Stage stage) { return openFiles(stage, MD);} - - - public static File openREADME(Stage stage) { return openFile(stage, README); } - public static File[] openREADMEs(Stage stage) { return openFiles(stage, README);} - - - public static File openBIBTEX(Stage stage) { return openFile(stage, BIBTEX); } - public static File[] openBIBTEXs(Stage stage) { return openFiles(stage, BIBTEX);} - - - public static File openLATEX(Stage stage) { return openFile(stage, LATEX); } - public static File[] openLATEXs(Stage stage) { return openFiles(stage, LATEX);} - - - public static File openSQL(Stage stage) { return openFile(stage, SQL); } - public static File[] openSQLs(Stage stage) { return openFiles(stage, SQL);} - - public static File openPHP() { return openFile(PHP); } - public static File[] openPHPs() { return openFiles(PHP);} - - - public static File openCSS() { return openFile(CSS); } - public static File[] openCSSs() { return openFiles(CSS);} - - - public static File openJS() { return openFile(JS); } - public static File[] openJSs() { return openFiles(JS);} - - - public static File openJSON() { return openFile(JSON); } - public static File[] openJSONs() { return openFiles(JSON);} - - - public static File openJAVA() { return openFile(JAVA); } - public static File[] openJAVAs() { return openFiles(JAVA);} - - - public static File openKOTLIN() { return openFile(KOTLIN); } - public static File[] openKOTLINs() { return openFiles(KOTLIN);} - - - public static File openSCALA() { return openFile(SCALA); } - public static File[] openSCALAs() { return openFiles(SCALA);} - - - public static File openPYTHON() { return openFile(PYTHON); } - public static File[] openPYTHONs() { return openFiles(PYTHON);} - - - public static File openRUBY() { return openFile(RUBY); } - public static File[] openRUBYs() { return openFiles(RUBY);} - - - public static File openC() { return openFile(C); } - public static File[] openCs() { return openFiles(C);} - - - public static File openCPP() { return openFile(CPP); } - public static File[] openCPPs() { return openFiles(CPP);} - - - public static File openCSHARP() { return openFile(CSHARP); } - public static File[] openCSHARPs() { return openFiles(CSHARP);} - - - public static File openINO() { return openFile(INO); } - public static File[] openINOs() { return openFiles(INO);} - - - public static File openVB() { return openFile(VB); } - public static File[] openVBs() { return openFiles(VB);} - - - public static File openGO() { return openFile(GO); } - public static File[] openGOs() { return openFiles(GO);} - - - public static File openRUST() { return openFile(RUST); } - public static File[] openRUSTs() { return openFiles(RUST);} - - - public static File openSWIFT() { return openFile(SWIFT); } - public static File[] openSWIFTs() { return openFiles(SWIFT);} - - - public static File openTEXT() { return openFile(TEXT); } - public static File[] openTEXTs() { return openFiles(TEXT);} - - - public static File openMD() { return openFile(MD); } - public static File[] openMDs() { return openFiles(MD);} - - - public static File openREADME() { return openFile(README); } - public static File[] openREADMEs() { return openFiles(README);} - - - public static File openBIBTEX() { return openFile(BIBTEX); } - public static File[] openBIBTEXs() { return openFiles(BIBTEX);} - - - public static File openLATEX() { return openFile(LATEX); } - public static File[] openLATEXs() { return openFiles(LATEX);} - - - public static File openSQL() { return openFile(SQL); } - public static File[] openSQLs() { return openFiles(SQL);} - - - - public static File saveFile(Stage stage) { return convert(fc.showOpenDialog(stage))[0]; } - public static File[] saveFiles(Stage stage) { return convert(fc.showOpenMultipleDialog(stage)); } - - public static File saveFile() { return saveFile(new Stage()); } - public static File[] saveFiles() { return saveFiles(new Stage()); } - - public static File saveFile(Stage stage, FileChooser.ExtensionFilter...extensions) { return saveFile(stage, Arrays.asList(extensions)); } - - public static File saveFile(Stage stage, Collection extensions) { - fc.getExtensionFilters().addAll(extensions); - File file = saveFile(stage); - fc.getExtensionFilters().clear(); - return file; - } - - public static File saveFile(FileChooser.ExtensionFilter...extensions) { return saveFile(new Stage(), extensions); } - public static File saveFile(Collection extensions) { return saveFile(new Stage(), extensions); } - - public static File[] saveFiles(Stage stage, FileChooser.ExtensionFilter...extensions) { return saveFiles(stage, Arrays.asList(extensions)); } - - public static File[] saveFiles(Stage stage, Collection extensions) { - fc.getExtensionFilters().addAll(extensions); - File[] files = saveFiles(stage); - fc.getExtensionFilters().clear(); - return files; - } - - public static File[] saveFiles(FileChooser.ExtensionFilter...extensions) { return saveFiles(new Stage(), extensions); } - public static File[] saveFiles(Collection extensions) { return saveFiles(new Stage(), extensions); } - - - public static File savePNG(Stage stage) { return saveFile(stage, PNG); } - public static File[] savePNGs(Stage stage) { return saveFiles(stage, PNG); } - public static File savePNG() { return saveFile(PNG); } - public static File[] savePNGs() { return saveFiles(PNG); } - - - public static File saveJPG(Stage stage) { return saveFile(stage, JPG); } - public static File[] saveJPGs(Stage stage) { return saveFiles(stage, JPG); } - public static File saveJPG() { return saveFile(JPG); } - public static File[] saveJPGs() { return saveFiles(JPG); } - - - public static File saveICO(Stage stage) { return saveFile(stage, ICO); } - public static File[] saveICOs(Stage stage) { return saveFiles(stage, ICO); } - public static File saveICO() { return saveFile(ICO); } - public static File[] saveICOs() { return saveFiles(ICO); } - - - public static File saveGIF(Stage stage) { return saveFile(stage, GIF); } - public static File[] saveGIFs(Stage stage) { return saveFiles(stage, GIF); } - public static File saveGIF() { return saveFile(GIF); } - public static File[] saveGIFs() { return saveFiles(GIF); } - - - public static File saveJFIF(Stage stage) { return saveFile(stage, JFIF); } - public static File[] saveJFIFs(Stage stage) { return saveFiles(stage, JFIF); } - public static File saveJFIF() { return saveFile(JFIF); } - public static File[] saveJFIFs() { return saveFiles(JFIF); } - - - public static File savePDF(Stage stage) { return saveFile(stage, PDF); } - public static File[] savePDFs(Stage stage) { return saveFiles(stage, PDF);} - public static File savePDF() { return saveFile(PDF); } - public static File[] savePDFs() { return saveFiles(PDF);} - - - public static File saveHTML(Stage stage) { return saveFile(stage, HTML); } - public static File[] saveHTMLs(Stage stage) { return saveFiles(stage, HTML);} - public static File saveHTML() { return saveFile(HTML); } - public static File[] saveHTMLs() { return saveFiles(HTML);} - - - public static File saveXHTML(Stage stage) { return saveFile(stage, XHTML); } - public static File[] saveXHTMLs(Stage stage) { return saveFiles(stage, XHTML);} - public static File saveXHTML() { return saveFile(XHTML); } - public static File[] saveXHTMLs() { return saveFiles(XHTML);} - - - public static File savePHTML(Stage stage) { return saveFile(stage, PHTML); } - public static File[] savePHTMLs(Stage stage) { return saveFiles(stage, PHTML);} - public static File savePHTML() { return saveFile(PHTML); } - public static File[] savePHTMLs() { return saveFiles(PHTML);} - - - public static File saveXML(Stage stage) { return saveFile(stage, XML); } - public static File[] saveXMLs(Stage stage) { return saveFiles(stage, XML);} - public static File saveXML() { return saveFile(XML); } - public static File[] saveXMLs() { return saveFiles(XML);} - - - public static File saveFXML(Stage stage) { return saveFile(stage, FXML); } - public static File[] saveFXMLs(Stage stage) { return saveFiles(stage, FXML);} - public static File saveFXML() { return saveFile(FXML); } - public static File[] saveFXMLs() { return saveFiles(FXML);} - - - public static File savePHP(Stage stage) { return saveFile(stage, PHP); } - public static File[] savePHPs(Stage stage) { return saveFiles(stage, PHP);} - - - public static File saveCSS(Stage stage) { return saveFile(stage, CSS); } - public static File[] saveCSSs(Stage stage) { return saveFiles(stage, CSS);} - - - public static File saveJS(Stage stage) { return saveFile(stage, JS); } - public static File[] saveJSs(Stage stage) { return saveFiles(stage, JS);} - - - public static File saveJSON(Stage stage) { return saveFile(stage, JSON); } - public static File[] saveJSONs(Stage stage) { return saveFiles(stage, JSON);} - - - public static File saveJAVA(Stage stage) { return saveFile(stage, JAVA); } - public static File[] saveJAVAs(Stage stage) { return saveFiles(stage, JAVA);} - - - public static File saveKOTLIN(Stage stage) { return saveFile(stage, KOTLIN); } - public static File[] saveKOTLINs(Stage stage) { return saveFiles(stage, KOTLIN);} - - - public static File saveSCALA(Stage stage) { return saveFile(stage, SCALA); } - public static File[] saveSCALAs(Stage stage) { return saveFiles(stage, SCALA);} - - - public static File savePYTHON(Stage stage) { return saveFile(stage, PYTHON); } - public static File[] savePYTHONs(Stage stage) { return saveFiles(stage, PYTHON);} - - - public static File saveRUBY(Stage stage) { return saveFile(stage, RUBY); } - public static File[] saveRUBYs(Stage stage) { return saveFiles(stage, RUBY);} - - - public static File saveC(Stage stage) { return saveFile(stage, C); } - public static File[] saveCs(Stage stage) { return saveFiles(stage, C);} - - - public static File saveCPP(Stage stage) { return saveFile(stage, CPP); } - public static File[] saveCPPs(Stage stage) { return saveFiles(stage, CPP);} - - - public static File saveCSHARP(Stage stage) { return saveFile(stage, CSHARP); } - public static File[] saveCSHARPs(Stage stage) { return saveFiles(stage, CSHARP);} - - - public static File saveINO(Stage stage) { return saveFile(stage, INO); } - public static File[] saveINOs(Stage stage) { return saveFiles(stage, INO);} - - - public static File saveVB(Stage stage) { return saveFile(stage, VB); } - public static File[] saveVBs(Stage stage) { return saveFiles(stage, VB);} - - - public static File saveGO(Stage stage) { return saveFile(stage, GO); } - public static File[] saveGOs(Stage stage) { return saveFiles(stage, GO);} - - - public static File saveRUST(Stage stage) { return saveFile(stage, RUST); } - public static File[] saveRUSTs(Stage stage) { return saveFiles(stage, RUST);} - - - public static File saveSWIFT(Stage stage) { return saveFile(stage, SWIFT); } - public static File[] saveSWIFTs(Stage stage) { return saveFiles(stage, SWIFT);} - - - public static File saveTEXT(Stage stage) { return saveFile(stage, TEXT); } - public static File[] saveTEXTs(Stage stage) { return saveFiles(stage, TEXT);} - - - public static File saveMD(Stage stage) { return saveFile(stage, MD); } - public static File[] saveMDs(Stage stage) { return saveFiles(stage, MD);} - - - public static File saveREADME(Stage stage) { return saveFile(stage, README); } - public static File[] saveREADMEs(Stage stage) { return saveFiles(stage, README);} - - - public static File saveBIBTEX(Stage stage) { return saveFile(stage, BIBTEX); } - public static File[] saveBIBTEXs(Stage stage) { return saveFiles(stage, BIBTEX);} - - - public static File saveLATEX(Stage stage) { return saveFile(stage, LATEX); } - public static File[] saveLATEXs(Stage stage) { return saveFiles(stage, LATEX);} - - - public static File saveSQL(Stage stage) { return saveFile(stage, SQL); } - public static File[] saveSQLs(Stage stage) { return saveFiles(stage, SQL);} - - public static File savePHP() { return saveFile(PHP); } - public static File[] savePHPs() { return saveFiles(PHP);} - - - public static File saveCSS() { return saveFile(CSS); } - public static File[] saveCSSs() { return saveFiles(CSS);} - - - public static File saveJS() { return saveFile(JS); } - public static File[] saveJSs() { return saveFiles(JS);} - - - public static File saveJSON() { return saveFile(JSON); } - public static File[] saveJSONs() { return saveFiles(JSON);} - - - public static File saveJAVA() { return saveFile(JAVA); } - public static File[] saveJAVAs() { return saveFiles(JAVA);} - - - public static File saveKOTLIN() { return saveFile(KOTLIN); } - public static File[] saveKOTLINs() { return saveFiles(KOTLIN);} - - - public static File saveSCALA() { return saveFile(SCALA); } - public static File[] saveSCALAs() { return saveFiles(SCALA);} - - - public static File savePYTHON() { return saveFile(PYTHON); } - public static File[] savePYTHONs() { return saveFiles(PYTHON);} - - - public static File saveRUBY() { return saveFile(RUBY); } - public static File[] saveRUBYs() { return saveFiles(RUBY);} - - - public static File saveC() { return saveFile(C); } - public static File[] saveCs() { return saveFiles(C);} - - - public static File saveCPP() { return saveFile(CPP); } - public static File[] saveCPPs() { return saveFiles(CPP);} - - - public static File saveCSHARP() { return saveFile(CSHARP); } - public static File[] saveCSHARPs() { return saveFiles(CSHARP);} - - - public static File saveINO() { return saveFile(INO); } - public static File[] saveINOs() { return saveFiles(INO);} - - - public static File saveVB() { return saveFile(VB); } - public static File[] saveVBs() { return saveFiles(VB);} - - - public static File saveGO() { return saveFile(GO); } - public static File[] saveGOs() { return saveFiles(GO);} - - - public static File saveRUST() { return saveFile(RUST); } - public static File[] saveRUSTs() { return saveFiles(RUST);} - - - public static File saveSWIFT() { return saveFile(SWIFT); } - public static File[] saveSWIFTs() { return saveFiles(SWIFT);} - - - public static File saveTEXT() { return saveFile(TEXT); } - public static File[] saveTEXTs() { return saveFiles(TEXT);} - - - public static File saveMD() { return saveFile(MD); } - public static File[] saveMDs() { return saveFiles(MD);} - - - public static File saveREADME() { return saveFile(README); } - public static File[] saveREADMEs() { return saveFiles(README);} - - - public static File saveBIBTEX() { return saveFile(BIBTEX); } - public static File[] saveBIBTEXs() { return saveFiles(BIBTEX);} - - - public static File saveLATEX() { return saveFile(LATEX); } - public static File[] saveLATEXs() { return saveFiles(LATEX);} - - - public static File saveSQL() { return saveFile(SQL); } - public static File[] saveSQLs() { return saveFiles(SQL);} - - public static File getDirectory(Stage stage) { return convert(dc.showDialog(stage))[0]; } - - public boolean hasNext() { return in.hasNext(); } - - public int count(String substring) { - String input = read(); - String[] words = input.split(substring); - return words.length-1; - } - - public int count(char substring) { return count(substring+""); } - - public int count(int substring) { return count(substring+""); } - - public int count(double substring) { return count(substring+""); } - - public int count(float substring) { return count(substring+""); } - - public String[] split(String delimeter) { - String input = read(); - String[] words = input.split(delimeter); - return words; - } - - public String read() { - try { - return new String(Files.readAllBytes(Paths.get(getAbsolutePath()))); - } catch (IOException e) {} - return ""; - } - - public String readLine() { - if(in != null) { - if(hasNext()) return in.nextLine(); - else { - try { Scanner inp = new Scanner(this); return inp.nextLine(); } catch (IOException e) {} - } - } return ""; - } - - public String[] readLines() { - List lines = Collections.emptyList(); - try { lines = Files.readAllLines(Paths.get(getAbsolutePath()), StandardCharsets.UTF_8); } catch (IOException e) {} - return lines.toArray(new String[lines.size()]); - } - - public void close() { - try { in.close(); } catch(NullPointerException ex) {} - try { out.close(); } catch(NullPointerException ex) {} - } - - public String relativize(File file) { return toURI().relativize(file.toURI()).getPath(); } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if(obj == null || !(obj instanceof File)) return false; - return getAbsolutePath().equals(((File) obj).getAbsolutePath()); - } - - public boolean equals(File other) { return getAbsolutePath().equals(other.getAbsolutePath()); } - - public File clone() { return new File(getAbsolutePath()); } - - public int compareTo(File o) { return super.compareTo(o); } -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Events.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Events.kt deleted file mode 100644 index 942da78..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Events.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.thepyprogrammer.fxtools.methods - -import com.thepyprogrammer.fxtools.point.Point -import javafx.scene.input.* - -val MouseEvent.screenPoint: Point - get() = Point(screenX, screenY) - -val MouseEvent.point: Point - get() = Point(x, y) - -val MouseEvent.scenePoint: Point - get() = Point(sceneX, sceneY) - - -val DragEvent.screenPoint: Point - get() = Point(screenX, screenY) - -val DragEvent.point: Point - get() = Point(x, y) - -val DragEvent.scenePoint: Point - get() = Point(sceneX, sceneY) - - -val ContextMenuEvent.screenPoint: Point - get() = Point(screenX, screenY) - -val ContextMenuEvent.point: Point - get() = Point(x, y) - -val ContextMenuEvent.scenePoint: Point - get() = Point(sceneX, sceneY) - - -val GestureEvent.screenPoint: Point - get() = Point(screenX, screenY) - -val GestureEvent.point: Point - get() = Point(x, y) - -val GestureEvent.scenePoint: Point - get() = Point(sceneX, sceneY) - - -val ScrollEvent.screenPoint: Point - get() = Point(screenX, screenY) - -val ScrollEvent.point: Point - get() = Point(x, y) - -val ScrollEvent.scenePoint: Point - get() = Point(sceneX, sceneY) - - - diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Nodes.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Nodes.kt deleted file mode 100644 index f04ed0a..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Nodes.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.thepyprogrammer.fxtools.methods - -import javafx.geometry.Pos -import javafx.geometry.Rectangle2D -import javafx.scene.Node -import javafx.scene.control.Control -import javafx.scene.layout.VBox - -val Node.centeredNode - get() = VBox(this).apply { alignment = Pos.CENTER } - -val Control.absoluteRect - get() = Rectangle2D( - localToScene(layoutBounds.minX, layoutBounds.minY).x + scene.window.x, - localToScene(layoutBounds.minX, layoutBounds.minY).y + scene.window.y, - width, - height - ) - - - diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Observables.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Observables.kt deleted file mode 100644 index 51b0f1d..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Observables.kt +++ /dev/null @@ -1,26 +0,0 @@ -package com.thepyprogrammer.fxtools.methods - -import com.thepyprogrammer.fxtools.point.Point -import javafx.beans.property.DoubleProperty -import javafx.beans.property.Property -import javafx.beans.property.SimpleDoubleProperty -import javafx.beans.value.ObservableValue -import javafx.geometry.Point2D - -infix operator fun DoubleProperty.plus(other: DoubleProperty): DoubleProperty = - SimpleDoubleProperty().apply { bind(this@plus.add(other)) } - -infix operator fun DoubleProperty.minus(other: DoubleProperty): DoubleProperty = - SimpleDoubleProperty().apply { bind(this@minus.subtract(other)) } - -infix operator fun DoubleProperty.div(other: DoubleProperty): DoubleProperty = - SimpleDoubleProperty().apply { bind(this@div.divide(other)) } - -infix operator fun DoubleProperty.times(other: DoubleProperty): DoubleProperty = - SimpleDoubleProperty().apply { bind(this@times.multiply(other)) } - -fun Point2D.toPoint() = Point(this) - -infix fun Property.bindBidirectional(other: ObservableValue) { - bind(other) -} diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Stages.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Stages.kt deleted file mode 100644 index 394f958..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/methods/Stages.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.thepyprogrammer.fxtools.methods - -import com.thepyprogrammer.fxtools.point.Point -import javafx.application.Application -import javafx.geometry.Rectangle2D -import javafx.stage.Screen -import javafx.stage.Stage - -fun Application.fullScreen(stage: Stage) { - val screen = Screen.getPrimary() - val bounds: Rectangle2D = screen.visualBounds - - stage.x = bounds.minX - stage.y = bounds.minY - stage.width = bounds.width - stage.height = bounds.height -} - -fun List.hide() { - forEach { it.hide() } -} - -var Stage.point: Point -get() = Point(x, y) -set(point) { - x = point.x - y = point.y -} diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/point/Point.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/point/Point.kt deleted file mode 100644 index 7c233cf..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/point/Point.kt +++ /dev/null @@ -1,143 +0,0 @@ -package com.thepyprogrammer.fxtools.point - -import com.thepyprogrammer.fxtools.methods.minus -import com.thepyprogrammer.fxtools.methods.plus -import com.thepyprogrammer.fxtools.methods.times -import javafx.beans.property.DoubleProperty -import javafx.beans.property.SimpleDoubleProperty -import javafx.geometry.Point2D -import kotlin.math.sqrt - - -data class Point( - var xProperty: DoubleProperty = SimpleDoubleProperty(0.0), - var yProperty: DoubleProperty = SimpleDoubleProperty(0.0) -) : Cloneable, Comparable { - - var x: Double - get() = xProperty.get() - set(other) = xProperty.set(other) - - var y: Double - get() = yProperty.get() - set(other) = yProperty.set(other) - - constructor(x: Double, y: Double): this() { - set(x, y) - } - - constructor(point: Point2D) : this(point.x, point.y) - - operator fun set(x: Double, y: Double) { - this.x = x - this.y = y - } - - infix fun set(point: Point2D) { - this.x = point.x - this.y = point.y - } - - override fun toString(): String { - return "($xProperty, $yProperty)" - } - - fun copy(): Point { - return Point(x, y) - } - - public override fun clone(): Point { - return copy() - } - - infix fun bindX(xProperty: DoubleProperty) { - this.xProperty.bind(xProperty) - } - - infix fun bindXBidirectional(xProperty: DoubleProperty) { - this.xProperty.bindBidirectional(xProperty) - } - - infix fun bindY(yProperty: DoubleProperty) { - this.yProperty.bind(yProperty) - } - - infix fun bindYBidirectional(yProperty: DoubleProperty) { - this.yProperty.bindBidirectional(yProperty) - } - - infix fun bind(p: Point) { - bindX(p.xProperty) - bindY(p.yProperty) - } - - infix fun bindBidirectional(p: Point) { - bindXBidirectional(p.xProperty) - bindYBidirectional(p.yProperty) - } - - fun toPoint2d(): Point2D { - return Point2D(x, y) - } - - override infix operator fun compareTo(other: Any?): Int { - return if (other is Point2D) compareTo(other) else if (other is Point) compareTo(other) else 0 - } - - infix operator fun compareTo(p: Point): Int { - return if (x != p.x) (x - p.x).toInt() else (y - p.y).toInt() - } - - infix operator fun compareTo(p: Point2D): Int { - return if (x != p.x) (x - p.x).toInt() else (x - p.y).toInt() - } - - infix fun fromOrigin(p: Point): Point { - return Point(x - p.x, y - p.y) - } - - private val hypotenuse: Double - get() = sqrt(x * x + y * y) - - infix fun hypotenuseFrom(p: Point): Double { - return fromOrigin(p).hypotenuse - } - - fun sumOfProperties(): DoubleProperty { - return xProperty + yProperty - } - - infix operator fun plus(p: Point): Point = Point( - xProperty + p.xProperty, - yProperty + p.yProperty - ) - - fun plus(xProperty: DoubleProperty, yProperty: DoubleProperty): Point = Point( - xProperty + this.xProperty, - yProperty + this.yProperty - ) - - infix operator fun times(p: Point): DoubleProperty = - scale(p).sumOfProperties() - - infix operator fun times(scalingProperty: DoubleProperty): DoubleProperty = - (xProperty * scalingProperty) + (yProperty * scalingProperty) - - infix fun scaleX(scalingProperty: DoubleProperty) = - Point((xProperty * scalingProperty), (yProperty)) - - infix fun scaleY(scalingProperty: DoubleProperty) = - Point((xProperty), (yProperty * scalingProperty)) - - fun scale(scalingXProperty: DoubleProperty, scalingYProperty: DoubleProperty): Point = - this scaleX scalingXProperty scaleY scalingYProperty - - infix fun scale(scalingProperty: DoubleProperty): Point = scale(scalingProperty, scalingProperty) - - infix fun scale(p: Point): Point = scale(p.xProperty, p.yProperty) - - infix operator fun minus(p: Point): Point = Point( - xProperty - p.xProperty, - yProperty - p.yProperty - ) -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/point/Points.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/point/Points.kt deleted file mode 100644 index b06f61e..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/point/Points.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.thepyprogrammer.fxtools.point - -import javafx.geometry.Rectangle2D - -fun Rectangle2D.contains(screenPoint: Point) = contains(screenPoint.toPoint2d()) - diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/resizable/ResizeHelper.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/resizable/ResizeHelper.kt deleted file mode 100644 index 2b2cb6d..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/resizable/ResizeHelper.kt +++ /dev/null @@ -1,116 +0,0 @@ -package com.thepyprogrammer.fxtools.resizable - -import javafx.event.EventHandler -import javafx.scene.Cursor -import javafx.scene.Node -import javafx.scene.Parent -import javafx.scene.input.MouseEvent -import javafx.stage.Stage - - -// Based on code created by Alexander Berg - -fun addResizeListener(stage: Stage) { - val resizeListener = ResizeListener(stage) - - with(stage.scene) { - addEventHandler(MouseEvent.MOUSE_MOVED, resizeListener) - addEventHandler(MouseEvent.MOUSE_PRESSED, resizeListener) - addEventHandler(MouseEvent.MOUSE_DRAGGED, resizeListener) - addEventHandler(MouseEvent.MOUSE_EXITED, resizeListener) - addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, resizeListener) - - val children = root.childrenUnmodifiable - - for (child in children) { - addListenerDeeply(child, resizeListener) - } - } -} - -fun addListenerDeeply(node: Node, listener: EventHandler?) { - with(node) { - addEventHandler(MouseEvent.MOUSE_MOVED, listener) - addEventHandler(MouseEvent.MOUSE_PRESSED, listener) - addEventHandler(MouseEvent.MOUSE_DRAGGED, listener) - addEventHandler(MouseEvent.MOUSE_EXITED, listener) - addEventHandler(MouseEvent.MOUSE_EXITED_TARGET, listener) - - if (this is Parent) { - val children = childrenUnmodifiable - for (child in children) { - addListenerDeeply(child, listener) - } - } - } -} - -class ResizeListener(private val stage: Stage) : EventHandler { - private var cursorEvent = Cursor.DEFAULT - private val border = 4 - private var startX = 0.0 - private var startY = 0.0 - - override fun handle(mouseEvent: MouseEvent) { - val mouseEventType = mouseEvent.eventType - val scene = stage.scene - val mouseEventX = mouseEvent.sceneX - val mouseEventY = mouseEvent.sceneY - val sceneWidth = scene.width - val sceneHeight = scene.height - when { - MouseEvent.MOUSE_MOVED == mouseEventType -> { - cursorEvent = when { - mouseEventX < border && mouseEventY < border -> Cursor.NW_RESIZE - mouseEventX < border && mouseEventY > sceneHeight - border -> Cursor.SW_RESIZE - mouseEventX > sceneWidth - border && mouseEventY < border -> Cursor.NE_RESIZE - mouseEventX > sceneWidth - border && mouseEventY > sceneHeight - border -> Cursor.SE_RESIZE - mouseEventX < border -> Cursor.W_RESIZE - mouseEventX > sceneWidth - border -> Cursor.E_RESIZE - mouseEventY < border -> Cursor.N_RESIZE - mouseEventY > sceneHeight - border -> Cursor.S_RESIZE - else -> Cursor.DEFAULT - } - scene.cursor = cursorEvent - } - MouseEvent.MOUSE_EXITED == mouseEventType || MouseEvent.MOUSE_EXITED_TARGET == mouseEventType -> - scene.cursor = Cursor.DEFAULT - MouseEvent.MOUSE_PRESSED == mouseEventType -> { - startX = stage.width - mouseEventX - startY = stage.height - mouseEventY - } - MouseEvent.MOUSE_DRAGGED == mouseEventType -> { - when { - Cursor.DEFAULT != cursorEvent -> { - if (Cursor.W_RESIZE != cursorEvent && Cursor.E_RESIZE != cursorEvent) { - val minHeight = if (stage.minHeight > border * 2) stage.minHeight else (border * 2).toDouble() - if (Cursor.NW_RESIZE == cursorEvent || Cursor.N_RESIZE == cursorEvent || Cursor.NE_RESIZE == cursorEvent) { - if (stage.height > minHeight || mouseEventY < 0) { - stage.height = stage.y - mouseEvent.screenY + stage.height - stage.y = mouseEvent.screenY - } - } else { - if (stage.height > minHeight || mouseEventY + startY - stage.height > 0) { - stage.height = mouseEventY + startY - } - } - } - if (Cursor.N_RESIZE != cursorEvent && Cursor.S_RESIZE != cursorEvent) { - val minWidth = if (stage.minWidth > border * 2) stage.minWidth else (border * 2).toDouble() - if (Cursor.NW_RESIZE == cursorEvent || Cursor.W_RESIZE == cursorEvent || Cursor.SW_RESIZE == cursorEvent) { - if (stage.width > minWidth || mouseEventX < 0) { - stage.width = stage.x - mouseEvent.screenX + stage.width - stage.x = mouseEvent.screenX - } - } else { - if (stage.width > minWidth || mouseEventX + startX - stage.width > 0) { - stage.width = mouseEventX + startX - } - } - } - } - } - } - } - } -} diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/resizable/ResizeMode.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/resizable/ResizeMode.kt deleted file mode 100644 index 3dd31ac..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/resizable/ResizeMode.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.thepyprogrammer.fxtools.resizable - -enum class ResizeMode { - NONE, - TOP, - LEFT, - BOTTOM, - RIGHT, - TOP_LEFT, - TOP_RIGHT, - BOTTOM_LEFT, - BOTTOM_RIGHT -} \ No newline at end of file diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/util/Types.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/util/Types.kt deleted file mode 100644 index b18ce71..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/util/Types.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.thepyprogrammer.fxtools.util - -fun Boolean.toInt() = if(this) 1 else 0 - -fun Boolean.given(block: () -> Unit): Boolean { - if (this) block() - return this -} diff --git a/src/main/kotlin/com/thepyprogrammer/fxtools/zoomable/ZoomableScrollPane.kt b/src/main/kotlin/com/thepyprogrammer/fxtools/zoomable/ZoomableScrollPane.kt deleted file mode 100644 index dde9c88..0000000 --- a/src/main/kotlin/com/thepyprogrammer/fxtools/zoomable/ZoomableScrollPane.kt +++ /dev/null @@ -1,95 +0,0 @@ -package com.thepyprogrammer.fxtools.zoomable - -import com.thepyprogrammer.fxtools.methods.centeredNode -import com.thepyprogrammer.fxtools.methods.point -import javafx.beans.property.ObjectProperty -import javafx.beans.property.SimpleObjectProperty -import javafx.geometry.Point2D -import javafx.scene.Group -import javafx.scene.Node -import javafx.scene.control.ScrollPane -import kotlin.math.exp - -class ZoomableScrollPane( - var targetProperty: ObjectProperty = SimpleObjectProperty() -): ScrollPane() { - var scaleValue = 0.7 - val zoomIntensity = 0.02 - var zoomNode: Node? = null - - private var target: Node - get() = targetProperty.get() - set(target) { - targetProperty.set(target) - zoomNode = Group(target) - content = outerNode(zoomNode as Group) - - updateScale() - } - - private fun outerNode(node: Node) = - node.centeredNode.apply { - setOnScroll { e -> - e.consume() - onScroll(e.textDeltaY, e.point.toPoint2d()) - } - } - - private fun updateScale() { - target.scaleX = scaleValue - target.scaleY = scaleValue - } - - private fun onScroll(wheelDelta: Double, mousePoint: Point2D) { - val zoomFactor = exp(wheelDelta * zoomIntensity) - - val innerBounds = zoomNode!!.layoutBounds - val viewportBounds = viewportBounds - - // calculate pixel offsets from [0, 1] range - - // calculate pixel offsets from [0, 1] range - val valX = hvalue * (innerBounds.width - viewportBounds.width) - val valY = vvalue * (innerBounds.height - viewportBounds.height) - - scaleValue *= zoomFactor - updateScale() - layout() // refresh ScrollPane scroll positions & target bounds - - - // convert target coordinates to zoomTarget coordinates - - // convert target coordinates to zoomTarget coordinates - val posInZoomTarget: Point2D = target.parentToLocal(zoomNode!!.parentToLocal(mousePoint)) - - // calculate adjustment of scroll position (pixels) - - // calculate adjustment of scroll position (pixels) - val adjustment: Point2D = - target.localToParentTransform.deltaTransform(posInZoomTarget.multiply(zoomFactor - 1)) - - // convert back to [0, 1] range - // (too large/small values are automatically corrected by ScrollPane) - - // convert back to [0, 1] range - // (too large/small values are automatically corrected by ScrollPane) - val updatedInnerBounds = zoomNode!!.boundsInLocal - hvalue = (valX + adjustment.x) / (updatedInnerBounds.width - viewportBounds.width) - vvalue = (valY + adjustment.y) / (updatedInnerBounds.height - viewportBounds.height) - } - - init { - isPannable = true - hbarPolicy = ScrollBarPolicy.NEVER - vbarPolicy = ScrollBarPolicy.NEVER - isFitToHeight = true // center - isFitToWidth = true // center - - } - - constructor(target: Node): this() { - this.target = target - } - - -} \ No newline at end of file diff --git a/src/main/kotlin/gui/Combobox.kt b/src/main/kotlin/gui/Combobox.kt new file mode 100644 index 0000000..1e709bd --- /dev/null +++ b/src/main/kotlin/gui/Combobox.kt @@ -0,0 +1,74 @@ +package gui + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.ArrowDropDown +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.sp +import androidx.compose.ui.unit.toSize + +@Preview +@Composable +fun Combobox(label: String, selectedItem: MutableState, items: List, modifier: Modifier = Modifier, + fontSize: TextUnit = 12.sp, onValueChanged: () -> Unit) { + val expanded = remember { mutableStateOf(false) } + val textfieldSize = remember { mutableStateOf(Size.Zero)} + + val icon = Icons.Filled.ArrowDropDown + + Column(modifier = modifier) { + OutlinedTextField( + value = selectedItem.value.toString(), + onValueChange = { }, + modifier = Modifier + .fillMaxWidth() + .onGloballyPositioned { coordinates -> + // This value is used to assign to the Dropdown the same width + textfieldSize.value = coordinates.size.toSize() + }, + label = { Text(label, fontSize = fontSize, color = MaterialTheme.colors.primary) }, + trailingIcon = { + Icon(icon,"", Modifier.clickable { expanded.value = !expanded.value }) + }, + textStyle = TextStyle(fontSize = fontSize), + colors = TextFieldDefaults.outlinedTextFieldColors( + focusedBorderColor = MaterialTheme.colors.primary, + unfocusedBorderColor = MaterialTheme.colors.primary + ) + ) + + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false }, + modifier = Modifier.width( + with(LocalDensity.current) { + textfieldSize.value.width.toDp() + } + ) + ) { + items.forEach { + DropdownMenuItem(onClick = { + onValueChanged() + selectedItem.value = it + expanded.value = false + }) { + Text(text = it.toString(), fontSize = fontSize) + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/gui/NodesPane.kt b/src/main/kotlin/gui/NodesPane.kt new file mode 100644 index 0000000..92218c8 --- /dev/null +++ b/src/main/kotlin/gui/NodesPane.kt @@ -0,0 +1,85 @@ +package gui + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.VerticalScrollbar +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.rememberScrollbarAdapter +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.PointerEventType +import androidx.compose.ui.input.pointer.isSecondaryPressed +import androidx.compose.ui.input.pointer.onPointerEvent +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import backend.image_processing.preprocess.Preprocessor +import backend.image_processing.preprocess.blurring.BlurringNode +import backend.image_processing.preprocess.edge_detection.CannyEdgeNode +import backend.image_processing.preprocess.masking.ThresholdingNode + +@OptIn(ExperimentalComposeUiApi::class) +@Preview +@Composable +fun NodesPane(preprocessor: Preprocessor) { + val items = listOf(BlurringNode(), ThresholdingNode(), CannyEdgeNode()) + val expanded = remember { mutableStateOf(false) } + + Box { + val state = rememberLazyListState() + + LazyColumn(modifier = Modifier.width(320.dp).padding(end = 12.dp), state) { + items(preprocessor.nodes) { + when (it) { + is BlurringNode -> BlurringPane(it) + is ThresholdingNode -> ThresholdingPane(it) + is CannyEdgeNode -> CannyEdgePane(it) + } + } + } + + VerticalScrollbar( + modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(), + adapter = rememberScrollbarAdapter( + scrollState = state + ) + ) + + Column(modifier = Modifier.align(Alignment.BottomEnd)) { + FloatingActionButton( + backgroundColor = MaterialTheme.colors.primary, + contentColor = Color.White, + modifier = Modifier.width(45.dp).height(45.dp), + onClick = { + expanded.value = true + }, + ) { + Icon(Icons.Filled.Add, "") + } + + DropdownMenu( + expanded = expanded.value, + onDismissRequest = { expanded.value = false } + ) { + items.forEach { + DropdownMenuItem(onClick = { + expanded.value = false + preprocessor.nodes.add(it.clone()) + }) { + Text(text = it.name, fontSize = 12.sp) + } + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/gui/ProcessingPane.kt b/src/main/kotlin/gui/ProcessingPane.kt new file mode 100644 index 0000000..8a679d4 --- /dev/null +++ b/src/main/kotlin/gui/ProcessingPane.kt @@ -0,0 +1,339 @@ +package gui + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.TooltipArea +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import backend.image_processing.Processing +import backend.image_processing.preprocess.blurring.Blurring +import backend.image_processing.preprocess.blurring.BlurringNode +import backend.image_processing.preprocess.edge_detection.CannyEdgeNode +import backend.image_processing.preprocess.masking.ThresholdingNode + +@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class) +@Preview +@Composable +fun ProcessingPane(node: Processing, onDelete: () -> Unit, options: @Composable () -> Unit) { + val helpDialog = remember { mutableStateOf(false) } + val deleteDialog = remember { mutableStateOf(false) } + val count = remember { mutableStateOf(0) } + + // Store the pane in a card + Card(elevation = 10.dp, modifier = Modifier.padding(10.dp), shape = RoundedCornerShape(10.dp)) { + Column(Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Box { + Row(Modifier.padding(10.dp).fillMaxWidth(), Arrangement.spacedBy(5.dp)) { + // The title + Text(node.name, fontSize = 20.sp, fontWeight = FontWeight.Bold) + + // The help tooltip + TooltipArea( + tooltip = { + Surface( + modifier = Modifier.shadow(4.dp), + color = Color(50, 50, 50, 255), + shape = RoundedCornerShape(4.dp) + ) { + Text( + text = node.help, + fontSize = 8.sp, + modifier = Modifier.padding(5.dp), + color = Color(255, 255, 255) + ) + } + }, + modifier = Modifier.padding(start = 0.dp), + delayMillis = 600 + ) { + // The help button + IconButton(onClick = { helpDialog.value = true }, modifier = Modifier.size(23.dp)) { + Icon( + painterResource("help_black_24dp.svg"), contentDescription = "", + tint = MaterialTheme.colors.primary + ) + } + } + } + + Column(modifier = Modifier.align(Alignment.TopEnd)) { + // Colourspace selector + TextButton( + onClick = { + count.value++ + count.value %= node.inputColourspaces.size + node.inputColourspace = node.inputColourspaces[count.value] + } + ) { + Text(node.inputColourspaces[count.value].toString(), fontSize = 12.sp) + } + } + } + + options() + + // The delete button + /* + IconButton(onClick = { deleteDialog.value = true }, + modifier = Modifier.size(23.dp).align(Alignment.End)) { + Icon( + Icons.Filled.Delete, contentDescription = "", + tint = MaterialTheme.colors.primary + ) + } + */ + } + } + + // Open the help dialog + if (helpDialog.value) { + AlertDialog( + title = { Text("Help") }, + text = { Text(node.help) }, + confirmButton = { + TextButton({ helpDialog.value = false }) { Text("Ok") } + }, + onDismissRequest = { helpDialog.value = false }, + modifier = Modifier.size(300.dp, 200.dp).padding(10.dp) + ) + } + + // Check if user would like to delete + if (deleteDialog.value) { + AlertDialog( + title = { Text("Confirm deletion of node?") }, + text = { Text("Would you like to delete this node? There is no turning back.") }, + confirmButton = { TextButton({ deleteDialog.value = false; onDelete() }) { Text("Yes") } }, + dismissButton = { TextButton({ deleteDialog.value = false }) { Text("No") } }, + onDismissRequest = { deleteDialog.value = false }, + modifier = Modifier.size(300.dp, 200.dp).padding(10.dp) + ) + } +} + +@Preview +@Composable +fun BlurringPane(node: BlurringNode) { + val kernelSize = remember { mutableStateOf(3.0f) } + val blurType = remember { mutableStateOf(Blurring.GAUSSIAN) } + + ProcessingPane(node, {}) { + Column { + // For adjusting kernel size + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Kernel Size: ", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Slider( + value = kernelSize.value, + valueRange = 3.0f .. 31.0f, + onValueChange = { + kernelSize.value = it + node.kernelSize = kernelSize.value.toInt() / 2 * 2 + 1 + }, + modifier = Modifier.width(125.dp) + ) + + Text( + (kernelSize.value.toInt() / 2 * 2 + 1).toString(), + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + + // Changing type of blurring + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Combobox( + "Blur Type", blurType, + listOf(Blurring.GAUSSIAN, Blurring.MEDIAN, Blurring.BOX_FILTER), + modifier = Modifier.height(53.dp), + onValueChanged = { node.blurType = blurType.value } + ) + } + } + } +} + +@OptIn(ExperimentalMaterialApi::class) +@Preview +@Composable +fun ThresholdingPane(node: ThresholdingNode) { + val thresholdRange = remember { mutableStateOf(0.0f .. 255.0f) } + val binarise = remember { mutableStateOf(true) } + + ProcessingPane(node, {}) { + Column { + /* + // For adjusting minimum threshold + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Min Threshold: ", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Slider( + value = minThreshold.value, + valueRange = 0.0f .. 255.0f, + onValueChange = { + minThreshold.value = it + node.minThreshold = it.toDouble() + }, + modifier = Modifier.width(125.dp) + ) + + Text( + minThreshold.value.toInt().toString(), + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + + // For adjusting maximum threshold + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Max Threshold: ", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Slider( + value = maxThreshold.value, + valueRange = 0.0f .. 255.0f, + onValueChange = { + maxThreshold.value = it + node.maxThreshold = it.toDouble() + }, + modifier = Modifier.width(125.dp) + ) + + Text( + maxThreshold.value.toInt().toString(), + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + */ + + // Checkbox for binarisation + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Binarise", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Checkbox( + checked = binarise.value, + onCheckedChange = { + binarise.value = it + node.binarise = it + }, + colors = CheckboxDefaults.colors( + checkedColor = MaterialTheme.colors.primary, + uncheckedColor = MaterialTheme.colors.onSurface.copy(alpha = 0.6f), + checkmarkColor = MaterialTheme.colors.surface, + disabledColor = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.disabled), + disabledIndeterminateColor = MaterialTheme.colors.primary.copy(alpha = ContentAlpha.disabled) + ) + ) + } + + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Threshold Range: ", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + RangeSlider( + values = thresholdRange.value, + valueRange = 0.0f .. 255.0f, + onValueChange = { + thresholdRange.value = it + node.minThreshold = it.start.toDouble() + node.maxThreshold = it.endInclusive.toDouble() + }, + modifier = Modifier.fillMaxWidth() + ) + } + } + } +} + +@Preview +@Composable +fun CannyEdgePane(node: CannyEdgeNode) { + val kernelSize = remember { mutableStateOf(3.0f) } + val threshold = remember { mutableStateOf(200.0f) } + + ProcessingPane(node, {}) { + Column { + // For adjusting kernel size + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Kernel Size: ", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Slider( + value = kernelSize.value, + valueRange = 3.0f .. 31.0f, + onValueChange = { + kernelSize.value = it + node.kernelSize = kernelSize.value.toInt() / 2 * 2 + 1 + }, + modifier = Modifier.width(125.dp) + ) + + Text( + (kernelSize.value.toInt() / 2 * 2 + 1).toString(), + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + + // Changing threshold + Row(modifier = Modifier.padding(10.dp), Arrangement.spacedBy(5.dp)) { + Text( + "Threshold: ", + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + + Slider( + value = threshold.value, + valueRange = 0.0f .. 600.0f, + onValueChange = { + threshold.value = it + node.threshold = threshold.value.toDouble() + }, + modifier = Modifier.width(125.dp) + ) + + Text( + threshold.value.toInt().toString(), + fontSize = 12.sp, + modifier = Modifier.align(Alignment.CenterVertically) + ) + } + } + } +} diff --git a/src/main/kotlin/gui/VideoPlayer.kt b/src/main/kotlin/gui/VideoPlayer.kt new file mode 100644 index 0000000..de81d59 --- /dev/null +++ b/src/main/kotlin/gui/VideoPlayer.kt @@ -0,0 +1,77 @@ +package gui + +import androidx.compose.desktop.ui.tooling.preview.Preview +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.PlayArrow +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.loadImageBitmap +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import backend.Video +import java.io.File + +@Preview +@Composable +fun VideoPlayer(video: Video) { + val playVideo = remember { mutableStateOf(false) } + val threadCreated = remember { mutableStateOf(false) } + val imageBitmap = remember { mutableStateOf(loadImageBitmap(File("test.png").inputStream())) } + + Column(modifier = Modifier.width(950.dp).padding(10.dp), Arrangement.spacedBy(5.dp)) { + Image( + imageBitmap.value, + contentDescription = "" + ) + + Row(horizontalArrangement = Arrangement.spacedBy(5.dp)) { + // The play button + IconButton(onClick = { + // Switch between play and pause + playVideo.value = !playVideo.value + + // Check if the thread has been created + if (!threadCreated.value) { + threadCreated.value = true + + // Create the thread + Thread { + while (video.hasNext()) { + try { + if (!playVideo.value) video.seek(video.currentFrame) + video.next().write("test.png") + + imageBitmap.value = loadImageBitmap(File("test.png").inputStream()) + } catch (ignored: ConcurrentModificationException) {} + } + }.start() + } + }) { + Icon( + if (playVideo.value) painterResource("pause_black_24dp.svg") + else painterResource("play_arrow_black_24dp.svg"), + contentDescription = "", + tint = if (playVideo.value) Color.Black else Color.Green + ) + } + + // Show the frame number + Text(video.currentFrame.toString(), fontSize = 12.sp, modifier = Modifier.align(Alignment.CenterVertically)) + + // The slider to adjust the time-stamp + Slider( + value = video.currentFrame.toFloat(), + valueRange = 0.0f..video.totalFrames.toFloat(), + onValueChange = { video.seek(it.toInt()) } + ) + } + } +} diff --git a/src/main/resources/about/about.fxml b/src/main/resources/about/about.fxml deleted file mode 100644 index 3b0e3ee..0000000 --- a/src/main/resources/about/about.fxml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/fonts/MathMajor/Math Major.ttf b/src/main/resources/fonts/MathMajor/Math Major.ttf deleted file mode 100644 index 97153e2..0000000 Binary files a/src/main/resources/fonts/MathMajor/Math Major.ttf and /dev/null differ diff --git a/src/main/resources/fonts/MathMajor/readme.html b/src/main/resources/fonts/MathMajor/readme.html deleted file mode 100644 index 9cb396e..0000000 --- a/src/main/resources/fonts/MathMajor/readme.html +++ /dev/null @@ -1,199 +0,0 @@ - - - - - - - Math MajorFontsgeek - - - - - - - - - - - - -
- - -
- - - - -
-

Math Major

-

This font was downloaded from fontsgeek.com - . You can visit fontsgeek.com for - thousands of free fonts.

-

View - Charmap and other information Browse other - free fonts

-

You will be shortly redirected to fontsgeek.

-
-
- - -
- - - - diff --git a/src/main/resources/fonts/Quicksand/OFL.txt b/src/main/resources/fonts/Quicksand/OFL.txt deleted file mode 100644 index 5186b46..0000000 --- a/src/main/resources/fonts/Quicksand/OFL.txt +++ /dev/null @@ -1,93 +0,0 @@ -Copyright 2011 The Quicksand Project Authors (https://github.com/andrew-paglinawan/QuicksandFamily), with Reserved Font Name “Quicksand”. - -This Font Software is licensed under the SIL Open Font License, Version 1.1. -This license is copied below, and is also available with a FAQ at: -http://scripts.sil.org/OFL - - ------------------------------------------------------------ -SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 ------------------------------------------------------------ - -PREAMBLE -The goals of the Open Font License (OFL) are to stimulate worldwide -development of collaborative font projects, to support the font creation -efforts of academic and linguistic communities, and to provide a free and -open framework in which fonts may be shared and improved in partnership -with others. - -The OFL allows the licensed fonts to be used, studied, modified and -redistributed freely as long as they are not sold by themselves. The -fonts, including any derivative works, can be bundled, embedded, -redistributed and/or sold with any software provided that any reserved -names are not used by derivative works. The fonts and derivatives, -however, cannot be released under any other type of license. The -requirement for fonts to remain under this license does not apply -to any document created using the fonts or their derivatives. - -DEFINITIONS -"Font Software" refers to the set of files released by the Copyright -Holder(s) under this license and clearly marked as such. This may -include source files, build scripts and documentation. - -"Reserved Font Name" refers to any names specified as such after the -copyright statement(s). - -"Original Version" refers to the collection of Font Software components as -distributed by the Copyright Holder(s). - -"Modified Version" refers to any derivative made by adding to, deleting, -or substituting -- in part or in whole -- any of the components of the -Original Version, by changing formats or by porting the Font Software to a -new environment. - -"Author" refers to any designer, engineer, programmer, technical -writer or other person who contributed to the Font Software. - -PERMISSION & CONDITIONS -Permission is hereby granted, free of charge, to any person obtaining -a copy of the Font Software, to use, study, copy, merge, embed, modify, -redistribute, and sell modified and unmodified copies of the Font -Software, subject to the following conditions: - -1) Neither the Font Software nor any of its individual components, -in Original or Modified Versions, may be sold by itself. - -2) Original or Modified Versions of the Font Software may be bundled, -redistributed and/or sold with any software, provided that each copy -contains the above copyright notice and this license. These can be -included either as stand-alone text files, human-readable headers or -in the appropriate machine-readable metadata fields within text or -binary files as long as those fields can be easily viewed by the user. - -3) No Modified Version of the Font Software may use the Reserved Font -Name(s) unless explicit written permission is granted by the corresponding -Copyright Holder. This restriction only applies to the primary font name as -presented to the users. - -4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font -Software shall not be used to promote, endorse or advertise any -Modified Version, except to acknowledge the contribution(s) of the -Copyright Holder(s) and the Author(s) or with their explicit written -permission. - -5) The Font Software, modified or unmodified, in part or in whole, -must be distributed entirely under this license, and must not be -distributed under any other license. The requirement for fonts to -remain under this license does not apply to any document created -using the Font Software. - -TERMINATION -This license becomes null and void if any of the above conditions are -not met. - -DISCLAIMER -THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE -COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL -DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM -OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/main/resources/fonts/Quicksand/Quicksand-VariableFont_wght.ttf b/src/main/resources/fonts/Quicksand/Quicksand-VariableFont_wght.ttf deleted file mode 100644 index 887908a..0000000 Binary files a/src/main/resources/fonts/Quicksand/Quicksand-VariableFont_wght.ttf and /dev/null differ diff --git a/src/main/resources/fonts/Quicksand/README.txt b/src/main/resources/fonts/Quicksand/README.txt deleted file mode 100644 index 2da54c8..0000000 --- a/src/main/resources/fonts/Quicksand/README.txt +++ /dev/null @@ -1,67 +0,0 @@ -Quicksand Variable Font -======================= - -This download contains Quicksand as both a variable font and static fonts. - -Quicksand is a variable font with this axis: - wght - -This means all the styles are contained in a single file: - Quicksand-VariableFont_wght.ttf - -If your app fully supports variable fonts, you can now pick intermediate styles -that aren’t available as static fonts. Not all apps support variable fonts, and -in those cases you can use the static font files for Quicksand: - static/Quicksand-Light.ttf - static/Quicksand-Regular.ttf - static/Quicksand-Medium.ttf - static/Quicksand-SemiBold.ttf - static/Quicksand-Bold.ttf - -Get started ------------ - -1. Install the font files you want to use - -2. Use your app's font picker to view the font family and all the -available styles - -Learn more about variable fonts -------------------------------- - - https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts - https://variablefonts.typenetwork.com - https://medium.com/variable-fonts - -In desktop apps - - https://theblog.adobe.com/can-variable-fonts-illustrator-cc - https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts - -Online - - https://developers.google.com/fonts/docs/getting_started - https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide - https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts - -Installing fonts - - MacOS: https://support.apple.com/en-us/HT201749 - Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux - Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows - -Android Apps - - https://developers.google.com/fonts/docs/android - https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts - -License -------- -Please read the full license text (OFL.txt) to understand the permissions, -restrictions and requirements for usage, redistribution, and modification. - -You can use them freely in your products & projects - print or digital, -commercial or otherwise. However, you can't sell the fonts on their own. - -This isn't legal advice, please consider consulting a lawyer and see the full -license for all details. diff --git a/src/main/resources/fonts/Quicksand/static/Quicksand-Bold.ttf b/src/main/resources/fonts/Quicksand/static/Quicksand-Bold.ttf deleted file mode 100644 index edfa93f..0000000 Binary files a/src/main/resources/fonts/Quicksand/static/Quicksand-Bold.ttf and /dev/null differ diff --git a/src/main/resources/fonts/Quicksand/static/Quicksand-Light.ttf b/src/main/resources/fonts/Quicksand/static/Quicksand-Light.ttf deleted file mode 100644 index 42ef072..0000000 Binary files a/src/main/resources/fonts/Quicksand/static/Quicksand-Light.ttf and /dev/null differ diff --git a/src/main/resources/fonts/Quicksand/static/Quicksand-Medium.ttf b/src/main/resources/fonts/Quicksand/static/Quicksand-Medium.ttf deleted file mode 100644 index 7eadfad..0000000 Binary files a/src/main/resources/fonts/Quicksand/static/Quicksand-Medium.ttf and /dev/null differ diff --git a/src/main/resources/fonts/Quicksand/static/Quicksand-Regular.ttf b/src/main/resources/fonts/Quicksand/static/Quicksand-Regular.ttf deleted file mode 100644 index cb1596d..0000000 Binary files a/src/main/resources/fonts/Quicksand/static/Quicksand-Regular.ttf and /dev/null differ diff --git a/src/main/resources/fonts/Quicksand/static/Quicksand-SemiBold.ttf b/src/main/resources/fonts/Quicksand/static/Quicksand-SemiBold.ttf deleted file mode 100644 index b280a9d..0000000 Binary files a/src/main/resources/fonts/Quicksand/static/Quicksand-SemiBold.ttf and /dev/null differ diff --git a/src/main/resources/help_black_24dp.svg b/src/main/resources/help_black_24dp.svg new file mode 100644 index 0000000..d9467d5 --- /dev/null +++ b/src/main/resources/help_black_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/image/close_black_24dp.svg b/src/main/resources/image/close_black_24dp.svg deleted file mode 100644 index 5f1267d..0000000 --- a/src/main/resources/image/close_black_24dp.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/main/resources/image/outline_close_black_24dp.png b/src/main/resources/image/outline_close_black_24dp.png deleted file mode 100644 index c7e27e4..0000000 Binary files a/src/main/resources/image/outline_close_black_24dp.png and /dev/null differ diff --git a/src/main/resources/image/pausebutton.png b/src/main/resources/image/pausebutton.png deleted file mode 100644 index ada827c..0000000 Binary files a/src/main/resources/image/pausebutton.png and /dev/null differ diff --git a/src/main/resources/image/pausebutton.png.jpg b/src/main/resources/image/pausebutton.png.jpg deleted file mode 100644 index 5bc668d..0000000 Binary files a/src/main/resources/image/pausebutton.png.jpg and /dev/null differ diff --git a/src/main/resources/image/playbutton.png b/src/main/resources/image/playbutton.png deleted file mode 100644 index 20a5ad7..0000000 Binary files a/src/main/resources/image/playbutton.png and /dev/null differ diff --git a/src/main/resources/image/playbutton.png.jpg b/src/main/resources/image/playbutton.png.jpg deleted file mode 100644 index 57f02d0..0000000 Binary files a/src/main/resources/image/playbutton.png.jpg and /dev/null differ diff --git a/src/main/resources/images/Me.jpg b/src/main/resources/images/Me.jpg deleted file mode 100644 index 5e72669..0000000 Binary files a/src/main/resources/images/Me.jpg and /dev/null differ diff --git a/src/main/resources/images/about/Me.jpg b/src/main/resources/images/about/Me.jpg deleted file mode 100644 index 5e72669..0000000 Binary files a/src/main/resources/images/about/Me.jpg and /dev/null differ diff --git a/src/main/resources/images/about/Me.png b/src/main/resources/images/about/Me.png deleted file mode 100644 index 94a15bb..0000000 Binary files a/src/main/resources/images/about/Me.png and /dev/null differ diff --git a/src/main/resources/images/about/NUSH.png b/src/main/resources/images/about/NUSH.png deleted file mode 100644 index 4196b2d..0000000 Binary files a/src/main/resources/images/about/NUSH.png and /dev/null differ diff --git a/src/main/resources/images/icons/phyton.jpg b/src/main/resources/images/icons/phyton.jpg deleted file mode 100644 index ae67f5d..0000000 Binary files a/src/main/resources/images/icons/phyton.jpg and /dev/null differ diff --git a/src/main/resources/images/icons/phyton.png b/src/main/resources/images/icons/phyton.png deleted file mode 100644 index bb5af26..0000000 Binary files a/src/main/resources/images/icons/phyton.png and /dev/null differ diff --git a/src/main/resources/images/lightning.gif b/src/main/resources/images/lightning.gif deleted file mode 100644 index 4538238..0000000 Binary files a/src/main/resources/images/lightning.gif and /dev/null differ diff --git a/src/main/resources/images/menu/backspace.png b/src/main/resources/images/menu/backspace.png deleted file mode 100644 index 30a5f63..0000000 Binary files a/src/main/resources/images/menu/backspace.png and /dev/null differ diff --git a/src/main/resources/images/menu/copy.png b/src/main/resources/images/menu/copy.png deleted file mode 100644 index 256e736..0000000 Binary files a/src/main/resources/images/menu/copy.png and /dev/null differ diff --git a/src/main/resources/images/menu/cut.jpg b/src/main/resources/images/menu/cut.jpg deleted file mode 100644 index 8e1e5a8..0000000 Binary files a/src/main/resources/images/menu/cut.jpg and /dev/null differ diff --git a/src/main/resources/images/menu/delete.png b/src/main/resources/images/menu/delete.png deleted file mode 100644 index ddd48e2..0000000 Binary files a/src/main/resources/images/menu/delete.png and /dev/null differ diff --git a/src/main/resources/images/menu/duplicate.png b/src/main/resources/images/menu/duplicate.png deleted file mode 100644 index 3190a76..0000000 Binary files a/src/main/resources/images/menu/duplicate.png and /dev/null differ diff --git a/src/main/resources/images/menu/exml.png b/src/main/resources/images/menu/exml.png deleted file mode 100644 index b3228e8..0000000 Binary files a/src/main/resources/images/menu/exml.png and /dev/null differ diff --git a/src/main/resources/images/menu/image.png b/src/main/resources/images/menu/image.png deleted file mode 100644 index e221efe..0000000 Binary files a/src/main/resources/images/menu/image.png and /dev/null differ diff --git a/src/main/resources/images/menu/new.jpg b/src/main/resources/images/menu/new.jpg deleted file mode 100644 index 65dd52c..0000000 Binary files a/src/main/resources/images/menu/new.jpg and /dev/null differ diff --git a/src/main/resources/images/menu/new.png b/src/main/resources/images/menu/new.png deleted file mode 100644 index 2450b11..0000000 Binary files a/src/main/resources/images/menu/new.png and /dev/null differ diff --git a/src/main/resources/images/menu/open.png b/src/main/resources/images/menu/open.png deleted file mode 100644 index e46a284..0000000 Binary files a/src/main/resources/images/menu/open.png and /dev/null differ diff --git a/src/main/resources/images/menu/paste.png b/src/main/resources/images/menu/paste.png deleted file mode 100644 index 280203f..0000000 Binary files a/src/main/resources/images/menu/paste.png and /dev/null differ diff --git a/src/main/resources/images/menu/save.png b/src/main/resources/images/menu/save.png deleted file mode 100644 index 541f7e1..0000000 Binary files a/src/main/resources/images/menu/save.png and /dev/null differ diff --git a/src/main/resources/images/menu/saveAs.png b/src/main/resources/images/menu/saveAs.png deleted file mode 100644 index 65bcc44..0000000 Binary files a/src/main/resources/images/menu/saveAs.png and /dev/null differ diff --git a/src/main/resources/images/menu/xml.png b/src/main/resources/images/menu/xml.png deleted file mode 100644 index 19266bd..0000000 Binary files a/src/main/resources/images/menu/xml.png and /dev/null differ diff --git a/src/main/resources/images/splash/lightning.gif b/src/main/resources/images/splash/lightning.gif deleted file mode 100644 index 4538238..0000000 Binary files a/src/main/resources/images/splash/lightning.gif and /dev/null differ diff --git a/src/main/resources/images/widgets/bulb.png b/src/main/resources/images/widgets/bulb.png deleted file mode 100644 index 9b33f4b..0000000 Binary files a/src/main/resources/images/widgets/bulb.png and /dev/null differ diff --git a/src/main/resources/images/widgets/cell.png b/src/main/resources/images/widgets/cell.png deleted file mode 100644 index 5982053..0000000 Binary files a/src/main/resources/images/widgets/cell.png and /dev/null differ diff --git a/src/main/resources/images/widgets/resistor.png b/src/main/resources/images/widgets/resistor.png deleted file mode 100644 index 26b0113..0000000 Binary files a/src/main/resources/images/widgets/resistor.png and /dev/null differ diff --git a/src/main/resources/mainframe.fxml b/src/main/resources/mainframe.fxml deleted file mode 100644 index 1385674..0000000 --- a/src/main/resources/mainframe.fxml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/image/delete_black_24dp.svg b/src/main/resources/pause_black_24dp.svg similarity index 54% rename from src/main/resources/image/delete_black_24dp.svg rename to src/main/resources/pause_black_24dp.svg index 90a74fa..4104cb0 100644 --- a/src/main/resources/image/delete_black_24dp.svg +++ b/src/main/resources/pause_black_24dp.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/main/resources/play_arrow_black_24dp.svg b/src/main/resources/play_arrow_black_24dp.svg new file mode 100644 index 0000000..178bd3a --- /dev/null +++ b/src/main/resources/play_arrow_black_24dp.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/main/resources/style/angle-picker.css b/src/main/resources/style/angle-picker.css deleted file mode 100644 index bfd0ecb..0000000 --- a/src/main/resources/style/angle-picker.css +++ /dev/null @@ -1,8 +0,0 @@ -.angle-picker { - -} - -.angle-picker .text-field { - -fx-highlight-fill: #e0e0e0; - -fx-faint-focus-color: #6a696c; -} \ No newline at end of file diff --git a/src/main/resources/style/splash.css b/src/main/resources/style/splash.css deleted file mode 100644 index 4d6a414..0000000 --- a/src/main/resources/style/splash.css +++ /dev/null @@ -1,34 +0,0 @@ -.progress-bar { - -fx-indeterminate-bar-length: 60; - -fx-indeterminate-bar-escape: true; - -fx-indeterminate-bar-flip: true; - -fx-indeterminate-bar-animation-time: 2; -} - -.progress-bar > .track { - -fx-text-box-border: black; - -fx-control-inner-background: black; -} -.progress-bar .bar { - -fx-padding:1px; - -fx-background-insets:0; - -fx-background-color: white; - -fx-background-radius: 2; -} - -.label { - -fx-padding: 2px; - -fx-text-fill: white; -} - -.progress-bar:indeterminate .bar { - -fx-background-color: linear-gradient(to left, transparent, -fx-accent); -} - -.progress-bar:disabled { - -fx-opacity: -fx-disabled-opacity; -} - -.pane { - -fx-background-color:black; -} \ No newline at end of file diff --git a/src/main/resources/style/style.css b/src/main/resources/style/style.css deleted file mode 100644 index 9f10df3..0000000 --- a/src/main/resources/style/style.css +++ /dev/null @@ -1,21 +0,0 @@ -#buttonlink { - -fx-underline: false; - -fx-border-color: transparent; - -fx-text-alignment: center; -} - -.menu-button { - -fx-font-weight: normal; -} - -.menu-bar .menu-button:hover, .menu-bar .menu-button:focused, .menu-bar .menu-button:showing { - -fx-font-weight: bold; -} - -.menu-item { - -fx-font-weight: normal; -} - -.menu-item:hover{ - -fx-font-weight: bold; -} diff --git a/src/main/resources/stylesheets/about.css b/src/main/resources/stylesheets/about.css deleted file mode 100644 index 227a9ee..0000000 --- a/src/main/resources/stylesheets/about.css +++ /dev/null @@ -1,58 +0,0 @@ -#parent { - -fx-background-color: white; -} - -.label36 { - -fx-font-family: "Calibri Light"; - -fx-font-size: 36; -} - -.label18 { - -fx-font-family: "Calibri Light"; - -fx-font-size: 18; -} - -.label16 { - -fx-font-family: "Calibri Light"; - -fx-font-size: 16; -} - -.label14 { - -fx-font-family: "Calibri Light"; - -fx-font-size: 14; -} - -.label12 { - -fx-font-family: "Calibri Light"; - -fx-font-size: 12; -} - -.candara18 { - -fx-font-family: "Candara Light"; - -fx-font-size: 18; -} - -.candara16 { - -fx-font-family: "Candara Light"; - -fx-font-size: 16; -} - -.candara14 { - -fx-font-family: "Candara Light"; - -fx-font-size: 14; -} - -.candara12 { - -fx-font-family: "Candara Light"; - -fx-font-size: 12; -} - -.title { - -fx-font-family: "Corbel Light"; - -fx-font-size: 36; -} - -.reference { - -fx-font-family: "Bookman Old Style"; - -fx-font-size: 12; -} \ No newline at end of file diff --git a/src/main/resources/stylesheets/menubar.css b/src/main/resources/stylesheets/menubar.css deleted file mode 100644 index ff23b96..0000000 --- a/src/main/resources/stylesheets/menubar.css +++ /dev/null @@ -1,40 +0,0 @@ -.menu-button { - -fx-font-weight: normal; -} - -.menu-bar .menu-button:hover, .menu-bar .menu-button:focused, .menu-bar .menu-button:showing { - -fx-font-weight: bold; -} - -.menu-item { - -fx-font-weight: normal; -} - -.menu-item:hover .menu-item:focused .menu-item:showing { - -fx-font-weight: bold; - -fx-background-color: white; - -fx-text-fill: black; -} - -#menu-bar { - -fx-background-color: white; -} - -.topbar { - -fx-background-color: white; -} - -.topLabel { - -fx-text-fill: black; - -fx-background-color: white; - -fx-font-family: "Calibri Light"; - -fx-font-size: 16; -} - -#translator { - -fx-background-color: white; -} - -.home { - -fx-font-size: 14; -} \ No newline at end of file diff --git a/src/main/resources/stylesheets/splash.css b/src/main/resources/stylesheets/splash.css deleted file mode 100644 index bae6d84..0000000 --- a/src/main/resources/stylesheets/splash.css +++ /dev/null @@ -1,36 +0,0 @@ -.progress-bar { - -fx-indeterminate-bar-length: 60; - -fx-indeterminate-bar-escape: true; - -fx-indeterminate-bar-flip: true; - -fx-indeterminate-bar-animation-time: 2; -} - -.progress-bar > .track { - -fx-text-box-border: black; - -fx-control-inner-background: black; - -fx-background-color: black; -} - -.progress-bar .bar { - -fx-padding: 1px; - -fx-background-insets: 0; - -fx-background-color: white; - -fx-background-radius: 2; -} - -.label { - -fx-padding: 2px; - -fx-text-fill: white; -} - -.progress-bar:indeterminate .bar { - -fx-background-color: linear-gradient(to left, transparent, -fx-accent); -} - -.progress-bar:disabled { - -fx-opacity: -fx-disabled-opacity; -} - -.pane { - -fx-background-color: black; -} \ No newline at end of file diff --git a/src/main/resources/stylesheets/style.css b/src/main/resources/stylesheets/style.css deleted file mode 100644 index 6b26205..0000000 --- a/src/main/resources/stylesheets/style.css +++ /dev/null @@ -1,18 +0,0 @@ -#buttonlink { - -fx-underline: false; - -fx-border-color: transparent; - -fx-text-alignment: center; -} - -.tab:selected .tab-label .tablabel { - -fx-background-color: #add8e6; -} - -.focusedTab { - -fx-background-color: #add8e6; -} - -.unfocusedTab { - -fx-background-color: #ffffbf; -} - diff --git a/src/main/resources/stylesheets/tab.css b/src/main/resources/stylesheets/tab.css deleted file mode 100644 index af5c868..0000000 --- a/src/main/resources/stylesheets/tab.css +++ /dev/null @@ -1,30 +0,0 @@ -.statusbar { - -fx-border-color: black; - -fx-border-width: 0.2px; -} - -.widgetLabel { - -fx-background-color: white; -} - -.header { - -fx-font-family: "Calibri Light", sans-serif; - -fx-font-size: 18; - -fx-text-fill: #9f9f9f; -} - -.below { - -fx-font-size: 11; - -fx-font-family: "Courier New"; - -fx-text-fill: #9f9f9f; -} - -.tableHeader { - -fx-font-family: "Calibri Light", sans-serif; - -fx-font-size: 14; -} - -.table { - -fx-font-family: "Calibri Light", sans-serif; - -fx-font-size: 12; -} \ No newline at end of file diff --git a/src/main/resources/stylesheets/tooltip.css b/src/main/resources/stylesheets/tooltip.css deleted file mode 100644 index b42ea5f..0000000 --- a/src/main/resources/stylesheets/tooltip.css +++ /dev/null @@ -1,64 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Quicksand&display=swap'); - -@font-face { - font-family: Quicksand; - src: url("../fonts/Quicksand/Quicksand-VariableFont_wght.ttf") format('truetype'); -} - -@font-face { - font-family: Quicksand Bold; - src: url("../fonts/Quicksand/static/Quicksand-Bold.ttf") format('truetype'); -} - -@font-face { - font-family: Quicksand Light; - src: url("../fonts/Quicksand/static/Quicksand-Light.ttf") format('truetype'); -} - -@font-face { - font-family: Quicksand Medium; - src: url("../fonts/Quicksand/static/Quicksand-Medium.ttf") format('truetype'); -} - -@font-face { - font-family: Quicksand Regular; - src: url("../fonts/Quicksand/static/Quicksand-Regular.ttf") format('truetype'); -} - -@font-face { - font-family: Quicksand SemiBold; - src: url("../fonts/Quicksand/static/Quicksand-SemiBold.ttf") format('truetype'); -} - -@font-face { - font-family: "Math Major", sans-serif; - src: url("../fonts/MathMajor/Math Major.ttf") format('truetype'); -} - -body { - font-family: Quicksand, sans-serif; -} - -h3 { - color: #F4AC41; - margin-bottom: 0; - font-family: Quicksand SemiBold, sans-serif; -} - -b { - font-family: "Math Major", sans-serif; -} - -p { - font-family: Quicksand Regular, sans-serif; - height: 150px; - padding: 12px 20px; - box-sizing: border-box; - border-radius: 10px; - background-color: #f8f8f8; - resize: none; - border: 6px solid #CCC; - outline: none; - width: 100%; - color: black; -} \ No newline at end of file diff --git a/src/main/resources/stylesheets/widgets.css b/src/main/resources/stylesheets/widgets.css deleted file mode 100644 index ad0d694..0000000 --- a/src/main/resources/stylesheets/widgets.css +++ /dev/null @@ -1,16 +0,0 @@ -.parallel { - -fx-stroke-color: black; - -fx-stroke-width: 4px; - -fx-fill: transparent; -} - -#content { - -fx-background-color: white; -} - -.focused { - -fx-border-width: 1px; - -fx-border-color: #007aff; - -fx-border-style: dashed; - -fx-border-radius: 7px; -} \ No newline at end of file diff --git a/src/main/resources/tab.fxml b/src/main/resources/tab.fxml deleted file mode 100644 index 40c7ed8..0000000 --- a/src/main/resources/tab.fxml +++ /dev/null @@ -1,42 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/main/resources/videos/test.mp4 b/src/main/resources/videos/test.mp4 deleted file mode 100644 index 230c22a..0000000 Binary files a/src/main/resources/videos/test.mp4 and /dev/null differ