Lumiera  0.pre.03
»edit your freedom«
body-canvas-widget.cpp
Go to the documentation of this file.
1 /*
2  BodyCanvasWidget - custom drawing canvas to display the timeline body
3 
4  Copyright (C)
5  2016, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
14 
39 #include "stage/gtk-base.hpp"
44 #include "stage/style-scheme.hpp"
45 
46 #include "common/advice.hpp"
47 #include "lib/util.hpp"
48 
49 #include <utility>
50 
51 
52 using lib::time::Time;
53 using util::max;
54 using util::isnil;
55 using Gdk::Rectangle;
56 using std::string;
57 using std::move;
58 
59 
60 
61 namespace stage {
62 namespace timeline {
63 
64  using CairoC = PCairoContext const&;
65  using StyleC = PStyleContext const&;
66 
67  namespace { // details of track background painting
68 
69  const int INITIAL_TIMERULER_HEIGHT_px = 30;
70  const int INITIAL_CONTENT_HEIGHT_px = 100;
71  const int MINIMAL_CONTENT_WIDTH_px = 100;
72 
75  lumiera::advice::Request<PStyleContext> trackRulerStyle{"style(trackRuler)"};
76 
77  cuString
78  slopeClassName(int depth)
79  {
80  switch (depth)
81  {
82  case 1: return CLASS_slope_deep1;
83  case 2: return CLASS_slope_deep2;
84  case 3: return CLASS_slope_deep3;
85  case 4: return CLASS_slope_deep4;
86  default:return CLASS_slope_verydeep;
87  }
88  }
89  const uint SLOPE_CAP_DEPTH = 5;
90 
91 
97  void
99  {
100  static bool styleSetupDone{false};
101  if (styleSetupDone) return;
102 
103  StyleC styleRuler{trackRulerStyle.getAdvice()};
104  StyleC styleBody {trackBodyStyle.getAdvice()};
105 
106  TrackBody::decoration.ruler = styleRuler->get_margin().get_top()
107  + styleRuler->get_margin().get_bottom()
108  + styleRuler->get_border().get_top()
109  + styleRuler->get_border().get_bottom()
110  + styleRuler->get_padding().get_top()
111  + styleRuler->get_padding().get_bottom()
112  ;
113  TrackBody::decoration.content = styleBody->get_margin().get_top()
114  + styleBody->get_margin().get_bottom()
115  + styleBody->get_padding().get_top()
116  + styleBody->get_padding().get_bottom()
117  ;
118  TrackBody::decoration.trackPad = styleBody->get_margin().get_top()
119  + styleBody->get_padding().get_top()
120  ;
121  TrackBody::decoration.topMar = styleBody->get_margin().get_top();
122  TrackBody::decoration.botMar = styleBody->get_margin().get_bottom();
123 
124  for (uint depth=SLOPE_CAP_DEPTH; depth>0; --depth)
125  {
126 // styleBody->context_save(); // <<<---does not work. Asked on SO: https://stackoverflow.com/q/57342478
127  styleBody->add_class (slopeClassName(depth));
128 
129  TrackBody::decoration.borders[depth] = styleBody->get_border().get_bottom();
130  TrackBody::decoration.borders[0] = styleBody->get_border().get_top(); // Note: we use a common size for all opening borders
131 
132  styleBody->remove_class (slopeClassName(depth));
133 // styleBody->context_restore(); // <<<---does not work...
134  }
135  styleSetupDone = true;
136  }
137 
138 
154  : public ProfileInterpreter
155  {
156  protected:
157  CairoC cox_;
158  StyleC style_; // CSS style for the main track body
159  StyleC styleR_; // CSS style for the an overview ruler
160  PixSpan visible_; // vertical extension of the timeline
161 
164  int line_ = 0;
165 
166  void
167  fillBackground (StyleC style, int height)
168  {
169  style->render_background (cox_
170  ,visible_.b // left start of the rectangle
171  ,line_ // top of the rectangle
172  ,visible_.delta() // width of the area
173  ,height // height to fill
174  );
175  }
176 
177  public:
178  AbstractTrackRenderer (CairoC currentDrawContext, DisplayManager& layout)
179  : cox_{currentDrawContext}
180  , style_{trackBodyStyle.getAdvice()}
181  , styleR_{trackRulerStyle.getAdvice()}
182  , visible_{layout.getPixSpan()}
183  { }
184  /*
185  * Note: we store a const& to the advice in the member fields.
186  * This is potentially dangerous, but seems adequate here, since
187  * the renderer instance does not outlive the BodyCanvasWidget,
188  * and the style context accessed through the advice is created
189  * once, at application startup. Please take into account that
190  * this drawing code is invoked very frequently from GUI thread.
191  */
192  };
193 
194 
196  : public AbstractTrackRenderer
197  {
198 
200  void
201  prelude() override
202  {
203  int topMargin = style_->get_margin().get_top();
204  line_ += topMargin;
205  }
206 
209  void
210  coda (uint pad) override
211  {
212  int bottomPad = pad + style_->get_margin().get_bottom();
213  line_ += bottomPad;
214  }
215 
218  void
219  ruler (uint contentHeight) override
220  {
221  int marTop = styleR_->get_margin().get_top();
222  int marBot = styleR_->get_margin().get_bottom();
223  int padTop = styleR_->get_padding().get_top();
224  int padBot = styleR_->get_padding().get_bottom();
225  int frameT = styleR_->get_border().get_top();
226  int frameB = styleR_->get_border().get_bottom();
227 
228  int heightWithFrame = contentHeight + padTop+padBot + frameT+frameB;
229 
230  line_ += marTop;
231  fillBackground(styleR_, heightWithFrame);
232  styleR_->render_frame (cox_
233  ,visible_.b
234  ,line_
235  ,visible_.delta()
236  ,heightWithFrame
237  );
238  line_ += heightWithFrame;
239  line_ += marBot;
240  }
241 
243  void
244  gap (uint h) override
245  {
246  line_ += h;
247  }
248 
251  void
252  content (uint contentHeight) override
253  {
254  int marTop = style_->get_margin().get_top();
255  int marBot = style_->get_margin().get_bottom();
256  int padTop = style_->get_padding().get_top();
257  int padBot = style_->get_padding().get_bottom();
258  int heightWithPadding = contentHeight + padTop+padBot;
259 
260  line_ += marTop;
261  fillBackground (style_, heightWithPadding);
262  line_ += heightWithPadding;
263  line_ += marBot;
264  }
265 
268  void
269  open() override
270  {
271 // style_->context_save(); // <<<---does not work. Asked on SO: https://stackoverflow.com/q/57342478
272  style_->add_class (slopeClassName (1));
273  int slopeWidth = style_->get_border().get_top();
274  style_->render_frame_gap(cox_
275  ,visible_.b - slopeWidth
276  ,line_
277  ,visible_.delta() + 2*slopeWidth
278  ,2*slopeWidth
279  //_______________________________we only need the top side of the frame
280  ,Gtk::PositionType::POS_BOTTOM
281  ,visible_.b
282  ,visible_.e + 2*slopeWidth
283  );
284 // style_->context_restore(); // <<<---does not work...
285  style_->remove_class (slopeClassName(1));
286  line_ += slopeWidth;
287  }
288 
295  void
296  close (uint n) override
297  {
298 // style_->context_save(); // <<<---does not work. Asked on SO: https://stackoverflow.com/q/57342478
299  style_->add_class (slopeClassName(n));
300  int slopeWidth = style_->get_border().get_bottom();
301  line_ -= slopeWidth; // set back to create room for the (invisible) top side of the frame
302  style_->render_frame_gap(cox_
303  ,visible_.b - slopeWidth
304  ,line_
305  ,visible_.delta() + 2*slopeWidth
306  ,2*slopeWidth
307  //_______________________________we only need the bottom side of the frame
308  ,Gtk::PositionType::POS_TOP
309  ,visible_.b
310  ,visible_.e + 2*slopeWidth
311  );
312 // style_->context_restore(); // <<<---does not work...
313  style_->remove_class (slopeClassName(n));
314  line_ += 2*slopeWidth;
315  }
316 
317  public:
318  using AbstractTrackRenderer::AbstractTrackRenderer;
319  };
320 
321 
322 
324  : public AbstractTrackRenderer
325  {
326 
328  void
329  prelude() override
330  {
331  /* nothing to paint */
332  line_ += style_->get_margin().get_top();
333  }
334 
337  void
338  coda (uint pad) override
339  {
340  /* nothing to paint */
341  line_ += pad + style_->get_margin().get_bottom();
342  }
343 
346  void
347  ruler (uint contentHeight) override
348  {
349  int marTop = styleR_->get_margin().get_top();
350  int marBot = styleR_->get_margin().get_bottom();
351  int padTop = styleR_->get_padding().get_top();
352  int padBot = styleR_->get_padding().get_bottom();
353  int frameT = styleR_->get_border().get_top();
354  int frameB = styleR_->get_border().get_bottom();
355 
356  int heightWithFrame = contentHeight + padTop+padBot + frameT+frameB;
357 
358  /* nothing to paint */
359  line_ += marTop
360  + heightWithFrame
361  + marBot;
362  }
363 
365  void
366  gap (uint h) override
367  {
368  /* nothing to paint */
369  line_ += h;
370  }
371 
375  void
376  content (uint contentHeight) override
377  {
378  int marTop = style_->get_margin().get_top();
379  int marBot = style_->get_margin().get_bottom();
380  int padTop = style_->get_padding().get_top();
381  int padBot = style_->get_padding().get_bottom();
382  int heightWithPadding = contentHeight + padTop+padBot;
383 
384  /* nothing to paint */
385  line_ += marTop
386  + heightWithPadding
387  + marBot;
388  }
389 
391  void
392  open() override
393  {
394 // style_->context_save(); // <<<---does not work. Asked on SO: https://stackoverflow.com/q/57342478
395  style_->add_class (slopeClassName (1));
396  int slopeWidth = style_->get_border().get_top();
397 // style_->context_restore(); // <<<---does not work...
398  style_->remove_class (slopeClassName(1));
399  line_ += slopeWidth;
400  }
401 
403  void
404  close (uint n) override
405  {
406 // style_->context_save(); // <<<---does not work. Asked on SO: https://stackoverflow.com/q/57342478
407  style_->add_class (slopeClassName(n));
408  int slopeWidth = style_->get_border().get_bottom();
409 // style_->context_restore(); // <<<---does not work...
410  style_->remove_class (slopeClassName(n));
411  line_ += slopeWidth;
412  }
413 
414  public:
415  using AbstractTrackRenderer::AbstractTrackRenderer;
416  };
417 
418 
419  template<class PINT, bool isRuler>
420  auto
421  makeRenderer (DisplayManager& layout, BodyCanvasWidget::ProfileGetter& getProfile)
422  {
423  return [&](CairoC cox)
424  {
425  PINT concreteRenderScheme{cox, layout};
426  getProfile().performWith (concreteRenderScheme, isRuler);
427  };
428  }
429 
430  // abbreviations for readability
433  const bool RULER = true;
434  const bool BODY = false;
435  }
436 
437 
438 
439 
440 
441  BodyCanvasWidget::~BodyCanvasWidget() { }
442 
443 
444  BodyCanvasWidget::BodyCanvasWidget (DisplayManager& displayManager)
445  : Glib::ObjectBase("body") // enables use of custom CSS properties (on 'gtkmm__CustomObject_body')
446  , Gtk::Box{Gtk::ORIENTATION_VERTICAL}
447  , layout_{displayManager}
448  , profile_{}
449  , rootBody_{nullptr}
450  , contentArea_{}
451  , rulerArea_{contentArea_.get_hadjustment(), Gtk::Adjustment::create (0,0,0,0,0,0)}
452  , rulerCanvas_{makeRenderer<Grounding,RULER>(layout_,getProfile), makeRenderer<Overlay,RULER>(layout_,getProfile)}
453  , mainCanvas_ {makeRenderer<Grounding,BODY> (layout_,getProfile), makeRenderer<Overlay,BODY> (layout_,getProfile)}
454  {
455  get_style_context()->add_class(CLASS_timeline);
456  get_style_context()->add_class(CLASS_timeline_body);
457 
458  // respond to any structure changes of the timeline by recomputing the TrackProfile
459  layout_.signalStructureChange_.connect (sigc::mem_fun (*this, &BodyCanvasWidget::slotStructureChange));
460 
461  // on demand access and possible (re)establish the current "profile" of the tracks for drawing...
462  getProfile = [this]() -> TrackProfile&
463  {
464  maybeRebuildLayout();
465  return profile_;
466  };
467 
468  // initially set up some dummy space. Will be updated to match on first draw() call...
469  adjustCanvasSize(MINIMAL_CONTENT_WIDTH_px, INITIAL_CONTENT_HEIGHT_px, INITIAL_TIMERULER_HEIGHT_px);
470 
471  this->set_border_width (0);
472  this->property_expand() = true; // dynamically grab any available additional space
473  this->pack_start (rulerArea_, Gtk::PACK_SHRINK);
474  this->pack_start (contentArea_, Gtk::PACK_EXPAND_WIDGET);
475 
476  rulerArea_.set_shadow_type (Gtk::SHADOW_NONE);
477  rulerArea_.set_policy (Gtk::POLICY_NEVER, Gtk::POLICY_NEVER);
478  rulerArea_.property_expand() = false;
479  rulerArea_.add (rulerCanvas_);
480  contentArea_.set_shadow_type (Gtk::SHADOW_NONE);
481  contentArea_.set_policy (Gtk::POLICY_ALWAYS, Gtk::POLICY_ALWAYS); // always use both scrollbars ////////////////TICKET #1208 : 2/2002 Gtk::POLICY_AUTOMATIC hides scrollbar after focus loss
482  contentArea_.property_expand() = true; // dynamically grab additional space
483  contentArea_.add (mainCanvas_);
484 
485  // realise all initially configured elements....
486  this->show_all();
487  }
488 
489 
490 
499  void
501  {
502  rootBody_ = &rootTrackBody;
503  }
504 
505  void
506  BodyCanvasWidget::disable()
507  {
508  profile_.clear();
509  rootBody_ = nullptr;
510  }
511 
512 
514  void
516  {
517  profile_.clear();
518  }
519 
520 
526  void
528  {
529  while (rootBody_ and isnil (profile_))
530  {
532  layout_.triggerDisplayEvaluation();
533  }
534  ENSURE (not isnil (profile_), "DisplayEvaluation logic broken");
535  }
536 
537 
538 
546  void
547  BodyCanvasWidget::adjustCanvasSize(int canvasWidth, int contentHeight, int rulerHeight)
548  {
549  auto adjust = [](Gtk::Layout& canvas, guint newWidth, guint newHeight) -> void
550  {
551  guint currWidth{0}, currHeight{0};
552  canvas.get_size(currWidth, currHeight);
553  if (currWidth != newWidth or currHeight != newHeight)
554  {
555  canvas.set_size(newWidth, newHeight);
556  // Note: must force GTK at least to claim the necessary height,
557  // otherwise the enclosing Box won't reflow and adapt;
558  // implicitly this defines minimum timeline window width
559  canvas.set_size_request(MINIMAL_CONTENT_WIDTH_px, newHeight);
560  }
561  };
562 
563  adjust (rulerCanvas_, canvasWidth, rulerHeight);
564  adjust (mainCanvas_, canvasWidth, contentHeight);
565  }
566 
567 
568  void
569  BodyCanvasWidget::forceRedraw()
570  {
571  rulerCanvas_.queue_draw();
572  mainCanvas_.queue_draw();
573  }
574 
576  BodyCanvasWidget::getCanvas (int yPos)
577  {
578  return mainCanvas_;
579  }
580 
581 
582  /* ==== Interface: CanvasHook ===== */
583 
584  void
585  BodyCanvasWidget::hook (Gtk::Widget& widget, int xPos, int yPos)
586  {
588  getCanvas(yPos).put (widget, xPos, yPos);
589  }
590 
591  void
592  BodyCanvasWidget::remove (Gtk::Widget& widget)
593  {
595  getCanvas(0).remove (widget);
596  }
597 
598  void
599  BodyCanvasWidget::move (Gtk::Widget& widget, int xPos, int yPos)
600  {
602  getCanvas(yPos).move (widget, xPos, yPos);
603  }
604 
607  {
608  return layout_;
609  }
610 
616  void
618  {
619  // Traverse TrackBody structure and populate the (track)profile
620  uint contentHeight = rootBody_->establishTrackSpace (profile_);
621  uint rulerHeight = rootBody_->calcRulerHeight();
622  adjustCanvasSize(layout_.getPixSpan().delta(), contentHeight, rulerHeight);
623 
625  }
626 
627  void
628  BodyCanvasWidget::completeLayout (DisplayEvaluation&)
629  { /* nothing to do */ }
630 
631 
632 
633 
634 
635 
636  TimelineCanvas::TimelineCanvas (_Renderer groundingFun, _Renderer overlayFun)
637  : Gtk::Layout{}
638  , renderGrounding_{groundingFun}
639  , renderOverlay_{overlayFun}
640  { }
641 
642 
652  bool
654  {
655  // draw track structure behind all widgets
656  openCanvas (cox);
657  drawGrounding (cox);
658  closeCanvas (cox);
659 
660  // cause child widgets to be redrawn
661  bool event_is_handled = Gtk::Layout::on_draw(cox);
662 
663  // draw dynamic markers and locators on top
664  openCanvas (cox);
665  drawOverlays (cox);
666  closeCanvas (cox);
667 
668  return event_is_handled;
669  }
670 
671 
679  void
681  {
682  auto adjH = get_hadjustment();
683  auto adjV = get_vadjustment();
684  double offH = adjH->get_value();
685  double offV = adjV->get_value();
686 
687  cox->save();
688  cox->translate(-offH, -offV);
689  }
690 
691 
696  void
698  {
699  cox->restore();
700  }
701 
702 
707  void
709  {
710  renderGrounding_(cox);
711  }
712 
713 
718  void
720  {
721  renderOverlay_(cox);
722  }
723 
724 
725 
726 }}// namespace stage::timeline
void close(uint n) override
paint closing slope to finish nested sub tracks
void maybeRebuildLayout()
Possibly (re)build the allocation and distribution of layout space.
PixSpan getPixSpan()
the overall horizontal pixel span to cover by this timeline
SignalStructureChange signalStructureChange_
signal to be invoked whenever the virtual structure of the corresponding timeline changes...
void closeCanvas(CairoC)
Finish and close the virtual drawing canvas established by openCanvas().
Access point for the advised entity (client).
Definition: advice.hpp:428
std::function< TrackProfile &()> ProfileGetter
a way to get and possibly (re)compute the current TrackProfile
Mix-in interface to allow for concrete CanvasHooked widgets to adapt themselves to the metric current...
Definition: canvas-hook.hpp:73
Widget to render the body of timeline display, by custom drawing into a canvas control.
void content(uint contentHeight) override
fill background of track content area with the given vertical extension
static Decoration decoration
storage for common style/padding settings
Definition: track-body.hpp:102
void ruler(uint contentHeight) override
draw grounding of an overview/ruler track with the given height
void open() override
render overlays covering the opening slope towards nested tracks
void coda(uint pad) override
finish painting overlays a the bottom of the track body area
void openCanvas(CairoC)
Prepare the drawing canvas to work within our virtual canvas coordinate system.
Lumiera&#39;s internal time value datatype.
Definition: timevalue.hpp:299
void prelude() override
overlays to show at top of the track body area
model::DisplayMetric & getMetric() const override
access the component to handle layout metric
void ruler(uint contentHeight) override
draw overlays on top of overview/ruler track
Visitor and state holder for a collaborative layout adjustment pass.
Expecting Advice and giving Advice: a cross-cutting collaboration of loosely coupled participants...
Borders borders
width of up to 6 levels of combined upward slope borders (defined in CSS)
Definition: track-body.hpp:70
Abstraction: service for the widgets to translate themselves into screen layout.
void content(uint contentHeight) override
place overlays on top of of track content area,
void prelude() override
create spacing at the top of the track body area
Description of the structure and arrangement of tracks for display in the UI.
void adjustCanvasSize(int canvasWidth, int totalHeight, int rulerHeight)
After the (recent) display evaluation pass has negotiated the required space for the currently presen...
Lumiera GTK UI implementation root.
Definition: guifacade.cpp:37
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
void drawGrounding(CairoC)
Establish and render the structure of (possibly nested) tracks and overview rulers.
void gap(uint h) override
insert additional padding/gap (typically below a ruler)
void installForkRoot(TrackBody &rootTrackBody)
The Lumiera Timeline model does not rely on a list of tracks, as most conventional video editing soft...
Helper to organise and draw the space allocated for a fork of sub-tracks.
Definition: track-body.hpp:86
This helper class serves to manage the layout and display of the horizontally extended space of a "tr...
virtual bool on_draw(CairoC) override
Custom drawing of the timeline content area.
void close(uint n) override
render overlays covering the closing slope towards nested tracks
Abstraction to build the layout for the track spaces within timeline display.
void establishLayout(DisplayEvaluation &) override
respond to the DisplayEvaluation pass.
Definition of access keys for uniform UI styling.
void slotStructureChange() noexcept
force rebuilding of theTrackProfile whenever the global timeline structure changes ...
A set of basic GTK includes for the UI.
void setupAdditionalTrackPadding_fromCSS()
Adjust the vertical space to accommodate for additional decorations as required by the CSS style rule...
Interface for coordination of the overall timeline display.
void open() override
paint opening slope to enter nested sub tracks
lumiera::advice::Request< PStyleContext > trackBodyStyle
request a pre-defined CSS style context for the track body