From d0c3cbbb01c524682c2254cf88887f3fe15dd7db Mon Sep 17 00:00:00 2001 From: chaorace Date: Wed, 18 May 2016 19:17:25 +0000 Subject: [PATCH] Big! Minizes to taskbar now. Automatically launches minimized if started using arguments --- src/main/java/GUI.scala | 132 ++++++++++++++++++++++++++++-------- src/main/java/Main.scala | 17 +++-- src/main/resources/icon.png | Bin 0 -> 468 bytes 3 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 src/main/resources/icon.png diff --git a/src/main/java/GUI.scala b/src/main/java/GUI.scala index 76d6d4c..c20bb8d 100644 --- a/src/main/java/GUI.scala +++ b/src/main/java/GUI.scala @@ -1,12 +1,19 @@ +import java.awt.event.{ActionEvent, ActionListener} +import java.awt.{MenuItem, PopupMenu, SystemTray, TrayIcon} +import java.io.FileInputStream +import java.lang.Boolean +import javafx.beans.value.{ChangeListener, ObservableValue} +import javax.imageio.ImageIO + import org.jnativehook.GlobalScreen -import scalafx.application.JFXApp +import scalafx.application.{JFXApp, Platform} import scalafx.application.JFXApp.PrimaryStage import scalafx.geometry.Insets import scalafx.scene.Scene import scalafx.scene.control.Alert.AlertType import scalafx.scene.control._ -import scalafx.scene.layout.{VBox, HBox, FlowPane, BorderPane} +import scalafx.scene.layout.{BorderPane, FlowPane, HBox, VBox} import scalafx.scene.paint.Color import scalafx.Includes._ import scalafx.scene.text.Font @@ -15,16 +22,21 @@ import scalafx.scene.text.Font /** * Created by Chris on 8/4/2015. This is the GUI wrapper for the engine. This can be completely bypassed by console commands */ -class Gui extends JFXApp { +class Gui(startHidden: Boolean, config: Config) extends JFXApp { + Platform.implicitExit = false + var engineThread:Thread = null + var engine = false + + //GUI inputfields + val pollingField = new InputField("Polling Rate", Tooltip(Main.pollingRateDesc)){inputBox.text = config.pollingRate.getOrElse("").toString} + val startupField = new InputField("Tolerance", Tooltip(Main.startupThresholdDesc)){inputBox.text = config.startupThreshold.getOrElse("").toString} + val giveupField = new InputField("Sensitivity", Tooltip(Main.giveupThresholdDesc)){inputBox.text = config.giveupThreshold.getOrElse("").toString} + val dragField = new InputField("Friction", Tooltip(Main.dragDesc)){inputBox.text = config.drag.getOrElse("").toString} + //Gui setup stage = new PrimaryStage { title = s"${Main.name} by ${Main.author}" scene = new Scene { - val pollingField = new InputField("Polling Rate", Tooltip(Main.pollingRateDesc)) - val startupField = new InputField("Tolerance", Tooltip(Main.startupThresholdDesc)) - val giveupField = new InputField("Sensitivity", Tooltip(Main.giveupThresholdDesc)) - val dragField = new InputField("Friction", Tooltip(Main.dragDesc)) - fill = Color.Beige content = new BorderPane { padding = Insets(15) @@ -53,35 +65,101 @@ class Gui extends JFXApp { } //Engine starting logic. Converts fields to values to be used by the engine bottom = new ToggleButton{ - var engineThread:Thread = null - text <== when(selected) choose "Stop/Reload Settings" otherwise "Start" - onAction = handle {selected.value match{ - case true => - try{ - engineThread = new Thread(new Engine(new Config(pollingField.value, startupField.value, giveupField.value, dragField.value))) - engineThread.start() - }catch{ - case e: NumberFormatException => - new Alert(AlertType.Error, "Bad value entry").show() - selected.value = false - } - case false => engineThread.stop() - }} - //If the user tries closing the GUI while the engine is running, the thread gets shut down - onCloseRequest = handle { - GlobalScreen.unregisterNativeHook() - if(engineThread != null) engineThread.stop() + + def updateText = text = if(engine) "Stop/Reload Settings" else "Start" + updateText + onAction = handle { + engineToggler() + updateText + } + onShowing = handle{ + updateText } } + //If the user minimizes the GUI, send it to the system tray + iconified.addListener(new ChangeListener[Boolean] { + override def changed(observable: ObservableValue[_ <: Boolean], oldValue: Boolean, newValue: Boolean): Unit = { + if(iconified.value){toggleTray(true)} + } + }) + //If the user tries closing the GUI while the engine is running, the thread gets shut down + onCloseRequest = handle { + GlobalScreen.unregisterNativeHook() + toggleTray(false) + if(engineThread != null) engineThread.stop() + Platform.exit() + } + } } } + + //Tray icon setup + val tray = SystemTray.getSystemTray + val defaultAction = new ActionListener(){ + override def actionPerformed(e: ActionEvent): Unit = { + Platform.runLater(toggleTray(false)) + } + } + val engineAction = new ActionListener(){ + override def actionPerformed(e: ActionEvent): Unit = { + Platform.runLater(engineToggler()) + } + } + val showGuiItem = new MenuItem("Show GUI") + showGuiItem.addActionListener(defaultAction) + val toggleEngineItem = new MenuItem("On/Off Toggle") + toggleEngineItem.addActionListener(engineAction) + val popup = new PopupMenu() + popup.add(showGuiItem) + popup.add(toggleEngineItem) + val image = ImageIO.read(getClass.getResourceAsStream("icon.png")) + val trayIcon = new TrayIcon(image, Main.name, popup) + trayIcon.addActionListener(defaultAction) + + //If set to start hidden, start hidden + if(startHidden){ + toggleTray(true) + } + + //Hides and shows GUI + def toggleTray(t: Boolean): Unit ={ + //There were basically no references on the internet for how to do this using JFX8, so AWT it is + if(t){ + //If true, hide stage and insert tray icon. Otherwise, show stage and remove tray icon + + if(SystemTray.isSupported){ + stage.hide() + tray.add(trayIcon) + } + }else{ + stage.setIconified(false) + stage.show() + tray.remove(trayIcon) + } + } + + def engineToggler(): Unit ={ + engine match{ + case false => + engine = true + try{ + engineThread = new Thread(new Engine(new Config(pollingField.value, startupField.value, giveupField.value, dragField.value))) + engineThread.start() + }catch{ + case e: NumberFormatException => + new Alert(AlertType.Error, "Bad value entry").show() + engine = false + } + case true => engineThread.stop(); engine = false + } + } } class InputField(description: String, tt: Tooltip) extends HBox { //Template for input fields with a built-in description and tooltip padding = Insets(5) - private val inputBox = new TextField { + val inputBox = new TextField { maxWidth = 40 } children = List(new Label(description + ": ") { diff --git a/src/main/java/Main.scala b/src/main/java/Main.scala index 2a37547..33e83a3 100644 --- a/src/main/java/Main.scala +++ b/src/main/java/Main.scala @@ -5,9 +5,11 @@ import com.beust.jcommander.{JCommander, Parameter} * Created by Chris on 8/4/2015. Main execution environment */ -case class Config(pollingRate: Option[Double], startupThreshold: Option[Double], giveupThreshold: Option[Double], drag: Option[Double]) +case class Config(pollingRate: Option[Double] = None, startupThreshold: Option[Double] = None, giveupThreshold: Option[Double] = None, drag: Option[Double] = None) + object Main extends App { + //Strings final val name = "Virtual Trackball" final val version = "1.0" @@ -54,7 +56,7 @@ object Main extends App { override def main(args: Array[String]): Unit = { //If there are no command line arguments, boot to gui if (args.isEmpty) { - val gui = new Gui + val gui = new Gui(false, Config()) gui.main(new Array[String](0)) } else { //An unreasonable amount of work to get a generally crummy library to play nice with Scala. 0/10 experience, would not recommend JCommander for Scala use @@ -83,14 +85,15 @@ object Main extends App { } //Start engine with provided parameters - - val engine = new Engine(new Config(pollingRate, startupThreshold, giveupThreshold, drag)) - engine.run() + val gui = new Gui(true, new Config(pollingRate, startupThreshold, giveupThreshold, drag)) +// val engine = new Engine(new Config(pollingRate, startupThreshold, giveupThreshold, drag)) +// engine.run() }catch{ //If anything goes wrong at all, give up and run with defaults case e: Exception => - val engine = new Engine(new Config(None, None, None, None)) - engine.run() + val gui = new Gui(true, Config()) +// val engine = new Engine(Config()) +// engine.run() } } } diff --git a/src/main/resources/icon.png b/src/main/resources/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..12fa3354856144a0165bc0cfce9036a05b191f16 GIT binary patch literal 468 zcmV;_0W1EAP)EL3i zt4ouiLjzsn9}x1moZxFrr(8J0d(ZPb@4e^zZg9_1>%cNUi-`EWiHi-w7Vrdk41CJ% z=fD+k84-6?zP2WA122IdFi#P{3Gf0Ksc#};{$GISb7|AuyWPnK;_s%0Mzb5t|n8*l>*E8H;m07~E- z_yBxeb@*8QtPWNaSZ`uw5`$!qt3LPwd<6D@rn*S$POD=Cn#qh(^ViC0q=&#M@N>;b zC2)}9_rQ;cxc$e85i!pq`#I-6@H*4yKs*2Zz3RfPA5(1moc{m;u