dockline_node_renderer.vala 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446
  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 DocklineNodeRenderer : NodeRenderer {
  23. private Pango.Layout layout;
  24. internal DocklineNodeRenderer(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. uint source_height = 0;
  57. uint sink_height = 0;
  58. foreach (DockRenderer dock_renderer in dock_renderers) {
  59. if (dock_renderer.get_dock() is GFlow.Source)
  60. source_height += dock_renderer.get_min_height();
  61. if (dock_renderer.get_dock() is GFlow.Sink)
  62. sink_height += dock_renderer.get_min_height();
  63. }
  64. mh += uint.max(sink_height, source_height);
  65. Gtk.Widget child = children.nth_data(0);
  66. if (child != null) {
  67. int child_height, _;
  68. child.get_preferred_height(out child_height, out _);
  69. mh += child_height;
  70. }
  71. return mh;
  72. }
  73. /**
  74. * Returns the minimum width this node has to have
  75. */
  76. public override uint get_min_width(List<DockRenderer> dock_renderers,
  77. List<Gtk.Widget> children,
  78. int border_width,
  79. Gtk.Widget? title=null) {
  80. uint mw = 0;
  81. int t = 0;
  82. if (title != null) {
  83. int min_width, _;
  84. title.get_preferred_width(out min_width, out _);
  85. mw = min_width + 3*border_width + delete_btn_size;
  86. }
  87. if (this.layout.get_text() != "") {
  88. int width, height;
  89. this.layout.get_pixel_size(out width, out height);
  90. mw = width + title_spacing + delete_btn_size;
  91. }
  92. uint mw_sources = 0;
  93. uint mw_sinks = 0;
  94. foreach (DockRenderer dr in dock_renderers) {
  95. t = dr.get_min_width();
  96. if (dr.get_dock() is GFlow.Sink)
  97. mw_sinks = uint.max(t, mw_sinks);
  98. if (dr.get_dock() is GFlow.Source)
  99. mw_sources = uint.max(t, mw_sources);
  100. }
  101. mw = mw_sinks + mw_sources;
  102. Gtk.Widget child = children.nth_data(0);
  103. if (child != null) {
  104. int child_width, _;
  105. child.get_preferred_width(out child_width, out _);
  106. if (child_width > mw)
  107. mw = child_width;
  108. }
  109. return mw + border_width*2;
  110. }
  111. /**
  112. * Returns true if the point is on the close-button of the node
  113. */
  114. public override bool is_on_closebutton(Gdk.Point p,
  115. Gtk.Allocation alloc,
  116. uint border_width) {
  117. int x = p.x;
  118. int y = p.y;
  119. int x_left, x_right, y_top, y_bot;
  120. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  121. x_left = alloc.x + alloc.width - delete_btn_size
  122. - (int)border_width;
  123. x_right = x_left + delete_btn_size;
  124. y_top = alloc.y + (int)border_width;
  125. y_bot = y_top + delete_btn_size;
  126. } else {
  127. x_left = alloc.x + (int)border_width;
  128. x_right = x_left + delete_btn_size;
  129. y_top = alloc.y + (int)border_width;
  130. y_bot = y_top + delete_btn_size;
  131. }
  132. return x > x_left && x < x_right && y > y_top && y < y_bot;
  133. }
  134. /**
  135. * Returns true if the point is in the resize-drag area
  136. */
  137. public override bool is_on_resize_handle(Gdk.Point p,
  138. Gtk.Allocation alloc,
  139. uint border_width) {
  140. int x = p.x;
  141. int y = p.y;
  142. int x_right, x_left, y_bot, y_top;
  143. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  144. x_right = alloc.x + alloc.width;
  145. x_left = x_right - resize_handle_size;
  146. y_bot = alloc.y + alloc.height;
  147. y_top = y_bot - resize_handle_size;
  148. } else {
  149. x_left = alloc.x;
  150. x_right = x_left + resize_handle_size;
  151. y_bot = alloc.y + alloc.height;
  152. y_top = y_bot - resize_handle_size;
  153. }
  154. return x > x_left && x < x_right && y > y_top && y < y_bot;
  155. }
  156. /**
  157. * Returns the position of the given dock.
  158. * This is obviously bullshit. GFlow.Docks should be able to know
  159. * their own position
  160. */
  161. public override bool get_dock_position(GFlow.Dock d,
  162. List<DockRenderer> dock_renderers,
  163. int border_width,
  164. Gtk.Allocation alloc,
  165. out int x,
  166. out int y,
  167. Gtk.Widget? title=null) {
  168. int i = 0;
  169. x = y = 0;
  170. uint title_offset = this.get_title_line_height(title);
  171. foreach(DockRenderer dock_renderer in dock_renderers) {
  172. GFlow.Dock s = dock_renderer.get_dock();
  173. if (s == d) {
  174. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  175. if (s is GFlow.Sink) {
  176. x += alloc.x + border_width
  177. + dock_renderer.dockpoint_height/2;
  178. y += alloc.y + border_width + (int)title_offset
  179. + dock_renderer.dockpoint_height/2 + i
  180. * dock_renderer.get_min_height();
  181. return true;
  182. } else if (s is GFlow.Source) {
  183. x += alloc.x - border_width
  184. + alloc.width - dock_renderer.dockpoint_height/2;
  185. y += alloc.y + border_width + (int)title_offset
  186. + dock_renderer.dockpoint_height/2 + i
  187. * dock_renderer.get_min_height();
  188. return true;
  189. }
  190. } else {
  191. if (s is GFlow.Sink) {
  192. x += alloc.x - border_width
  193. + alloc.width - dock_renderer.dockpoint_height/2;
  194. y += alloc.y + border_width + (int)title_offset
  195. + dock_renderer.dockpoint_height/2 + i
  196. * dock_renderer.get_min_height();
  197. return true;
  198. } else if (s is GFlow.Source) {
  199. x += alloc.x + border_width
  200. + dock_renderer.dockpoint_height/2;
  201. y += alloc.y + border_width + (int)title_offset
  202. + dock_renderer.dockpoint_height/2 + i
  203. * dock_renderer.get_min_height();
  204. return true;
  205. }
  206. }
  207. }
  208. if (s.get_type() == d.get_type()) {
  209. i++;
  210. }
  211. }
  212. return false;
  213. }
  214. /**
  215. * Determines whether the mousepointer is hovering over a dock on this node
  216. */
  217. public override GFlow.Dock? get_dock_on_position(Gdk.Point p,
  218. List<DockRenderer> dock_renderers,
  219. uint border_width,
  220. Gtk.Allocation alloc,
  221. Gtk.Widget? title=null) {
  222. int x = p.x;
  223. int y = p.y;
  224. int source_count = 0;
  225. int sink_count = 0;
  226. int dock_x, dock_y, mh;
  227. uint title_offset;
  228. title_offset = this.get_title_line_height(title);
  229. foreach (DockRenderer dock_renderer in dock_renderers) {
  230. GFlow.Dock s = dock_renderer.get_dock();
  231. mh = dock_renderer.get_min_height();
  232. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  233. if (s is GFlow.Sink) {
  234. dock_x = alloc.x + (int)border_width;
  235. dock_y = alloc.y + (int)border_width + (int)title_offset
  236. + sink_count * mh;
  237. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  238. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  239. return s;
  240. sink_count++;
  241. } else if (s is GFlow.Source) {
  242. dock_x = alloc.x + alloc.width
  243. - (int)border_width
  244. - dock_renderer.dockpoint_height;
  245. dock_y = alloc.y + (int)border_width + (int)title_offset
  246. + source_count * mh;
  247. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  248. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  249. return s;
  250. source_count++;
  251. }
  252. } else {
  253. if (s is GFlow.Sink) {
  254. dock_x = alloc.x + alloc.width
  255. - (int)border_width
  256. - dock_renderer.dockpoint_height;
  257. dock_y = alloc.y + (int)border_width + (int)title_offset
  258. + sink_count * mh;
  259. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  260. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  261. return s;
  262. sink_count++;
  263. } else if (s is GFlow.Source) {
  264. dock_x = alloc.x + (int)border_width;
  265. dock_y = alloc.y + (int)border_width + (int)title_offset
  266. + source_count * mh;
  267. if (x > dock_x && x < dock_x + dock_renderer.dockpoint_height
  268. && y > dock_y && y < dock_y + dock_renderer.dockpoint_height )
  269. return s;
  270. source_count++;
  271. }
  272. }
  273. }
  274. return null;
  275. }
  276. /**
  277. * Returns a Gtk.StyleContext matching a given selector
  278. */
  279. private Gtk.StyleContext get_style() {
  280. var b = new Gtk.Button();
  281. return b.get_style_context();
  282. }
  283. /**
  284. * Draw this node on the given cairo context
  285. */
  286. public override void draw_node(Gtk.Widget w, Cairo.Context cr,
  287. Gtk.Allocation alloc,
  288. List<DockRenderer> dock_renderers,
  289. List<Gtk.Widget> children,
  290. int border_width,
  291. NodeProperties node_properties,
  292. Gtk.Widget? title=null) {
  293. bool editable = node_properties.editable;
  294. bool deletable = node_properties.deletable;
  295. bool resizable = node_properties.resizable;
  296. bool selected = node_properties.selected;
  297. var sc = this.get_style();
  298. sc.save();
  299. sc.render_background(cr, alloc.x, alloc.y, alloc.width, alloc.height);
  300. sc.render_frame(cr, alloc.x, alloc.y, alloc.width, alloc.height);
  301. sc.restore();
  302. int y_offset = 0;
  303. if (title != null) {
  304. int pref_title_height, _;
  305. title.get_preferred_height(out _, out pref_title_height);
  306. Gtk.Allocation title_alloc = {0,0,0,0};
  307. title_alloc.x = (int)border_width;
  308. title_alloc.y = (int)border_width;
  309. title_alloc.width = alloc.width - 3 * (int)border_width - delete_btn_size;
  310. title_alloc.height = pref_title_height;
  311. title.size_allocate(title_alloc);
  312. this.child_redraw(title);
  313. } else if (this.layout.get_text() != "") {
  314. sc.save();
  315. cr.save();
  316. sc.add_class(Gtk.STYLE_CLASS_BUTTON);
  317. Gdk.RGBA col = sc.get_color(Gtk.StateFlags.NORMAL);
  318. cr.set_source_rgba(col.red,col.green,col.blue,col.alpha);
  319. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  320. cr.move_to(alloc.x + border_width,
  321. alloc.y + (int) border_width + y_offset);
  322. } else {
  323. cr.move_to(alloc.x + 2*border_width + delete_btn_size,
  324. alloc.y + (int) border_width + y_offset);
  325. }
  326. Pango.cairo_show_layout(cr, this.layout);
  327. cr.restore();
  328. sc.restore();
  329. }
  330. if (editable && deletable) {
  331. Gtk.IconTheme it = Gtk.IconTheme.get_default();
  332. try {
  333. cr.save();
  334. Gdk.Pixbuf icon_pix = it.load_icon("edit-delete", delete_btn_size, 0);
  335. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  336. Gdk.cairo_set_source_pixbuf(
  337. cr, icon_pix,
  338. alloc.x+alloc.width-delete_btn_size-border_width,
  339. alloc.y+border_width
  340. );
  341. } else {
  342. Gdk.cairo_set_source_pixbuf(
  343. cr, icon_pix,
  344. alloc.x+border_width,
  345. alloc.y+border_width
  346. );
  347. }
  348. cr.paint();
  349. } catch (GLib.Error e) {
  350. warning("Could not load close-node-icon 'edit-delete'");
  351. } finally {
  352. cr.restore();
  353. }
  354. }
  355. y_offset += (int)this.get_title_line_height(title);
  356. int y_offset_sources = y_offset;
  357. int x_offset_sources = 0;
  358. foreach (DockRenderer dock_renderer in dock_renderers) {
  359. if (dock_renderer.get_dock() is GFlow.Sink) {
  360. x_offset_sources = int.max(x_offset_sources, dock_renderer.get_min_width());
  361. }
  362. }
  363. foreach (DockRenderer dock_renderer in dock_renderers) {
  364. if (dock_renderer.get_dock() is GFlow.Sink) {
  365. dock_renderer.draw_dock(w, cr, sc, alloc.x + (int)border_width,
  366. alloc.y+y_offset + (int) border_width, alloc.width);
  367. y_offset += dock_renderer.get_min_height();
  368. } else if (dock_renderer.get_dock() is GFlow.Source) {
  369. dock_renderer.draw_dock(w, cr, sc, alloc.x-(int)border_width,
  370. alloc.y+y_offset_sources + (int) border_width, alloc.width);
  371. y_offset_sources += dock_renderer.get_min_height();
  372. }
  373. }
  374. y_offset = int.max(y_offset, y_offset_sources);
  375. Gtk.Widget child = children.nth_data(0);
  376. if (child != null) {
  377. Gtk.Allocation child_alloc = {0,0,0,0};
  378. child_alloc.x = (int)border_width;
  379. child_alloc.y = (int)border_width + y_offset;
  380. child_alloc.width = alloc.width - 2 * (int)border_width;
  381. child_alloc.height = alloc.height - 2 * (int)border_width - y_offset;
  382. child.size_allocate(child_alloc);
  383. this.child_redraw(child);
  384. }
  385. // Draw resize handle
  386. if (resizable) {
  387. sc.save();
  388. cr.save();
  389. cr.set_source_rgba(0.5,0.5,0.5,0.5);
  390. if ((this.get_style().get_state() & Gtk.StateFlags.DIR_LTR) > 0 ){
  391. cr.move_to(alloc.x + alloc.width,
  392. alloc.y + alloc.height);
  393. cr.line_to(alloc.x + alloc.width - resize_handle_size,
  394. alloc.y + alloc.height);
  395. cr.line_to(alloc.x + alloc.width,
  396. alloc.y + alloc.height - resize_handle_size);
  397. } else {
  398. cr.move_to(alloc.x,
  399. alloc.y + alloc.height);
  400. cr.line_to(alloc.x + resize_handle_size,
  401. alloc.y + alloc.height);
  402. cr.line_to(alloc.x,
  403. alloc.y + alloc.height - resize_handle_size);
  404. }
  405. cr.fill();
  406. cr.stroke();
  407. cr.restore();
  408. sc.restore();
  409. }
  410. if (selected) {
  411. draw_rubberband(w, cr, alloc.x, alloc.y, Gtk.StateFlags.NORMAL, &alloc.width, &alloc.height);
  412. }
  413. }
  414. }
  415. }