canvas.go 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338
  1. package glfw
  2. import (
  3. "image"
  4. "math"
  5. "fyne.io/fyne/v2"
  6. "fyne.io/fyne/v2/canvas"
  7. "fyne.io/fyne/v2/internal"
  8. "fyne.io/fyne/v2/internal/app"
  9. "fyne.io/fyne/v2/internal/driver"
  10. "fyne.io/fyne/v2/internal/driver/common"
  11. "fyne.io/fyne/v2/theme"
  12. "fyne.io/fyne/v2/widget"
  13. )
  14. // Declare conformity with Canvas interface
  15. var _ fyne.Canvas = (*glCanvas)(nil)
  16. type glCanvas struct {
  17. common.Canvas
  18. content fyne.CanvasObject
  19. menu fyne.CanvasObject
  20. padded bool
  21. size fyne.Size
  22. onTypedRune func(rune)
  23. onTypedKey func(*fyne.KeyEvent)
  24. onKeyDown func(*fyne.KeyEvent)
  25. onKeyUp func(*fyne.KeyEvent)
  26. // shortcut fyne.ShortcutHandler
  27. scale, detectedScale, texScale float32
  28. context driver.WithContext
  29. }
  30. func (c *glCanvas) Capture() image.Image {
  31. var img image.Image
  32. runOnDraw(c.context.(*window), func() {
  33. img = c.Painter().Capture(c)
  34. })
  35. return img
  36. }
  37. func (c *glCanvas) Content() fyne.CanvasObject {
  38. c.RLock()
  39. retval := c.content
  40. c.RUnlock()
  41. return retval
  42. }
  43. func (c *glCanvas) DismissMenu() bool {
  44. c.RLock()
  45. menu := c.menu
  46. c.RUnlock()
  47. if menu != nil && menu.(*MenuBar).IsActive() {
  48. menu.(*MenuBar).Toggle()
  49. return true
  50. }
  51. return false
  52. }
  53. func (c *glCanvas) InteractiveArea() (fyne.Position, fyne.Size) {
  54. return fyne.Position{}, c.Size()
  55. }
  56. func (c *glCanvas) MinSize() fyne.Size {
  57. c.RLock()
  58. defer c.RUnlock()
  59. return c.canvasSize(c.content.MinSize())
  60. }
  61. func (c *glCanvas) OnKeyDown() func(*fyne.KeyEvent) {
  62. return c.onKeyDown
  63. }
  64. func (c *glCanvas) OnKeyUp() func(*fyne.KeyEvent) {
  65. return c.onKeyUp
  66. }
  67. func (c *glCanvas) OnTypedKey() func(*fyne.KeyEvent) {
  68. return c.onTypedKey
  69. }
  70. func (c *glCanvas) OnTypedRune() func(rune) {
  71. return c.onTypedRune
  72. }
  73. func (c *glCanvas) Padded() bool {
  74. return c.padded
  75. }
  76. func (c *glCanvas) PixelCoordinateForPosition(pos fyne.Position) (int, int) {
  77. c.RLock()
  78. texScale := c.texScale
  79. c.RUnlock()
  80. multiple := c.Scale() * texScale
  81. scaleInt := func(x float32) int {
  82. return int(math.Round(float64(x * multiple)))
  83. }
  84. return scaleInt(pos.X), scaleInt(pos.Y)
  85. }
  86. func (c *glCanvas) Resize(size fyne.Size) {
  87. // This might not be the ideal solution, but it effectively avoid the first frame to be blurry due to the
  88. // rounding of the size to the loower integer when scale == 1. It does not affect the other cases as far as we tested.
  89. // This can easily be seen with fyne/cmd/hello and a scale == 1 as the text will happear blurry without the following line.
  90. nearestSize := fyne.NewSize(float32(math.Ceil(float64(size.Width))), float32(math.Ceil(float64(size.Height))))
  91. c.Lock()
  92. c.size = nearestSize
  93. c.Unlock()
  94. for _, overlay := range c.Overlays().List() {
  95. if p, ok := overlay.(*widget.PopUp); ok {
  96. // TODO: remove this when #707 is being addressed.
  97. // “Notifies” the PopUp of the canvas size change.
  98. p.Refresh()
  99. } else {
  100. overlay.Resize(nearestSize)
  101. }
  102. }
  103. c.RLock()
  104. c.content.Resize(c.contentSize(nearestSize))
  105. c.content.Move(c.contentPos())
  106. if c.menu != nil {
  107. c.menu.Refresh()
  108. c.menu.Resize(fyne.NewSize(nearestSize.Width, c.menu.MinSize().Height))
  109. }
  110. c.RUnlock()
  111. }
  112. func (c *glCanvas) Scale() float32 {
  113. c.RLock()
  114. defer c.RUnlock()
  115. return c.scale
  116. }
  117. func (c *glCanvas) SetContent(content fyne.CanvasObject) {
  118. content.Resize(content.MinSize()) // give it the space it wants then calculate the real min
  119. c.Lock()
  120. // the pass above makes some layouts wide enough to wrap, so we ask again what the true min is.
  121. newSize := c.size.Max(c.canvasSize(content.MinSize()))
  122. c.setContent(content)
  123. c.Unlock()
  124. c.Resize(newSize)
  125. c.SetDirty()
  126. }
  127. func (c *glCanvas) SetOnKeyDown(typed func(*fyne.KeyEvent)) {
  128. c.onKeyDown = typed
  129. }
  130. func (c *glCanvas) SetOnKeyUp(typed func(*fyne.KeyEvent)) {
  131. c.onKeyUp = typed
  132. }
  133. func (c *glCanvas) SetOnTypedKey(typed func(*fyne.KeyEvent)) {
  134. c.onTypedKey = typed
  135. }
  136. func (c *glCanvas) SetOnTypedRune(typed func(rune)) {
  137. c.onTypedRune = typed
  138. }
  139. func (c *glCanvas) SetPadded(padded bool) {
  140. c.Lock()
  141. content := c.content
  142. c.padded = padded
  143. pos := c.contentPos()
  144. c.Unlock()
  145. content.Move(pos)
  146. }
  147. func (c *glCanvas) reloadScale() {
  148. w := c.context.(*window)
  149. w.viewLock.RLock()
  150. windowVisible := w.visible
  151. w.viewLock.RUnlock()
  152. if !windowVisible {
  153. return
  154. }
  155. c.Lock()
  156. c.scale = c.context.(*window).calculatedScale()
  157. c.Unlock()
  158. c.SetDirty()
  159. c.context.RescaleContext()
  160. }
  161. func (c *glCanvas) Size() fyne.Size {
  162. c.RLock()
  163. defer c.RUnlock()
  164. return c.size
  165. }
  166. func (c *glCanvas) ToggleMenu() {
  167. c.RLock()
  168. menu := c.menu
  169. c.RUnlock()
  170. if menu != nil {
  171. menu.(*MenuBar).Toggle()
  172. }
  173. }
  174. func (c *glCanvas) buildMenu(w *window, m *fyne.MainMenu) {
  175. c.Lock()
  176. defer c.Unlock()
  177. c.setMenuOverlay(nil)
  178. if m == nil {
  179. return
  180. }
  181. if hasNativeMenu() {
  182. setupNativeMenu(w, m)
  183. } else {
  184. c.setMenuOverlay(buildMenuOverlay(m, w))
  185. }
  186. }
  187. // canvasSize computes the needed canvas size for the given content size
  188. func (c *glCanvas) canvasSize(contentSize fyne.Size) fyne.Size {
  189. canvasSize := contentSize.Add(fyne.NewSize(0, c.menuHeight()))
  190. if c.Padded() {
  191. pad := theme.Padding() * 2
  192. canvasSize = canvasSize.Add(fyne.NewSize(pad, pad))
  193. }
  194. return canvasSize
  195. }
  196. func (c *glCanvas) contentPos() fyne.Position {
  197. contentPos := fyne.NewPos(0, c.menuHeight())
  198. if c.Padded() {
  199. contentPos = contentPos.Add(fyne.NewPos(theme.Padding(), theme.Padding()))
  200. }
  201. return contentPos
  202. }
  203. func (c *glCanvas) contentSize(canvasSize fyne.Size) fyne.Size {
  204. contentSize := fyne.NewSize(canvasSize.Width, canvasSize.Height-c.menuHeight())
  205. if c.Padded() {
  206. pad := theme.Padding() * 2
  207. contentSize = contentSize.Subtract(fyne.NewSize(pad, pad))
  208. }
  209. return contentSize
  210. }
  211. func (c *glCanvas) menuHeight() float32 {
  212. switch c.menu {
  213. case nil:
  214. // no menu or native menu -> does not consume space on the canvas
  215. return 0
  216. default:
  217. return c.menu.MinSize().Height
  218. }
  219. }
  220. func (c *glCanvas) overlayChanged() {
  221. c.SetDirty()
  222. }
  223. func (c *glCanvas) paint(size fyne.Size) {
  224. clips := &internal.ClipStack{}
  225. if c.Content() == nil {
  226. return
  227. }
  228. c.Painter().Clear()
  229. paint := func(node *common.RenderCacheNode, pos fyne.Position) {
  230. obj := node.Obj()
  231. if _, ok := obj.(fyne.Scrollable); ok {
  232. inner := clips.Push(pos, obj.Size())
  233. c.Painter().StartClipping(inner.Rect())
  234. }
  235. if size.Width <= 0 || size.Height <= 0 { // iconifying on Windows can do bad things
  236. return
  237. }
  238. c.Painter().Paint(obj, pos, size)
  239. }
  240. afterPaint := func(node *common.RenderCacheNode) {
  241. if _, ok := node.Obj().(fyne.Scrollable); ok {
  242. clips.Pop()
  243. if top := clips.Top(); top != nil {
  244. c.Painter().StartClipping(top.Rect())
  245. } else {
  246. c.Painter().StopClipping()
  247. }
  248. }
  249. }
  250. c.WalkTrees(paint, afterPaint)
  251. }
  252. func (c *glCanvas) setContent(content fyne.CanvasObject) {
  253. c.content = content
  254. c.SetContentTreeAndFocusMgr(content)
  255. }
  256. func (c *glCanvas) setMenuOverlay(b fyne.CanvasObject) {
  257. c.menu = b
  258. c.SetMenuTreeAndFocusMgr(b)
  259. if c.menu != nil && !c.size.IsZero() {
  260. c.content.Resize(c.contentSize(c.size))
  261. c.content.Move(c.contentPos())
  262. c.menu.Refresh()
  263. c.menu.Resize(fyne.NewSize(c.size.Width, c.menu.MinSize().Height))
  264. }
  265. }
  266. func (c *glCanvas) applyThemeOutOfTreeObjects() {
  267. c.RLock()
  268. menu := c.menu
  269. padded := c.padded
  270. c.RUnlock()
  271. if menu != nil {
  272. app.ApplyThemeTo(menu, c) // Ensure our menu gets the theme change message as it's out-of-tree
  273. }
  274. c.SetPadded(padded) // refresh the padding for potential theme differences
  275. }
  276. func newCanvas() *glCanvas {
  277. c := &glCanvas{scale: 1.0, texScale: 1.0}
  278. c.Initialize(c, c.overlayChanged)
  279. c.setContent(&canvas.Rectangle{FillColor: theme.BackgroundColor()})
  280. c.padded = true
  281. return c
  282. }