minimap.vala 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  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. * A Widget that draws a minmap of a {@link GtkFlow.NodeView}
  24. *
  25. * Please set the nodeview property after integrating the referenced
  26. * {@link GtkFlow.NodeView} into its respective container
  27. */
  28. public class Minimap : Gtk.DrawingArea {
  29. private GtkFlow.NodeView? _nodeview = null;
  30. private Gtk.ScrolledWindow? _scrolledwindow = null;
  31. private ulong draw_signal = 0;
  32. private ulong hadjustment_signal = 0;
  33. private ulong vadjustment_signal = 0;
  34. private int offset_x = 0;
  35. private int offset_y = 0;
  36. private double ratio = 0.0;
  37. private int rubber_width = 0;
  38. private int rubber_height = 0;
  39. private bool move_rubber = false;
  40. /**
  41. * The nodeview that this Minimap should depict
  42. *
  43. * You may either add a {@link GtkFlow.NodeView} directly or a
  44. * {@link Gtk.ScrolledWindow} that contains a {@link GtkFlow.NodeView}
  45. * as its child.
  46. */
  47. public NodeView nodeview {
  48. get {
  49. return this._nodeview;
  50. }
  51. set {
  52. if (this._nodeview != null) {
  53. GLib.SignalHandler.disconnect(this._nodeview, this.draw_signal);
  54. }
  55. if (this._scrolledwindow != null) {
  56. GLib.SignalHandler.disconnect(this._nodeview, this.hadjustment_signal);
  57. GLib.SignalHandler.disconnect(this._nodeview, this.vadjustment_signal);
  58. }
  59. if (value == null) {
  60. this._nodeview = null;
  61. this._scrolledwindow = null;
  62. } else {
  63. this._nodeview = value;
  64. this._scrolledwindow = null;
  65. if (value.get_parent() is Gtk.ScrolledWindow) {
  66. this._scrolledwindow = value.get_parent() as Gtk.ScrolledWindow;
  67. } else {
  68. if (value.get_parent() is Gtk.Viewport) {
  69. if (value.get_parent().get_parent() is Gtk.ScrolledWindow) {
  70. this._scrolledwindow = value.get_parent().get_parent() as Gtk.ScrolledWindow;
  71. this.hadjustment_signal = this._scrolledwindow.hadjustment.notify["value"].connect(
  72. ()=>{this.queue_draw();}
  73. );
  74. this.vadjustment_signal = this._scrolledwindow.vadjustment.notify["value"].connect(
  75. ()=>{this.queue_draw();}
  76. );
  77. }
  78. }
  79. }
  80. this.draw_signal = this._nodeview.draw_minimap.connect(()=>{this.queue_draw(); });
  81. }
  82. this.queue_draw();
  83. }
  84. }
  85. private Gtk.EventControllerMotion ctr_motion;
  86. private Gtk.GestureClick ctr_click;
  87. /**
  88. * Create a new Minimap
  89. */
  90. public Minimap() {
  91. this.set_size_request(50,50);
  92. this.ctr_motion = new Gtk.EventControllerMotion();
  93. this.add_controller(this.ctr_motion);
  94. this.ctr_click = new Gtk.GestureClick();
  95. this.add_controller(this.ctr_click);
  96. this.ctr_click.pressed.connect((n,x,y)=> {this.do_button_press_event(x,y);});
  97. this.ctr_click.end.connect(()=>{this.do_button_release_event(); });
  98. this.ctr_motion.motion.connect((x,y)=>{this.do_motion_notify_event(x,y); });
  99. }
  100. private void do_button_press_event(double x, double y) {
  101. this.move_rubber = true;
  102. double halloc = double.max(0, x - offset_x - rubber_width / 2) * ratio;
  103. double valloc = double.max(0, y - offset_y - rubber_height / 2) * ratio;
  104. this._scrolledwindow.hadjustment.value = halloc;
  105. this._scrolledwindow.vadjustment.value = valloc;
  106. }
  107. private void do_motion_notify_event(double x, double y) {
  108. if (!this.move_rubber || this._scrolledwindow == null) {
  109. return;
  110. }
  111. double halloc = double.max(0, x - offset_x - rubber_width / 2) * ratio;
  112. double valloc = double.max(0, y - offset_y - rubber_height / 2) * ratio;
  113. this._scrolledwindow.hadjustment.value = halloc;
  114. this._scrolledwindow.vadjustment.value = valloc;
  115. }
  116. private void do_button_release_event() {
  117. this.move_rubber = false;
  118. }
  119. /**
  120. * Draws the minimap. This method is called internally
  121. */
  122. public override void snapshot(Gtk.Snapshot sn) {
  123. Graphene.Rect rect;
  124. Gtk.Allocation own_alloc;
  125. this.get_allocation(out own_alloc);
  126. if (this._nodeview != null) {
  127. Gtk.Allocation nv_alloc;
  128. this._nodeview.get_allocation(out nv_alloc);
  129. this.offset_x = 0;
  130. this.offset_y = 0;
  131. int height = 0;
  132. int width = 0;
  133. if (own_alloc.width> own_alloc.height) {
  134. width = (int)((double) nv_alloc.width / nv_alloc.height * own_alloc.height);
  135. height = own_alloc.height;
  136. offset_x = (own_alloc.width - width ) / 2;
  137. } else {
  138. height = (int)((double) nv_alloc.height / nv_alloc.width * own_alloc.width);
  139. width = own_alloc.width;
  140. offset_y = (own_alloc.height - height ) / 2;
  141. }
  142. this.ratio = (double) nv_alloc.width / width;
  143. var child = this.nodeview.get_first_child();
  144. while (child != null) {
  145. var n = (Node)child;
  146. Gtk.Allocation alloc;
  147. n.get_allocation(out alloc);
  148. Gdk.RGBA color;
  149. if (n.highlight_color != null) {
  150. color = n.highlight_color;
  151. } else {
  152. color = {0.4f,0.4f,0.4f,0.5f};
  153. }
  154. rect = Graphene.Rect().init(
  155. (int)(offset_x + alloc.x/ratio),
  156. (int)(offset_y + alloc.y/ratio),
  157. (int)(alloc.width/ratio),
  158. (int)(alloc.height/ratio)
  159. );
  160. sn.append_color(color, rect);
  161. child = child.get_next_sibling();
  162. }
  163. if (this._scrolledwindow != null) {
  164. Gtk.Allocation sw_alloc;
  165. this._scrolledwindow.get_allocation(out sw_alloc);
  166. if (sw_alloc.width < nv_alloc.width || sw_alloc.height < nv_alloc.height) {
  167. rect = Graphene.Rect().init(
  168. (int)(offset_x + this._scrolledwindow.hadjustment.value / ratio),
  169. (int)(offset_y + this._scrolledwindow.vadjustment.value / ratio),
  170. (int)(sw_alloc.width / ratio),
  171. (int)(sw_alloc.height / ratio)
  172. );
  173. sn.append_color({0.0f,0.2f,0.6f,0.5f}, rect);
  174. }
  175. }
  176. }
  177. }
  178. }
  179. }