adam-gui/vendor/fyne.io/fyne/v2/widget/popup.go

289 lines
8.9 KiB
Go
Raw Permalink Normal View History

2024-04-29 19:13:50 +02:00
package widget
import (
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/canvas"
"fyne.io/fyne/v2/internal/widget"
"fyne.io/fyne/v2/theme"
)
// PopUp is a widget that can float above the user interface.
// It wraps any standard elements with padding and a shadow.
// If it is modal then the shadow will cover the entire canvas it hovers over and block interactions.
type PopUp struct {
BaseWidget
Content fyne.CanvasObject
Canvas fyne.Canvas
innerPos fyne.Position
innerSize fyne.Size
modal bool
overlayShown bool
}
// Hide this widget, if it was previously visible
func (p *PopUp) Hide() {
if p.overlayShown {
p.Canvas.Overlays().Remove(p)
p.overlayShown = false
}
p.BaseWidget.Hide()
}
// Move the widget to a new position. A PopUp position is absolute to the top, left of its canvas.
// For PopUp this actually moves the content so checking Position() will not return the same value as is set here.
func (p *PopUp) Move(pos fyne.Position) {
if p.modal {
return
}
p.innerPos = pos
p.Refresh()
}
// Resize changes the size of the PopUp's content.
// PopUps always have the size of their canvas, but this call updates the
// size of the content portion.
//
// Implements: fyne.Widget
func (p *PopUp) Resize(size fyne.Size) {
p.innerSize = size
// The canvas size might not have changed and therefore the Resize won't trigger a layout.
// Until we have a widget.Relayout() or similar, the renderer's refresh will do the re-layout.
p.Refresh()
}
// Show this pop-up as overlay if not already shown.
func (p *PopUp) Show() {
if !p.overlayShown {
p.Canvas.Overlays().Add(p)
p.overlayShown = true
}
p.Refresh()
p.BaseWidget.Show()
}
// ShowAtPosition shows this pop-up at the given position.
func (p *PopUp) ShowAtPosition(pos fyne.Position) {
p.Move(pos)
p.Show()
}
// ShowAtRelativePosition shows this pop-up at the given position relative to stated object.
//
// Since 2.4
func (p *PopUp) ShowAtRelativePosition(rel fyne.Position, to fyne.CanvasObject) {
withRelativePosition(rel, to, p.ShowAtPosition)
}
// Tapped is called when the user taps the popUp background - if not modal then dismiss this widget
func (p *PopUp) Tapped(_ *fyne.PointEvent) {
if !p.modal {
p.Hide()
}
}
// TappedSecondary is called when the user right/alt taps the background - if not modal then dismiss this widget
func (p *PopUp) TappedSecondary(_ *fyne.PointEvent) {
if !p.modal {
p.Hide()
}
}
// MinSize returns the size that this widget should not shrink below
func (p *PopUp) MinSize() fyne.Size {
p.ExtendBaseWidget(p)
return p.BaseWidget.MinSize()
}
// CreateRenderer is a private method to Fyne which links this widget to its renderer
func (p *PopUp) CreateRenderer() fyne.WidgetRenderer {
p.ExtendBaseWidget(p)
background := canvas.NewRectangle(theme.OverlayBackgroundColor())
if p.modal {
underlay := canvas.NewRectangle(theme.ShadowColor())
objects := []fyne.CanvasObject{underlay, background, p.Content}
return &modalPopUpRenderer{
widget.NewShadowingRenderer(objects, widget.DialogLevel),
popUpBaseRenderer{popUp: p, background: background},
underlay,
}
}
objects := []fyne.CanvasObject{background, p.Content}
return &popUpRenderer{
widget.NewShadowingRenderer(objects, widget.PopUpLevel),
popUpBaseRenderer{popUp: p, background: background},
}
}
// ShowPopUpAtPosition creates a new popUp for the specified content at the specified absolute position.
// It will then display the popup on the passed canvas.
func ShowPopUpAtPosition(content fyne.CanvasObject, canvas fyne.Canvas, pos fyne.Position) {
newPopUp(content, canvas).ShowAtPosition(pos)
}
// ShowPopUpAtRelativePosition shows a new popUp for the specified content at the given position relative to stated object.
// It will then display the popup on the passed canvas.
//
// Since 2.4
func ShowPopUpAtRelativePosition(content fyne.CanvasObject, canvas fyne.Canvas, rel fyne.Position, to fyne.CanvasObject) {
withRelativePosition(rel, to, func(pos fyne.Position) {
ShowPopUpAtPosition(content, canvas, pos)
})
}
func newPopUp(content fyne.CanvasObject, canvas fyne.Canvas) *PopUp {
ret := &PopUp{Content: content, Canvas: canvas, modal: false}
ret.ExtendBaseWidget(ret)
return ret
}
// NewPopUp creates a new popUp for the specified content and displays it on the passed canvas.
func NewPopUp(content fyne.CanvasObject, canvas fyne.Canvas) *PopUp {
return newPopUp(content, canvas)
}
// ShowPopUp creates a new popUp for the specified content and displays it on the passed canvas.
func ShowPopUp(content fyne.CanvasObject, canvas fyne.Canvas) {
newPopUp(content, canvas).Show()
}
func newModalPopUp(content fyne.CanvasObject, canvas fyne.Canvas) *PopUp {
p := &PopUp{Content: content, Canvas: canvas, modal: true}
p.ExtendBaseWidget(p)
return p
}
// NewModalPopUp creates a new popUp for the specified content and displays it on the passed canvas.
// A modal PopUp blocks interactions with underlying elements, covered with a semi-transparent overlay.
func NewModalPopUp(content fyne.CanvasObject, canvas fyne.Canvas) *PopUp {
return newModalPopUp(content, canvas)
}
// ShowModalPopUp creates a new popUp for the specified content and displays it on the passed canvas.
// A modal PopUp blocks interactions with underlying elements, covered with a semi-transparent overlay.
func ShowModalPopUp(content fyne.CanvasObject, canvas fyne.Canvas) {
p := newModalPopUp(content, canvas)
p.Show()
}
type popUpBaseRenderer struct {
popUp *PopUp
background *canvas.Rectangle
}
func (r *popUpBaseRenderer) padding() fyne.Size {
return fyne.NewSize(theme.InnerPadding(), theme.InnerPadding())
}
func (r *popUpBaseRenderer) offset() fyne.Position {
return fyne.NewPos(theme.InnerPadding()/2, theme.InnerPadding()/2)
}
type popUpRenderer struct {
*widget.ShadowingRenderer
popUpBaseRenderer
}
func (r *popUpRenderer) Layout(_ fyne.Size) {
innerSize := r.popUp.innerSize.Max(r.popUp.MinSize())
r.popUp.Content.Resize(innerSize.Subtract(r.padding()))
innerPos := r.popUp.innerPos
if innerPos.X+innerSize.Width > r.popUp.Canvas.Size().Width {
innerPos.X = r.popUp.Canvas.Size().Width - innerSize.Width
if innerPos.X < 0 {
innerPos.X = 0 // TODO here we may need a scroller as it's wider than our canvas
}
}
if innerPos.Y+innerSize.Height > r.popUp.Canvas.Size().Height {
innerPos.Y = r.popUp.Canvas.Size().Height - innerSize.Height
if innerPos.Y < 0 {
innerPos.Y = 0 // TODO here we may need a scroller as it's longer than our canvas
}
}
r.popUp.Content.Move(innerPos.Add(r.offset()))
r.background.Resize(innerSize)
r.background.Move(innerPos)
r.LayoutShadow(innerSize, innerPos)
}
func (r *popUpRenderer) MinSize() fyne.Size {
return r.popUp.Content.MinSize().Add(r.padding())
}
func (r *popUpRenderer) Refresh() {
r.background.FillColor = theme.OverlayBackgroundColor()
expectedContentSize := r.popUp.innerSize.Max(r.popUp.MinSize()).Subtract(r.padding())
shouldRelayout := r.popUp.Content.Size() != expectedContentSize
if r.background.Size() != r.popUp.innerSize || r.background.Position() != r.popUp.innerPos || shouldRelayout {
r.Layout(r.popUp.Size())
}
if r.popUp.Canvas.Size() != r.popUp.BaseWidget.Size() {
r.popUp.BaseWidget.Resize(r.popUp.Canvas.Size())
}
r.popUp.Content.Refresh()
r.background.Refresh()
r.ShadowingRenderer.RefreshShadow()
}
type modalPopUpRenderer struct {
*widget.ShadowingRenderer
popUpBaseRenderer
underlay *canvas.Rectangle
}
func (r *modalPopUpRenderer) Layout(canvasSize fyne.Size) {
r.underlay.Resize(canvasSize)
padding := r.padding()
innerSize := r.popUp.innerSize.Max(r.popUp.Content.MinSize().Add(padding))
requestedSize := innerSize.Subtract(padding)
size := r.popUp.Content.MinSize().Max(requestedSize)
size = size.Min(canvasSize.Subtract(padding))
pos := fyne.NewPos((canvasSize.Width-size.Width)/2, (canvasSize.Height-size.Height)/2)
r.popUp.Content.Move(pos)
r.popUp.Content.Resize(size)
innerPos := pos.Subtract(r.offset())
r.background.Move(innerPos)
r.background.Resize(size.Add(padding))
r.LayoutShadow(innerSize, innerPos)
}
func (r *modalPopUpRenderer) MinSize() fyne.Size {
return r.popUp.Content.MinSize().Add(r.padding())
}
func (r *modalPopUpRenderer) Refresh() {
r.underlay.FillColor = theme.ShadowColor()
r.background.FillColor = theme.OverlayBackgroundColor()
expectedContentSize := r.popUp.innerSize.Max(r.popUp.MinSize()).Subtract(r.padding())
shouldLayout := r.popUp.Content.Size() != expectedContentSize
if r.background.Size() != r.popUp.innerSize || shouldLayout {
r.Layout(r.popUp.Size())
}
if r.popUp.Canvas.Size() != r.popUp.BaseWidget.Size() {
r.popUp.BaseWidget.Resize(r.popUp.Canvas.Size())
}
r.popUp.Content.Refresh()
r.background.Refresh()
}
func withRelativePosition(rel fyne.Position, to fyne.CanvasObject, f func(position fyne.Position)) {
d := fyne.CurrentApp().Driver()
c := d.CanvasForObject(to)
if c == nil {
fyne.LogError("Could not locate parent object to display relative to", nil)
f(rel)
return
}
pos := d.AbsolutePositionForObject(to).Add(rel)
f(pos)
}