node.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  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. /**
  23. * Defines an object that can be added to a Nodeview
  24. *
  25. * Implement this if you want custom nodes that have their own
  26. * drawing routines and special behaviour
  27. */
  28. public interface NodeRenderer : Gtk.Widget {
  29. /**
  30. * The {@link GFlow.Node} that this Node represents
  31. */
  32. public abstract GFlow.Node n {get; protected set;}
  33. /**
  34. * Expresses wheter this node is marked via rubberband selection
  35. */
  36. public abstract bool marked {get; internal set;}
  37. /**
  38. * Returns a {@link Dock} if the given {@link GFlow.Dock} resides
  39. * in this node.
  40. */
  41. public abstract Dock? retrieve_dock(GFlow.Dock d);
  42. /**
  43. * Returns the value of this node's margin
  44. */
  45. public abstract int get_margin();
  46. /**
  47. * Click offset: x coordinate
  48. *
  49. * Holds the offset-position relative to the origin of the
  50. * node at which this node has been clicked the last time.
  51. */
  52. public abstract double click_offset_x {get; protected set; default=0;}
  53. /**
  54. * Click offset: y coordinate
  55. *
  56. * Holds the offset-position relative to the origin of the
  57. * node at which this node has been clicked the last time.
  58. */
  59. public abstract double click_offset_y {get; protected set; default=0;}
  60. /**
  61. * Resize start width
  62. *
  63. * Hold the original width of the node when the last resize process
  64. * had been started
  65. */
  66. public abstract double resize_start_width {get; protected set; default=0;}
  67. /**
  68. * Resize start height
  69. *
  70. * Hold the original height of the node when the last resize process
  71. * had been started
  72. */
  73. public abstract double resize_start_height {get; protected set; default=0;}
  74. }
  75. /**
  76. * A Simple node representation
  77. *
  78. * The default {@link NodeRenderer} that comes with libgtkflow. Use this
  79. * To wrap your {@link GFlow.Node}s in order to add them to a {@link NodeView}
  80. */
  81. public class Node : Gtk.Widget, NodeRenderer {
  82. construct {
  83. set_css_name("gtkflow_node");
  84. }
  85. public const int MARGIN = 10;
  86. private Gtk.Grid grid;
  87. private Gtk.GestureClick ctr_click;
  88. public GFlow.Node n {get; protected set;}
  89. /**
  90. * {@inheritDoc}
  91. */
  92. public bool marked {get; internal set;}
  93. /**
  94. * User-controlled node resizability
  95. *
  96. * Set to true if this should be resizable
  97. */
  98. public bool resizable {get; set; default=true;}
  99. public Gdk.RGBA? highlight_color {get; set; default=null;}
  100. /**
  101. * A widget to use for the node title instead of the name-label
  102. *
  103. * TODO: implement
  104. */
  105. public Gtk.Widget title_widget {get; set;}
  106. private Gtk.Label title_label;
  107. private Gtk.Button delete_button;
  108. /**
  109. * {@inheritDoc}
  110. */
  111. public double click_offset_x {get; protected set; default=0;}
  112. /**
  113. * {@inheritDoc}
  114. */
  115. public double click_offset_y {get; protected set; default=0;}
  116. /**
  117. * {@inheritDoc}
  118. */
  119. public double resize_start_width {get; protected set; default=0;}
  120. /**
  121. * {@inheritDoc}
  122. */
  123. public double resize_start_height {get; protected set; default=0;}
  124. // TODO: implement individual widgets as dock labels
  125. // private HashTable<GFlow.Dock, Gtk.Widget> widgets;
  126. private int n_docks = 0;
  127. /**
  128. * Instantiate a new node
  129. *
  130. * You are required to pass a {@link GFlow.Node} to this constructor.
  131. */
  132. public Node(GFlow.Node n) {
  133. this.n = n;
  134. this.grid = new Gtk.Grid();
  135. this.grid.column_homogeneous = false;
  136. this.grid.column_spacing = 5;
  137. this.grid.row_homogeneous = false;
  138. this.grid.row_spacing = 5;
  139. this.grid.hexpand = true;
  140. this.grid.vexpand = true;
  141. this.grid.halign = Gtk.Align.FILL;
  142. this.grid.valign = Gtk.Align.FILL;
  143. this.grid.margin_top = Node.MARGIN;
  144. this.grid.margin_bottom = Node.MARGIN;
  145. this.grid.margin_start = Node.MARGIN;
  146. this.grid.margin_end = Node.MARGIN;
  147. this.grid.set_parent(this);
  148. this.set_layout_manager(new Gtk.BinLayout());
  149. this.ctr_click = new Gtk.GestureClick();
  150. this.add_controller(this.ctr_click);
  151. this.ctr_click.pressed.connect((n, x, y) => { this.press_button(n,x,y); });
  152. this.ctr_click.end.connect(() => { this.release_button(); });
  153. this.title_label = new Gtk.Label(n.name);
  154. this.grid.attach(this.title_label, 0, 0, 2, 1);
  155. var delete_icon = new Gtk.Image.from_icon_name("edit-delete");
  156. this.delete_button = new Gtk.Button();
  157. this.delete_button.child = delete_icon;
  158. this.delete_button.clicked.connect(this.cb_delete);
  159. this.grid.attach(this.delete_button, 2, 0, 1, 1);
  160. foreach (GFlow.Source s in n.get_sources()) {
  161. this.source_added(s);
  162. }
  163. foreach (GFlow.Sink s in n.get_sinks()) {
  164. this.sink_added(s);
  165. }
  166. }
  167. /**
  168. * Retrieve a Dock-Widget from this node.
  169. *
  170. * Gives you the GtkFlow.Dock-object that corresponds to the given
  171. * GFlow.Dock. Returns null if the searched Dock is not associated
  172. * with any of the Dock-Widgets in this node.
  173. */
  174. public Dock? retrieve_dock (GFlow.Dock d) {
  175. var c = this.grid.get_first_child();
  176. while (c != null) {
  177. if (!(c is Dock)) {
  178. c = c.get_next_sibling();
  179. continue;
  180. }
  181. var dw = (Dock)c;
  182. if (dw.d == d) return dw;
  183. c = c.get_next_sibling();
  184. }
  185. return null;
  186. }
  187. /**
  188. * {@inheritDoc}
  189. */
  190. public int get_margin() {
  191. return Node.MARGIN;
  192. }
  193. private void cb_delete() {
  194. var nv = this.get_parent() as NodeView;
  195. nv.remove(this);
  196. }
  197. /**
  198. * Adds a child widget to this node
  199. */
  200. public void add_child(Gtk.Widget child) {
  201. this.grid.attach(child, 0, 2 + n_docks, 3, 1);
  202. }
  203. /**
  204. * Removes a child widget from this node
  205. */
  206. public void remove_child(Gtk.Widget child) {
  207. child.unparent();
  208. }
  209. /**
  210. * {@inheritDoc}
  211. */
  212. public override void dispose() {
  213. this.grid.unparent();
  214. base.dispose();
  215. }
  216. private void sink_added(GFlow.Sink s) {
  217. var dock = new Dock(s);
  218. var label = new Gtk.Label(s.name);
  219. label.hexpand = true;
  220. label.halign = Gtk.Align.FILL;
  221. label.justify = Gtk.Justification.LEFT;
  222. this.grid.attach(dock, 0, 1 + ++n_docks, 1, 1);
  223. this.grid.attach(label, 1, 1 + n_docks, 1, 1);
  224. }
  225. private void source_added(GFlow.Source s) {
  226. var dock = new Dock(s);
  227. var label = new Gtk.Label(s.name);
  228. label.hexpand = true;
  229. label.halign = Gtk.Align.FILL;
  230. label.justify = Gtk.Justification.RIGHT;
  231. this.grid.attach(dock, 2, 1 + ++n_docks, 1, 1);
  232. this.grid.attach(label, 1, 1 + n_docks, 1, 1);
  233. }
  234. private void press_button(int n_click, double x, double y) {
  235. var picked_widget = this.pick(x,y, Gtk.PickFlags.NON_TARGETABLE);
  236. bool do_processing = false;
  237. if (picked_widget == this || picked_widget == this.grid) {
  238. do_processing = true;
  239. } else if (picked_widget.get_parent() == this.grid) {
  240. if (picked_widget is Gtk.Label || picked_widget is Gtk.Image) {
  241. do_processing = true;
  242. }
  243. }
  244. if (!do_processing) return;
  245. Gdk.Rectangle resize_area = {this.get_width()-8, this.get_height()-8,8,8};
  246. var nv = this.get_parent() as NodeView;
  247. if (resize_area.contains_point((int)x,(int)y)) {
  248. nv.resize_node = this;
  249. this.resize_start_width = this.get_width();
  250. this.resize_start_height = this.get_height();
  251. } else {
  252. nv.move_node = this;
  253. }
  254. this.click_offset_x = x;
  255. this.click_offset_y = y;
  256. }
  257. private void release_button() {
  258. var nv = this.get_parent() as NodeView;
  259. nv.move_node = null;
  260. nv.resize_node = null;
  261. nv.queue_allocate();
  262. }
  263. /**
  264. * {@inheritDoc}
  265. */
  266. public new void set_parent(Gtk.Widget w) {
  267. if (!(w is NodeView)) {
  268. warning("Trying to add a GtkFlow.Node to something that is not a GtkFlow.NodeView!");
  269. return;
  270. }
  271. base.set_parent(w);
  272. }
  273. protected override void snapshot (Gtk.Snapshot sn) {
  274. var rect = Graphene.Rect().init(0,0,this.get_width(), this.get_height());
  275. var rrect = Gsk.RoundedRect().init_from_rect(rect, 5f);
  276. Gdk.RGBA color;
  277. Gdk.RGBA grey_color;
  278. if (this.marked) {
  279. color = {0.0f,0.2f,0.5f,0.8f};
  280. grey_color = {0.0f,0.2f,0.6f,0.5f};
  281. } else {
  282. color = {0.5f,0.5f,0.5f,0.8f};
  283. grey_color = {0.6f,0.6f,0.6f,0.5f};
  284. }
  285. Gdk.RGBA[] border_color = {color,color,color,color};
  286. float[] thicc = {1f,1f,1f,1f};
  287. sn.append_color(grey_color ,rect );
  288. if (this.highlight_color != null) {
  289. sn.append_color(this.highlight_color ,rect );
  290. }
  291. sn.append_border(rrect, thicc, border_color);
  292. sn.append_outset_shadow(rrect, grey_color, 2f, 2f, 3f, 3f);
  293. if (this.resizable) {
  294. var cr = sn.append_cairo(rect);
  295. cr.save();
  296. cr.set_source_rgba(0.6,0.6,0.6,0.9);
  297. cr.set_line_width(8.0);
  298. cr.move_to(this.get_width()+2,this.get_height()-6);
  299. cr.line_to(this.get_width()-6,this.get_height()+2);
  300. cr.stroke();
  301. cr.restore();
  302. }
  303. base.snapshot(sn);
  304. }
  305. }
  306. }