Scala Swing Newbie - scala

Im trying to create an login window for an app im doing. I have searched all day for an example but I cant seem to find anything that helps. My basic structure is as follows:
// App.scala
object App extends SimpleSwingApplication {
val ui = new BorderPanel {
//content
}
def top = new MainFrame {
title = "title"
contents = ui
}
}
So whats the strategy to create a login box without the mainframe showing and closing it after login and displaying the mainframe. thanks

Here is working example. Took it from one of my projects and adjusted a little bit for you:
import swing._
import scala.swing.BorderPanel.Position._
object App extends SimpleSwingApplication {
val ui = new BorderPanel {
//content
}
def top = new MainFrame {
title = "title"
contents = ui
}
val auth = new LoginDialog().auth.getOrElse(throw new IllegalStateException("You should login!!!"))
}
case class Auth(userName: String, password: String)
class LoginDialog extends Dialog {
var auth: Option[Auth] = None
val userName = new TextField
val password = new PasswordField
title = "Login"
modal = true
contents = new BorderPanel {
layout(new BoxPanel(Orientation.Vertical) {
border = Swing.EmptyBorder(5,5,5,5)
contents += new Label("User Name:")
contents += userName
contents += new Label("Password:")
contents += password
}) = Center
layout(new FlowPanel(FlowPanel.Alignment.Right)(
Button("Login") {
if (makeLogin()) {
auth = Some(Auth(userName.text, password.text))
close()
} else {
Dialog.showMessage(this, "Wrong username or password!", "Login Error", Dialog.Message.Error)
}
}
)) = South
}
def makeLogin() = true // here comes you login logic
centerOnScreen()
open()
}
As you can see I'm generally using modal dialog, so it will block during application initialization. There 2 outcomes: either user makes successful login and sees your main frame or he closes login dialog and IllegalStateException would be thrown.

Related

Using Zxing Library with Jetpack compose

I am trying to implement qr scanner using zxing library. For this, i have added a button on screen, and on click of it, i am launching scanner as below
Button(
onClick = {
val intentIntegrator = IntentIntegrator(context)
intentIntegrator.setPrompt(QrScanLabel)
intentIntegrator.setOrientationLocked(true)
intentIntegrator.initiateScan()
},
modifier = Modifier
.fillMaxWidth()
) {
Text(
text = QrScanLabel
)
}
but, it launches an intent, which expects onActivityResult method to get back the results. And Jetpack compose uses rememberLauncherForActivityResult like below
val intentLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult()
) {
if (it.resultCode != RESULT_OK) {
return#rememberLauncherForActivityResult
}
...
}
but how do we integrate both things together here?
I make a provisional solution with same library:
Gradle dependencies:
implementation('com.journeyapps:zxing-android-embedded:4.1.0') { transitive = false }
implementation 'com.google.zxing:core:3.4.0'
My new Screen with jetpack compose and camera capture, that works for my app:
#Composable
fun AdminClubMembershipScanScreen(navController: NavHostController) {
val context = LocalContext.current
var scanFlag by remember {
mutableStateOf(false)
}
val compoundBarcodeView = remember {
CompoundBarcodeView(context).apply {
val capture = CaptureManager(context as Activity, this)
capture.initializeFromIntent(context.intent, null)
this.setStatusText("")
capture.decode()
this.decodeContinuous { result ->
if(scanFlag){
return#decodeContinuous
}
scanFlag = true
result.text?.let { barCodeOrQr->
//Do something and when you finish this something
//put scanFlag = false to scan another item
scanFlag = false
}
//If you don't put this scanFlag = false, it will never work again.
//you can put a delay over 2 seconds and then scanFlag = false to prevent multiple scanning
}
}
}
AndroidView(
modifier = Modifier,
factory = { compoundBarcodeView },
)
}
Since zxing-android-embedded:4.3.0 there is a ScanContract, which can be used directly from Compose:
val scanLauncher = rememberLauncherForActivityResult(
contract = ScanContract(),
onResult = { result -> Log.i(TAG, "scanned code: ${result.contents}") }
)
Button(onClick = { scanLauncher.launch(ScanOptions()) }) {
Text(text = "Scan barcode")
}
Addendum to the accepted answer
This answer dives into the issues commented on by #Bharat Kumar and #Jose Pose S
in the accepted answer.
I basically just implemented the accepted answer in my code and then added the following code just after the defining compundBarCodeView
DisposableEffect(key1 = "someKey" ){
compoundBarcodeView.resume()
onDispose {
compoundBarcodeView.pause()
}
}
this makes sure the scanner is only active while it is in the foreground and unbourdens our device.
TL;DR
In escence even after you scan a QR code successfully and leave the scanner screen, the barcodeview will "haunt" you by continuing to scan from the backstack. which you usually dont want. And even if you use a boolean flag to prevent the scanner from doing anything after the focus has switched away from the scanner it will still burden your processor and slow down your UI since there is still a process constantly decrypting hi-res images in the background.
I have a problem, I've the same code as you, but i don't know why it's showing me a black screen
Code AddProduct
#ExperimentalPermissionsApi
#Composable
fun AddProduct(
navController: NavController
) {
val context = LocalContext.current
var scanFlag by remember {
mutableStateOf(false)
}
val compoundBarcodeView = remember {
CompoundBarcodeView(context).apply {
val capture = CaptureManager(context as Activity, this)
capture.initializeFromIntent(context.intent, null)
this.setStatusText("")
capture.decode()
this.decodeContinuous { result ->
if(scanFlag){
return#decodeContinuous
}
scanFlag = true
result.text?.let { barCodeOrQr->
//Do something
}
scanFlag = false
}
}
}
AndroidView(
modifier = Modifier.fillMaxSize(),
factory = { compoundBarcodeView },
)
}

