DUDS
Distributed Update of Data from Something
PriorityGridLayout.cpp
Go to the documentation of this file.
1 /*
2  * This file is part of the DUDS project. It is subject to the BSD-style
3  * license terms in the LICENSE file found in the top-level directory of this
4  * distribution and at https://github.com/jjackowski/duds/blob/master/LICENSE.
5  * No part of DUDS, including this file, may be copied, modified, propagated,
6  * or distributed except according to the terms contained in the LICENSE file.
7  *
8  * Copyright (C) 2020 Jeff Jackowski
9  */
12 #include <duds/general/Errors.hpp>
13 
14 namespace duds { namespace ui { namespace graphics {
15 
16 bool PriorityGridLayout::RowData::minCols(int mc) noexcept {
17  if (panels.size() <= mc) {
18  panels.resize(mc + 1, std::pair<unsigned int, PanelStatus*>(0, nullptr));
19  return false;
20  }
21  return true;
22 }
23 
25  minCols(c);
26  return panels[c];
27 }
28 
30  if (rv.size() <= ms) {
31  rv.resize(ms + 1);
32  return false;
33  }
34  return true;
35 }
36 
37 unsigned int PriorityGridLayout::panelAt(RowVec &rv, const GridLocation &gl) noexcept {
38  // spot exists?
39  if (minRows(rv, gl.r + 1) && rv[gl.r].minCols(gl.c + 1)) {
40  return rv[gl.r].panels[gl.c].first;
41  }
42  return 0;
43 }
44 
45 
47  const PanelSptr &pspt,
48  const GridLayoutConfig &conf
49 ) : panel(pspt), config(conf), hidden(true) { }
50 
52  const PanelSptr &pspt,
53  GridLayoutConfig &&conf
54 ) : panel(pspt), config(std::move(conf)), hidden(true) { }
55 
56 void PriorityGridLayout::maxRowHeight(int row, std::int16_t height) {
57  if (rowMaxHeight.size() <= row) {
58  rowMaxHeight.resize(row + 1, 0x7FFF);
59  }
60  rowMaxHeight[row] = height;
61 }
62 
63 std::int16_t PriorityGridLayout::maxRowHeight(int row) const {
64  if (rowMaxHeight.size() <= row) {
65  return 0x7FFF;
66  }
67  return rowMaxHeight[row];
68 }
69 
71  const PanelSptr &panel,
72  const GridLayoutConfig &config,
73  unsigned int pri
74 ) {
75  if (pri == 0) {
77  }
78  std::pair<GridConfig::iterator, bool> res =
79  configs.try_emplace(pri, PanelStatus(panel, config));
80  if (res.second) {
81  try {
82  panel->added(this, pri);
83  return true;
84  } catch (...) {
85  configs.erase(pri);
86  throw;
87  }
88  }
89  return false;
90 }
91 
93  const PanelSptr &panel,
94  const GridSizeStep &config,
95  unsigned int pri
96 ) {
97  if (pri == 0) {
99  }
100  std::pair<GridConfig::iterator, bool> res =
101  configs.try_emplace(pri, PanelStatus(panel, GridLayoutConfig(config)));
102  if (res.second) {
103  try {
104  panel->added(this, pri);
105  return true;
106  } catch (...) {
107  configs.erase(pri);
108  throw;
109  }
110  }
111  return false;
112 }
113 
115  const PanelSptr &panel,
116  const GridLayoutConfig &config
117 ) {
118  unsigned int pri;
119  if (configs.empty()) {
120  pri = 1;
121  } else {
122  GridConfig::const_reverse_iterator last = configs.crbegin();
123  pri = last->first + 1;
124  }
125  addOrReplace(panel, config, pri);
126  return pri;
127 }
128 
130  const PanelSptr &panel,
131  const GridSizeStep &config
132 ) {
133  unsigned int pri;
134  if (configs.empty()) {
135  pri = 1;
136  } else {
137  GridConfig::const_reverse_iterator last = configs.crbegin();
138  pri = last->first + 1;
139  }
140  addOrReplace(panel, config, pri);
141  return pri;
142 }
143 
145  const PanelSptr &panel,
146  const GridLayoutConfig &config,
147  unsigned int pri
148 ) {
149  if (pri == 0) {
151  }
152  configs[pri] = PanelStatus(panel, config);
153  try {
154  panel->added(this, pri);
155  } catch (...) {
156  configs.erase(pri);
157  throw;
158  }
159 }
160 
162  const PanelSptr &panel,
163  const GridSizeStep &config,
164  unsigned int pri
165 ) {
166  if (pri == 0) {
168  }
169  configs[pri] = PanelStatus(panel, GridLayoutConfig(config));
170  try {
171  panel->added(this, pri);
172  } catch (...) {
173  configs.erase(pri);
174  throw;
175  }
176 }
177 
178 void PriorityGridLayout::remove(unsigned int pri) {
179  GridConfig::iterator iter = configs.find(pri);
180  if (iter != configs.end()) {
181  iter->second.panel->removing(this, pri);
182  configs.erase(iter);
183  }
184 }
185 
187  GridConfig::iterator iter = std::find_if(
188  configs.begin(),
189  configs.end(),
190  [panel] (auto i) {
191  return i.second.panel == panel;
192  }
193  );
194  if (iter != configs.end()) {
195  iter->second.panel->removing(this, iter->first);
196  configs.erase(iter);
197  }
198 }
199 
201  GridConfig::iterator iter = configs.find(pri);
202  if (iter == configs.end()) {
204  }
205  return iter->second.config;
206 }
207 
208 const GridLayoutConfig &PriorityGridLayout::panelConfig(unsigned int pri) const {
209  GridConfig::const_iterator iter = configs.find(pri);
210  if (iter == configs.end()) {
212  }
213  return iter->second.config;
214 }
215 
217  // tabulated data on each row
218  RowVec rdat;
219  rdat.reserve(8);
220  // total dimensions used
221  ImageDimensions total(0, 0);
222  int placed = 0;
223  // rows with height expansion requests
224  int heightExpand = 0;
225  // place items into grid positions in priority order
226  for (auto &pstat : configs) {
227  // re-initialize to initial size-step
228  pstat.second.sizeStep = 0;
229  // hide if flagged as hidden or no size-steps
230  pstat.second.hidden = (pstat.second.sizeStep < 0) ||
231  pstat.second.config.sizes.empty() ||
232  ((pstat.second.config.flags & GridLayoutConfig::PanelHidden) > 0);
233  // skip hidden panels
234  if (pstat.second.hidden) {
235  continue;
236  }
237  RowData *row = nullptr;
238  ImageDimensions maxarea;
239  GridLocation gl;
240  bool done = false;
241  // find the first size-step that fits
242  for (; pstat.second.sizeStep < pstat.second.config.sizes.size();
243  ++pstat.second.sizeStep
244  ) {
245  // advanced to hidden spot?
246  if (pstat.second.config.sizes[pstat.second.sizeStep].flags &
248  // continue to next spot
249  continue;
250  }
251  // copy location for quicker access
252  gl = pstat.second.currentStep().loc;
253  // see if the spot is available
254  if (!panelAt(rdat, gl)) {
255  // attempt to use this spot
256  // row change?
257  if (row != &(rdat[gl.r])) {
258  // get the row data that covers the spot
259  row = &(rdat[gl.r]);
260  // compute the largest area currently available
261  maxarea.w = fill.w - row->used.w;
262  maxarea.h = std::min(
263  maxRowHeight(gl.r),
264  (std::int16_t)(fill.h - total.h + row->used.h)
265  );
266  }
267  // minimum requested size fits?
268  if (maxarea.fits(pstat.second.config.sizes[pstat.second.sizeStep].minDim)) {
269  done = true;
270  break;
271  }
272  }
273  // else, spot not availble; loop for next size-step
274  }
275  // found a spot?
276  if (done) {
277  assert(maxarea.fits(pstat.second.config.sizes[pstat.second.sizeStep].minDim));
278  assert(gl == pstat.second.config.sizes[pstat.second.sizeStep].loc);
279  // when running here, a suitable spot was found
280  // record the usage of this spot
281  (*row)[gl.c] = RowData::KeyPanel(pstat.first, &pstat.second);
282  // store minimum dimensions as current panel dimensions
283  pstat.second.dim =
284  pstat.second.config.sizes[pstat.second.sizeStep].minDim;
285  // tabulate area used
286  row->used.w += pstat.second.dim.w;
287  total.h -= row->used.h;
288  row->used.h = std::max(row->used.h, pstat.second.dim.h);
289  total.h += row->used.h;
290  // note a width expansion request
291  if (pstat.second.flags() & GridLayoutConfig::PanelWidthExpand) {
292  row->widthExpand++;
293  }
294  // height expansion
295  if (!row->heightExpand && // only note expansion once
296  (pstat.second.flags() & GridLayoutConfig::PanelHeightExpand)
297  ) {
298  row->heightExpand = true;
299  heightExpand++;
300  }
301  ++placed;
302  }
303  // reached end without finding a spot?
304  else /* if (!done) */ {
305  // mark as hidden
306  pstat.second.hidden = true;
307  }
308  }
309  // consider height expansion
310  int addnlH, extraH;
311  if (heightExpand) {
312  addnlH = (fill.h - total.h) / heightExpand;
313  extraH = (fill.h - total.h) % heightExpand;
314  assert((total.h + addnlH + extraH) == fill.h);
315  // if there may be maximum height limitations present . . .
316  if (!rowMaxHeight.empty()) {
317  // . . . check for rows expanding past the maximum
318  for (int idx = 0; idx < rdat.size(); ++idx) {
319  RowData &row = rdat[idx];
320  if (row.heightExpand) {
321  int eh = addnlH + row.used.h; // expanded height
322  if (extraH) {
323  ++eh;
324  }
325  int mh = maxRowHeight(idx); // max height
326  assert(mh >= 0); // should not have a height greater than max
327  if (mh < eh) {
328  // enlarge to maximum
329  total.h += mh - row.used.h;
330  row.used.h = mh;
331  // don't make it any taller
332  row.heightExpand = false;
333  --heightExpand;
334  }
335  }
336  }
337  // the algorithm needs to be iterative to be perfect; just do one
338  // iteration and hope it fits well
339  if (heightExpand) {
340  addnlH = (fill.h - total.h) / heightExpand;
341  extraH = (fill.h - total.h) % heightExpand;
342  assert((total.h + addnlH + extraH) == fill.h);
343  }
344  }
345  } else {
346  addnlH = extraH = 0;
347  }
348  // compute finalized image dimensions for the panels
349  for (int idx = 0; idx < rdat.size(); ++idx) {
350  RowData &row = rdat[idx];
351  if (row.heightExpand) {
352  assert(heightExpand);
353  row.used.h += addnlH;
354  if (extraH) {
355  row.used.h++;
356  extraH--;
357  }
358  int mh = maxRowHeight(idx); // max height
359  // past max?
360  if (row.used.h > mh) {
361  // allow following rows to use unused space
362  if (--heightExpand) {
363  addnlH += (row.used.h - mh) / heightExpand;
364  }
365  row.used.h = mh;
366  }
367  }
368  int additonal = 0, extra = 0;
369  if (row.widthExpand) {
370  // compute amount of expansion
371  additonal = (fill.w - row.used.w) / row.widthExpand;
372  extra = (fill.w - row.used.w) % row.widthExpand;
373  assert((row.used.w + additonal * row.widthExpand + extra) == fill.w);
374  row.used.w = fill.w; // should be true by the end of the next loop
375  }
376  int pref = 0; // index of item getting width remainder
377  // apply width expansion
378  for (int col = 0; col < row.panels.size(); ++col) {
379  RowData::KeyPanel &kp = row.panels[col];
380  assert((kp.first && kp.second) || (!kp.first && !kp.second));
381  // used column?
382  if (kp.first) {
383  assert(~kp.second->flags() & GridLayoutConfig::PanelHidden);
384  assert(!kp.second->hidden);
385  // update height
386  kp.second->dim.h = row.used.h;
387  // width expansion requested?
388  if (
389  row.widthExpand &&
390  (kp.second->flags() & GridLayoutConfig::PanelWidthExpand)
391  ) {
392  kp.second->dim.w += additonal;
393  // higher priortity?
394  if (extra && (kp.first < row.panels[pref].first)) {
395  pref = col;
396  }
397  }
398  }
399  }
400  if (extra) {
401  // apply more to the highest priority panel
402  row.panels[pref].second->dim.w += extra;
403  }
404  }
405  // compute finalized image locations for the panels
406  total.h = 0;
407  for (auto &row : rdat) {
408  total.w = 0;
409  for (auto &col : row.panels) {
410  // used column?
411  if (col.first) {
412  col.second->loc.x = total.w;
413  col.second->loc.y = total.h;
414  total.w += col.second->dim.w;
415  }
416  }
417  assert(total.w == row.used.w);
418  assert(total.w <= fill.w);
419  total.h += row.used.h;
420  }
421  assert(total.h <= fill.h);
422  return placed;
423 }
424 
426  { // check dest size
427  ImageLocation chk = (offset + fill) - ImageLocation(1, 1);
428  if (!dest->dimensions().withinBounds(chk)) {
433  );
434  }
435  }
436  // Render each panel. This is done in priority order because of the data
437  // structure used; rendering could be done in random order and succeed.
438  for (auto pstat : configs) {
439  // not hidden?
440  if (!pstat.second.hidden) {
441  ImageLocation off(0, 0);
442  ImageDimensions dim(pstat.second.dim);
443  PanelMargins margin = { 0, 0, 0, 0 };
444  const BppImage *img;
445  try {
446  img = pstat.second.panel->render(
447  off,
448  dim,
449  margin,
450  pstat.second.sizeStep
451  );
452  } catch (boost::exception &be) {
453  be << PanelPriority(pstat.first) <<
454  PanelSizeStep(pstat.second.sizeStep);
455  throw;
456  }
457  // showing something other than blank space?
458  if (img) {
459  ImageDimensions dimIncMar(
460  dim.w + margin.l + margin.r,
461  dim.h + margin.t + margin.b
462  );
463  // fit check
464  if (!pstat.second.dim.fits(dimIncMar)) {
465  // failed
467  PanelPriority(pstat.first) <<
468  PanelSizeStep(pstat.second.sizeStep) <<
469  ImageErrorLocation(off) <<
470  ImageErrorSourceDimensions(dimIncMar) <<
471  ImageErrorTargetDimensions(pstat.second.dim)
472  );
473  }
474  // potentially adjust location based on justification or
475  // centering options
476  GridLayoutConfig::Flags flags = pstat.second.flags();
477  ImageLocation loc = pstat.second.loc + offset;
478  loc.x += margin.l;
479  loc.y += margin.t;
480  // width
481  if (pstat.second.dim.w != dimIncMar.w) {
483  loc.x += pstat.second.dim.w - dimIncMar.w;
484  } else if (flags & GridLayoutConfig::PanelCenterHoriz) {
485  loc.x += (pstat.second.dim.w - dimIncMar.w) / 2;
486  }
487  }
488  // height
489  if (pstat.second.dim.h != dimIncMar.h) {
491  loc.y += pstat.second.dim.h - dimIncMar.h;
492  } else if (flags & GridLayoutConfig::PanelCenterVert) {
493  loc.y += (pstat.second.dim.h - dimIncMar.h) / 2;
494  }
495  }
496  // output!
497  dest->write(img, loc, off, dim);
498  }
499  }
500  }
501 }
502 
504  GridConfig::const_iterator iter = configs.find(pri);
505  if (iter == configs.end()) {
506  return ImageDimensions(0, 0);
507  }
508  return iter->second.dim;
509 }
510 
512  GridConfig::const_iterator iter = configs.find(pri);
513  if (iter == configs.end()) {
514  return ImageLocation(0, 0);
515  }
516  return iter->second.loc;
517 }
518 
522  unsigned int pri
523 ) const {
524  GridConfig::const_iterator iter = configs.find(pri);
525  if (iter == configs.end()) {
526  return false;
527  }
528  dim = iter->second.dim;
529  loc = iter->second.loc;
530  return true;
531 }
532 
533 } } }
const ImageDimensions & dimensions() const
Returns the dimensions of the image.
Definition: BppImage.hpp:1112
static constexpr Flags PanelHeightExpand
Request that this panel&#39;s height be expanded past the requested minumum if there is extra space avail...
Stores a location within an image.
Definition: BppImage.hpp:33
std::vector< KeyPanel > panels
The panels in column order, left to right.
std::uint16_t r
The row position.
std::int16_t y
Vertical coordinate.
Definition: BppImage.hpp:41
std::uint16_t l
Left margin size.
Definition: Panel.hpp:27
std::int16_t x
Horizontal coordinate.
Definition: BppImage.hpp:37
std::uint16_t r
Right margin size.
Definition: Panel.hpp:31
GridLayoutConfig & panelConfig(unsigned int pri)
Returns the layout configuration for the specified panel.
ImageLocation loc
The location on the target image as determined by layout().
void maxRowHeight(int row, std::int16_t height)
Sets the maximum height of a row.
A single size-step used by a PriorityGridLayout to place a Panel.
static constexpr Flags PanelWidthExpand
Request that this panel&#39;s width be expanded past the requested minumum if there is extra space availa...
GridLayoutConfig::Flags flags() const
Returns the configuration flags to use for rendering.
bool hidden
True to indicate that the panel will not be rendered.
STL namespace.
std::uint16_t b
Bottom margin size.
Definition: Panel.hpp:39
move_impl move(unsigned int c, unsigned int r)
Display stream manipulator that moves the display cursor to the given location.
The location of a panel in a PriorityGridLayout.
static unsigned int panelAt(RowVec &rv, const GridLocation &gl) noexcept
Returns the priority key for the panel at the given location, or zero if there is no panel...
int widthExpand
Number of panels requesting width expansion.
Stores the dimensions of an image.
Definition: BppImage.hpp:125
static constexpr Flags PanelCenterVert
Center the panel vertically within its grid spot.
ImageLocation layoutLocation(unsigned int pri) const
Returns the location assigned to the panel at priority pri by layout(), or { 0, 0 }...
ImageLocation offset
The upper right location of the destination image where the topmost row and leftmost column will be p...
std::uint16_t c
The column position.
std::vector< RowData > RowVec
Type used inside layout() to store data on all the rows.
std::uint16_t t
Top margin size.
Definition: Panel.hpp:35
An attempt was made to add a panel using an invalid priority value.
boost::error_info< struct Info_ImageDimensions, ImageDimensions > ImageErrorSourceDimensions
Image dimensions for a source image relevant to the error.
Definition: BppImage.hpp:298
static constexpr Flags PanelJustifyDown
Place the panel&#39;s bottom edge to the far bottom in its grid spot.
std::pair< unsigned int, PanelStatus * > KeyPanel
Stores a panel priority key and a pointer to the associated PanelStatus object inside configs...
bool add(const PanelSptr &panel, const GridLayoutConfig &config, unsigned int pri)
Adds a panel in an unused priority spot, or fail to add if the spot is already used.
void write(const BppImage *const src, const ImageLocation &destLoc, const ImageLocation &srcLoc, const ImageDimensions &srcSize, Direction srcDir=HorizInc, Operation op=OpSet)
Writes the specified portion of the source into this image.
Definition: BppImage.cpp:430
static constexpr Flags PanelHidden
The panel is not showm; this prevents it from being placed in the layout.
GridLayoutConfig config
The panel&#39;s layout configuration.
A problem with image bounds, such as the use of a location beyond the image&#39;s dimensions.
GridConfig configs
The mapping of Panel objects by priority key.
static bool minRows(RowVec &rv, int ms)
Ensures a minimum number of rows in rv, and returns true if at least that many rows already existed...
bool layoutPosition(ImageDimensions &dim, ImageLocation &loc, unsigned int pri) const
Provides the dimensions and location assigned to the panel at priority pri by layout() ...
void render(BppImage *dest)
Renders all visible panels to the provided image.
static constexpr Flags PanelJustifyRight
Place the panel&#39;s right edge to the far right in its grid spot.
constexpr BitsType flags() const
Returns the value stored in the object.
Definition: BitFlags.hpp:147
Thrown by Panels during rendering if the panel discovers the size required for its image will not fit...
ImageDimensions layoutDimensions(unsigned int pri) const
Returns the dimensions assigned to the panel at priority pri by layout(), or { 0, 0 }...
void remove(unsigned int pri)
Removes the panel in the given priority spot.
General graphics related code.
Definition: HD44780.hpp:15
constexpr bool withinBounds(const ImageLocation &loc) const
Returns true if the given location is within the bounds specified by this object. ...
Definition: BppImage.hpp:182
ImageDimensions dim
The maximum dimensions allocated to the panel by layout().
Informs a PriorityGridLayout object where to place and how large to make Panel objects.
boost::error_info< struct Info_ImageLocation, ImageLocation > ImageErrorLocation
An image location relevant to the error.
Definition: BppImage.hpp:288
int layout()
Places all panels into general positions.
boost::error_info< struct Info_PanelSizeStep, int > PanelSizeStep
Identifies a Panel&#39;s selected size-step in errors.
std::vector< std::int16_t > rowMaxHeight
Maximum heights for rows.
std::shared_ptr< Panel > PanelSptr
A shared pointer to a Panel.
Definition: Panel.hpp:107
boost::error_info< struct Info_PanelPriority, int > PanelPriority
Identifies a Panel object&#39;s layout priority in errors.
void addOrReplace(const PanelSptr &panel, const GridLayoutConfig &config, unsigned int pri)
Adds a panel or replaces an existing panel at the given priority spot.
Internal data structure used by layout() to store information on each row that is only needed to plac...
General errors.
Defines the size of a margin bordering a panel; the size of the margin is included in computations fo...
Definition: Panel.hpp:23
#define DUDS_THROW_EXCEPTION(x)
Works like BOOST_THROW_EXCEPTION, but includes a stack trace if DUDS_ERRORS_VERBOSE is defined...
Definition: Errors.hpp:48
An image that uses a single bit to represent the state of each pixel; a black or white picture...
Definition: BppImage.hpp:321
A panel identified by priority key could not be found.
boost::error_info< struct Info_FrameDimensions, ImageDimensions > ImageErrorTargetDimensions
Image dimensions for a target image relevant to the error.
Definition: BppImage.hpp:303
constexpr bool fits(const ImageDimensions &id) const
Returns true if the given dimensions are not larger than this object&#39;s dimensions.
Definition: BppImage.hpp:189
bool minCols(int mc) noexcept
Ensures the minimum number of allocated columns includes the given index.
bool heightExpand
True when a panel requests height expansion.
ImageDimensions used
Minimum used area of the panels.
static constexpr Flags PanelCenterHoriz
Center the panel horizontally within its grid spot.
ImageDimensions fill
The area to fill in the destination image.