| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344 |
- package glfw
- import (
- "image"
- "math"
- "fyne.io/fyne/v2"
- "fyne.io/fyne/v2/canvas"
- "fyne.io/fyne/v2/internal"
- "fyne.io/fyne/v2/internal/app"
- "fyne.io/fyne/v2/internal/driver"
- "fyne.io/fyne/v2/internal/driver/common"
- "fyne.io/fyne/v2/theme"
- "fyne.io/fyne/v2/widget"
- )
- // Declare conformity with Canvas interface
- var _ fyne.Canvas = (*glCanvas)(nil)
- type glCanvas struct {
- common.Canvas
- content fyne.CanvasObject
- menu fyne.CanvasObject
- padded, debug bool
- size fyne.Size
- onTypedRune func(rune)
- onTypedKey func(*fyne.KeyEvent)
- onKeyDown func(*fyne.KeyEvent)
- onKeyUp func(*fyne.KeyEvent)
- // shortcut fyne.ShortcutHandler
- scale, detectedScale, texScale float32
- context driver.WithContext
- }
- func (c *glCanvas) Capture() image.Image {
- var img image.Image
- runOnDraw(c.context.(*window), func() {
- img = c.Painter().Capture(c)
- })
- return img
- }
- func (c *glCanvas) Content() fyne.CanvasObject {
- c.RLock()
- retval := c.content
- c.RUnlock()
- return retval
- }
- func (c *glCanvas) DismissMenu() bool {
- c.RLock()
- menu := c.menu
- c.RUnlock()
- if menu != nil && menu.(*MenuBar).IsActive() {
- menu.(*MenuBar).Toggle()
- return true
- }
- return false
- }
- func (c *glCanvas) InteractiveArea() (fyne.Position, fyne.Size) {
- return fyne.Position{}, c.Size()
- }
- func (c *glCanvas) MinSize() fyne.Size {
- c.RLock()
- defer c.RUnlock()
- return c.canvasSize(c.content.MinSize())
- }
- func (c *glCanvas) OnKeyDown() func(*fyne.KeyEvent) {
- return c.onKeyDown
- }
- func (c *glCanvas) OnKeyUp() func(*fyne.KeyEvent) {
- return c.onKeyUp
- }
- func (c *glCanvas) OnTypedKey() func(*fyne.KeyEvent) {
- return c.onTypedKey
- }
- func (c *glCanvas) OnTypedRune() func(rune) {
- return c.onTypedRune
- }
- func (c *glCanvas) Padded() bool {
- return c.padded
- }
- func (c *glCanvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) {
- c.RLock()
- texScale := c.texScale
- c.RUnlock()
- multiple := c.Scale() * texScale
- scaleInt := func(x float32) int {
- return int(math.Round(float64(x * multiple)))
- }
- return scaleInt(pos.X), scaleInt(pos.Y)
- }
- func (c *glCanvas) Resize(size fyne.Size) {
- // This might not be the ideal solution, but it effectively avoid the first frame to be blurry due to the
- // rounding of the size to the loower integer when scale == 1. It does not affect the other cases as far as we tested.
- // This can easily be seen with fyne/cmd/hello and a scale == 1 as the text will happear blurry without the following line.
- nearestSize := fyne.NewSize(float32(math.Ceil(float64(size.Width))), float32(math.Ceil(float64(size.Height))))
- c.Lock()
- c.size = nearestSize
- c.Unlock()
- for _, overlay := range c.Overlays().List() {
- if p, ok := overlay.(*widget.PopUp); ok {
- // TODO: remove this when #707 is being addressed.
- // “Notifies” the PopUp of the canvas size change.
- p.Refresh()
- } else {
- overlay.Resize(nearestSize)
- }
- }
- c.RLock()
- content := c.content
- contentSize := c.contentSize(nearestSize)
- contentPos := c.contentPos()
- menu := c.menu
- menuHeight := c.menuHeight()
- c.RUnlock()
- content.Resize(contentSize)
- content.Move(contentPos)
- if menu != nil {
- menu.Refresh()
- menu.Resize(fyne.NewSize(nearestSize.Width, menuHeight))
- }
- }
- func (c *glCanvas) Scale() float32 {
- c.RLock()
- defer c.RUnlock()
- return c.scale
- }
- func (c *glCanvas) SetContent(content fyne.CanvasObject) {
- content.Resize(content.MinSize()) // give it the space it wants then calculate the real min
- c.Lock()
- // the pass above makes some layouts wide enough to wrap, so we ask again what the true min is.
- newSize := c.size.Max(c.canvasSize(content.MinSize()))
- c.setContent(content)
- c.Unlock()
- c.Resize(newSize)
- c.SetDirty()
- }
- func (c *glCanvas) SetOnKeyDown(typed func(*fyne.KeyEvent)) {
- c.onKeyDown = typed
- }
- func (c *glCanvas) SetOnKeyUp(typed func(*fyne.KeyEvent)) {
- c.onKeyUp = typed
- }
- func (c *glCanvas) SetOnTypedKey(typed func(*fyne.KeyEvent)) {
- c.onTypedKey = typed
- }
- func (c *glCanvas) SetOnTypedRune(typed func(rune)) {
- c.onTypedRune = typed
- }
- func (c *glCanvas) SetPadded(padded bool) {
- c.Lock()
- content := c.content
- c.padded = padded
- pos := c.contentPos()
- c.Unlock()
- content.Move(pos)
- }
- func (c *glCanvas) reloadScale() {
- w := c.context.(*window)
- w.viewLock.RLock()
- windowVisible := w.visible
- w.viewLock.RUnlock()
- if !windowVisible {
- return
- }
- c.Lock()
- c.scale = w.calculatedScale()
- c.Unlock()
- c.SetDirty()
- c.context.RescaleContext()
- }
- func (c *glCanvas) Size() fyne.Size {
- c.RLock()
- defer c.RUnlock()
- return c.size
- }
- func (c *glCanvas) ToggleMenu() {
- c.RLock()
- menu := c.menu
- c.RUnlock()
- if menu != nil {
- menu.(*MenuBar).Toggle()
- }
- }
- func (c *glCanvas) buildMenu(w *window, m *fyne.MainMenu) {
- c.Lock()
- defer c.Unlock()
- c.setMenuOverlay(nil)
- if m == nil {
- return
- }
- if hasNativeMenu() {
- setupNativeMenu(w, m)
- } else {
- c.setMenuOverlay(buildMenuOverlay(m, w))
- }
- }
- // canvasSize computes the needed canvas size for the given content size
- func (c *glCanvas) canvasSize(contentSize fyne.Size) fyne.Size {
- canvasSize := contentSize.Add(fyne.NewSize(0, c.menuHeight()))
- if c.Padded() {
- return canvasSize.Add(fyne.NewSquareSize(theme.Padding() * 2))
- }
- return canvasSize
- }
- func (c *glCanvas) contentPos() fyne.Position {
- contentPos := fyne.NewPos(0, c.menuHeight())
- if c.Padded() {
- return contentPos.Add(fyne.NewSquareOffsetPos(theme.Padding()))
- }
- return contentPos
- }
- func (c *glCanvas) contentSize(canvasSize fyne.Size) fyne.Size {
- contentSize := fyne.NewSize(canvasSize.Width, canvasSize.Height-c.menuHeight())
- if c.Padded() {
- return contentSize.Subtract(fyne.NewSquareSize(theme.Padding() * 2))
- }
- return contentSize
- }
- func (c *glCanvas) menuHeight() float32 {
- if c.menu == nil {
- return 0 // no menu or native menu -> does not consume space on the canvas
- }
- return c.menu.MinSize().Height
- }
- func (c *glCanvas) overlayChanged() {
- c.SetDirty()
- }
- func (c *glCanvas) paint(size fyne.Size) {
- clips := &internal.ClipStack{}
- if c.Content() == nil {
- return
- }
- c.Painter().Clear()
- paint := func(node *common.RenderCacheNode, pos fyne.Position) {
- obj := node.Obj()
- if _, ok := obj.(fyne.Scrollable); ok {
- inner := clips.Push(pos, obj.Size())
- c.Painter().StartClipping(inner.Rect())
- }
- if size.Width <= 0 || size.Height <= 0 { // iconifying on Windows can do bad things
- return
- }
- c.Painter().Paint(obj, pos, size)
- }
- afterPaint := func(node *common.RenderCacheNode, pos fyne.Position) {
- if _, ok := node.Obj().(fyne.Scrollable); ok {
- clips.Pop()
- if top := clips.Top(); top != nil {
- c.Painter().StartClipping(top.Rect())
- } else {
- c.Painter().StopClipping()
- }
- }
- if c.debug {
- c.DrawDebugOverlay(node.Obj(), pos, size)
- }
- }
- c.WalkTrees(paint, afterPaint)
- }
- func (c *glCanvas) setContent(content fyne.CanvasObject) {
- c.content = content
- c.SetContentTreeAndFocusMgr(content)
- }
- func (c *glCanvas) setMenuOverlay(b fyne.CanvasObject) {
- c.menu = b
- c.SetMenuTreeAndFocusMgr(b)
- if c.menu != nil && !c.size.IsZero() {
- c.content.Resize(c.contentSize(c.size))
- c.content.Move(c.contentPos())
- c.menu.Refresh()
- c.menu.Resize(fyne.NewSize(c.size.Width, c.menu.MinSize().Height))
- }
- }
- func (c *glCanvas) applyThemeOutOfTreeObjects() {
- c.RLock()
- menu := c.menu
- padded := c.padded
- c.RUnlock()
- if menu != nil {
- app.ApplyThemeTo(menu, c) // Ensure our menu gets the theme change message as it's out-of-tree
- }
- c.SetPadded(padded) // refresh the padding for potential theme differences
- }
- func newCanvas() *glCanvas {
- c := &glCanvas{scale: 1.0, texScale: 1.0, padded: true}
- c.Initialize(c, c.overlayChanged)
- c.setContent(&canvas.Rectangle{FillColor: theme.BackgroundColor()})
- c.debug = fyne.CurrentApp().Settings().BuildType() == fyne.BuildDebug
- return c
- }
|