Scala Swing skips repaint of the Frame

I am currently working on an implementation of the game Othello in Scala and so far it's working pretty nicely. When implementing the GUI though (using Scala Swing) I've stumbled across an issue that I can't seem to fix.
When playing against the computer opponent the Frame seems to be repainted only when the bot is done making its move.
The game is also playable through the terminal and doing so updates the Frame properly every time regardless of the player configuration (Player vs Player or Player vs Computer). Also playing player vs player using the GUI exclusively presents no issues at all.
It might be an oversight on my behalf, but so far I am unable to find a solution and would greatly appreciate any help.
So far I have tried various combinations of revalidating and repainting the individual panels, adding and removing listeners, changing my implementation of the reactor pattern to the one provided by Scala Swing, adding Thread.sleep to see if there could be a scheduling conflict of sorts.
import java.awt.Color
import othello.controller.Controller
import javax.swing.ImageIcon
import javax.swing.border.LineBorder
import scala.swing.event.MouseClicked
import scala.swing.{BorderPanel, BoxPanel, Dimension, FlowPanel, GridPanel, Label, Orientation}
class TablePanel(controller: Controller) extends FlowPanel {
val sides = 32
val sidesColor: Color = Color.lightGray
val squareSize = 52
def tableSize: Int = controller.board.size
def edgeLength: Int = tableSize * squareSize
def rows: BoxPanel = new BoxPanel(Orientation.Vertical) {
background = sidesColor
preferredSize = new Dimension(sides, edgeLength)
contents += new Label {
preferredSize = new Dimension(sides, sides)
}
contents += new GridPanel(tableSize, 1) {
background = sidesColor
for { i <- 1 to rows } contents += new Label(s"$i")
}
}
def columns: GridPanel = new GridPanel(1, tableSize) {
background = sidesColor
preferredSize = new Dimension(edgeLength, sides)
for { i <- 0 until columns } contents += new Label(s"${(i + 65).toChar}")
}
def table: GridPanel = new GridPanel(tableSize, tableSize) {
background = new Color(10, 90, 10)
for {
col <- 0 until columns
row <- 0 until rows
} contents += square(col, row)
}
def square(row: Int, col: Int): Label = new Label {
border = new LineBorder(new Color(30, 30, 30, 140), 1)
preferredSize = new Dimension(squareSize, squareSize)
icon = controller.board.valueOf(col, row) match {
case -1 => new ImageIcon("resources/big_dot.png")
case 0 => new ImageIcon("resources/empty.png")
case 1 => new ImageIcon("resources/black_shadow.png")
case 2 => new ImageIcon("resources/white_shadow.png")
}
listenTo(mouse.clicks)
reactions += {
case _: MouseClicked =>
if (controller.options.contains((col, row))) controller.set(col, row)
else if (controller.board.gameOver) controller.newGame()
else controller.highlight()
}
}
def redraw(): Unit = {
contents.clear
contents += new BorderPanel {
add(rows, BorderPanel.Position.West)
add(new BoxPanel(Orientation.Vertical) {
contents += columns
contents += table
}, BorderPanel.Position.East)
}
repaint
}
}
import scala.swing._
import othello.controller._
import othello.util.Observer
import scala.swing.event.Key
class SwingGui(controller: Controller) extends Frame with Observer {
controller.add(this)
lazy val tablePanel = new TablePanel(controller)
lazy val mainFrame: MainFrame = new MainFrame {
title = "Othello"
menuBar = menus
contents = tablePanel
centerOnScreen
// peer.setAlwaysOnTop(true)
resizable = false
visible = true
}
def menus: MenuBar = new MenuBar {
contents += new Menu("File") {
mnemonic = Key.F
contents += new MenuItem(Action("New Game") {
controller.newGame()
})
contents += new MenuItem(Action("Quit") {
controller.exit()
})
}
contents += new Menu("Edit") {
mnemonic = Key.E
contents += new MenuItem(Action("Undo") {
controller.undo()
})
contents += new MenuItem(Action("Redo") {
controller.redo()
})
}
contents += new Menu("Options") {
mnemonic = Key.O
contents += new MenuItem(Action("Highlight possible moves") {
controller.highlight()
})
contents += new MenuItem(Action("Reduce board size") {
controller.resizeBoard("-")
})
contents += new MenuItem(Action("Increase board size") {
controller.resizeBoard("+")
})
contents += new MenuItem(Action("Reset board size") {
controller.resizeBoard(".")
})
contents += new Menu("Game mode") {
contents += new MenuItem(Action("Player vs. Computer") {
controller.setupPlayers("1")
})
contents += new MenuItem(Action("Player vs. Player") {
controller.setupPlayers("2")
})
}
}
}
def update: Boolean = {
tablePanel.redraw()
mainFrame.pack
mainFrame.centerOnScreen
mainFrame.repaint
true
}
}
The expected behavior is a repainted Frame on every turn. The actual result is the Frame only being repainted after the opponent made a move. It only happens when playing player vs bot exclusively through clicking the UI.
I don't think the problem is in the code you have shown, but I would bet that you are blocking the "event dispatch thread" (the UI thread) with your AI computation for the computer player.
In a Swing app, there is a special thread called the "event dispatch thread" that is responsible for handling messages from the O/S including dealing with repaint messages. All UI event handlers will be invoked on this thread. If you use that thread to do any computations which take a long time (such as a computer move in a game like this), any UI updates will be blocked until the thread becomes free.
This tutorial has more info: https://docs.oracle.com/javase/tutorial/uiswing/concurrency/index.html
You need to move the AI onto a background thread and release the event dispatch thread to deal with repaints. This might be tricky to implement if you are not familiar with multi-threaded programs! Good luck.
As Rich suggested, the solution was concurrency. Making the search algorithm a Future-type solved the issue for now. It might not be the cleanest implementation as it is the first time I'm using Future, but this is what it looks like now:
def selectAndSet(): Future[_] = Future(if (!board.gameOver && player.isBot) {
new MoveSelector(this).select() match {
case Success(square) => set(square)
case _ => omitPlayer()
}
selectAndSet()
})(ExecutionContext.global)

