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)
5  2023, 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 
43 #include "stage/style-scheme.hpp"
44 #include "lib/util.hpp"
45 
46 #include <cmath>
47 
48 
49 
50 using util::min;
51 using util::max;
52 using std::ceil;
53 
54 
55 namespace stage {
56 namespace timeline {
57 
58  namespace {//---------Implementation-details--Stave-Bracket-design-----------------------
59 
60  const uint FALLBACK_FONT_SIZE_px = 12.5; // (assuming 96dpi and 10 Point font)
61  const uint POINT_PER_INCH = 72; // typographic point ≔ 1/72 inch
62 
63  const double BASE_WIDTH_PER_EM = 0.5; // scale factor: width of double line relative to font size
64 
65  const double ORG = 0.0;
66  const double PHI = (1.0 + sqrt(5)) / 2.0; // Golden Ratio Φ ≔ ½·(1+√5) ≈ 1.6180339887498948482
67  const double PHI_MAJOR = PHI - 1.0; // 1/Φ = Φ-1
68  const double PHI_MINOR = 2.0 - PHI; // 1-1/Φ = 2-Φ
69  const double PHISQUARE = 1.0 + PHI; // Φ² = Φ+1
70  const double PHI_MINSQ = 5.0 - 3*PHI; // Φ-minor of Φ-minor : (2-Φ)²= 2²-4Φ + Φ²
71 
72  const double BAR_WIDTH = PHI_MINOR; // the main (bold) vertical bar line is right aligned to axis
73  const double BAR_LEFT = -BAR_WIDTH;
74  const double LIN_WIDTH = PHI_MINSQ; // thin line is Φ-minor of bold line (which itself is Φ-minor)
75  const double LIN_LEFT = PHI_MAJOR - LIN_WIDTH; // main line and thin line create a Φ-division
76 
77  const double SQUARE_TIP_X = PHISQUARE - PHI_MINOR;
78  const double SQUARE_TIP_Y = -PHISQUARE;
79  const double SQUARE_MINOR = 1.0;
80 
81  const double ARC_O_XC = -(3.0 + PHI);
82  const double ARC_O_YC = -6.8541019662496847; // +Y points downwards
83  const double ARC_O_R = 8.0574801069408135; // Radius of the arc segment
84  const double ARC_O_TIP = 0.5535743588970450; // Radians ↻ clockwise from +X
85  const double ARC_O_END = 1.0172219678978512;
86 
87  const double ARC_I_XC = -2.5;
88  const double ARC_I_YC = -7.3541019662496883;
89  const double ARC_I_R = 6.6978115661011230;
90  const double ARC_I_TIP = 0.7853981633974485;
91  const double ARC_I_END = 1.2490457723982538;
92 
97  double
98  getAbsoluteFontSize(StyleC style)
99  {
100  Pango::FontDescription font = style->get_font (Gtk::STATE_FLAG_NORMAL);
101  auto sizeSpec = double(font.get_size()) / PANGO_SCALE;
102  // Note: size specs are given as integers with multiplier PANGO_SCALE (typically 1024)
103  if (sizeSpec <=0) return FALLBACK_FONT_SIZE_px;
104  if (not font.get_size_is_absolute())
105  {// size is given relative (in points)
106  auto screen = style->get_screen();
107  if (not screen) return FALLBACK_FONT_SIZE_px;
108  double dpi = screen->get_resolution();
109  sizeSpec *= dpi / POINT_PER_INCH;
110  } // spec{points}/point_per_inch*pixel_per_inch ⟼ pixel
111  return sizeSpec;
112  }
113 
120  double
121  baseWidth (StyleC style)
122  {
123  return BASE_WIDTH_PER_EM * getAbsoluteFontSize (style);
124  }
125 
140  double
141  determineScale (StyleC style, int givenHeight)
142  {
143  auto required = 2*PHISQUARE + style->get_padding().get_top()
144  + style->get_padding().get_bottom();
145  auto maxScale = givenHeight / (required);
146  return min (maxScale, baseWidth (style));
147  }
148 
154  int
155  calcRequiredWidth (StyleC style, int givenHeight)
156  {
157  return ceil (PHISQUARE * determineScale (style,givenHeight)
158  +style->get_padding().get_right()
159  +style->get_padding().get_left()
160  );
161  }
162 
164  int
165  calcDesiredWidth (StyleC style)
166  {
167  return ceil (PHISQUARE * baseWidth (style)
168  +style->get_padding().get_right()
169  +style->get_padding().get_left()
170  );
171  }
172 
176  double
177  anchorLeft (StyleC style, double scale)
178  {
179  return style->get_padding().get_left()
180  + scale * BAR_WIDTH;
181  }
182 
186  double
187  anchorUpper (StyleC style, double scale)
188  {
189  return style->get_padding().get_top()
190  - scale * SQUARE_TIP_Y;
191  }
192 
196  double
197  anchorLower (StyleC style, double scale, int canvasHeight)
198  {
199  auto lowerAnchor
200  = canvasHeight
201  - (style->get_padding().get_bottom()
202  - scale * SQUARE_TIP_Y);
203  auto minHeight = PHISQUARE*scale + style->get_padding().get_top();
204  return max (lowerAnchor, minHeight); // Fallback: both caps back to back
205  }
206 
207 
216  void
217  drawCap (CairoC cox, Gdk::RGBA colour, double ox, double oy, double scale, bool upside=true)
218  {
219  cox->save();
220  cox->translate (ox,oy);
221  cox->scale (scale, upside? scale:-scale);
222  cox->set_source_rgba(colour.get_red()
223  ,colour.get_green()
224  ,colour.get_blue()
225  ,colour.get_alpha());
226  // draw the inner contour of the bracket cap,
227  // which is the outer arc from left top of the bar to the tip point
228  cox->move_to(BAR_LEFT, ORG);
229  cox->arc_negative(ARC_O_XC,ARC_O_YC,ARC_O_R, ARC_O_END, ARC_O_TIP);
230  // draw the outer contour of the bracket cap,
231  // which is the inner arc from tip point to Φ-minor of the enclosing square
232  cox->arc (ARC_I_XC,ARC_I_YC,ARC_I_R, ARC_I_TIP, ARC_I_END);
233  cox->close_path();
234  //
235  cox->fill();
236  //
237  cox->restore();
238  }
239 
241  void
242  drawBar (CairoC cox, Gdk::RGBA colour, double leftX, double upperY, double lowerY, double scale)
243  {
244  cox->save();
245  cox->translate (leftX, upperY);
246  cox->scale (scale, scale);
247  cox->set_source_rgba(colour.get_red()
248  ,colour.get_green()
249  ,colour.get_blue()
250  ,colour.get_alpha());
251  //
252  double height = max (0.0, (lowerY - upperY)/scale);
253  cox->rectangle(BAR_LEFT, -SQUARE_MINOR, BAR_WIDTH, height + 2*SQUARE_MINOR);
254  cox->rectangle(LIN_LEFT, ORG, LIN_WIDTH, height);
255  //
256  cox->fill();
257  //
258  cox->restore();
259  }
260 
268  void
269  connect (CairoC cox, Gdk::RGBA colour
270  ,double leftX, double upperY, double lowerY, double width, double scale
271  ,std::vector<uint> connectors)
272  {
273  double limit = lowerY - upperY;
274  double line = leftX + scale*(LIN_LEFT + LIN_WIDTH/2);
275  double rad = scale * PHI_MAJOR;
276  cox->save();
277  // shift connectors to join below top cap
278  cox->translate (line, upperY);
279  // fill circle with a lightened yellow hue
280  cox->set_source_rgb(1 - 0.2*(colour.get_red())
281  ,1 - 0.2*(colour.get_green())
282  ,1 - 0.5*(1 - colour.get_blue()) );
283  // draw a circle joint on top of the small vertical line
284  for (uint off : connectors)
285  if (off <= limit)
286  {
287  cox->move_to(rad,off);
288  cox->arc ( 0,off, rad, 0, 2 * M_PI);
289  cox->close_path();
290  }
291  //
292  cox->fill_preserve();
293  cox->set_source_rgba(colour.get_red()
294  ,colour.get_green()
295  ,colour.get_blue()
296  ,colour.get_alpha());
297  cox->set_line_width(scale*LIN_WIDTH*PHI_MAJOR);
298  cox->stroke();
299  //
300  // draw connecting arrows...
301  cox->translate(rad,0);
302  // Note: arrow tip uses complete width, reaches into the padding-right
303  double len = width-line-rad-1; // -1 to create room for a sharp miter
304  ASSERT (len > 0);
305  double arr = len * PHI_MINOR;
306  double bas = scale * PHI_MINOR;
307  for (uint off : connectors)
308  if (off <= limit)
309  {
310  cox->move_to(ORG,off);
311  cox->line_to(arr,off);
312  // draw arrow head...
313  cox->move_to(arr,off-bas);
314  cox->line_to(len,off);
315  cox->line_to(arr,off+bas);
316  cox->close_path();
317  }
318  cox->set_miter_limit(20); // to create sharp arrow tip
319  cox->fill_preserve();
320  cox->stroke();
321  //
322  cox->restore();
323  }
324 
325  }//(End)Implementation details (drawing design)
326 
327 
328 
329 
330 
331 
332  StaveBracketWidget::~StaveBracketWidget() { }
333 
334  StaveBracketWidget::StaveBracketWidget ()
335  : _Base{}
336  , connectors_{}
337  {
338  get_style_context()->add_class (CLASS_fork_bracket);
339  this->property_expand() = false;
340  }
341 
342 
353  bool
355  {
356  // invoke (presumably empty) base implementation....
357  bool event_is_handled = _Base::on_draw (cox);
358 
359  StyleC style = this->get_style_context();
360  auto colour = style->get_color (Gtk::STATE_FLAG_NORMAL);
361  int height = this->get_allocated_height();
362  int width = this->get_width();
363  double scale = determineScale (style, height);
364  double left = anchorLeft (style, scale);
365  double upper = anchorUpper (style,scale);
366  double lower = anchorLower (style, scale, height);
367 
368  drawCap (cox, colour, left, upper, scale, true);
369  drawCap (cox, colour, left, lower, scale, false);
370  drawBar (cox, colour, left, upper, lower, scale);
371  connect (cox, colour, left, upper, lower, width, scale, connectors_);
372 
373  return event_is_handled;
374  }
375 
376 
378  Gtk::SizeRequestMode
380  {
381  return Gtk::SizeRequestMode::SIZE_REQUEST_WIDTH_FOR_HEIGHT;
382  }
383 
389  void
390  StaveBracketWidget::get_preferred_width_for_height_vfunc (int givenHeight, int& minimum_width, int& natural_width) const
391  {
392  StyleC style = this->get_style_context();
393  minimum_width = natural_width = calcRequiredWidth (style, givenHeight);
394  }
395 
396  void
397  StaveBracketWidget::get_preferred_width_vfunc (int& minimum_width, int& natural_width) const
398  {
399  StyleC style = this->get_style_context();
400  minimum_width = natural_width = calcDesiredWidth (style);
401  }
402 
403 
404 }}// 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:37
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.