Create a verbose console inside Golang app using Fyne - go

Comming from Python with PyQt gui, I was used to add kind of console in my programm. The purpose was to indicate to the user information on the processes in progress, on the execution errors encountered, etc.
In Python/PyQt, I was using QLineEdit to do that. It was pretty easy to use. Just create and insert the widget in my gui and add a row for each information by calling appen().
For example, the console could say "esedb loading" when loading an esedb file, then "esedb file loaded" when finished, then "esedb parsing" for the next step, etc...
Now, I'm learning Golang with Fyne and I'm looking for a way to do something similar.
I found widget.NewTextGrid() but it doesn't work as I expect.
I can't just append new line. If I understand well, I have to store text in a string variable
Could you advice me about the way to do that ?
Thanks!
package main
import (
//"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
func main() {
myapp := app.New()
myappGui := myapp.NewWindow("Example")
myappGui.Resize(fyne.NewSize(400, 600))
textConsole := widget.NewTextGrid()

TextGrid is a complex component designed for managing character specific font styles in a monospace arrangement (like a terminal etc).
For performance I would recommend a VBox in a Scroll widget where each line is another appended Label (you can set them to monospace text style as well). If you want the text to be interactive then as other answers have said the NewMultiLineEntry is likely for you.
Text is complex and we are working hard to optimise more of the complex usages and large file handling, so it will get smoother in later releases…

widget.TextGrid does not have a method to append a line, but it does support querying its current content using TextGrid.Text(). So what you may do is set a new text that is its current content and the new line concatenated, e.g.:
textConsole.SetText(textConsole.Text() + "\n" + line)
But know that widget.TextGrid does not support scrolling: its size will be dictated by its string content. You can make it scrollable of course by using a container.Scroll.
For example:
func main() {
myapp := app.New()
w := myapp.NewWindow("Example")
w.Resize(fyne.NewSize(500, 300))
textConsole := widget.NewTextGrid()
scrollPane := container.NewScroll(textConsole)
w.SetContent(scrollPane)
go func() {
for {
textConsole.SetText(textConsole.Text() + time.Now().String() + "\n")
scrollPane.ScrollToBottom()
time.Sleep(time.Second)
}
}()
w.ShowAndRun()
}
Alternatively you may use a multiline widget.Entry. It also supports selecting any part of it, and by default it's also editable. You may disable editing of course. It supports scrolling by default.
See this example:
func main() {
myapp := app.New()
w := myapp.NewWindow("Example")
w.Resize(fyne.NewSize(500, 300))
textConsole := widget.NewMultiLineEntry()
textConsole.Disable() // Disable editing
w.SetContent(textConsole)
go func() {
for {
textConsole.SetText(textConsole.Text + time.Now().String() + "\n")
time.Sleep(time.Second)
}
}()
w.ShowAndRun()
}

Related

GUI via Fyne: changing elements on app page on button clicking

What is the best practice to change elements on an app page at button clicking.
For example, I have such code
package main
import (
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Hello")
hello := widget.NewLabel("Hello Fyne!")
w.SetContent(container.NewVBox(
hello,
widget.NewButton("Hi!", func() {
// do something
}),
))
w.ShowAndRun()
}
I want to change elements on this window if clicking on a NewButton. And display a new Buttons with different functions at their clicking
If you want to change the content of a container you will want to set the Container to a variable so you can access its methods and fields later to manipulate the content.
content := container.NewVBox(…)
w.SetContent(container)
Then you can use methods on content or change its Objects field then call Refresh().

ellipsis expansion fyne NewVBox

I'm trying to create a Fyne vertical box with a series of buttons but can't figure out the basic mechanism. I think this is a Go question, not a Fyne question, something I don't understand about go.
Here is a minimal program to show what I mean:
package main
import (
"fmt"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget"
)
func main() {
a := app.New()
w := a.NewWindow("Button List")
btn0 := widget.NewButton("button 0", func() {
fmt.Println("Pressed 0")
})
btn1 := widget.NewButton("button 1", func() {
fmt.Println("Pressed 1")
})
btns := []*widget.Button{btn0, btn1}
vbox := container.NewVBox(
// does work
btns[0],
btns[1],
// doesn't work
// btns...,
)
w.SetContent(
vbox,
)
w.ShowAndRun()
}
My understanding is that the argument btns... should produce the same effect as the list of arguments btn[0], btn[1], but it apparently doesn't. If I comment out the lines
btn[0],
btn[1],
and uncomment the line
btns...
I get the error message
cannot use btns (type []*"fyne.io/fyne/v2/widget".Button) as type
[]fyne.CanvasObject in argument to container.NewVBox
So, my newbie questions:
whats going on here, i.e., why doesn't btns... work?
what should I be using as the argument to NewVBox instead?
To do what you're wanting to do here you need to modify the slice of *widget.Button to be a slice of fyne.CanvasObject.
When spreading into a variadic parameter like this, the types have to match exactly to what the variadic parameter is expecting. This means the type needs to be the interface itself and not a type that implements the interface.
In your case, the following will work:
btns := []fyne.CanvasObject{btn0, btn1}
vbox := container.NewVBox(btns...)

How to resize window to dialog size in Fyne

I am using the Fyne package for Go. All I want to do is display a dialog, and have the window automatically resize to the maximum size of the dialog. I've gone over the documentation and looked at examples but I can't find it anywhere! The window is always 0x0 pixels. If I resize the window out I can tell that the dialog has a maximum height and width but I can't seem to feed that back into the size of the window. Here is my code:
import (
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/dialog"
)
var myApp fyne.App
func main() {
myApp = app.New()
myWindow := myApp.NewWindow("test")
cnfm := dialog.NewConfirm("Test Dialog!", "Are you sure you want to interact with this test dialog?", loseCallback, myWindow)
cnfm.Show()
cnfm.Resize(cnfm.MaxSize())
myWindow.Show()
// uncomment to have a window greater than 0x0
//myWindow.Resize(fyne.NewSize(375, 180))
myApp.Run()
}
func loseCallback(yes bool) {
myApp.Quit()
}
This seems really basic but I just can't find any way to achieve this! I've tried playing with layouts, containers, etc but nothing makes a difference, the window is always 0x0. I would really appreciate some help with this!
Fyne doesn't really have a concept of MaxSize. There is MinSize() (what a component should never be smaller than) and it's Size() which is the current size on screen.
Dialogs appear over the current content so are not constrained, or expaneded, by the content layout etc.
As a dialog is not designed to be the main content of a while I wonder if changing your content to be inside the window would be more suitable (as the dialog is really used when something in the app is happening and you want to interrupt it).
For example:
import (
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/container"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
)
var myApp fyne.App
func main() {
myApp = app.New()
myWindow := myApp.NewWindow("test")
yes := widget.NewButton("Yes", closeCallback)
yes.Importance = widget.HighImportance
myWindow.SetContent(container.NewVBox(
widget.NewLabel("Are you sure you want to interact with this test dialog?"),
container.NewHBox(layout.NewSpacer(),
widget.NewButton("No", closeCallback), yes,
layout.NewSpacer())))
myWindow.ShowAndRun()
}
func closeCallback() {
myApp.Quit()
}

Making a full screen Terminal application with Go

I'm trying to build a full screen terminal application. I'm using Go as my language of choice. I've figured out how to read from os.Stdin, but I'm unclear on how to clear the terminal window and manipulate the cursor position. I also want to capture the terminal input without it being printed (echoed back).
My questions are:
How can I effectively clear and print to the terminal with column/row coordinates?
How do I stop the terminal from printing keys pressed
My intent:
I want to create a full screen terminal application that renders it's own UI and handles input internally (hot keys/navigation/etc...).
If there are any libraries that cover this sort of use case please feel free to suggest them.
The easiest way to clear the terminal and set position is via ansi escape codes. However, this may not be the ideal way as variation in terminals may come back to bite you.
fmt.Print("\033[2J") //Clear screen
fmt.Printf("\033[%d;%dH", line, col) // Set cursor position
A better alternative would be to use a library like goncurses or termbox-go (credit: second is from Tim Cooper's comment).
With such a library you can do things like this:
import (
gc "code.google.com/p/goncurses"
)
func main() {
s, err := gc.Init()
if err != nil {
panic(err)
}
defer gc.End()
s.Move(5, 2)
s.Println("Hello")
s.GetChar()
}
Code above copied from Rosetta Code
As of December 2019, I would recommend using rivo/tview library.
(goncurses mentioned by #vastlysuperiorman has not been updated since June 2019 and termbox-go is explicitly declared unmaintained).
Here's the "hello world" app, taken from the project's README (reformatted for readability):
package main
import (
"github.com/rivo/tview"
)
func main() {
box := tview.NewBox().
SetBorder(true).
SetTitle("Hello, world!")
if err := tview.NewApplication().SetRoot(box, true).Run(); err != nil {
panic(err)
}
}
tview provides screenshots and example code as well as the standard godoc reference.
To stop the terminal from printing keys pressed you can use the below code:
import (
"fmt"
"syscall"
"golang.org/x/crypto/ssh/terminal"
)
func main(){
fmt.Print("Enter Value: ")
byteInput, _ := terminal.ReadPassword(int(syscall.Stdin))
input:= string(byteInput)
fmt.Println() // it's necessary to add a new line after user's input
fmt.Printf("Your input is '%s'", input)
}

Cursor key terminal input in Go

I am creating a Go application for usage in a terminal. The following code asks a user to input text to the terminal.
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
for {
fmt.Println("Please input something and use arrows to move along the text left and right")
in := bufio.NewReader(os.Stdin)
_, err := in.ReadString('\n')
if err != nil {
fmt.Println(err)
}
}
}
The problem is that a user cannot use left and right arrows to go along the just inputted text in order to modify it. When he presses arrows, the console prints ^[[D^[[C^[[A^[[B signs.
The output:
Please input something and use arrows to move along the text left and right
hello^[[D^[[C^[[A^[[B
How to make arrow keys behave more user-friendly and let a human navigate along the just inputted text, using left and right arrows?
I guess, I should pay attention to libraries like termbox-go or gocui but how to use them exactly for this purpose, I do not know.
A simpler example would be carmark/pseudo-terminal-go, where you can put a terminal in raw mode and benefit from the full up-down-left-right cursor moves.
From terminal.go#NewTerminal()
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
// a local terminal, that terminal must first have been put into raw mode.
// prompt is a string that is written at the start of each input line (i.e.
// "> ").
func NewTerminal(c io.ReadWriter, prompt string) *Terminal
See terminal/terminal.go and terminal/terminal_test.go, as well as MakeRaw()

Resources