Search code examples
gofyne

fyne custom widget not refreshed


I'm trying to write a custom widget that responds to mouse events. Here is a very simple example

package main

import (
    "fyne.io/fyne/v2"
    "fyne.io/fyne/v2/app"
    "fyne.io/fyne/v2/canvas"
    "fyne.io/fyne/v2/container"
    "fyne.io/fyne/v2/driver/desktop"
    "fyne.io/fyne/v2/layout"
    "fyne.io/fyne/v2/widget"
    "image/color"
)

type TestWidget struct {
    widget.BaseWidget
    X float32
    Y float32
}

func NewTestWidget() *TestWidget {
    return &TestWidget{}
}

func (w *TestWidget) CreateRenderer() fyne.WidgetRenderer {
    return TestWidgetRenderer{w}
}

func (w *TestWidget) MinSize() fyne.Size {
    return fyne.NewSize(400, 200)
}

// MouseIn is a hook that is called if the mouse pointer enters the element.
func (w *TestWidget) MouseIn(event *desktop.MouseEvent) {

}

// MouseMoved is a hook that is called if the mouse pointer moved over the element.
func (w *TestWidget) MouseMoved(event *desktop.MouseEvent) {
    w.X, w.Y = event.Position.X, event.Position.Y
    w.Refresh()
}

// MouseOut is a hook that is called if the mouse pointer leaves the element.
func (w *TestWidget) MouseOut() {

}

type TestWidgetRenderer struct {
    w *TestWidget
}

// Destroy is for internal use.
func (r TestWidgetRenderer) Destroy() {
}

func (r TestWidgetRenderer) MinSize() fyne.Size {
    return fyne.Size{400, 200}
}

// Layout is a hook that is called if the widget needs to be laid out.
// This should never call Refresh.
func (r TestWidgetRenderer) Layout(size fyne.Size) {
}

// Objects returns all objects that should be drawn.
func (r TestWidgetRenderer) Objects() []fyne.CanvasObject {
    res := make([]fyne.CanvasObject, 0)
    res = append(res, &canvas.Line{
        Position1:   fyne.Position{0, 0},
        Position2:   fyne.Position{r.w.X, r.w.Y},
        Hidden:      false,
        StrokeColor: color.Black,
        StrokeWidth: 5,
    })
    return res
}

// Refresh is a hook that is called if the widget has updated and needs to be redrawn.
// This might trigger a Layout.
func (r TestWidgetRenderer) Refresh() {
}

func main() {
    adminApp := app.New()
    mainWindow := adminApp.NewWindow("")
    mainWindow.SetContent(container.New(layout.NewHBoxLayout(), NewTestWidget()))

    mainWindow.ShowAndRun()
}

This widget is supposed to draw a black line from the top left corner of the window to the position of the mouse pointer. In reality, it does not draw anything until I resize the window. In other words, the widget's X and Y fields are set, but it is not refreshed (not re-rendered). Even though I call widget.Refresh() from the MouseMoved event handler.

What am I doing wrong?

UPDATE I just realized that when BaseWidget.Refresh() is called, it does nothing at all. I could catch this in the debugger:

enter image description here

The "impl" is always nil (I guess because the widget is not using any already existing widget?) and so renderer.Refresh() is never called.

I'm not sure what it means. Does it mean that it is not possible to write a custom widget that is not based on any existing widget?


Solution

  • You have used BaseWidget, but didn’t call ExtendBaseWidget in your constructor.

    Perhaps the docs here could be a little clearer https://docs.fyne.io/extend/custom-widget