node.vala 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  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 delegate Gtk.Widget NodeTitleFactory(Node node);
  23. public errordomain NodeError {
  24. TITLE_ALREADY_INITIALIZED;
  25. }
  26. /**
  27. * Defines an object that can be added to a Nodeview
  28. *
  29. * Implement this if you want custom nodes that have their own
  30. * drawing routines and special behaviour
  31. */
  32. public interface NodeRenderer : Gtk.Widget {
  33. /**
  34. * The {@link GFlow.Node} that this Node represents
  35. */
  36. public abstract GFlow.Node n {get; protected set;}
  37. /**
  38. * Expresses wheter this node is marked via rubberband selection
  39. */
  40. public abstract bool marked {get; internal set;}
  41. /**
  42. * Returns a {@link Dock} if the given {@link GFlow.Dock} resides
  43. * in this node.
  44. */
  45. public abstract Dock? retrieve_dock(GFlow.Dock d);
  46. /**
  47. * Returns the value of this node's margin
  48. */
  49. public abstract int get_margin();
  50. /**
  51. * Click offset: x coordinate
  52. *
  53. * Holds the offset-position relative to the origin of the
  54. * node at which this node has been clicked the last time.
  55. */
  56. public abstract double click_offset_x {get; protected set; default=0;}
  57. /**
  58. * Click offset: y coordinate
  59. *
  60. * Holds the offset-position relative to the origin of the
  61. * node at which this node has been clicked the last time.
  62. */
  63. public abstract double click_offset_y {get; protected set; default=0;}
  64. /**
  65. * Resize start width
  66. *
  67. * Hold the original width of the node when the last resize process
  68. * had been started
  69. */
  70. public abstract double resize_start_width {get; protected set; default=0;}
  71. /**
  72. * Resize start height
  73. *
  74. * Hold the original height of the node when the last resize process
  75. * had been started
  76. */
  77. public abstract double resize_start_height {get; protected set; default=0;}
  78. }
  79. public class NodeDockLabelWidgetFactory : Object {
  80. public GFlow.Node node {
  81. get;
  82. private set;
  83. }
  84. public NodeDockLabelWidgetFactory(GFlow.Node node) {
  85. this.node = node;
  86. }
  87. public virtual Gtk.Widget create_dock_label(GFlow.Dock dock) {
  88. var label = new Gtk.Label(dock.name);
  89. label.justify = Gtk.Justification.LEFT;
  90. label.hexpand = true;
  91. if (dock is GFlow.Source) {
  92. label.halign = Gtk.Align.END;
  93. } else {
  94. label.halign = Gtk.Align.START;
  95. }
  96. return label;
  97. }
  98. }
  99. /**
  100. * A Simple node representation
  101. *
  102. * The default {@link NodeRenderer} that comes with libgtkflow. Use this
  103. * To wrap your {@link GFlow.Node}s in order to add them to a {@link NodeView}
  104. */
  105. public class Node : Gtk.Widget, NodeRenderer {
  106. private static string CSS = "
  107. .gtkflow_node {
  108. background: rgba(0.6, 0.6, 0.6, 0.2);
  109. border-radius: 5px;
  110. border: 1px solid rgba(128, 128, 128, 0.8);
  111. box-shadow: 2px 2px 3px 3px rgba(153, 153, 153, 0.5);
  112. }
  113. .gtkflow_node_marked {
  114. background: rgba(0, 51, 128, 0.8);
  115. border-radius: 5px;
  116. border: 1px solid rgba(0, 51, 128, 0.8);
  117. box-shadow: 2px 2px 3px 3px rgba(0, 51, 153, 0.5);
  118. }
  119. ";
  120. private static Gtk.CssProvider css = new Gtk.CssProvider();
  121. private static bool initialized = false;
  122. private static void init() {
  123. if (Node.initialized) return;
  124. Node.css.load_from_data(Node.CSS.data);
  125. Node.initialized = true;
  126. }
  127. private bool title_initialized = false;
  128. construct {
  129. set_css_name("gtkflow_node");
  130. this.notify["marked"].connect(this.marked_changed);
  131. }
  132. private const int MARGIN_DEFAULT = 10;
  133. private Gtk.Grid grid;
  134. private Gtk.GestureClick ctr_click;
  135. public GFlow.Node n {get; protected set;}
  136. private NodeDockLabelWidgetFactory dock_label_factory;
  137. /**
  138. * {@inheritDoc}
  139. */
  140. public bool marked {get; internal set;}
  141. public Gdk.RGBA? highlight_color {get; set; default=null;}
  142. /**
  143. * {@inheritDoc}
  144. */
  145. public double click_offset_x {get; protected set; default=0;}
  146. /**
  147. * {@inheritDoc}
  148. */
  149. public double click_offset_y {get; protected set; default=0;}
  150. /**
  151. * {@inheritDoc}
  152. */
  153. public double resize_start_width {get; protected set; default=0;}
  154. /**
  155. * {@inheritDoc}
  156. */
  157. public double resize_start_height {get; protected set; default=0;}
  158. public bool render_resize_handle;
  159. private int n_docks = 0;
  160. private int margin = 0;
  161. ~Node() {
  162. this.grid.unparent();
  163. }
  164. /**
  165. * Instantiate a new node
  166. *
  167. * You are required to pass a {@link GFlow.Node} to this constructor.
  168. */
  169. public Node(GFlow.Node n) {
  170. this.with_margin(n, MARGIN_DEFAULT, new NodeDockLabelWidgetFactory(n));
  171. var title_box = new Gtk.Box(Gtk.Orientation.HORIZONTAL, 3);
  172. var title_label = new Gtk.Label("");
  173. title_label.set_markup ("<b>%s</b>".printf(n.name));
  174. title_label.hexpand = true;
  175. title_label.halign = Gtk.Align.START;
  176. title_box.append(title_label);
  177. var delete_icon = new Gtk.Image.from_icon_name("edit-delete");
  178. var delete_button = new Gtk.Button();
  179. delete_button.child = delete_icon;
  180. delete_button.has_frame = false;
  181. delete_button.clicked.connect(this.remove);
  182. title_box.append(delete_button);
  183. try {
  184. set_title(title_box);
  185. } catch (NodeError e) {
  186. message("Could not set title in node");
  187. }
  188. }
  189. public Node.with_margin(GFlow.Node n, int margin, NodeDockLabelWidgetFactory dock_label_factory) {
  190. Node.init();
  191. this.n = n;
  192. this.dock_label_factory = dock_label_factory;
  193. this.margin = margin;
  194. this.add_css_class("gtkflow_node");
  195. this.get_style_context().add_provider(Node.css,Gtk.STYLE_PROVIDER_PRIORITY_USER);
  196. this.grid = new Gtk.Grid();
  197. this.grid.column_homogeneous = false;
  198. this.grid.column_spacing = 5;
  199. this.grid.row_homogeneous = false;
  200. this.grid.row_spacing = 5;
  201. this.grid.hexpand = true;
  202. this.grid.vexpand = true;
  203. this.grid.halign = Gtk.Align.FILL;
  204. this.grid.valign = Gtk.Align.FILL;
  205. this.grid.margin_top = this.margin;
  206. this.grid.margin_bottom = this.margin;
  207. this.grid.margin_start = this.margin;
  208. this.grid.margin_end = this.margin;
  209. this.grid.set_parent(this);
  210. this.set_layout_manager(new Gtk.BinLayout());
  211. this.ctr_click = new Gtk.GestureClick();
  212. this.add_controller(this.ctr_click);
  213. this.ctr_click.pressed.connect(this.press_button);
  214. this.ctr_click.end.connect(this.release_button);
  215. var motion_controller = new Gtk.EventControllerMotion();
  216. motion_controller.motion.connect(this.hover_over);
  217. this.add_controller(motion_controller);
  218. foreach (GFlow.Source s in n.get_sources()) {
  219. this.source_added(s);
  220. }
  221. foreach (GFlow.Sink s in n.get_sinks()) {
  222. this.sink_added(s);
  223. }
  224. }
  225. /**
  226. * Retrieve a Dock-Widget from this node.
  227. *
  228. * Gives you the GtkFlow.Dock-object that corresponds to the given
  229. * GFlow.Dock. Returns null if the searched Dock is not associated
  230. * with any of the Dock-Widgets in this node.
  231. */
  232. public Dock? retrieve_dock (GFlow.Dock d) {
  233. var c = this.grid.get_first_child();
  234. while (c != null) {
  235. if (!(c is Dock)) {
  236. c = c.get_next_sibling();
  237. continue;
  238. }
  239. var dw = (Dock)c;
  240. if (dw.d == d) return dw;
  241. c = c.get_next_sibling();
  242. }
  243. return null;
  244. }
  245. /**
  246. * {@inheritDoc}
  247. */
  248. public int get_margin() {
  249. return this.margin;
  250. }
  251. public void remove() {
  252. var nv = this.get_parent() as NodeView;
  253. nv.remove(this);
  254. }
  255. /**
  256. * Adds a child widget to this node
  257. */
  258. public void add_child(Gtk.Widget child) {
  259. this.grid.attach(child, 0, 2 + n_docks, 3, 1);
  260. }
  261. public void set_title(Gtk.Widget title) throws NodeError {
  262. if (!this.title_initialized) {
  263. this.grid.attach(title, 0, 0, 3, 1);
  264. this.title_initialized = true;
  265. } else {
  266. throw new NodeError.TITLE_ALREADY_INITIALIZED("Title may only be initialized once");
  267. }
  268. }
  269. /**
  270. * Removes a child widget from this node
  271. */
  272. public void remove_child(Gtk.Widget child) {
  273. child.unparent();
  274. }
  275. protected override void dispose() {
  276. this.grid.unparent();
  277. base.dispose();
  278. }
  279. /**
  280. * {@inheritDoc}
  281. */
  282. private void sink_added(GFlow.Sink s) {
  283. var dock = new Dock(s);
  284. var dock_label = dock_label_factory.create_dock_label(dock.d);
  285. this.grid.attach(dock, 0, 1 + ++n_docks, 1, 1);
  286. this.grid.attach(dock_label, 1, 1 + n_docks, 1, 1);
  287. }
  288. private void source_added(GFlow.Source s) {
  289. var dock = new Dock(s);
  290. var dock_label = dock_label_factory.create_dock_label(dock.d);
  291. this.grid.attach(dock, 2, 1 + ++n_docks, 1, 1);
  292. this.grid.attach(dock_label, 1, 1 + n_docks, 1, 1);
  293. }
  294. private void press_button(int n_click, double x, double y) {
  295. var picked_widget = this.pick(x,y, Gtk.PickFlags.NON_TARGETABLE);
  296. bool do_processing = true;
  297. if (picked_widget is GtkFlow.Dock ) {
  298. do_processing = false;
  299. }
  300. if (!do_processing) return;
  301. Gdk.Rectangle resize_area = {this.get_width()-8, this.get_height()-8,8,8};
  302. var nv = this.get_parent() as NodeView;
  303. if (this.n.resizable && resize_area.contains_point((int)x,(int)y)) {
  304. nv.resize_node = this;
  305. this.resize_start_width = this.get_width();
  306. this.resize_start_height = this.get_height();
  307. } else {
  308. nv.move_node = this;
  309. }
  310. this.click_offset_x = x;
  311. this.click_offset_y = y;
  312. }
  313. private void hover_over(double x, double y) {
  314. if (!this.n.resizable) {
  315. return;
  316. }
  317. Gdk.Rectangle resize_area = {this.get_width()-8, this.get_height()-8,8,8};
  318. if (resize_area.contains_point((int)x,(int)y)) {
  319. this.set_cursor_from_name("nwse-resize");
  320. } else {
  321. this.set_cursor_from_name("default");
  322. }
  323. }
  324. private void release_button() {
  325. var nv = this.get_parent() as NodeView;
  326. nv.move_node = null;
  327. nv.resize_node = null;
  328. nv.queue_allocate();
  329. }
  330. /**
  331. * {@inheritDoc}
  332. */
  333. public new void set_parent(Gtk.Widget w) {
  334. if (!(w is NodeView)) {
  335. warning("Trying to add a GtkFlow.Node to something that is not a GtkFlow.NodeView!");
  336. return;
  337. }
  338. base.set_parent(w);
  339. }
  340. private void marked_changed() {
  341. if (this.marked) {
  342. this.add_css_class("gtkflow_node_marked");
  343. } else {
  344. this.remove_css_class("gtkflow_node_marked");
  345. }
  346. }
  347. }
  348. }