Lumiera  0.pre.03
»edit your freedom«
stave-bracket-widget.cpp
Go to the documentation of this file.
1 /*
2  StaveBracketWidget - track body area to show overview and timecode and markers
3 
4  Copyright (C) Lumiera.org
5  2023, 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 
52 #include "stage/style-scheme.hpp"
53 #include "lib/util.hpp"
54 
55 #include <cmath>
56 
57 
58 
59 using util::min;
60 using util::max;
61 using std::ceil;
62 
63 
64 namespace stage {
65 namespace timeline {
66 
67  namespace {//---------Implementation-details--Stave-Bracket-design-----------------------
68 
69  const uint FALLBACK_FONT_SIZE_px = 12.5; // (assuming 96dpi and 10 Point font)
70  const uint POINT_PER_INCH = 72; // typographic point ≔ 1/72 inch
71 
72  const double BASE_WIDTH_PER_EM = 0.5; // scale factor: width of double line relative to font size
73 
74  const double ORG = 0.0;
75  const double PHI = (1.0 + sqrt(5)) / 2.0; // Golden Ratio Φ ≔ ½·(1+√5) ≈ 1.6180339887498948482
76  const double PHI_MAJOR = PHI - 1.0; // 1/Φ = Φ-1
77  const double PHI_MINOR = 2.0 - PHI; // 1-1/Φ = 2-Φ
78  const double PHISQUARE = 1.0 + PHI; // Φ² = Φ+1
79  const double PHI_MINSQ = 5.0 - 3*PHI; // Φ-minor of Φ-minor : (2-Φ)²= 2²-4Φ + Φ²
80 
81  const double BAR_WIDTH = PHI_MINOR; // the main (bold) vertical bar line is right aligned to axis
82  const double BAR_LEFT = -BAR_WIDTH;
83  const double LIN_WIDTH = PHI_MINSQ; // thin line is Φ-minor of bold line (which itself is Φ-minor)
84  const double LIN_LEFT = PHI_MAJOR - LIN_WIDTH; // main line and thin line create a Φ-division
85 
86  const double SQUARE_TIP_X = PHISQUARE - PHI_MINOR;
87  const double SQUARE_TIP_Y = -PHISQUARE;
88  const double SQUARE_MINOR = 1.0;
89 
90  const double ARC_O_XC = -(3.0 + PHI);
91  const double ARC_O_YC = -6.8541019662496847; // +Y points downwards
92  const double ARC_O_R = 8.0574801069408135; // Radius of the arc segment
93  const double ARC_O_TIP = 0.5535743588970450; // Radians ↻ clockwise from +X
94  const double ARC_O_END = 1.0172219678978512;
95 
96  const double ARC_I_XC = -2.5;
97  const double ARC_I_YC = -7.3541019662496883;
98  const double ARC_I_R = 6.6978115661011230;
99  const double ARC_I_TIP = 0.7853981633974485;
100  const double ARC_I_END = 1.2490457723982538;
101 
106  double
107  getAbsoluteFontSize(StyleC style)
108  {
109  Pango::FontDescription font = style->get_font (Gtk::STATE_FLAG_NORMAL);
110  auto sizeSpec = double(font.get_size()) / PANGO_SCALE;
111  // Note: size specs are given as integers with multiplier PANGO_SCALE (typically 1024)
112  if (sizeSpec <=0) return FALLBACK_FONT_SIZE_px;
113  if (not font.get_size_is_absolute())
114  {// size is given relative (in points)
115  auto screen = style->get_screen();
116  if (not screen) return FALLBACK_FONT_SIZE_px;
117  double dpi = screen->get_resolution();
118  sizeSpec *= dpi / POINT_PER_INCH;
119  } // spec{points}/point_per_inch*pixel_per_inch ⟼ pixel
120  return sizeSpec;
121  }
122 
129  double
130  baseWidth (StyleC style)
131  {
132  return BASE_WIDTH_PER_EM * getAbsoluteFontSize (style);
133  }
134 
149  double
150  determineScale (StyleC style, int givenHeight)
151  {
152  auto required = 2*PHISQUARE + style->get_padding().get_top()
153  + style->get_padding().get_bottom();
154  auto maxScale = givenHeight / (required);
155  return min (maxScale, baseWidth (style));
156  }
157 
163  int
164  calcRequiredWidth (StyleC style, int givenHeight)
165  {
166  return ceil (PHISQUARE * determineScale (style,givenHeight)
167  +style->get_padding().get_right()
168  +style->get_padding().get_left()
169  );
170  }
171 
173  int
174  calcDesiredWidth (StyleC style)
175  {
176  return ceil (PHISQUARE * baseWidth (style)
177  +style->get_padding().get_right()
178  +style->get_padding().get_left()
179  );
180  }
181 
185  double
186  anchorLeft (StyleC style, double scale)
187  {
188  return style->get_padding().get_left()
189  + scale * BAR_WIDTH;
190  }
191 
195  double
196  anchorUpper (StyleC style, double scale)
197  {
198  return style->get_padding().get_top()
199  - scale * SQUARE_TIP_Y;
200  }
201 
205  double
206  anchorLower (StyleC style, double scale, int canvasHeight)
207  {
208  auto lowerAnchor
209  = canvasHeight
210  - (style->get_padding().get_bottom()
211  - scale * SQUARE_TIP_Y);
212  auto minHeight = PHISQUARE*scale + style->get_padding().get_top();
213  return max (lowerAnchor, minHeight); // Fallback: both caps back to back
214  }
215 
216 
225  void
226  drawCap (CairoC cox, Gdk::RGBA colour, double ox, double oy, double scale, bool upside=true)
227  {
228  cox->save();
229  cox->translate (ox,oy);
230  cox->scale (scale, upside? scale:-scale);
231  cox->set_source_rgba(colour.get_red()
232  ,colour.get_green()
233  ,colour.get_blue()
234  ,colour.get_alpha());
235  // draw the inner contour of the bracket cap,
236  // which is the outer arc from left top of the bar to the tip point
237  cox->move_to(BAR_LEFT, ORG);
238  cox->arc_negative(ARC_O_XC,ARC_O_YC,ARC_O_R, ARC_O_END, ARC_O_TIP);
239  // draw the outer contour of the bracket cap,
240  // which is the inner arc from tip point to Φ-minor of the enclosing square
241  cox->arc (ARC_I_XC,ARC_I_YC,ARC_I_R, ARC_I_TIP, ARC_I_END);
242  cox->close_path();
243  //
244  cox->fill();
245  //
246  cox->restore();
247  }
248 
250  void
251  drawBar (CairoC cox, Gdk::RGBA colour, double leftX, double upperY, double lowerY, double scale)
252  {
253  cox->save();
254  cox->translate (leftX, upperY);
255  cox->scale (scale, scale);
256  cox->set_source_rgba(colour.get_red()
257  ,colour.get_green()
258  ,colour.get_blue()
259  ,colour.get_alpha());
260  //
261  double height = max (0.0, (lowerY - upperY)/scale);
262  cox->rectangle(BAR_LEFT, -SQUARE_MINOR, BAR_WIDTH, height + 2*SQUARE_MINOR);
263  cox->rectangle(LIN_LEFT, ORG, LIN_WIDTH, height);
264  //
265  cox->fill();
266  //
267  cox->restore();
268  }
269 
277  void
278  connect (CairoC cox, Gdk::RGBA colour
279  ,double leftX, double upperY, double lowerY, double width, double scale
280  ,std::vector<uint> connectors)
281  {
282  double limit = lowerY - upperY;
283  double line = leftX + scale*(LIN_LEFT + LIN_WIDTH/2);
284  double rad = scale * PHI_MAJOR;
285  cox->save();
286  // shift connectors to join below top cap
287  cox->translate (line, upperY);
288  // fill circle with a lightened yellow hue
289  cox->set_source_rgb(1 - 0.2*(colour.get_red())
290  ,1 - 0.2*(colour.get_green())
291  ,1 - 0.5*(1 - colour.get_blue()) );
292  // draw a circle joint on top of the small vertical line
293  for (uint off : connectors)
294  if (off <= limit)
295  {
296  cox->move_to(rad,off);
297  cox->arc ( 0,off, rad, 0, 2 * M_PI);
298  cox->close_path();
299  }
300  //
301  cox->fill_preserve();
302  cox->set_source_rgba(colour.get_red()
303  ,colour.get_green()
304  ,colour.get_blue()
305  ,colour.get_alpha());
306  cox->set_line_width(scale*LIN_WIDTH*PHI_MAJOR);
307  cox->stroke();
308  //
309  // draw connecting arrows...
310  cox->translate(rad,0);
311  // Note: arrow tip uses complete width, reaches into the padding-right
312  double len = width-line-rad-1; // -1 to create room for a sharp miter
313  ASSERT (len > 0);
314  double arr = len * PHI_MINOR;
315  double bas = scale * PHI_MINOR;
316  for (uint off : connectors)
317  if (off <= limit)
318  {
319  cox->move_to(ORG,off);
320  cox->line_to(arr,off);
321  // draw arrow head...
322  cox->move_to(arr,off-bas);
323  cox->line_to(len,off);
324  cox->line_to(arr,off+bas);
325  cox->close_path();
326  }
327  cox->set_miter_limit(20); // to create sharp arrow tip
328  cox->fill_preserve();
329  cox->stroke();
330  //
331  cox->restore();
332  }
333 
334  }//(End)Implementation details (drawing design)
335 
336 
337 
338 
339 
340 
341  StaveBracketWidget::~StaveBracketWidget() { }
342 
343  StaveBracketWidget::StaveBracketWidget ()
344  : _Base{}
345  , connectors_{}
346  {
347  get_style_context()->add_class (CLASS_fork_bracket);
348  this->property_expand() = false;
349  }
350 
351 
362  bool
364  {
365  // invoke (presumably empty) base implementation....
366  bool event_is_handled = _Base::on_draw (cox);
367 
368  StyleC style = this->get_style_context();
369  auto colour = style->get_color (Gtk::STATE_FLAG_NORMAL);
370  int height = this->get_allocated_height();
371  int width = this->get_width();
372  double scale = determineScale (style, height);
373  double left = anchorLeft (style, scale);
374  double upper = anchorUpper (style,scale);
375  double lower = anchorLower (style, scale, height);
376 
377  drawCap (cox, colour, left, upper, scale, true);
378  drawCap (cox, colour, left, lower, scale, false);
379  drawBar (cox, colour, left, upper, lower, scale);
380  connect (cox, colour, left, upper, lower, width, scale, connectors_);
381 
382  return event_is_handled;
383  }
384 
385 
387  Gtk::SizeRequestMode
389  {
390  return Gtk::SizeRequestMode::SIZE_REQUEST_WIDTH_FOR_HEIGHT;
391  }
392 
398  void
399  StaveBracketWidget::get_preferred_width_for_height_vfunc (int givenHeight, int& minimum_width, int& natural_width) const
400  {
401  StyleC style = this->get_style_context();
402  minimum_width = natural_width = calcRequiredWidth (style, givenHeight);
403  }
404 
405  void
406  StaveBracketWidget::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const
407  {
408  StyleC style = this->get_style_context();
409  minimum_width = natural_width = calcDesiredWidth (style);
410  }
411 
412 
413 }}// namespace stage::timeline
bool on_draw(CairoC cox) override
Custom drawing: a »stave bracket« to indicate track scope.
Widget to group tracks visually in the Timeline presentation.
double anchorLower(StyleC style, double scale, int canvasHeight)
place bottom cap vertical anchor, mirroring top cap
void connect(CairoC cox, Gdk::RGBA colour, double leftX, double upperY, double lowerY, double width, double scale, std::vector< uint > connectors)
Indicate connection to nested sub-Track scopes.
double anchorLeft(StyleC style, double scale)
place left anchor reference line to right side of bold bar.
double getAbsoluteFontSize(StyleC style)
Use contextual CSS style information to find out about the standard font size
double determineScale(StyleC style, int givenHeight)
determine the base metric, taking into account the available canvas size.
void get_preferred_width_for_height_vfunc(int, int &, int &) const override
The structural outline adapts flexible in vertical direction, but requires a proportional horizontal ...
double anchorUpper(StyleC style, double scale)
place top cap vertical anchor, down from canvas upside.
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...
void drawBar(CairoC cox, Gdk::RGBA colour, double leftX, double upperY, double lowerY, double scale)
draw the double bar to fit between upper and lower cap
Gtk::SizeRequestMode get_request_mode_vfunc() const final
indicate layout oriented towards vertical extension
double baseWidth(StyleC style)
Setup the base metric for this bracket drawing based on CSS styling.
void drawCap(CairoC cox, Gdk::RGBA colour, double ox, double oy, double scale, bool upside=true)
Draw the curved end cap of the bracket, inspired by musical notation.
Definition of access keys for uniform UI styling.