289 lines
8.9 KiB
Go
289 lines
8.9 KiB
Go
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)
|
|
}
|