demo-ofw-rotbutton.lua 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  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 GtkDemo = lgi.GtkDemo
  8. local log = lgi.log.domain 'gtk-demo'
  9. GtkDemo:class('RotatedBin', Gtk.Bin)
  10. function GtkDemo.RotatedBin:_init()
  11. self.has_window = true
  12. self.priv.angle = 0
  13. end
  14. local function to_child(bin, widget_x, widget_y)
  15. local s, c = math.sin(bin.priv.angle), math.cos(bin.priv.angle)
  16. local child_area = bin.priv.child.allocation
  17. local w = c * child_area.width + s * child_area.height
  18. local h = s * child_area.width + c * child_area.height
  19. local x = widget_x - w / 2
  20. local y = widget_y - h / 2
  21. local xr = x * c + y * s
  22. local yr = y * c - x * s
  23. return xr + child_area.width / 2, yr + child_area.height / 2
  24. end
  25. local function to_parent(bin, offscreen_x, offscreen_y)
  26. local s, c = math.sin(bin.priv.angle), math.cos(bin.priv.angle)
  27. local child_area = bin.priv.child.allocation
  28. local w = c * child_area.width + s * child_area.height
  29. local h = s * child_area.width + c * child_area.height
  30. local x = offscreen_x - child_area.width / 2
  31. local y = offscreen_y - child_area.height / 2
  32. local xr = x * c - y * s
  33. local yr = x * s + y * c
  34. return xr + child_area.width - w / 2, yr + child_area.height - h / 2
  35. end
  36. function GtkDemo.RotatedBin:do_realize()
  37. self.realized = true
  38. -- Create Gdk.Window and bind it with the widget.
  39. local events = self.events
  40. events.EXPOSURE_MASK = true
  41. events.POINTER_MOTION_MASK = true
  42. events.BUTTON_PRESS_MASK = true
  43. events.BUTTON_RELEASE_MASK = true
  44. events.SCROLL_MASK = true
  45. events.ENTER_NOTIFY_MASK = true
  46. events.LEAVE_NOTIFY_MASK = true
  47. local attributes = Gdk.WindowAttr {
  48. x = self.allocation.x + self.border_width,
  49. y = self.allocation.y + self.border_width,
  50. width = self.allocation.width - 2 * self.border_width,
  51. height = self.allocation.height - 2 * self.border_width,
  52. window_type = 'CHILD',
  53. event_mask = Gdk.EventMask(events),
  54. visual = self:get_visual(),
  55. wclass = 'INPUT_OUTPUT',
  56. }
  57. local window = Gdk.Window.new(self:get_parent_window(), attributes,
  58. { 'X', 'Y', 'VISUAL' })
  59. self:set_window(window)
  60. window.widget = self
  61. local bin = self
  62. function window:on_pick_embedded_child(widget_x, widget_y)
  63. if bin.priv.child and bin.priv.child.visible then
  64. local x, y = to_child(bin, widget_x, widget_y)
  65. local child_area = bin.allocation
  66. if x >= 0 and x < child_area.width
  67. and y >= 0 and y < child_area.height then
  68. return bin.priv.offscreen_window
  69. end
  70. end
  71. end
  72. -- Create and hook up the offscreen window.
  73. attributes.window_type = 'OFFSCREEN'
  74. local child_requisition = Gtk.Requisition { width = 0, height = 0 }
  75. if self.priv.child and self.priv.child.visible then
  76. local child_allocation = self.priv.child.allocation
  77. attributes.width = child_allocation.width
  78. attributes.height = child_allocation.height
  79. end
  80. self.priv.offscreen_window = Gdk.Window.new(self.root_window, attributes,
  81. { 'X', 'Y', 'VISUAL' })
  82. self.priv.offscreen_window.widget = self
  83. if self.priv.child then
  84. self.priv.child:set_parent_window(bin.priv.offscreen_window)
  85. end
  86. Gdk.offscreen_window_set_embedder(self.priv.offscreen_window, window)
  87. function self.priv.offscreen_window:on_to_embedder(offscreen_x, offscreen_y)
  88. return to_parent(bin, offscreen_x, offscreen_y)
  89. end
  90. function self.priv.offscreen_window:on_from_embedder(parent_x, parent_y)
  91. return to_child(bin, parent_x, parent_y)
  92. end
  93. -- Set background of the windows according to current context.
  94. self.style_context:set_background(window)
  95. self.style_context:set_background(self.priv.offscreen_window)
  96. self.priv.offscreen_window:show()
  97. end
  98. function GtkDemo.RotatedBin:do_unrealize()
  99. -- Destroy offscreen window.
  100. self.priv.offscreen_window.widget = nil
  101. self.priv.offscreen_window:destroy()
  102. self.priv.offscreen_window = nil
  103. -- Chain to parent.
  104. GtkDemo.RotatedBin._parent.do_unrealize(self)
  105. end
  106. function GtkDemo.RotatedBin:do_child_type()
  107. return self.priv.child and GObject.Type.NONE or Gtk.Widget
  108. end
  109. function GtkDemo.RotatedBin:do_add(widget)
  110. if not self.priv.child then
  111. if self.priv.offscreen_window then
  112. widget:set_parent_window(self.priv.offscreen_window)
  113. end
  114. widget:set_parent(self)
  115. self.priv.child = widget
  116. else
  117. log.warning("GtkDemo.RotatedBin cannot have more than one child")
  118. end
  119. end
  120. function GtkDemo.RotatedBin:do_remove(widget)
  121. local was_visible = widget.visible
  122. if self.priv.child == widget then
  123. widget:unparent()
  124. self.priv.child = nil
  125. if was_visible and self.visible then
  126. self:queue_resize()
  127. end
  128. end
  129. end
  130. function GtkDemo.RotatedBin:do_forall(include_internals, callback)
  131. if self.priv.child then
  132. callback(self.priv.child, callback.user_data)
  133. end
  134. end
  135. function GtkDemo.RotatedBin:set_angle(angle)
  136. self.priv.angle = angle
  137. self:queue_resize()
  138. self.priv.offscreen_window:geometry_changed()
  139. end
  140. local function size_request(self)
  141. local child_requisition = Gtk.Requisition()
  142. if self.priv.child and self.priv.child.visible then
  143. child_requisition = self.priv.child:get_preferred_size()
  144. end
  145. local s, c = math.sin(self.priv.angle), math.cos(self.priv.angle)
  146. local w = c * child_requisition.width + s * child_requisition.height
  147. local h = s * child_requisition.width + c * child_requisition.height
  148. return w + 2 * self.border_width, h + 2 * self.border_width
  149. end
  150. function GtkDemo.RotatedBin:do_get_preferred_width()
  151. local w, h = size_request(self)
  152. return w, w
  153. end
  154. function GtkDemo.RotatedBin:do_get_preferred_height()
  155. local w, h = size_request(self)
  156. return h, h
  157. end
  158. function GtkDemo.RotatedBin:do_size_allocate(allocation)
  159. self:set_allocation(allocation)
  160. local w = allocation.width - self.border_width * 2
  161. local h = allocation.height - self.border_width * 2
  162. if self.realized then
  163. self.window:move_resize(allocation.x + self.border_width,
  164. allocation.y + self.border_width,
  165. w, h)
  166. end
  167. if self.priv.child and self.priv.child.visible then
  168. local s, c = math.sin(self.priv.angle), math.cos(self.priv.angle)
  169. local child_requisition = self.priv.child:get_preferred_size()
  170. local child_allocation = Gtk.Allocation {
  171. height = child_requisition.height }
  172. if c == 0 then child_allocation.width = h / s
  173. elseif s == 0 then child_allocation.width = w / c
  174. else child_allocation.width = math.min(
  175. (w - s * child_allocation.height) / c,
  176. (h - c * child_allocation.width) / s)
  177. end
  178. if self.realized then
  179. self.priv.offscreen_window:move_resize(child_allocation.x,
  180. child_allocation.y,
  181. child_allocation.width,
  182. child_allocation.height)
  183. end
  184. child_allocation.x = 0
  185. child_allocation.y = 0
  186. self.priv.child:size_allocate(child_allocation)
  187. end
  188. end
  189. function GtkDemo.RotatedBin:do_damage(event)
  190. self.window:invalidate_rect(nil, false)
  191. return true
  192. end
  193. function GtkDemo.RotatedBin:_class_init()
  194. -- Unfortunately, damage-event signal does not have virtual
  195. -- function associated, so we have to go through following funky
  196. -- dance to install default signal handler.
  197. GObject.signal_override_class_closure(
  198. GObject.signal_lookup('damage-event', Gtk.Widget),
  199. GtkDemo.RotatedBin,
  200. GObject.Closure(GtkDemo.RotatedBin.do_damage,
  201. Gtk.Widget.on_damage_event))
  202. end
  203. function GtkDemo.RotatedBin:do_draw(cr)
  204. if cr:should_draw_window(self.window) then
  205. if self.priv.child and self.priv.child.visible then
  206. local surface = Gdk.offscreen_window_get_surface(
  207. self.priv.offscreen_window)
  208. local child_area = self.priv.child.allocation
  209. -- transform
  210. local s, c = math.sin(self.priv.angle), math.cos(self.priv.angle)
  211. local w = c * child_area.width + s * child_area.height
  212. local h = s * child_area.width + c * child_area.height
  213. cr:translate((w - child_area.width) / 2,
  214. (h - child_area.height) / 2)
  215. cr:translate(child_area.width / 2, child_area.height / 2)
  216. cr:rotate(self.priv.angle)
  217. cr:translate(-child_area.width / 2, -child_area.height / 2)
  218. -- clip
  219. cr:rectangle(0, 0,
  220. self.priv.offscreen_window:get_width(),
  221. self.priv.offscreen_window:get_height())
  222. cr:clip()
  223. -- paint
  224. cr:set_source_surface(surface, 0, 0)
  225. cr:paint()
  226. end
  227. end
  228. if cr:should_draw_window(self.priv.offscreen_window) then
  229. Gtk.render_background(self.style_context, cr, 0, 0,
  230. self.priv.offscreen_window:get_width(),
  231. self.priv.offscreen_window:get_height())
  232. if self.priv.child then
  233. self:propagate_draw(self.priv.child, cr)
  234. end
  235. end
  236. return false
  237. end
  238. local window = Gtk.Window {
  239. title = "Rotated widget",
  240. border_width = 10,
  241. Gtk.Box {
  242. orientation = 'VERTICAL',
  243. Gtk.Scale {
  244. id = 'scale',
  245. orientation = 'HORIZONTAL',
  246. adjustment = Gtk.Adjustment {
  247. lower = 0,
  248. upper = math.pi / 2,
  249. step_increment = 0.01,
  250. },
  251. draw_value = false,
  252. },
  253. GtkDemo.RotatedBin {
  254. id = 'bin',
  255. Gtk.Button {
  256. label = "A Button",
  257. expand = true,
  258. },
  259. },
  260. }
  261. }
  262. function window.child.scale:on_value_changed()
  263. window.child.bin:set_angle(self.adjustment.value)
  264. end
  265. window:override_background_color(0, Gdk.RGBA.parse('black'))
  266. window:show_all()
  267. return window
  268. end,
  269. "Offscreen windows/Rotated button",
  270. table.concat {
  271. [[Offscreen windows can be used to transform parts or a ]],
  272. [[widget hierarchy. Note that the rotated button is fully functional.]],
  273. }