ScrollPane automatically scrolls down, but not all the way down in Scala Swing

I'm having an issue with a project I'm working on. The original code is too big to poste here but I wrote a short version which has the same issue.
Basically, I wrote some kind of chatbox that adds Labels containing the replies to a BoxPanel, one after another. Ideally the user wouldn't need to scroll manually, the scrolling would happen automatically and show the very last reply of the chat.
The issue is that when using scrollBar.peer.setValue(scrollBar.peer.getMaximum),
the chat doesn't automatically scroll all the way down, it never shows the very last line of the chat, even after I revalidate() the chat before scrolling down.
this is what the code looks like:
import scala.swing._
import scala.swing.event._
class testUI extends MainFrame {
title = "testScroll"
preferredSize = new Dimension(400, 400)
val chat = new BoxPanel(Orientation.Vertical)
val inputField = new TextField {
listenTo(mouse.clicks, this.keys)
reactions += {
case KeyPressed(_, Key.Enter, _, _) => if (this.text != "") { printToChat(this.text) }
}
}
val scrollPanel = new ScrollPane(chat)
var scrollBar = scrollPanel.verticalScrollBar
val window = new BorderPanel {
add(inputField, BorderPanel.Position.North)
add(scrollPanel, BorderPanel.Position.Center)
}
contents = window
def printToChat(s: String) {
chat.contents += new Label(s) {
horizontalAlignment = Alignment.Right
border = Swing.EmptyBorder(20, 20, 0, 0) //(top, left, bottom, right)
}
chat.revalidate()
scrollToBottom()
inputField.text = ""
}
def scrollToBottom() {
scrollBar.peer.setValue(scrollBar.peer.getMaximum)
}
}
object testUI {
def main(args: Array[String]) {
val ui = new testUI
ui.visible = true
}
}
Thank you very much in advance :)
EDIT: I solved my issue thanks to camickr by wrapping my previous line of code with Swing.onEDT:
Swing.onEDT(scrollBar.peer.setValue(scrollBar.peer.getMaximum))
instead of just:
scrollBar.peer.setValue(scrollBar.peer.getMaximum)

