demo-ofw-mirror.lua 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. return function(parent, dir)
  2. local math = require 'math'
  3. local lgi = require 'lgi'
  4. local GObject = lgi.GObject
  5. local Gtk = lgi.Gtk
  6. local Gdk = lgi.Gdk
  7. local cairo = lgi.cairo
  8. local GtkDemo = lgi.GtkDemo
  9. local log = lgi.log.domain 'gtk-demo'
  10. GtkDemo:class('MirrorBin', Gtk.Bin)
  11. function GtkDemo.MirrorBin:_init()
  12. self.has_window = true
  13. end
  14. local function to_child(bin, widget_x, widget_y)
  15. return widget_x, widget_y
  16. end
  17. local function to_parent(bin, offscreen_x, offscreen_y)
  18. return offscreen_x, offscreen_y
  19. end
  20. function GtkDemo.MirrorBin:do_realize()
  21. self.realized = true
  22. -- Create Gdk.Window and bind it with the widget.
  23. local events = self.events
  24. events.EXPOSURE_MASK = true
  25. events.POINTER_MOTION_MASK = true
  26. events.BUTTON_PRESS_MASK = true
  27. events.BUTTON_RELEASE_MASK = true
  28. events.SCROLL_MASK = true
  29. events.ENTER_NOTIFY_MASK = true
  30. events.LEAVE_NOTIFY_MASK = true
  31. local attributes = Gdk.WindowAttr {
  32. x = self.allocation.x + self.border_width,
  33. y = self.allocation.y + self.border_width,
  34. width = self.allocation.width - 2 * self.border_width,
  35. height = self.allocation.height - 2 * self.border_width,
  36. window_type = 'CHILD',
  37. event_mask = Gdk.EventMask(events),
  38. visual = self:get_visual(),
  39. wclass = 'INPUT_OUTPUT',
  40. }
  41. local window = Gdk.Window.new(self:get_parent_window(), attributes,
  42. { 'X', 'Y', 'VISUAL' })
  43. self:set_window(window)
  44. window.widget = self
  45. local bin = self
  46. function window:on_pick_embedded_child(widget_x, widget_y)
  47. if bin.priv.child and bin.priv.child.visible then
  48. local x, y = to_child(bin, widget_x, widget_y)
  49. local child_area = bin.allocation
  50. if x >= 0 and x < child_area.width
  51. and y >= 0 and y < child_area.height then
  52. return bin.priv.offscreen_window
  53. end
  54. end
  55. end
  56. -- Create and hook up the offscreen window.
  57. attributes.window_type = 'OFFSCREEN'
  58. local child_requisition = Gtk.Requisition { width = 0, height = 0 }
  59. if self.priv.child and self.priv.child.visible then
  60. local child_allocation = self.priv.child.allocation
  61. attributes.width = child_allocation.width
  62. attributes.height = child_allocation.height
  63. end
  64. self.priv.offscreen_window = Gdk.Window.new(self.root_window, attributes,
  65. { 'X', 'Y', 'VISUAL' })
  66. self.priv.offscreen_window.widget = self
  67. if self.priv.child then
  68. self.priv.child:set_parent_window(bin.priv.offscreen_window)
  69. end
  70. Gdk.offscreen_window_set_embedder(self.priv.offscreen_window, window)
  71. function self.priv.offscreen_window:on_to_embedder(offscreen_x, offscreen_y)
  72. return to_parent(bin, offscreen_x, offscreen_y)
  73. end
  74. function self.priv.offscreen_window:on_from_embedder(parent_x, parent_y)
  75. return to_child(bin, parent_x, parent_y)
  76. end
  77. -- Set background of the windows according to current context.
  78. self.style_context:set_background(window)
  79. self.style_context:set_background(self.priv.offscreen_window)
  80. self.priv.offscreen_window:show()
  81. end
  82. function GtkDemo.MirrorBin:do_unrealize()
  83. -- Destroy offscreen window.
  84. self.priv.offscreen_window.widget = nil
  85. self.priv.offscreen_window:destroy()
  86. self.priv.offscreen_window = nil
  87. -- Chain to parent.
  88. GtkDemo.MirrorBin._parent.do_unrealize(self)
  89. end
  90. function GtkDemo.MirrorBin:do_child_type()
  91. return self.priv.child and GObject.Type.NONE or Gtk.Widget
  92. end
  93. function GtkDemo.MirrorBin:do_add(widget)
  94. if not self.priv.child then
  95. if self.priv.offscreen_window then
  96. widget:set_parent_window(self.priv.offscreen_window)
  97. end
  98. widget:set_parent(self)
  99. self.priv.child = widget
  100. else
  101. log.warning("GtkDemo.MirrorBin cannot have more than one child")
  102. end
  103. end
  104. function GtkDemo.MirrorBin:do_remove(widget)
  105. local was_visible = widget.visible
  106. if self.priv.child == widget then
  107. widget:unparent()
  108. self.priv.child = nil
  109. if was_visible and self.visible then
  110. self:queue_resize()
  111. end
  112. end
  113. end
  114. function GtkDemo.MirrorBin:do_forall(include_internals, callback)
  115. if self.priv.child then
  116. callback(self.priv.child, callback.user_data)
  117. end
  118. end
  119. local function size_request(self)
  120. local child_requisition = Gtk.Requisition()
  121. if self.priv.child and self.priv.child.visible then
  122. child_requisition = self.priv.child:get_preferred_size()
  123. end
  124. local w = child_requisition.width + 10 + 2 * self.border_width
  125. local h = child_requisition.height + 10 + 2 * self.border_width
  126. return w, h
  127. end
  128. function GtkDemo.MirrorBin:do_get_preferred_width()
  129. local w, h = size_request(self)
  130. return w, w
  131. end
  132. function GtkDemo.MirrorBin:do_get_preferred_height()
  133. local w, h = size_request(self)
  134. return h, h
  135. end
  136. function GtkDemo.MirrorBin:do_size_allocate(allocation)
  137. self:set_allocation(allocation)
  138. local w = allocation.width - self.border_width * 2
  139. local h = allocation.height - self.border_width * 2
  140. if self.realized then
  141. self.window:move_resize(allocation.x + self.border_width,
  142. allocation.y + self.border_width,
  143. w, h)
  144. end
  145. if self.priv.child and self.priv.child.visible then
  146. local child_requisition = self.priv.child:get_preferred_size()
  147. local child_allocation = Gtk.Allocation {
  148. height = child_requisition.height,
  149. width = child_requisition.width
  150. }
  151. if self.realized then
  152. self.priv.offscreen_window:move_resize(child_allocation.x,
  153. child_allocation.y,
  154. child_allocation.width,
  155. child_allocation.height)
  156. end
  157. self.priv.child:size_allocate(child_allocation)
  158. end
  159. end
  160. function GtkDemo.MirrorBin:do_damage(event)
  161. self.window:invalidate_rect(nil, false)
  162. return true
  163. end
  164. function GtkDemo.MirrorBin:_class_init()
  165. -- Unfortunately, damage-event signal does not have virtual
  166. -- function associated, so we have to go through following funky
  167. -- dance to install default signal handler.
  168. GObject.signal_override_class_closure(
  169. GObject.signal_lookup('damage-event', Gtk.Widget),
  170. GtkDemo.MirrorBin,
  171. GObject.Closure(GtkDemo.MirrorBin.do_damage,
  172. Gtk.Widget.on_damage_event))
  173. end
  174. function GtkDemo.MirrorBin:do_draw(cr)
  175. if cr:should_draw_window(self.window) then
  176. if self.priv.child and self.priv.child.visible then
  177. local surface = Gdk.offscreen_window_get_surface(
  178. self.priv.offscreen_window)
  179. local height = self.priv.offscreen_window:get_height()
  180. -- Paint the offscreen child
  181. cr:set_source_surface(surface, 0, 0)
  182. cr:paint()
  183. local matrix = cairo.Matrix {
  184. xx = 1, yx = 0, xy = 0.3, yy = 1, x0 = 0, y0 = 0 }
  185. matrix:scale(1, -1)
  186. matrix:translate(-10, -3 * height, - 10)
  187. cr:transform(matrix)
  188. cr:set_source_surface(surface, 0, height)
  189. -- Create linear gradient as mask-pattern to fade out the source
  190. local mask = cairo.LinearPattern(0, height, 0, 2 * height)
  191. mask:add_color_stop_rgba(0, 0, 0, 0, 0)
  192. mask:add_color_stop_rgba(0.25, 0, 0, 0, 0.01)
  193. mask:add_color_stop_rgba(0.5, 0, 0, 0, 0.25)
  194. mask:add_color_stop_rgba(0.75, 0, 0, 0, 0.5)
  195. mask:add_color_stop_rgba(1, 0, 0, 0, 1)
  196. -- Paint the reflection
  197. cr:mask(mask)
  198. end
  199. elseif cr:should_draw_window(self.priv.offscreen_window) then
  200. Gtk.render_background(self.style_context, cr, 0, 0,
  201. self.priv.offscreen_window:get_width(),
  202. self.priv.offscreen_window:get_height())
  203. if self.priv.child then
  204. self:propagate_draw(self.priv.child, cr)
  205. end
  206. end
  207. return false
  208. end
  209. local window = Gtk.Window {
  210. title = "Effects",
  211. border_width = 10,
  212. Gtk.Box {
  213. orientation = 'VERTICAL',
  214. expand = true,
  215. GtkDemo.MirrorBin {
  216. Gtk.Box {
  217. orientation = 'HORIZONTAL',
  218. spacing = 6,
  219. Gtk.Button {
  220. Gtk.Image {
  221. stock = Gtk.STOCK_GO_BACK,
  222. icon_size = 4,
  223. },
  224. },
  225. Gtk.Entry {
  226. expand = true,
  227. },
  228. Gtk.Button {
  229. Gtk.Image {
  230. stock = Gtk.STOCK_APPLY,
  231. icon_size = 4,
  232. },
  233. },
  234. },
  235. },
  236. },
  237. }
  238. window:show_all()
  239. return window
  240. end,
  241. "Offscreen windows/Effects",
  242. table.concat {
  243. [[Offscreen windows can be used to render elements multiple times ]],
  244. [[to achieve various effects.]]
  245. }