Using fyne widgets with OOP-style - go

I want to combine some standard widget in one custom widget. I can do it if put all of widget fields into one container like so:
package main
import (
"fmt"
"fyne.io/fyne"
"fyne.io/fyne/app"
"fyne.io/fyne/layout"
"fyne.io/fyne/widget"
)
type MyWidget struct {
widget.BaseWidget
Cont *fyne.Container
text *widget.Label
statusBar *widget.Label
b1 *widget.Button
b2 *widget.Button
count uint
}
func (t *MyWidget) Init() {
t.b1 = widget.NewButton("1", func() {
t.text.SetText("1")
t.count++
t.statusBar.SetText(fmt.Sprint(t.count))
})
t.b2 = widget.NewButton("2", func() { t.text.SetText("2") })
t.statusBar = widget.NewLabel("status")
bottom := fyne.NewContainerWithLayout(layout.NewCenterLayout(), t.statusBar)
t.text = widget.NewLabelWithStyle("0", fyne.TextAlignTrailing, fyne.TextStyle{Bold: true})
t.Cont = fyne.NewContainerWithLayout(layout.NewBorderLayout(nil, bottom, nil, nil),
bottom, fyne.NewContainerWithLayout(
layout.NewGridLayoutWithRows(4),
fyne.NewContainerWithLayout(layout.NewCenterLayout(), t.text),
layout.NewSpacer(),
fyne.NewContainerWithLayout(layout.NewGridLayout(2), t.b1, t.b2),
layout.NewSpacer(),
))
}
func Load() *MyWidget {
obj := &MyWidget{BaseWidget: widget.BaseWidget{}}
obj.Init()
return obj
}
func main() {
f := app.New()
w := f.NewWindow("")
obj := Load()
w.SetContent(obj.Cont)
w.ShowAndRun()
}
I used to use GUI toolkits where top widget has opportunity to set container for holding child widgets. Is it possible get solution with Fyne without exported inner container?

I would recommend you look at using a container instead. (I.e. ‘fyne.NewContainerWithLayout(myLayout, widgets...)’.
Widgets and containers are distinct in Fyne. Widgets are an encapsulation for logic, with a renderer to display, Containers are used to group multiple widgets.
There are some widgets that bridge the gap, such as widget.Box and widget.Group, but they typically expose a container, or re-export the container methods.
Generally you don’t make a tree of widgets, but rather a container tree with widgets at the loop.

Related

Go lang fyne change container content with button

I'm very new to coding so this is difficult for me to figure out how to do, I've tried looking docs n google but without help!
I got 3 buttons where I want one of them to change the new,vmax container with a new container having data specified in the button func, but I can't seem to figure out how I on the button can change the container content or the whole container
This is my code for now
package main
import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/app"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
)
var Menu = []string{"Home", "App-Status"}
func main() {
var W fyne.Window
a := app.New()
a.Settings().SetTheme(theme.DarkTheme())
W = a.NewWindow("Application-OutSight")
W.Resize(fyne.NewSize(640, 460))
text := widget.NewLabel("Welcome to This App")
// start container with welcome text
contentcontainer := container.NewMax(text)
split := (container.NewHSplit(
menuBar(Menu),
contentcontainer,
))
split.Offset = 0.2
W.SetContent(split)
W.ShowAndRun()
}
func menuBar(Menu []string) *widget.List {
listView := widget.NewList(func() int {
return len(Menu)
},
func() fyne.CanvasObject {
return widget.NewLabel("template")
},
func(id widget.ListItemID, o fyne.CanvasObject) {
o.(*widget.Label).SetText(Menu[id])
})
listView.OnSelected = func(id widget.ListItemID) {
if id == 0 {
//when i click here i want to change the start container to this container but not the sidebare as shown on picture only the container with the welcome text
somevaluefunction()
anothervaluefunction()
contentcontainer = container.NewMax(somevaluefunction(), anothervaluefunction())
// return or refresh the container in main with this new one
} else if id == 1 {
fmt.Println("app")
}
if id == 2 {
fmt.Println("exit")
}
}
return listView
}
You could change split.Objects[1] or put a named container inside there and set it’s content directly (avoiding the [1] to access second item of a split.

How to extend fyne BaseWidget - go gives error "type *SimpleCard has no field or method super"

I'm trying to extend fyne widget to have a simple clickable content with background.
I searched fyne widgets to find an example that could be used as a starter and found something similar in List/ListItem.
I basically copied the list item code and adapted it a little bit.
It does look similar to the simpler example on fyne documentation.
But for some unknown reason go gives me an error and I don't know what the cause is or how I can fix it:
custom_widget/simple_card.go:80:24: c.card.super undefined (type *SimpleCard has no field or method super)
Here is the code of the module (custom_widget/simple_card.go):
package custom_widget
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget"
"log"
)
// Declare conformity with interfaces.
var _ fyne.Widget = (*SimpleCard)(nil)
var _ fyne.Tappable = (*SimpleCard)(nil)
type SimpleCard struct {
widget.BaseWidget
onTapped func()
background *canvas.Rectangle
content fyne.CanvasObject
selected bool
}
func NewSimpleCard(content fyne.CanvasObject, tapped func()) *SimpleCard {
card := &SimpleCard{onTapped: tapped, content: content}
card.ExtendBaseWidget(card)
return card
}
// CreateRenderer is a private method to Fyne which links this custom_widget to its renderer.
func (c *SimpleCard) CreateRenderer() fyne.WidgetRenderer {
c.ExtendBaseWidget(c)
c.background = canvas.NewRectangle(theme.SelectionColor())
c.background.Hide()
objects := []fyne.CanvasObject{c.background, c.content}
// NewBaseRenderer and BaseRenderer are copied from
// https://github.com/fyne-io/fyne/blob/master/internal/widget/base_renderer.go
// because the functionality is marked internal in fyne !?
return &SimpleCardRenderer{NewBaseRenderer(objects), c}
}
func (c *SimpleCard) Tapped(_ *fyne.PointEvent) {
log.Println("I have been tapped")
if c.onTapped != nil {
c.selected = true
c.Refresh()
c.onTapped()
}
}
// Declare conformity with the WidgetRenderer interface.
var _ fyne.WidgetRenderer = (*SimpleCardRenderer)(nil)
type SimpleCardRenderer struct {
BaseRenderer
card *SimpleCard
}
// MinSize calculates the minimum size of a SimpleCardRenderer.
// This is based on the size of the status indicator and the size of the child object.
func (c *SimpleCardRenderer) MinSize() fyne.Size {
return c.card.content.MinSize()
}
// Layout the components of the SimpleCardRenderer custom_widget.
func (c *SimpleCardRenderer) Layout(size fyne.Size) {
c.card.background.Resize(size)
c.card.content.Resize(size)
}
func (c *SimpleCardRenderer) Refresh() {
if c.card.selected {
c.card.background.FillColor = theme.SelectionColor()
c.card.background.Show()
} else {
c.card.background.Hide()
}
c.card.background.Refresh()
canvas.Refresh(c.card.super()) // compiler error !
}
Remove all of the renderer type you created and in the CreateRenderer just return widget.NewSimpleRenderer(container .NewMax(c.background, c.content)). It is simpler than you think.
Copying code out of the main widgets is often not the best way as we have shortcuts and/or must support more functionality than your own widgets.