Scalafx. Start the Alert by Timer

I have a main window with some information. I can't start the Alert by timer. I need to show alert every 10 seconds(with working main window) and by the alert's button change the Label's text in the main window.
I have this code, but it's not work:
object main extends JFXApp {
stage = new JFXApp.PrimaryStage() {
scene = new Scene {
val MyLabel = new Label("SomeText")
root = new VBox {
children = MyLabel
}
}
val ButtonTypeOne = new ButtonType("Change the text")
val ButtonTypeTwo = new ButtonType("No")
val alert1 = new Alert(AlertType.Warning) {
initOwner(stage)
title = "Warning!!!"
headerText = "Header"
contentText = "Do you need to change the text?"
buttonTypes = Seq(ButtonTypeOne, ButtonTypeTwo, ButtonType.Cancel)
}
val result = alert1.showAndWait()
val timerA = new PauseTransition(Duration(5000))
timerA.onFinished = { _ =>
result match {
case Some(ButtonTypeOne) => /*Here I need to change text in MyLabel */
case Some(ButtonTypeTwo) => None
case _ => None
}
timerA.playFromStart()
}
timerA.play
}
}
There are a few different problems in your code:
You display the alert only once, regardless of what's happening with the timer.
The code that reacts to the result of the alert in the timer always looks at the same initial result.
Unfortunately, even if you move the val result = alert1.showAndWait inside of the timer's animation update event, JavaFX makes it illegal to call showAndWait within such a routine.
The solution is to create an onHidden event handler for alert1, which reacts to it being closed. The button type used to close the dialog is then stored in alert1.result, so you can use that to determine what action to take.
I've added a StringProperty to assist with the changing of the label value. The following example should be a good starting point for what you want to achieve...
import scalafx.Includes._
import scalafx.animation.PauseTransition
import scalafx.application.JFXApp
import scalafx.application.JFXApp.PrimaryStage
import scalafx.beans.property.StringProperty
import scalafx.scene.Scene
import scalafx.scene.layout.VBox
import scalafx.scene.control.{Alert, ButtonType, Label}
import scalafx.scene.control.Alert.AlertType
import scalafx.util.Duration
object main extends JFXApp {
// String property for the text in myLabel.
val strProp = StringProperty("Some Text")
// Declare this so that it's accessible from timerA.onFinished.
val myLabel = new Label {
// Bind the text property to the value of strProp.
// When strProp's value changes, so does the label's text.
text <== strProp
}
stage = new PrimaryStage() {
scene = new Scene {
root = new VBox {
children = myLabel
}
}
}
// Custom buttons.
val ButtonTypeOne = new ButtonType("Change the text")
val ButtonTypeTwo = new ButtonType("No")
// Create the timer. This goes off after 5,000ms (5 seconds) - after play is called.
val timerA = new PauseTransition(Duration(5000))
// Alert dialog.
// Note: JavaFX forbids use of showAndWait within animation processing, so we must use
// an onHidden event instead.
val alert1 = new Alert(AlertType.Warning) {
initOwner(stage)
title = "Warning!!!"
headerText = "Header"
contentText = "Do you need to change the text?"
buttonTypes = Seq(ButtonTypeOne, ButtonTypeTwo, ButtonType.Cancel)
}
// React to the dialog being closed.
alert1.onHidden = {_ =>
alert1.result.value match {
// If button type one, change the property value.
// Note alert1.result.value is a JavaFX ButtonType, so use .delegate for the match.
case ButtonTypeOne.delegate => strProp.value = "Changed!"
// Otherwise, do nothing.
case _ =>
}
// Start the timer once more.
// This is going to be a very annoying app! ;-)
timerA.playFromStart()
}
// When the timer goes off, show the alert.
timerA.onFinished = {_ =>
alert1.show()
}
// Start the timer for the first time.
timerA.play
}

