canvas.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  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, debug 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. content := c.content
  105. contentSize := c.contentSize(nearestSize)
  106. contentPos := c.contentPos()
  107. menu := c.menu
  108. menuHeight := c.menuHeight()
  109. c.RUnlock()
  110. content.Resize(contentSize)
  111. content.Move(contentPos)
  112. if menu != nil {
  113. menu.Refresh()
  114. menu.Resize(fyne.NewSize(nearestSize.Width, menuHeight))
  115. }
  116. }
  117. func (c *glCanvas) Scale() float32 {
  118. c.RLock()
  119. defer c.RUnlock()
  120. return c.scale
  121. }
  122. func (c *glCanvas) SetContent(content fyne.CanvasObject) {
  123. content.Resize(content.MinSize()) // give it the space it wants then calculate the real min
  124. c.Lock()
  125. // the pass above makes some layouts wide enough to wrap, so we ask again what the true min is.
  126. newSize := c.size.Max(c.canvasSize(content.MinSize()))
  127. c.setContent(content)
  128. c.Unlock()
  129. c.Resize(newSize)
  130. c.SetDirty()
  131. }
  132. func (c *glCanvas) SetOnKeyDown(typed func(*fyne.KeyEvent)) {
  133. c.onKeyDown = typed
  134. }
  135. func (c *glCanvas) SetOnKeyUp(typed func(*fyne.KeyEvent)) {
  136. c.onKeyUp = typed
  137. }
  138. func (c *glCanvas) SetOnTypedKey(typed func(*fyne.KeyEvent)) {
  139. c.onTypedKey = typed
  140. }
  141. func (c *glCanvas) SetOnTypedRune(typed func(rune)) {
  142. c.onTypedRune = typed
  143. }
  144. func (c *glCanvas) SetPadded(padded bool) {
  145. c.Lock()
  146. content := c.content
  147. c.padded = padded
  148. pos := c.contentPos()
  149. c.Unlock()
  150. content.Move(pos)
  151. }
  152. func (c *glCanvas) reloadScale() {
  153. w := c.context.(*window)
  154. w.viewLock.RLock()
  155. windowVisible := w.visible
  156. w.viewLock.RUnlock()
  157. if !windowVisible {
  158. return
  159. }
  160. c.Lock()
  161. c.scale = w.calculatedScale()
  162. c.Unlock()
  163. c.SetDirty()
  164. c.context.RescaleContext()
  165. }
  166. func (c *glCanvas) Size() fyne.Size {
  167. c.RLock()
  168. defer c.RUnlock()
  169. return c.size
  170. }
  171. func (c *glCanvas) ToggleMenu() {
  172. c.RLock()
  173. menu := c.menu
  174. c.RUnlock()
  175. if menu != nil {
  176. menu.(*MenuBar).Toggle()
  177. }
  178. }
  179. func (c *glCanvas) buildMenu(w *window, m *fyne.MainMenu) {
  180. c.Lock()
  181. defer c.Unlock()
  182. c.setMenuOverlay(nil)
  183. if m == nil {
  184. return
  185. }
  186. if hasNativeMenu() {
  187. setupNativeMenu(w, m)
  188. } else {
  189. c.setMenuOverlay(buildMenuOverlay(m, w))
  190. }
  191. }
  192. // canvasSize computes the needed canvas size for the given content size
  193. func (c *glCanvas) canvasSize(contentSize fyne.Size) fyne.Size {
  194. canvasSize := contentSize.Add(fyne.NewSize(0, c.menuHeight()))
  195. if c.Padded() {
  196. return canvasSize.Add(fyne.NewSquareSize(theme.Padding() * 2))
  197. }
  198. return canvasSize
  199. }
  200. func (c *glCanvas) contentPos() fyne.Position {
  201. contentPos := fyne.NewPos(0, c.menuHeight())
  202. if c.Padded() {
  203. return contentPos.Add(fyne.NewSquareOffsetPos(theme.Padding()))
  204. }
  205. return contentPos
  206. }
  207. func (c *glCanvas) contentSize(canvasSize fyne.Size) fyne.Size {
  208. contentSize := fyne.NewSize(canvasSize.Width, canvasSize.Height-c.menuHeight())
  209. if c.Padded() {
  210. return contentSize.Subtract(fyne.NewSquareSize(theme.Padding() * 2))
  211. }
  212. return contentSize
  213. }
  214. func (c *glCanvas) menuHeight() float32 {
  215. if c.menu == nil {
  216. return 0 // no menu or native menu -> does not consume space on the canvas
  217. }
  218. return c.menu.MinSize().Height
  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, pos fyne.Position) {
  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. if c.debug {
  250. c.DrawDebugOverlay(node.Obj(), pos, size)
  251. }
  252. }
  253. c.WalkTrees(paint, afterPaint)
  254. }
  255. func (c *glCanvas) setContent(content fyne.CanvasObject) {
  256. c.content = content
  257. c.SetContentTreeAndFocusMgr(content)
  258. }
  259. func (c *glCanvas) setMenuOverlay(b fyne.CanvasObject) {
  260. c.menu = b
  261. c.SetMenuTreeAndFocusMgr(b)
  262. if c.menu != nil && !c.size.IsZero() {
  263. c.content.Resize(c.contentSize(c.size))
  264. c.content.Move(c.contentPos())
  265. c.menu.Refresh()
  266. c.menu.Resize(fyne.NewSize(c.size.Width, c.menu.MinSize().Height))
  267. }
  268. }
  269. func (c *glCanvas) applyThemeOutOfTreeObjects() {
  270. c.RLock()
  271. menu := c.menu
  272. padded := c.padded
  273. c.RUnlock()
  274. if menu != nil {
  275. app.ApplyThemeTo(menu, c) // Ensure our menu gets the theme change message as it's out-of-tree
  276. }
  277. c.SetPadded(padded) // refresh the padding for potential theme differences
  278. }
  279. func newCanvas() *glCanvas {
  280. c := &glCanvas{scale: 1.0, texScale: 1.0, padded: true}
  281. c.Initialize(c, c.overlayChanged)
  282. c.setContent(&canvas.Rectangle{FillColor: theme.BackgroundColor()})
  283. c.debug = fyne.CurrentApp().Settings().BuildType() == fyne.BuildDebug
  284. return c
  285. }