How to use List widget inside scroll container in fyne?

Scroll collapses everytime i use list widget inside scroll container, if i use label widget then scroll container is full width and full height but when i use list widget it just collapses.
Not Working (Scroll collapses)
func ShowListDialog(win fyne.Window, messages []string){
list := widget.NewList(
func() int {
return len(messages)
},
func() fyne.CanvasObject {
return widget.NewLabel("label")
},
func(i widget.ListItemID, o fyne.CanvasObject) {
o.(*widget.Label).SetText(messages[i])
},
)
d := dialog.NewCustom("Messages", "Close" , container.NewScroll(list), win)
d.Resize(fyne.NewSize(500, 400))
d.Show()
}
Working for label (scroll has full width&height)
func ShowLabelDialog(win fyne.Window, message string){
d := dialog.NewCustom("Message", "Close", container.NewScroll(widget.NewLabel(message)), win)
d.Resize(fyne.NewSize(500, 400))
d.Show()
}
The list widget already contains a scroll - removing the outer one should resolve your issue.

Invalid Receiver Type - Possible to use with a struct of myType?

I'm just trying different things to learn go and understand the structures of how it works. Currently playing around with slices and custom types.
I have the following code which works fine and as expected.
package imgslice
import (
"fmt"
"image"
)
type imageData struct {
position int // Image Number
image *image.RGBA // Image Store
height int // Height In Pixels
width int // Width In Pixels
}
func init() {
fmt.Println("Starting")
lbl := &[]imageData{}
println(lbl)
InitImage(lbl, 3)
fmt.Printf("%#v\n", lbl)
}
// Initalise the imageData arrray
func InitImage(l *[]imageData, images int) {
var i int
var newRecord imageData
for i = 0; i < images; i++ {
newRecord = imageData{position: i}
*l = append(*l, newRecord)
}
return
}
I'm trying to re-write the InitImage function to work as a method (i think that is the correct term). But I get the error:
invalid receiver type *[]imageData ([]imageData is not a defined type)
(edit: Error is on the func (l *[]imageData) InitImageNew(images int) { line)
The only reason I want to do this is a) learning to see if it can be done and b) stylistically I think I prefer this do having the slice as an extra argument.
func (l *[]imageData) InitImageNew(images int) {
var i int
var newRecord imageData
for i = 0; i < images; i++ {
newRecord = imageData{position: i}
*l = append(*l, newRecord)
}
return
}
Looking at this answer: Function declaration syntax: things in parenthesis before function name
It seems like it should be possible - however this answer seems to say it's not possible: Golang monkey patching.
Is it possible to rewrite this so I could do
lbl := &[]imageData{}
lbl.InitImageNew(4)
You can only defined methods on named types (or pointers to named types). []Type is a compound type. You could make it a named type by defining it:
type TypeSlice []Type
And then define methods on it.
This is covered in the spec section on types.