Passing information between two stages with ScalaFX fails due to not setting the field in controller

I am trying to understand the message passing possibilities of ScalaFX in combination with ScalaFXML. I created a small example, which has two views with controllers defined in the FXML. The first (or Main) view, is supposed to send a String to the second (or Dialog) view, by a button press.
I followed the example of the ScalaFXML Github page and used a trait in order to get the controller and it's possible for me to call a method on the Dialog controller, which sets a field to different value. This method gets called correctly, but the field isn't overwritten, when I check by clicking a button. Does this have something to do with ScalaFXML injecting the Controller?
Bellow I have the code for the two controllers
#sfxml
class MainController(val clicky: Button,
val inputText: TextField,
val resultText: TextArea) {
/** Creates a new window (in addition to the main window) and passes the contents of the text view to this window */
def onButtonPressed(): Unit = {
// Create the new window
val dialogFXML: String = "/Dialog.fxml"
val resource = getClass.getResource(dialogFXML)
val rootView = FXMLView(resource, NoDependencyResolver)
val loader = new FXMLLoader(resource, NoDependencyResolver)
loader.load()
val controller = loader.getController[AmountReceiver]
controller.setAmount(inputText.text.value)
val dialog = new Stage() {
title = "Add Person"
scene = new Scene(rootView)
resizable = false
initModality(Modality.ApplicationModal)
}
if (!dialog.showing()) {
dialog.show()
} else {
dialog.requestFocus()
}
}
}
and
#sfxml
class DialogController(val minAmountOfWords: Label,
val sendToMainButton: Button,
val input: TextArea) extends AmountReceiver {
var amount: String = "empty"
def submitText(): Unit = {
println(s"The actual amount is ${this.amount}")
// Close Dialog
quitDialog()
}
override def setAmount(amount: String): Unit = {
this.amount = amount
println(s"Setting the amount to ${this.amount}")
}
def quitDialog(): Unit = {
val stage: Stage = input.getScene.getWindow.asInstanceOf[Stage]
stage.close()
}
}
Running this code and entering "2" in the textfield, will print the following output:
Setting the amount to 2
The actual amount is empty
I actually figured it out myself. The problem lays within the rootView, which is received by creating a new FXMLView. In order access the correct controller, the rootView has to be received by the loader, which is already used to get the controller.
So calling
val rootView = loader.getRoot[jfxs.Parent]
instead of
val rootView = FXMLView(resource, NoDependencyResolver)
Like in the above example, the rootView is passed to the Scene constructor.