default_node_renderer.vala 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417
  1. /********************************************************************
  2. # Copyright 2014-2022 Daniel 'grindhold' Brendle
  3. #
  4. # This file is part of libgtkflow.
  5. #
  6. # libgtkflow is free software: you can redistribute it and/or
  7. # modify it under the terms of the GNU Lesser General Public License
  8. # as published by the Free Software Foundation, either
  9. # version 3 of the License, or (at your option) any later
  10. # version.
  11. #
  12. # libgtkflow is distributed in the hope that it will be
  13. # useful, but WITHOUT ANY WARRANTY; without even the implied
  14. # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  15. # PURPOSE. See the GNU Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with libgtkflow.
  19. # If not, see http://www.gnu.org/licenses/.
  20. *********************************************************************/
  21. namespace GtkFlow {
  22. public class DefaultNodeRenderer : NodeRenderer {
  23. private Pango.Layout layout;
  24. internal DefaultNodeRenderer(Node n) {
  25. this.layout = (new Gtk.Label("")).create_pango_layout("");
  26. }
  27. public override void update_name_layout(string name) {
  28. this.layout.set_markup("<b>%s</b>".printf(name),-1);
  29. this.size_changed();
  30. }
  31. private uint get_title_line_height(Gtk.Widget? title=null) {
  32. // FIXME: this is a bad solution. it should not happen in the first place
  33. // probably related to the remaining Pango-CRITICALs. but it works.
  34. if (this.layout == null) {
  35. return 25;
  36. } else if (title != null) {
  37. int min_height = 1;
  38. int natural_height = 1;
  39. title.get_preferred_height(out min_height, out natural_height);
  40. return natural_height + 10;
  41. } else {
  42. int width, height;
  43. this.layout.get_pixel_size(out width, out height);
  44. return (uint)Math.fmax(height, delete_btn_size) + title_spacing;
  45. }
  46. }
  47. /**
  48. * Returns the minimum height this node has to have
  49. */
  50. public override uint get_min_height(List<DockRenderer> dock_renderers,
  51. List<Gtk.Widget> children,
  52. int border_width,
  53. Gtk.Widget? title=null) {
  54. uint mh = border_width*2;
  55. mh += this.get_title_line_height(title);
  56. foreach (DockRenderer dock_renderer in dock_renderers) {
  57. mh += dock_renderer.get_min_height();
  58. }
  59. Gtk.Widget child = children.nth_data(0);
  60. if (child != null) {
  61. int child_height, _;
  62. child.get_preferred_height(out child_height, out _);
  63. mh += child_height;
  64. }
  65. return mh;
  66. }
  67. /**
  68. * Returns the minimum width this node has to have
  69. */
  70. public override uint get_min_width(List<DockRenderer> dock_renderers,
  71. List<Gtk.Widget> children,
  72. int border_width,
  73. Gtk.Widget? title=null) {
  74. uint mw = 0;
  75. int t = 0;
  76. if (title != null) {
  77. int min_width, _;
  78. title.get_preferred_width(out min_width, out _);
  79. mw = min_width + 3*border_width + delete_btn_size;
  80. } else if (this.layout.get_text() != "") {
  81. int width, height;
  82. this.layout.get_pixel_size(out width, out height);
  83. mw = width + title_spacing + delete_btn_size;
  84. }
  85. foreach (DockRenderer dr in dock_renderers) {
  86. t = dr.get_min_width();
  87. if (t > mw)
  88. mw = t;
  89. }
  90. Gtk.Widget child = children.nth_data(0);
  91. if (child != null) {
  92. int child_width, _;
  93. child.get_preferred_width(out child_width, out _);
  94. if (child_width > mw)
  95. mw = child_width;
  96. }
  97. return mw + border_width*2;
  98. }
  99. /**
  100. * Returns true if the point is on the close-button of the node
  101. */
  102. public override bool is_on_closebutton(Gdk.Point p,
  103. Gtk.Allocation alloc,
  104. uint border_width) {
  105. int x = p.x;
  106. int y = p.y;
  107. int x_left, x_right, y_top, y_bot;
  108. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  109. x_left = alloc.x + alloc.width - delete_btn_size
  110. - (int)border_width;
  111. x_right = x_left + delete_btn_size;
  112. y_top = alloc.y + (int)border_width;
  113. y_bot = y_top + delete_btn_size;
  114. } else {
  115. x_left = alloc.x + (int)border_width;
  116. x_right = x_left + delete_btn_size;
  117. y_top = alloc.y + (int)border_width;
  118. y_bot = y_top + delete_btn_size;
  119. }
  120. return x > x_left && x < x_right && y > y_top && y < y_bot;
  121. }
  122. /**
  123. * Returns true if the point is in the resize-drag area
  124. */
  125. public override bool is_on_resize_handle(Gdk.Point p,
  126. Gtk.Allocation alloc,
  127. uint border_width) {
  128. int x = p.x;
  129. int y = p.y;
  130. int x_right, x_left, y_bot, y_top;
  131. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  132. x_right = alloc.x + alloc.width;
  133. x_left = x_right - resize_handle_size;
  134. y_bot = alloc.y + alloc.height;
  135. y_top = y_bot - resize_handle_size;
  136. } else {
  137. x_left = alloc.x;
  138. x_right = x_left + resize_handle_size;
  139. y_bot = alloc.y + alloc.height;
  140. y_top = y_bot - resize_handle_size;
  141. }
  142. return x > x_left && x < x_right && y > y_top && y < y_bot;
  143. }
  144. /**
  145. * Returns the position of the given dock.
  146. * This is obviously bullshit. GFlow.Docks should be able to know
  147. * their own position
  148. */
  149. public override bool get_dock_position(GFlow.Dock d,
  150. List<DockRenderer> dock_renderers,
  151. int border_width,
  152. Gtk.Allocation alloc,
  153. out int x,
  154. out int y,
  155. Gtk.Widget? title=null) {
  156. int i = 0;
  157. x = y = 0;
  158. uint title_offset = this.get_title_line_height(title);
  159. foreach(DockRenderer dock_renderer in dock_renderers) {
  160. GFlow.Dock s = dock_renderer.get_dock();
  161. if (s == d) {
  162. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  163. if (s is GFlow.Sink) {
  164. x += alloc.x + border_width
  165. + dock_renderer.dockpoint_height/2;
  166. y += alloc.y + border_width + (int)title_offset
  167. + dock_renderer.dockpoint_height/2 + i
  168. * dock_renderer.get_min_height();
  169. return true;
  170. } else if (s is GFlow.Source) {
  171. x += alloc.x - border_width
  172. + alloc.width - dock_renderer.dockpoint_height/2;
  173. y += alloc.y + border_width + (int)title_offset
  174. + dock_renderer.dockpoint_height/2 + i
  175. * dock_renderer.get_min_height();
  176. return true;
  177. }
  178. } else {
  179. if (s is GFlow.Sink) {
  180. x += alloc.x - border_width
  181. + alloc.width - dock_renderer.dockpoint_height/2;
  182. y += alloc.y + border_width + (int)title_offset
  183. + dock_renderer.dockpoint_height/2 + i
  184. * dock_renderer.get_min_height();
  185. return true;
  186. } else if (s is GFlow.Source) {
  187. x += alloc.x + border_width
  188. + dock_renderer.dockpoint_height/2;
  189. y += alloc.y + border_width + (int)title_offset
  190. + dock_renderer.dockpoint_height/2 + i
  191. * dock_renderer.get_min_height();
  192. return true;
  193. }
  194. }
  195. }
  196. i++;
  197. }
  198. return false;
  199. }
  200. /**
  201. * Determines whether the mousepointer is hovering over a dock on this node
  202. */
  203. public override GFlow.Dock? get_dock_on_position(Gdk.Point p,
  204. List<DockRenderer> dock_renderers,
  205. uint border_width,
  206. Gtk.Allocation alloc,
  207. Gtk.Widget? title=null ) {
  208. int x = p.x;
  209. int y = p.y;
  210. int i = 0;
  211. int dock_x, dock_y, mh;
  212. uint title_offset;
  213. title_offset = this.get_title_line_height(title);
  214. foreach (DockRenderer dock_renderer in dock_renderers) {
  215. GFlow.Dock s = dock_renderer.get_dock();
  216. mh = dock_renderer.get_min_height();
  217. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  218. if (s is GFlow.Sink) {
  219. dock_x = alloc.x + (int)border_width;
  220. dock_y = alloc.y + (int)border_width + (int)title_offset
  221. + i * mh;
  222. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  223. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  224. return s;
  225. } else if (s is GFlow.Source) {
  226. dock_x = alloc.x + alloc.width
  227. - (int)border_width
  228. - dock_renderer.dockpoint_height;
  229. dock_y = alloc.y + (int)border_width + (int)title_offset
  230. + i * mh;
  231. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  232. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  233. return s;
  234. }
  235. } else {
  236. if (s is GFlow.Sink) {
  237. dock_x = alloc.x + alloc.width
  238. - (int)border_width
  239. - dock_renderer.dockpoint_height;
  240. dock_y = alloc.y + (int)border_width + (int)title_offset
  241. + i * mh;
  242. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  243. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  244. return s;
  245. } else if (s is GFlow.Source) {
  246. dock_x = alloc.x + (int)border_width;
  247. dock_y = alloc.y + (int)border_width + (int)title_offset
  248. + i * mh;
  249. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  250. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  251. return s;
  252. }
  253. }
  254. i++;
  255. }
  256. return null;
  257. }
  258. /**
  259. * Returns a Gtk.StyleContext matching a given selector
  260. */
  261. private Gtk.StyleContext get_style() {
  262. var b = new Gtk.Button();
  263. return b.get_style_context();
  264. }
  265. /**
  266. * Draw this node on the given cairo context
  267. */
  268. public override void draw_node(Gtk.Widget w, Cairo.Context cr,
  269. Gtk.Allocation alloc,
  270. List<DockRenderer> dock_renderers,
  271. List<Gtk.Widget> children,
  272. int border_width,
  273. NodeProperties node_properties,
  274. Gtk.Widget? title=null) {
  275. bool editable = node_properties.editable;
  276. bool deletable = node_properties.deletable;
  277. bool resizable = node_properties.resizable;
  278. bool selected = node_properties.selected;
  279. var sc = this.get_style();
  280. sc.save();
  281. sc.render_background(cr, alloc.x, alloc.y, alloc.width, alloc.height);
  282. sc.render_frame(cr, alloc.x, alloc.y, alloc.width, alloc.height);
  283. sc.restore();
  284. int y_offset = 0;
  285. if (title != null) {
  286. int pref_title_height, _;
  287. title.get_preferred_height(out _, out pref_title_height);
  288. Gtk.Allocation title_alloc = {0,0,0,0};
  289. title_alloc.x = (int)border_width;
  290. title_alloc.y = (int)border_width;
  291. title_alloc.width = alloc.width - 3 * (int)border_width - delete_btn_size;
  292. title_alloc.height = pref_title_height;
  293. title.size_allocate(title_alloc);
  294. this.child_redraw(title);
  295. } else if (this.layout.get_text() != "") {
  296. sc.save();
  297. cr.save();
  298. sc.add_class(Gtk.STYLE_CLASS_BUTTON);
  299. Gdk.RGBA col = sc.get_color(Gtk.StateFlags.NORMAL);
  300. cr.set_source_rgba(col.red,col.green,col.blue,col.alpha);
  301. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  302. cr.move_to(alloc.x + border_width,
  303. alloc.y + (int) border_width + y_offset);
  304. } else {
  305. cr.move_to(alloc.x + 2*border_width + delete_btn_size,
  306. alloc.y + (int) border_width + y_offset);
  307. }
  308. Pango.cairo_show_layout(cr, this.layout);
  309. cr.restore();
  310. sc.restore();
  311. }
  312. if (editable && deletable) {
  313. Gtk.IconTheme it = Gtk.IconTheme.get_default();
  314. try {
  315. cr.save();
  316. Gdk.Pixbuf icon_pix = it.load_icon("edit-delete", delete_btn_size, 0);
  317. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  318. Gdk.cairo_set_source_pixbuf(
  319. cr, icon_pix,
  320. alloc.x+alloc.width-delete_btn_size-border_width,
  321. alloc.y+border_width
  322. );
  323. } else {
  324. Gdk.cairo_set_source_pixbuf(
  325. cr, icon_pix,
  326. alloc.x+border_width,
  327. alloc.y+border_width
  328. );
  329. }
  330. cr.paint();
  331. } catch (GLib.Error e) {
  332. warning("Could not load close-node-icon 'edit-delete'");
  333. } finally {
  334. cr.restore();
  335. }
  336. }
  337. y_offset += (int)this.get_title_line_height(title);
  338. foreach (DockRenderer dock_renderer in dock_renderers) {
  339. if (dock_renderer.get_dock() is GFlow.Sink) {
  340. dock_renderer.draw_dock(w, cr, sc, alloc.x + (int)border_width,
  341. alloc.y+y_offset + (int) border_width, alloc.width);
  342. } else if (dock_renderer.get_dock() is GFlow.Source) {
  343. dock_renderer.draw_dock(w, cr, sc, alloc.x-(int)border_width,
  344. alloc.y+y_offset + (int) border_width, alloc.width);
  345. }
  346. y_offset += dock_renderer.get_min_height();
  347. }
  348. Gtk.Widget child = children.nth_data(0);
  349. if (child != null) {
  350. Gtk.Allocation child_alloc = {0,0,0,0};
  351. child_alloc.x = (int)border_width;
  352. child_alloc.y = (int)border_width + y_offset;
  353. child_alloc.width = alloc.width - 2 * (int)border_width;
  354. child_alloc.height = alloc.height - 2 * (int)border_width - y_offset;
  355. child.size_allocate(child_alloc);
  356. this.child_redraw(child);
  357. }
  358. // Draw resize handle
  359. if (resizable) {
  360. sc.save();
  361. cr.save();
  362. cr.set_source_rgba(0.5,0.5,0.5,0.5);
  363. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  364. cr.move_to(alloc.x + alloc.width,
  365. alloc.y + alloc.height);
  366. cr.line_to(alloc.x + alloc.width - resize_handle_size,
  367. alloc.y + alloc.height);
  368. cr.line_to(alloc.x + alloc.width,
  369. alloc.y + alloc.height - resize_handle_size);
  370. } else {
  371. cr.move_to(alloc.x,
  372. alloc.y + alloc.height);
  373. cr.line_to(alloc.x + resize_handle_size,
  374. alloc.y + alloc.height);
  375. cr.line_to(alloc.x,
  376. alloc.y + alloc.height - resize_handle_size);
  377. }
  378. cr.fill();
  379. cr.stroke();
  380. cr.restore();
  381. sc.restore();
  382. }
  383. if (selected) {
  384. draw_rubberband(w, cr, alloc.x, alloc.y, Gtk.StateFlags.NORMAL, &alloc.width, &alloc.height);
  385. }
  386. }
  387. }
  388. }