Lumiera 0.pre.04~rc.1
»edit your freedom«
Loading...
Searching...
No Matches
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"
45
46#include "common/advice.hpp"
47#include "lib/util.hpp"
48
49#include <utility>
50
51
52using lib::time::Time;
53using util::max;
54using util::isnil;
55using Gdk::Rectangle;
56using std::string;
57using std::move;
58
59
60
61namespace stage {
62namespace timeline {
63
64 using CairoC = PCairoContext const&;
65 using StyleC = PStyleContext const&;
66
67 namespace { // details of track background painting
68
71 const int MINIMAL_CONTENT_WIDTH_px = 100;
72
76
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 }
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:
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
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
442
443
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
460
461 // on demand access and possible (re)establish the current "profile" of the tracks for drawing...
462 getProfile = [this]() -> TrackProfile&
463 {
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;
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
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
507 {
508 profile_.clear();
509 rootBody_ = nullptr;
510 }
511
512
514 void
519
520
526 void
528 {
529 while (rootBody_ and isnil (profile_))
530 {
531 setupAdditionalTrackPadding_fromCSS();
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
570 {
571 rulerCanvas_.queue_draw();
572 mainCanvas_.queue_draw();
573 }
574
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
630
631
632
633
634
635
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
712
713
718 void
723
724
725
726}}// namespace stage::timeline
Expecting Advice and giving Advice: a cross-cutting collaboration of loosely coupled participants.
Widget to render the body of timeline display, by custom drawing into a canvas control.
Lumiera's internal time value datatype.
Access point for the advised entity (client).
Definition advice.hpp:430
Mix-in interface to allow for concrete CanvasHooked widgets to adapt themselves to the metric current...
void adjustCanvasSize(int canvasWidth, int totalHeight, int rulerHeight)
After the (recent) display evaluation pass has negotiated the required space for the currently presen...
void slotStructureChange() noexcept
force rebuilding of theTrackProfile whenever the global timeline structure changes
void hook(Gtk::Widget &, int xPos=0, int yPos=0) override
model::DisplayMetric & getMetric() const override
access the component to handle layout metric
void installForkRoot(TrackBody &rootTrackBody)
The Lumiera Timeline model does not rely on a list of tracks, as most conventional video editing soft...
void establishLayout(DisplayEvaluation &) override
respond to the DisplayEvaluation pass.
void maybeRebuildLayout()
Possibly (re)build the allocation and distribution of layout space.
void completeLayout(DisplayEvaluation &) override
void move(Gtk::Widget &, int xPos, int yPos) override
void remove(Gtk::Widget &) override
std::function< TrackProfile &()> ProfileGetter
a way to get and possibly (re)compute the current TrackProfile
TimelineCanvas & getCanvas(int yPos)
Visitor and state holder for a collaborative layout adjustment pass.
Interface for coordination of the overall timeline display.
PixSpan getPixSpan()
the overall horizontal pixel span to cover by this timeline
virtual void triggerDisplayEvaluation()=0
cause a re-allocation of the complete layout
SignalStructureChange signalStructureChange_
signal to be invoked whenever the virtual structure of the corresponding timeline changes,...
void openCanvas(CairoC)
Prepare the drawing canvas to work within our virtual canvas coordinate system.
virtual bool on_draw(CairoC) override
Custom drawing of the timeline content area.
void drawGrounding(CairoC)
Establish and render the structure of (possibly nested) tracks and overview rulers.
void closeCanvas(CairoC)
Finish and close the virtual drawing canvas established by openCanvas().
TimelineCanvas(_Renderer groundingFun, _Renderer overlayFun)
std::function< void(CairoC)> _Renderer
Helper to organise and draw the space allocated for a fork of sub-tracks.
static Decoration decoration
storage for common style/padding settings
uint establishTrackSpace(TrackProfile &)
recursively establish the screen space allocation for this structure of nested tracks.
uint calcRulerHeight() const
sum up the vertical extension required by all overview rulers.
Description of the structure and arrangement of tracks for display in the UI.
void close(uint n) override
paint closing slope to finish nested sub tracks
void gap(uint h) override
insert additional padding/gap (typically below a ruler)
void content(uint contentHeight) override
fill background of track content area with the given vertical extension
void prelude() override
create spacing at the top of the track body area
void ruler(uint contentHeight) override
draw grounding of an overview/ruler track with the given height
void close(uint n) override
render overlays covering the closing slope towards nested tracks
void open() override
render overlays covering the opening slope towards nested tracks
void content(uint contentHeight) override
place overlays on top of of track content area,
void prelude() override
overlays to show at top of the track body area
void ruler(uint contentHeight) override
draw overlays on top of overview/ruler track
void coda(uint pad) override
finish painting overlays a the bottom of the track body area
Abstraction: service for the widgets to translate themselves into screen layout.
A set of basic GTK includes for the UI.
unsigned int uint
Definition integral.hpp:29
lumiera::advice::Request< PStyleContext > trackRulerStyle
auto makeRenderer(DisplayManager &layout, BodyCanvasWidget::ProfileGetter &getProfile)
lumiera::advice::Request< PStyleContext > trackBodyStyle
request a pre-defined CSS style context for the track body
void setupAdditionalTrackPadding_fromCSS()
Adjust the vertical space to accommodate for additional decorations as required by the CSS style rule...
PCairoContext const & CairoC
PStyleContext const & StyleC
Borders borders
width of up to 6 levels of combined upward slope borders (defined in CSS)
Lumiera GTK UI implementation root.
Definition guifacade.cpp:37
Glib::RefPtr< Gtk::StyleContext > PStyleContext
Definition gtk-base.hpp:95
Cairo::RefPtr< Cairo::Context > PCairoContext
Definition gtk-base.hpp:96
const uString cuString
Definition gtk-base.hpp:93
cuString CLASS_slope_deep4
cuString CLASS_timeline
cuString CLASS_slope_deep1
cuString CLASS_slope_deep2
cuString CLASS_timeline_body
cuString CLASS_slope_deep3
cuString CLASS_slope_verydeep
auto max(IT &&elms)
bool isnil(lib::time::Duration const &dur)
Definition of access keys for uniform UI styling.
This helper class serves to manage the layout and display of the horizontally extended space of a "tr...
Abstraction to build the layout for the track spaces within timeline display.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...