Lumiera  0.pre.03
»edit your freedom«
drag-relocate-controller.hpp
Go to the documentation of this file.
1 /*
2  DRAG-RELOCATE-CONTROLLER.hpp - concrete gesture controller to relocate a widget by dragging
3 
4  Copyright (C) Lumiera.org
5  2021, Hermann Vosseler <Ichthyostega@web.de>
6 
7  This program is free software; you can redistribute it and/or
8  modify it under the terms of the GNU General Public License as
9  published by the Free Software Foundation; either version 2 of
10  the License, or (at your option) any later version.
11 
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  GNU General Public License for more details.
16 
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to the Free Software
19  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20 
21 */
22 
23 
46 #ifndef STAGE_INTERACT_DRAG_RELOCATE_CONTROLLER_H
47 #define STAGE_INTERACT_DRAG_RELOCATE_CONTROLLER_H
48 
49 
50 #include "stage/gtk-base.hpp"
53 #include "lib/opaque-holder.hpp"
54 #include "lib/format-string.hpp"
55 #include "lib/format-cout.hpp"
56 //#include "lib/idi/entry-id.hpp"
57 //#include "lib/symbol.hpp"
58 #include "lib/nocopy.hpp"
59 #include "lib/util.hpp"
60 
61 //#include <string>
62 
63 
64 namespace stage {
65 namespace interact {
66 
67 // using lib::HashVal;
68 // using util::isnil;
69 // using std::string;
70  using util::isnil;
71  using util::_Fmt;
72 
73  namespace {
74  const gdouble DISTANCE_THRESHOLD = 5.0;
75 
78  const size_t OBSERVER_BUFF_SIZ = sizeof(void*)
79  + sizeof(Symbol)
80  + 4 * sizeof(void*);
81  }
82 
83 
106  : public InteractionState
107  {
108  bool buttonPressed_ = false;
109  Subject* subject_ = nullptr;
110  bool isInFormation_ = false;
111  gdouble anchorX_ = 0.0;
112  gdouble anchorY_ = 0.0;
113 
115 
116  Observer observer_;
117 
118  void
119  linkTrigger (Subject& subject, Symbol cmdID) override
120  {
121  REQUIRE (not isnil (cmdID));
122  auto& widget = subject.exposeWidget();
123  widget.signal_button_press_event().connect(
124  sigc::mem_fun (*this, &DragRelocateController::watchButton));
125  widget.signal_button_release_event().connect(
126  sigc::mem_fun (*this, &DragRelocateController::watchButton));
127  widget.signal_motion_notify_event().connect(
128  [&, cmdID](GdkEventMotion* motion) -> bool
129  {
130  try{ return maybeActivate(cmdID, subject, motion); }
131  ON_EXCEPTION_RETURN (false, "activate dragging gesture")
132  }
133  );
134  }
135 
136  bool
137  watchButton (GdkEventButton* button_event) noexcept
138  {
139  REQUIRE (button_event);
140  if (GDK_BUTTON_PRESS == button_event->type)
141  buttonPressed_ = true;
142  else
143  if (GDK_BUTTON_RELEASE == button_event->type)
144  {
145  buttonPressed_ = false;
146  if (isActive())
147  doCompleteGesture();
148  resetState();
149  }
150  std::cerr << _Fmt{"BUTT %s flag=%d"} % buttonPressed_ % button_event->type << std::endl;
151  return false;
152  }
153 
155  bool
156  maybeActivate (Symbol cmdID, Subject& subject, GdkEventMotion* motion_event)
157  {
158  if (not buttonPressed_)
159  return false; // Event not handled by this controller
160  REQUIRE (motion_event);
161  std::cerr << _Fmt{"MOVE x=%3.1f y=%3.1f subject=%s"}
162  % motion_event->x_root
163  % motion_event->y_root
164  % subject
165  << std::endl;
166  if (not isAnchored())
167  anchor (cmdID, subject, motion_event);
168  if (not isActive())
169  {
170  probeActivation (motion_event);
171  if (isActive())
172  initGestureTracking(cmdID, subject);
173  return false;
174  }
175  else
176  {
177  doTrackGesture (motion_event);
178  return true; // Event handled
179  }
180  }
181 
182 
183  /* === gesture implementation === */
184  bool
185  isActive()
186  {
187  return subject_
188  and isInFormation_;
189  }
190 
191  bool
192  isAnchored()
193  {
194  return bool{subject_};
195  }
196 
197  void
198  anchor (Symbol cmdID, Subject& subject, GdkEventMotion* motion_event)
199  {
200  REQUIRE (motion_event);
201  this->subject_ = & subject;
202  this->anchorX_ = motion_event->x_root;
203  this->anchorY_ = motion_event->y_root;
204  std::cerr << _Fmt{"ANCHOR at x=%3.1f y=%3.1f ('%s')"}
205  % anchorX_
206  % anchorY_
207  % cmdID
208  << std::endl;
209  }
210 
211  void
212  probeActivation (GdkEventMotion* motion_event)
213  {
214  isInFormation_ = DISTANCE_THRESHOLD < abs (motion_event->x_root - anchorX_)
215  or DISTANCE_THRESHOLD < abs (motion_event->y_root - anchorY_);
216  }
217 
218  void
219  initGestureTracking(Symbol cmdID, Subject& subject)
220  {
221  subject.buildGestureObserver (cmdID, Observer::Handle{observer_});
222  }
223 
224  void
225  doTrackGesture (GdkEventMotion* motion_event)
226  {
227  REQUIRE (motion_event);
228  gdouble deltaX = motion_event->x_root - this->anchorX_;
229  gdouble deltaY = motion_event->y_root - this->anchorY_;
230  // notify Subject to feed current delta
231  observer_->updateOffset (deltaX, deltaY);
232  }
233 
234  void
235  doCompleteGesture()
236  {
237  observer_->markGestureCompleted();
238  }
239 
240  void
241  resetState()
242  {
243  isInFormation_ = false;
244  anchorX_ = anchorY_ = 0.0;
245  subject_ = nullptr;
246  observer_.reset();
247  }
248 
249 
250  public:
252  : observer_{}
253  { }
254 
255  private:
256  };
257 
258 
259 
260 }} // namespace stage::interact
261 #endif /*STAGE_INTERACT_DRAG_RELOCATE_CONTROLLER_H*/
Abstraction: a component to watch, maintain and guide UI state.
bool maybeActivate(Symbol cmdID, Subject &subject, GdkEventMotion *motion_event)
Gesture detection state logic.
#define ON_EXCEPTION_RETURN(_VAL_, _OP_DESCR_)
convenience shortcut to catch and absorb any exception, then returning a default value instead...
Definition: error.hpp:313
Automatically use custom string conversion in C++ stream output.
Front-end for printf-style string template interpolation.
const size_t OBSERVER_BUFF_SIZ
heuristics for sizing the inline buffer where the Subject will construct its Observer/Adapter ...
A front-end for using printf-style formatting.
Abstract foundation for context dependent UI interactions.
Token or Atom with distinct identity.
Definition: symbol.hpp:126
Mix-Ins to allow or prohibit various degrees of copying and cloning.
A handle to allow for safe »remote implantation« of an unknown subclass into a given opaque InPlaceBu...
Definition: record.hpp:113
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:46
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
Helper allowing type erasure while holding the actual object inline.
virtual void buildGestureObserver(Symbol cmdID, Buffer)=0
prompt the Subject to build an Observer for the gesture in formation
virtual Gtk::Widget & exposeWidget()=0
the exposed widget can be used for wiring signal handlers
Gesture controller for dragging objects within the Timeline display.
A set of basic GTK includes for the UI.
Abstraction: support for binding command invocation into an UI context.
Role-Interface: the Subject of Interaction.
void linkTrigger(Subject &subject, Symbol cmdID) override
Hook up a trigger signal to initiate a specific interaction gesture.