fyne icon indicating copy to clipboard operation
fyne copied to clipboard

Widgets extend beyond the dialog window

Open vinser opened this issue 1 year ago • 8 comments

Checklist

  • [X] I have searched the issue tracker for open issues that relate to the same problem, before opening a new one.
  • [X] This issue only relates to a single bug. I will open new issues for any other problems.

Describe the bug

Malfunction of dialog.NewCustomConfirm. For some reason, the widgets extend beyond the dialog window Screenshot at 2024-01-04 20-05-49 but once you change the size of the parent window, they begin to display correctly Screenshot at 2024-01-04 20-06-47

How to reproduce

Run example code and press Free crop button. Sometime it works correctly and in such case close dialog and open it ones or twice more.

Screenshots

See bug description

Example code

Example code consists of:

  1. main.go
package main

import (
	"image"
	"image/color"
	"log"
	"os"

	_ "image/jpeg"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/canvas"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

const downscaleFactor float32 = 0.6

var (
	ScreenWidth  = 3840
	ScreenHeight = 2160
)

func main() {
	reader, err := os.Open("test.jpg")
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()
	m, _, err := image.Decode(reader)
	if err != nil {
		log.Fatal(err)
	}

	a := app.New()
	w := a.NewWindow("Photo")
	img := canvas.NewImageFromImage(m)
	top := container.NewHBox(widget.NewButton("Free crop", func() { doCropDialog(m, w) }))
	content := container.NewBorder(top, nil, nil, nil, img)
	w.SetMaster()
	w.SetContent(content)
	w.CenterOnScreen()
	w.Resize(fyne.NewSize(float32(ScreenWidth)*downscaleFactor, float32(ScreenHeight)*downscaleFactor))
	w.Show()
	a.Run()
}

func doCropDialog(m image.Image, w fyne.Window) {
	scale := fyne.CurrentApp().Settings().Scale()
	img := canvas.NewImageFromImage(m)
	img.FillMode = canvas.ImageFillOriginal
	img.ScaleMode = canvas.ImageScaleFastest
	border := canvas.NewRectangle(color.Transparent)
	border.StrokeColor = theme.PrimaryColor()
	border.StrokeWidth = 5
	center := container.NewWithoutLayout(img, border)
	imgSize := fyne.NewSize(float32(img.Image.Bounds().Dx())/scale, float32(img.Image.Bounds().Dy())/scale)
	img.Move(fyne.NewPos(0, 0))
	img.Resize(imgSize)
	var content fyne.CanvasObject
	var posTL, posBR fyne.Position
	posTL = fyne.NewPos(0, 0)
	posBR = fyne.NewPos(imgSize.Width, imgSize.Height)
	border.Move(posTL)
	border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))
	topEdge := widget.NewSlider(0, float64(imgSize.Height))
	topEdge.Orientation = widget.Vertical
	topEdge.Value = float64(imgSize.Height)
	leftEdge := widget.NewSlider(0, float64(imgSize.Width))
	leftEdge.Orientation = widget.Horizontal
	leftEdge.Value = 0
	bottomEdge := widget.NewSlider(0, float64(imgSize.Height))
	bottomEdge.Orientation = widget.Vertical
	bottomEdge.Value = 0
	rightEdge := widget.NewSlider(0, float64(imgSize.Width))
	rightEdge.Orientation = widget.Horizontal
	rightEdge.Value = float64(imgSize.Width)

	topEdge.OnChanged = func(v float64) {
		if v <= bottomEdge.Value {
			return
		}
		posTL.Y = imgSize.Height - float32(v)
		border.Move(posTL)
		border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))
	}
	leftEdge.OnChanged = func(v float64) {
		if v >= rightEdge.Value {
			return
		}
		posTL.X = float32(v)
		border.Move(posTL)
		border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))
	}
	bottomEdge.OnChanged = func(v float64) {
		if v >= topEdge.Value {
			return
		}
		posBR.Y = imgSize.Height - float32(v)
		border.Move(posTL)
		border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))
	}
	rightEdge.OnChanged = func(v float64) {
		if v <= leftEdge.Value {
			return
		}
		posBR.X = float32(v)
		border.Move(posTL)
		border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))
	}

	content = container.NewBorder(rightEdge, leftEdge, bottomEdge, topEdge, center)

	dlg := dialog.NewCustomConfirm("Crop image", "Crop", "Cancel", content, func(b bool) {}, w)
	// jolt(w) // Uncomment this line to see workaround  for right functioning 
	dlg.Show()
}

func jolt(w fyne.Window) {
	s := w.Content().Size().AddWidthHeight(1., 1.)
	w.Resize(s)
	s = w.Content().Size().AddWidthHeight(-1., -1.)
	w.Resize(s)
}
  1. test.jpg test

Fyne version

v2.4.1, v2.4.3

Go compiler version

go version go1.21.4 linux/arm64

Operating system and version

Ubuntu Release 22.04.3 LTS 64-bit Kernel Linux 4.9.337-35 aarch64 MATE 1.26.0