Same algorithm, multiple input and output type possibility in Go?

I'm currently using draw2d lib to render some image. I noticed the core algorithm and method are the same for building SVG, or PNG images.
I do need to render this images as SVG (for web use) and PNG (for PDF use)
The only difference is at the entry type and output.
for PNG rendering I've
as input:
var gc *draw2dimg.GraphicContext
var img *image.RGBA
img = image.NewRGBA(image.Rect(0, 0, xSize, ySize))
gc = draw2dimg.NewGraphicContext(img)
as output:
draw2dimg.SaveToPngFile(FileName, img)
and for SVG I've:
as input:
var gc *draw2dsvg.GraphicContext
var img *draw2dsvg.Svg
img = draw2dsvg.NewSvg()
gc = draw2dsvg.NewGraphicContext(img)
as output:
draw2dsvg.SaveToSvgFile(FileName, img)
between input and output I've the same implementation.
Is there any way in Go to use different input type and get the same implementation without have to duplicate some code?
As I mentioned in the comment, try to refactor your code by moving the core algorithm part into a function or may be to a different package. To illustrate the idea, bellow is the refactored version of example in https://github.com/llgcode/draw2d README.
package main
import (
"image"
"image/color"
"github.com/llgcode/draw2d"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/llgcode/draw2d/draw2dpdf"
"github.com/llgcode/draw2d/draw2dsvg"
)
func coreDraw(gc draw2d.GraphicContext) {
// Set some properties
gc.SetFillColor(color.RGBA{0x44, 0xff, 0x44, 0xff})
gc.SetStrokeColor(color.RGBA{0x44, 0x44, 0x44, 0xff})
gc.SetLineWidth(5)
// Draw a closed shape
gc.BeginPath() // Initialize a new path
gc.MoveTo(10, 10) // Move to a position to start the new path
gc.LineTo(100, 50)
gc.QuadCurveTo(100, 10, 10, 10)
gc.Close()
gc.FillStroke()
}
func main() {
format := "svg"
switch format {
case "png":
dest := image.NewRGBA(image.Rect(0, 0, 297, 210.0))
gc := draw2dimg.NewGraphicContext(dest)
coreDraw(gc)
draw2dimg.SaveToPngFile("hello.png", dest)
case "pdf":
dest := draw2dpdf.NewPdf("L", "mm", "A4")
gc := draw2dpdf.NewGraphicContext(dest)
coreDraw(gc)
draw2dpdf.SaveToPdfFile("hello.pdf", dest)
case "svg":
img := draw2dsvg.NewSvg()
gc := draw2dsvg.NewGraphicContext(img)
coreDraw(gc)
draw2dsvg.SaveToSvgFile("hello.svg", img)
}
}
The typical way to achieve code sharing in Go would be through the use of interfaces. See if you can break down the steps of your image algorithm into interface methods. Then those methods can be driven in a generic way by a single piece of code that works across all image formats, as in the runAlgorithm function below.
package main
import (
"image"
"github.com/llgcode/draw2d/draw2dimg"
"github.com/llgcode/draw2d/draw2dsvg"
)
// Common algorithm that is implemented for various image formats.
type ImageAlgorithm interface {
CreateImage(xSize, ySize int)
CreateGraphicsContext()
SaveImage(fileName string)
}
type RGBAAlgorithm struct {
gc *draw2dimg.GraphicContext
img *image.RGBA
}
func (alg *RGBAAlgorithm) CreateImage(xSize, ySize int) {
alg.img = image.NewRGBA(image.Rect(0, 0, xSize, ySize))
}
func (alg *RGBAAlgorithm) CreateGraphicsContext() {
alg.gc = draw2dimg.NewGraphicContext(alg.img)
}
func (alg *RGBAAlgorithm) SaveImage(fileName string) {
draw2dimg.SaveToPngFile(fileName, alg.img)
}
type SvgAlgorithm struct {
gc *draw2dsvg.GraphicContext
img *draw2dsvg.Svg
}
func (alg *SvgAlgorithm) CreateImage(xSize, ySize int) {
alg.img = draw2dsvg.NewSvg()
}
func (alg *SvgAlgorithm) CreateGraphicsContext() {
alg.gc = draw2dsvg.NewGraphicContext(alg.img)
}
func (alg *SvgAlgorithm) SaveImage(fileName string) {
draw2dsvg.SaveToSvgFile(fileName, alg.img)
}
func runAlgorithm(alg ImageAlgorithm) {
// input
alg.CreateImage(100, 100)
alg.CreateGraphicsContext()
// output
alg.SaveImage("out")
}
func main() {
runAlgorithm(&RGBAAlgorithm{})
runAlgorithm(&SvgAlgorithm{})
}

Resources