I am trying to add scroll bars to my application windows in Go using GXUI.
Say I have this code:
package main
import (
"fmt"
"github.com/google/gxui"
"github.com/google/gxui/drivers/gl"
"github.com/google/gxui/samples/flags"
"github.com/google/gxui/themes/dark"
)
func appMain(driver gxui.Driver) {
theme := dark.CreateTheme(driver)
window := theme.CreateWindow(800, 600, "Grid")
window.SetScale(flags.DefaultScaleFactor)
window.OnClose(driver.Terminate)
row := theme.CreateLinearLayout()
row.SetDirection(gxui.LeftToRight)
for c := 0; c < 4; c++ {
col := theme.CreateLinearLayout()
col.SetDirection(gxui.TopToBottom)
for r := 0; r < 100; r++ {
cell := theme.CreateLabel()
cell.SetText(fmt.Sprintf("%d", r*4+c))
col.AddChild(cell)
}
row.AddChild(col)
}
window.AddChild(row)
}
func main() {
gl.StartDriver(appMain)
}
When I run it, I get this window:
How can I get the window to have a scroll bar so that I can view all of the lines?
I wasn't able to do with help ScrollLayout, but I can propose this variant on the basis of examples from github.
package main
import (
"fmt"
"github.com/google/gxui"
"github.com/google/gxui/drivers/gl"
"github.com/google/gxui/math"
"github.com/google/gxui/samples/flags"
"github.com/google/gxui/themes/dark"
)
type customAdapter struct {
gxui.AdapterBase
}
func (a *customAdapter) Count() int {
return 1000
}
func (a *customAdapter) ItemAt(index int) gxui.AdapterItem {
return index
}
func (a *customAdapter) ItemIndex(item gxui.AdapterItem) int {
return item.(int)
}
func (a *customAdapter) Size(theme gxui.Theme) math.Size {
return math.Size{W: 200, H: 25}
}
func (a *customAdapter) Create(theme gxui.Theme, index int) gxui.Control {
layout1 := theme.CreateLinearLayout()
layout1.SetDirection(gxui.LeftToRight)
for c := 0; c < 4; c++ {
col := theme.CreateLinearLayout()
col.SetDirection(gxui.TopToBottom)
cell := theme.CreateLabel()
cell.SetText(fmt.Sprintf("%d", index*4+c))
col.AddChild(cell)
layout1.AddChild(col)
}
return layout1
}
func appMain(driver gxui.Driver) {
theme := dark.CreateTheme(driver)
window := theme.CreateWindow(600, 400, "Grid")
window.BorderPen()
window.SetScale(flags.DefaultScaleFactor)
window.OnClose(driver.Terminate)
adapter := &customAdapter{}
list := theme.CreateList()
list.SetAdapter(adapter)
list.SetOrientation(gxui.Vertical)
window.AddChild(list)
}
func main() {
gl.StartDriver(appMain)
}
Each line is placed in the list,their number and size are specified in the overridden methods. The advantage is that in the list already have the scrollbar.
The following code uses ScrollLayout to add a scroll bar to the window. The trick is to make ScrollLayout the window's child and make the next widget (in this case a LinearLayout) a child of the ScrollLayout.
package main
import (
"fmt"
"github.com/google/gxui"
"github.com/google/gxui/drivers/gl"
"github.com/google/gxui/samples/flags"
"github.com/google/gxui/themes/dark"
)
func appMain(driver gxui.Driver) {
theme := dark.CreateTheme(driver)
window := theme.CreateWindow(800, 600, "Grid")
window.SetScale(flags.DefaultScaleFactor)
window.OnClose(driver.Terminate)
sl := theme.CreateScrollLayout()
row := theme.CreateLinearLayout()
row.SetDirection(gxui.LeftToRight)
for c := 0; c < 4; c++ {
col := theme.CreateLinearLayout()
col.SetDirection(gxui.TopToBottom)
for r := 0; r < 100; r++ {
cell := theme.CreateLabel()
cell.SetText(fmt.Sprintf("%d", r*4+c))
col.AddChild(cell)
}
row.AddChild(col)
}
sl.SetChild(row)
window.AddChild(sl)
}
func main() {
gl.StartDriver(appMain)
}
Note that my computer gave me display issues when I increased the number of rows (the rightmost columns began to get cut off), but other people did not encounter this issue, so it is likely due to a bad installation on my end.
Related
I am making a simple application with fyne, but I have a problem with text placement.
As you can see from the image, I managed to position the form components with an edge from the window, but I can't do the same with the underlying components (the Move method doesn't work).
Here the main file:
package main
import (
"fmt"
"weather-app/ui"
"weather-app/utils"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
)
func main() {
prova, err := utils.MakeRequest("palermo", "it")
if err != nil {
panic(err)
} else {
fmt.Println(prova.Weather[0])
fmt.Println(prova.Main)
fmt.Println(prova.Sys)
fmt.Println(prova.Dt)
}
app := app.New()
window := app.NewWindow("Current Weather")
customForm := ui.GenerateCustomForm()
apiResults := ui.ShowApiResults("Cinisi", "20 °C")
wrapper := container.NewGridWithRows(
2,
customForm,
apiResults,
)
window.Resize(fyne.NewSize(445, 400))
window.SetContent(wrapper)
window.ShowAndRun()
}
The ui:
package ui
import (
"fmt"
"image/color"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
func onSubmitFunc(city, country string) {
fmt.Println("submit")
}
func GenerateCustomForm() *fyne.Container {
//creating widgets
cityEntry := widget.NewEntry()
countrEntry := widget.NewEntry()
button := widget.NewButton("Search", func() { onSubmitFunc(cityEntry.Text, countrEntry.Text) })
//set placeholder in text input
cityEntry.SetPlaceHolder("City")
countrEntry.SetPlaceHolder("Country (optional)")
//setting size
cityEntry.Resize(fyne.NewSize(250, 40))
countrEntry.Resize(fyne.NewSize(150, 40))
button.Resize(fyne.NewSize(160, 40))
//positioning
cityEntry.Move(fyne.NewPos(10, 10))
countrEntry.Move(fyne.NewPos(cityEntry.Size().Width+25, 10))
button.Move(fyne.NewPos(10, 25))
customForm := container.New(
layout.NewVBoxLayout(),
container.NewWithoutLayout(
cityEntry,
countrEntry,
),
container.NewWithoutLayout(
button,
),
)
return customForm
}
func ShowApiResults(city, temp string) *fyne.Container {
text := canvas.NewText("Current weather in "+city, color.White)
text.TextSize = 18
temperature := canvas.NewText(temp, color.White)
temperature.TextSize = 50
//sample image
r, _ := fyne.LoadResourceFromURLString("http://openweathermap.org/img/wn/09d#2x.png")
image := canvas.NewImageFromResource(r)
image.FillMode = canvas.ImageFillContain
image.Resize(fyne.NewSize(100, 100))
//wrap the components to have them on the same line
tempAndImg := container.New(
layout.NewGridLayout(2),
temperature,
image,
)
tempAndImg.Move(fyne.NewPos(20, 0)) //DOESN'T WORK
apiResults := container.New(
layout.NewVBoxLayout(),
text,
tempAndImg,
)
//i tried also this:
//apiResults.Move(fyne.NewPos(20, 0)) --- but nothing
return apiResults
}
I am still in the basics of fyne, I thank in advance anyone who can help me out.
Move method should work with container.NewWithoutLayout
tempAndImg := container.New(layout.NewGridLayout(2), container.NewWithoutLayout(temperature),container.NewWithoutLayout(image) ) temperature.Move(40, 0) image.Move(40,0) apiResults := container.New(layout.NewVBoxLayout(),container.NewWithoutLayout(text),tempAndImg) text.Move(40, 0)
the question is. i have NewVScroll in the NewVScroll i have NewVBoxLayout in NewVBoxLayout i have NewButton . the problem is when i press the NewButton it should loop over NewVScroll and then loop over NewVBoxLayout to find NewButton . but i don't know how to do it in fyne because i can't loop over var v1 fyne.CanvasObject
the error is : cannot range over v1 (variable of type fyne.CanvasObject)
this is the code
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
var cEntries *fyne.Container
func FindObject(object fyne.CanvasObject, objects []fyne.CanvasObject) (int, bool) {
for k1, v1 := range objects {
for _, v2 := range v1 {// the problem is here. i can't loop over fyne.CanvasObject
if v2 == object {
return k1, true
}
}
}
return 0, false
}
func w1() *fyne.Container {
wb := widget.NewButton("", nil)
wb.OnTapped = func() {
index, isin := FindObject(wb, cEntries.Objects)
fmt.Println(index, isin)
}
return container.New(layout.NewVBoxLayout(), wb)
}
func main() {
a := app.New()
w := a.NewWindow("")
wbAdd := widget.NewButton("+", nil)
cEntries = container.New(layout.NewVBoxLayout(), wbAdd, w1())
wbAdd.OnTapped = func() {
cEntries.Add(w1())
}
wsEntries := container.NewVScroll(cEntries)
w.SetContent(wsEntries)
w.ShowAndRun()
}
The object is a container, so in general you could iterate v1.(*fyne.Container).Objects.
However it looks like you could avoid looping in your code because the button always refers to an index that is set when the button is added - so you could pass the current index (length) to w1 and then increment it.
you don't need to loop over NewVBoxLayout because it is fyne.CanvasObject then if you want to know the index of the layout that contain this widget NewButton you can do it by just search for NewVBoxLayout in NewVScroll
this is the code
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
)
var cEntries *fyne.Container
func FindObject(object fyne.CanvasObject, objects []fyne.CanvasObject) (int, bool) {
for k1, v1 := range objects {
if v1 == object {
return k1, true
}
}
return 0, false
}
func w1() *fyne.Container {
wb := widget.NewButton("", nil)
c := container.New(layout.NewVBoxLayout(), wb)
wb.OnTapped = func() {
index, isin := FindObject(c, cEntries.Objects)
fmt.Println(index, isin)
}
return c
}
func main() {
a := app.New()
w := a.NewWindow("")
wbAdd := widget.NewButton("+", nil)
cEntries = container.New(layout.NewVBoxLayout(), wbAdd, w1())
wbAdd.OnTapped = func() {
cEntries.Add(w1())
}
wsEntries := container.NewVScroll(cEntries)
w.SetContent(wsEntries)
w.ShowAndRun()
}
see in this code you make the container that handle the button and then you search for the container that contain this button
In my Go program I start multiple worker groups for every department.
I want to wait for workers from each department to complete before exiting the program
I cannot use a single WaitGroups because in the actual scenario I may have to end any particular department and need to wait only on that.
This is simplified version of code, but it panics with a message
panic: runtime error: invalid memory address or nil pointer dereference
package main
import (
"fmt"
"sync"
"time"
)
var wgMap map[string]*sync.WaitGroup
func deptWorker(dName string, id int) {
defer wgMap[dName].Done()
fmt.Printf("Department %s : Worker %d starting\n", dName, id)
time.Sleep(time.Second)
fmt.Printf("Department %s : Worker %d done\n", dName, id)
}
func department(dName string) {
var wg sync.WaitGroup
for i := 1; i <= 3; i++ {
wg.Add(1)
go deptWorker(dName, i)
}
wgMap[dName] = &wg
}
func main() {
go department("medical")
go department("electronics")
wgMap["medical"].Wait()
wgMap["electronics"].Wait()
}
Two fix nil panic you simply need to use
var wgMap = map[string]*sync.WaitGroup{}
It will initialize the map. However, in my view, it's better here to create a new abstraction, let's name it 'WaitMap'.
It can be implemented in this way:
package main
import (
"fmt"
"sync"
"time"
)
type WaitMapObject struct {
wg map[string]int
mu sync.Mutex
cond sync.Cond
}
func WaitMap() *WaitMapObject {
m := &WaitMapObject{}
m.wg = make(map[string]int)
m.cond.L = &m.mu
return m
}
func (m *WaitMapObject) Wait(name string) {
m.mu.Lock()
for m.wg[name] != 0 {
m.cond.Wait()
}
m.mu.Unlock()
}
func (m *WaitMapObject) Done(name string) {
m.mu.Lock()
no := m.wg[name] - 1
if no < 0 {
panic("")
}
m.wg[name] = no
m.mu.Unlock()
m.cond.Broadcast()
}
func (m *WaitMapObject) Add(name string, no int) {
m.mu.Lock()
m.wg[name] = m.wg[name] + no
m.mu.Unlock()
}
func deptWorker(dName string, id int, wm *WaitMapObject) {
defer wm.Done(dName)
fmt.Printf("Department %s : Worker %d starting\n", dName, id)
time.Sleep(time.Second)
fmt.Printf("Department %s : Worker %d done\n", dName, id)
}
func department(dName string, wm *WaitMapObject) {
for i := 1; i <= 3; i++ {
wm.Add(dName,1)
go deptWorker(dName, i, wm)
}
wm.Done(dName)
}
func main() {
wm := WaitMap()
wm.Add("mediacal",1)
go department("medical", wm)
wm.Add("electronics",1)
go department("electronics", wm)
wm.Wait("medical")
wm.Wait("electronics")
}
I have a code sample from a book:
The Way To Go: A Thorough Introduction To The Go Programming Language
from which I could not figure out how something works. Look at the code:
package main
import (
"fmt"
)
type Any interface{}
type EvalFunc func(Any) (Any, Any)
func main() {
evenFunc := func(state Any) (Any, Any) {
os := state.(int)
ns := os + 2
return os, ns
}
even := BuildLazyIntEvaluator(evenFunc, 0)
for i := 0; i < 10; i++ {
fmt.Printf("%vth even: %v\n", i, even())
}
}
func BuildLazyEvaluator(evalFunc EvalFunc, initState Any) func() Any {
retValChan := make(chan Any)
loopFunc := func() {
var actState Any = initState
var retVal Any
for {
retVal, actState = evalFunc(actState)
retValChan <- retVal
}
}
retFunc := func() Any {
return <-retValChan
}
go loopFunc()
return retFunc
}
func BuildLazyIntEvaluator(evalFunc EvalFunc, initState Any) func() int {
ef := BuildLazyEvaluator(evalFunc, initState)
return func() int {
return ef().(int)
}
}
Look at the code line:
return ef().(int)
What's happening here? Does the compiler convert the result into an int type?
x := ef() // x is of type Any, which is actually an interface{}
y := x.(int) // this is a type assertion, if the contents of x are an interface, y will be assigned x's int value, otherwise the runtime will panic.
It's a type assertion - see the Go Spec.
I am looking for help understanding how to access struct fields that are inside a container.vector.Vector.
The following code:
package main
import "fmt"
import "container/vector"
func main() {
type Hdr struct {
H string
}
type Blk struct {
B string
}
a := new(vector.Vector)
a.Push(Hdr{"Header_1"})
a.Push(Blk{"Block_1"})
for i := 0; i < a.Len(); i++ {
fmt.Printf("a.At(%d) == %+v\n", i, a.At(i))
x := a.At(i)
fmt.Printf("%+v\n", x.H)
}
}
Produces the error prog.go:22: x.H undefined (type interface { } has no field or method H)
removing lines 21 and 22 produces:
a.At(0) == {H:Header_1}
a.At(1) == {B:Block_1}
So, how exactly does one access 'H' or 'B'? It seems like I need to convert those interfaces to structs, but... I dunno. I'm at a loss.
Thanks for any help.
Use a Go type switch or type assertion to distinguish between the Hdr and Blk types. For example,
package main
import (
"fmt"
"container/vector"
)
func main() {
type Hdr struct {
H string
}
type Blk struct {
B string
}
a := new(vector.Vector)
a.Push(Hdr{"Header_1"})
a.Push(Blk{"Block_1"})
for i := 0; i < a.Len(); i++ {
fmt.Printf("a.At(%d) == %+v\n", i, a.At(i))
x := a.At(i)
switch x := x.(type) {
case Hdr:
fmt.Printf("%+v\n", x.H)
case Blk:
fmt.Printf("%+v\n", x.B)
}
}
}
However, effective the weekly.2011-10-18 release:
The container/vector package has been deleted. Slices are better:
SliceTricks.
Therefore, for the latest releases,
package main
import "fmt"
func main() {
type Hdr struct {
H string
}
type Blk struct {
B string
}
var a []interface{}
a = append(a, Hdr{"Header_1"})
a = append(a, Blk{"Block_1"})
for i := 0; i < len(a); i++ {
fmt.Printf("a[%d]) == %+v\n", i, a[i])
x := a[i]
switch x := x.(type) {
case Hdr:
fmt.Printf("%+v\n", x.H)
case Blk:
fmt.Printf("%+v\n", x.B)
}
}
}