Additional Information

No response

vinser avatar Jan 04 '24 16:01 vinser

Significantly simplified the test example. It doesn't need a test sample. But this error occurs much less frequently with it. You have to start the dialog multiple times.

package main

import (
	"image/color"

	_ "image/jpeg"

	"fyne.io/fyne/v2"
	"fyne.io/fyne/v2/app"
	"fyne.io/fyne/v2/canvas"
	"fyne.io/fyne/v2/container"
	"fyne.io/fyne/v2/dialog"
	"fyne.io/fyne/v2/theme"
	"fyne.io/fyne/v2/widget"
)

func main() {
	a := app.New()
	w := a.NewWindow("Photo")
	top := container.NewHBox(widget.NewButton("Crop", func() { doCropDialog(w) }))
	content := container.NewBorder(top, nil, nil, nil)
	w.SetMaster()
	w.SetContent(content)
	w.CenterOnScreen()
	w.Resize(fyne.NewSize(1280, 720))
	w.Show()
	a.Run()
}

func doCropDialog(w fyne.Window) {
	imgWidth := float32(200)
	imgHeight := float32(400)
	border := canvas.NewRectangle(color.Transparent)
	border.StrokeColor = theme.PrimaryColor()
	border.StrokeWidth = 5
	var posTL, posBR fyne.Position
	posTL = fyne.NewPos(0, 0)
	posBR = fyne.NewPos(imgWidth, imgHeight)
	border.Move(posTL)
	border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))

	rect := canvas.NewRectangle(color.White)
	rect.SetMinSize(fyne.NewSize(imgWidth, imgHeight))
	leftEdge := widget.NewSlider(0, float64(imgWidth))
	leftEdge.Orientation = widget.Horizontal
	leftEdge.Value = 0
	leftEdge.OnChanged = func(v float64) {
		posTL.X = float32(v)
		border.Move(posTL)
		border.Resize(fyne.NewSize(posBR.X-posTL.X, posBR.Y-posTL.Y))
	}

	center := container.NewStack(rect, border)
	content := container.NewBorder(nil, leftEdge, nil, nil, center)

	dlg := dialog.NewCustomConfirm("Crop image", "Crop", "Cancel", content, func(b bool) {}, w)
	dlg.Show()
}

Malfunction screenshot Screenshot at 2024-01-10 10-02-42 Right functioning Screenshot at 2024-01-10 10-03-40 I don’t even know if it’s worth releasing a new app feature with such a bug :(

vinser avatar Jan 10 '24 05:01 vinser

Thanks for the simplified test. Somehow this crept into v2.4.0 on an existing feature and we have been trying to track it down.

andydotxyz avatar Jan 10 '24 07:01 andydotxyz

Maybe the dialogue just doesn't have enough time to render. If after dialog.Show() you put some code with a delay,

	s := w.Content().Size()
	w.Resize(s.AddWidthHeight(-1, -1))
	time.Sleep(10 * time.Millisecond)
	w.Resize(s)

then dialog rerenders normally

vinser avatar Jan 13 '24 03:01 vinser

This does not depend on the OS and hardware. I have the same bug on my Intel NUC with Windows 10

vinser avatar Jan 13 '24 03:01 vinser

Maybe the dialogue just doesn't have enough time to render.

If after dialog.Show() you put some code with a delay,


	s := w.Content().Size()

	w.Resize(s.AddWidthHeight(-1, -1))

	time.Sleep(10 * time.Millisecond)

	w.Resize(s)

then dialog rerenders normally

Adding workarounds like this should never be needed. The render loop only paints what is ready, this seems more likely an order issue potentially race related whereby the dialog thinks it is laid out when render happens.

andydotxyz avatar Jan 13 '24 09:01 andydotxyz

Maybe this issue is induced by high CPU load. Screenshot for above code load Screenshot at 2024-01-28 21-05-31 It is like #4574

vinser avatar Jan 28 '24 16:01 vinser

@andydotxyz is there progress on this issue ? it's a blocking item for our app. can I at least get some reason / workarounds. @vinser adding delay after dialog.Show() doesn't seem to work for me.

kvishal7 avatar Mar 17 '24 11:03 kvishal7

image

Suffering the same issues on my app as well.

	//create album button
	titleEntry := widget.NewEntry()
	titleEntry.PlaceHolder = "Enter title:"
	createButton := cwidget.NewButtonWithIcon("", theme.FolderNewIcon(), func() {
		dialog.ShowCustomConfirm(
			resource.KAlbumTitleDialogTitle,
			resource.KAlbumTitleConfirmTitle,
			resource.KAlbumTitleDismissTitle,
			titleEntry,
			func(confirm bool) {
				if confirm {
					v.client.AddAlbum(titleEntry.Text)
				}
			},
			getWindow(),
		)
	})

It is very very random. Sometimes the text entry is missing, sometimes the confirm button doesn't light up.

evanhyd avatar Apr 06 '24 06:04 evanhyd