DUDS
Distributed Update of Data from Something
BppMenuRenderer.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 menu { namespace renderers {
15 
16 namespace graphics = duds::ui::graphics;
17 
27 
29  const graphics::ImageDimensions &dim
30 ) {
31  iconDim = dim;
32  if (iconDim.empty()) {
33  iconTxMg = 0;
34  }
36 }
37 
38 void BppMenuRenderer::maxVisible(std::uint16_t i) {
39  if (flgs & HorizontalList) {
40  items = i;
42  }
43 }
44 
46  std::uint16_t width,
47  std::uint16_t margin,
48  std::uint16_t minsize,
49  ScrollBarPlacement place
50 ) {
51  // check for no change
52  if ((width == scrollWidth) && (margin == scrollMg)) return;
53  scrollWidth = width;
54  scrollMg = margin;
55  posInd = std::make_unique<duds::ui::graphics::BppPositionIndicator>(
56  minsize
57  );
60 }
61 
63  posInd.reset();
64  scrollMg = scrollWidth = 0;
66 }
67 
70 ) {
71  // the dimensions may need modification; scroll bar will be removed
73  int width = fitDim.w;
74  int place = (flgs & ScrollBarMask).flags();
75  // using scroll bar?
76  if (posInd) {
77  // vertical placement for scroll bar?
78  if (place < ScrollBottom) {
79  // remove its width
80  width -= scrollWidth + scrollMg;
81  fit.w = width;
82  } else {
83  // horizontal scroll?
84  // remove its size
85  fit.h -= scrollWidth + scrollMg;
86  }
87  }
88  if (flgs & HorizontalList) {
89  // max width for each item
90  width /= items;
91  }
92  // proposed text dimensions; do not alter dimensions to use until they fit
94  // compute text dimensions even if no text displayed; use result to test
95  // for display area that is too small
96  propTextDim.w = width;
97  // showing a separate value column?
98  if (valWidth) {
99  propTextDim.w -= valWidth + valMg;
100  }
101  // use font dimensions for minimum height
103  cache->font()->estimatedMaxCharacterSize();
104  propTextDim.h = fntDim.h;
105  // selection and disabled icons use the same space
108  selDisWidth = iDim.w;
109  propTextDim.w -= iDim.w;
110  propTextDim.h = std::max(propTextDim.h, iDim.h);
111  // next, toggle icons
113  toggleWidth = iDim.w;
114  propTextDim.w -= iDim.w;
115  propTextDim.h = std::max(propTextDim.h, iDim.h);
116  // when horizontal, the margin between items is part of the width
117  if (flgs & BppMenuRenderer::HorizontalList) {
118  propTextDim.w -= itemMg;
119  }
120  // showing an item specific icon column?
121  if (!iconDim.empty()) {
122  propTextDim.w -= iconDim.w + iconTxMg;
123  propTextDim.h = std::max(propTextDim.h, iconDim.h);
124  }
125  // too short or small to fit? must fit at least one character for text
126  // width, and not exceed height
127  if ((propTextDim.h > fit.h) ||
128  ((flgs & DoNotShowText) && (propTextDim.w < 0)) ||
129  ((~flgs & DoNotShowText) && (propTextDim.w < fntDim.w))
130  ) {
134  );
135  }
136  // showing item text?
137  if (~flgs & DoNotShowText) {
138  // use proposed text dimensions
139  textDim = propTextDim;
140  // width fits full space
141  itemDim.w = width;
142  } else {
143  // no text dimensions
144  textDim.w = textDim.h = 0;
145  // width is adequate to fit what will be displayed
146  itemDim.w = width - propTextDim.w;
147  }
148  itemDim.h = textDim.h;
149  // compute number of items visible, and determine if a partial item
150  // is visible
151  int fullshow;
152  if (flgs & HorizontalList) {
153  // use fit width that excludes scroll bar if present
154  fullshow = fit.w / (itemDim.w + itemMg);
155  fracshow = fit.w % (itemDim.w + itemMg);
156  } else {
157  // use fit width that excludes scroll bar if present
158  fullshow = fit.h / (itemDim.h + itemMg);
159  fracshow = fit.h % (itemDim.h + itemMg);
160  }
161  items = fullshow;
162  // only allow fractionally visible items if at least two full items
163  // can be shown, and the fractional part is at least a few pixels
164  if ((fullshow > 1) && (fracshow > (2 + itemMg))) {
165  items += (fracshow ? 1 : 0);
166  } else {
167  fracshow = 0;
168  }
169  // scroll bar
170  if (posInd) {
171  // size the scroll bar to fit
173  place == ScrollRight ? fitDim.w - scrollWidth : 0,
174  place == ScrollBottom ? fitDim.h - scrollWidth : 0
175  ));
177  place < ScrollBottom ? scrollWidth : fitDim.w,
178  place < ScrollBottom ? fitDim.h : scrollWidth
179  ));
181  }
182  // store new output dimension
183  destDim = fitDim;
184  // flag recalcuation done
186 }
187 
191 ) {
192  // need to have the string cache but don't have one?
193  if ((~flgs & DoNotShowText) && !cache) {
195  }
196  // no destination image provided?
197  if (!dest) {
199  }
200  // destination image dimensions will be used in a number of places
201  const graphics::ImageDimensions &fitDim = dest->dimensions();
202  // ensure item dimensions have been computed
203  if ((~flgs & Calculated) || (destDim != fitDim)) {
204  recalculateDimensions(fitDim);
205  // different number of items fit than shown previously?
206  if (items != mova.maxVisible()) {
207  // fix it
208  mova.maxVisible(items);
209  }
210  }
211  int place = (flgs & ScrollBarMask).flags();
212  int scrollSize = scrollMg + scrollWidth;
213  // have scroll bar that hides?
214  if (posInd && (~flgs & ScrollBarNeverHides) && (place < ScrollBottom)) {
215  // shown previously but not needed now?
216  if (mova.showingAll()) {
217  if (flgs & ScrollBarShown) {
218  // enlarge text label area
219  itemDim.w += scrollSize;
220  textDim.w += scrollSize;
221  flgs.clear(ScrollBarShown);
222  }
223  // no space used by missing scroll bar
224  scrollSize = 0;
225  } else if ((~flgs & ScrollBarShown) && !mova.showingAll()) {
226  // reduce text label area
227  itemDim.w -= scrollSize;
228  textDim.w -= scrollSize;
229  flgs.set(ScrollBarShown);
230  }
231  }
232  // should have caused recalculateDimensions() to throw
233  assert((destDim.w >= itemDim.w) && (destDim.h >= itemDim.h));
234  // ensure image is clear
235  dest->clearImage();
236  // If dimension allows for a fraction of an item, then all of top/first
237  // item should be shown and partial of bottom/last, until the last menu
238  // item is visible and the selection is about half-way there among visible
239  // items. Once selection is within half way across visible items, the items
240  // shift so that the last item is compltely visible.
242  // the visible index of the fractionally visible item
243  int fracidx;
244  // is there an item that will be partially visible?
245  if (fracshow && ((items - 1) < mova.size())) {
246  // last visible item will be rendered, and selected item is more than
247  // half-way to the end?
248  if (mova.showingLast() && (mova.selectedVisible() > (mova.size() / 2))) {
249  // the fractional item will be the first in the visible list
250  fracidx = 0;
251  } else {
252  // the fractional item will be the last in the visible list
253  fracidx = mova.size() - 1;
254  // write first items to the destinatiom image directly
255  img = dest;
256  }
257  } else {
258  fracidx = -1;
259  // write items to the destinatiom image directly
260  img = dest;
261  }
262  // make useful scroll bar data to avoid some more conditionals later
263  int startX;
264  if (place == ScrollLeft) {
265  startX = scrollSize;
266  } else {
267  startX = 0;
268  }
269  graphics::ImageLocation pos(startX, 0);
270  int idx = 0;
271  // render each item
272  for (duds::ui::menu::MenuItem* mitem : mova) {
273  // useful for advancing the position later, especially for horizontal
274  // menus
275  graphics::ImageLocation startPos(pos);
276  // working on a fractionally visible item?
277  if (idx == fracidx) {
278  // render into an image large enough for one menu item
279  pos = graphics::ImageLocation(0, 0);
281  }
282  // note selection status
283  bool selected = mova.selectedVisible() == idx;
284  // figure out if rendering should be inverted or not
285  //bool selectInv = selected && (flgs & InvertSelected);
286  // selection icon rendering
287  if (selIcon && selected) {
288  // selection icon is never rendered inverted, but inverting the
289  // item will be done later for the whole item
290  img->write(
291  selIcon,
292  pos
293  // maybe center vertically ???
294  );
295  }
296  // disabled icon rendering
297  if (disIcon && mitem->isDisabled()) {
298  // disabled icon is never rendered inverted; item cannot be selected
299  img->write(disIcon, pos);
300  }
301  if (selIcon || disIcon) {
302  pos.x += selDisWidth;
303  }
304  // toggled icon rendering
305  if (togOffIcon || togOnIcon) {
306  if (mitem->isToggle()) {
307  // render toggle off icon?
308  if (togOffIcon && !mitem->isToggledOn()) {
309  img->write(togOffIcon, pos);
310  } else if (togOnIcon && mitem->isToggledOn()) {
311  img->write(togOnIcon, pos);
312  }
313  }
314  // update the position for rendering the next item
315  pos.x += toggleWidth;
316  }
317  // if menu item icons should be rendered . . .
318  if (!iconDim.empty()) {
319  // . . . see if the item has an icon
320  BppIconItem *iitem = dynamic_cast<BppIconItem*>(mitem);
321  if (iitem && iitem->icon()) {
322  // have icon
323  img->write(
324  iitem->icon(),
325  pos,
326  iitem->icon()->dimensions().minExtent(iconDim)
327  );
328  }
329  // update the position for rendering the next item
330  pos.x += iconDim.w + iconTxMg;
331  }
332  // render text label
333  if (~flgs & DoNotShowText) {
335  // have label text?
336  if (!mitem->label().empty()) {
337  if (valWidth || mitem->value().empty() || (flgs & ValueRightJustified)) {
338  // get the rendered label text
339  text = cache->text(mitem->label());
340  }
341  if (!text && !(flgs & ValueRightJustified)) {
342  // get the rendered label and value text
343  text = cache->text(mitem->label() + " " + mitem->value());
344  } else if (!mitem->value().empty()) {
345  // render value text and label text separately
347  valtext = cache->text(mitem->value());
348  // compute width of value
350  textDim.minExtent(text->dimensions())
351  );
352  vdim.w = textDim.w - valMg - vdim.w;
353  // any space to fit value?
354  if (vdim.w > 0) {
355  img->write(
356  valtext,
358  textDim.w - valtext->dimensions().w,
359  pos.y
360  ),
361  vdim.minExtent(valtext->dimensions())
362  );
363  }
364  }
365  // put it in the destination image
366  img->write(
367  text,
368  pos,
369  // dimensions must not exceed text area or size of the text
370  textDim.minExtent(text->dimensions())
371  );
372  }
373  // put value text in own column?
374  if (valWidth) {
375  // update the position for rendering the value
376  pos.x += textDim.w + valMg;
377  // have value text?
378  if (!mitem->value().empty()) {
379  // render the value text
380  text = cache->text(
381  mitem->value(),
385  );
386  // remaining dimensions to fill
387  graphics::ImageDimensions valDim(itemDim.w - pos.x, itemDim.h);
388  // text left justified, exactly fits or is too long?
389  if (!(flgs & ValueRightJustified) || text->width() >= valDim.w) {
390  // write out the text; justification is irrelevant
391  img->write(text, pos, valDim);
392  // no need to blank out any area
393  } else {
394  // write out the text to the right
395  img->write(
396  text,
398  pos.x + valDim.w - text->width(),
399  pos.y
400  )
401  );
402  }
403  }
404  }
405  }
406  // invert selected item?
407  if (selected && (flgs & InvertSelected)) {
408  if (flgs & HorizontalList) {
409  img->drawBox(startPos, itemDim, graphics::BppImage::OpXor);
410  } else {
411  // faster than drawBox()
412  img->invertLines(pos.y, itemDim.h);
413  // scroll margin in inverted region?
414  if (scrollSize && (place < ScrollBottom)) {
415  // blank the margin only; scroll bar render will do the rest
416  img->drawBox(
417  place == ScrollRight ? destDim.w - scrollSize : 0,
418  pos.y, // y
419  scrollSize, // width == margin for scroll
420  itemDim.h, // height
421  false // state
422  );
423  }
424  }
425  }
426  // deal with fractionally visible item
427  if (idx == fracidx) {
428  // first item?
429  if (idx == 0) {
430  if (flgs & HorizontalList) {
431  dest->write(
432  img,
436  );
437  // advance to the right
438  pos.x = fracshow + itemMg;
439  } else {
440  dest->write(
441  img,
445  );
446  // advance downward
447  pos.x = startX;
448  pos.y = fracshow + itemMg;
449  }
450  // write the rest of the items to the destination image directly
451  img = dest;
452  }
453  // last item?
454  else {
455  if (flgs & HorizontalList) {
456  dest->write(
457  img,
458  startPos,
461  );
462  } else {
463  dest->write(
464  img,
465  startPos,
468  );
469  }
470  }
471  } else {
472  // move rendering position
473  if (flgs & HorizontalList) {
474  // advance to the right
475  pos.x = startPos.x + itemDim.w + itemMg;
476  } else {
477  // advance downward
478  pos.x = startX;
479  pos.y += itemDim.h + itemMg;
480  }
481  }
482  // keep track of display index
483  ++idx;
484  }
485  // scroll bar
486  if (posInd && (flgs & ScrollBarShown)) {
487  // using visible() seems like a good idea, but the item indices used in
488  // the next line are from a vector that includes all the menu's items,
489  // even if not visible, so using visible() will result in the scroll bar
490  // indicating the end of the menu early if items are hidden
491  posInd->range(mova.menu()->size()); //visible());
492  posInd->render(dest, mova.firstIndex(), mova.lastIndex());
493  }
494 }
495 
496 } } } }
static constexpr Flags InvertSelected
The selected item will be rendered inverted.
bool showingLast() const
True if the menu&#39;s last visible item is one of the currently visible items.
static constexpr Flags ScrollBarShown
Indicates that the scroll bar was rendered on the previous render call.
static constexpr Flags ScrollBarMask
Mask that defines where the ScrollBarPlacement value is put in the configuration flags.
static constexpr Flags ValueRightJustified
Right justify value text when values are placed in a column separate from the item label...
Stores a location within an image.
Definition: BppImage.hpp:33
std::uint16_t scrollMg
Space in pixels to have between label or value text and the scroll bar.
std::int16_t y
Vertical coordinate.
Definition: BppImage.hpp:41
static constexpr Flags AlignRight
Align each line to the right.
Definition: BppFont.hpp:192
std::int16_t x
Horizontal coordinate.
Definition: BppImage.hpp:37
constexpr bool empty() const
True if the dimensions indicate zero area.
Definition: BppImage.hpp:169
std::shared_ptr< const BppImage > ConstBppImageSptr
Definition: BppImage.hpp:1778
duds::ui::graphics::ImageDimensions textDim
The size of a menu item&#39;s text.
std::size_t size() const
Returns the number of visible menu items.
std::uint16_t selDisWidth
The maximum width of the selection icon and the disabled icon.
constexpr ImageDimensions minExtent(const ImageDimensions &dim) const
Returns new dimensions that only cover the union of this dimension and the given dimension.
Definition: BppImage.hpp:215
const duds::ui::graphics::ImageDimensions & iconDimensions() const
Returns the dimensions used for rendering icons stored inside menu items derived from BppIconItem...
duds::ui::graphics::ConstBppImageSptr togOffIcon
Icon used to denote a menu item in its toggled off state.
static constexpr Flags HorizontalList
Items are arranged horizontally instead of vertically.
BitFlags clear()
Clear all bits.
Definition: BitFlags.hpp:245
std::uint16_t itemMg
The margin in pixels between menu items.
static constexpr Flags Calculated
When set, indicates that internal dimension values have been calculated.
bool showingAll() const
True if the entire menu is currently visible.
duds::ui::graphics::ConstBppImageSptr togOnIcon
Icon used to denote a menu item in its toggled on state.
void removeScrollBar()
Removes the scroll bar from the menu.
static constexpr Flags ScrollBarNeverHides
True to always show the scroll bar.
Stores the dimensions of an image.
Definition: BppImage.hpp:125
BitFlags set(const BitFlags &bf)
Set all bits in this object that are set in bf.
Definition: BitFlags.hpp:259
std::uint16_t toggleWidth
The maximum width of the toggle icons.
Generalized item thingy that holds a bit-per-pixel image as an icon.
Definition: BppIconItem.hpp:21
std::uint16_t iconTxMg
Space in pixels to have between icons and text.
std::uint16_t scrollWidth
Thw width of the scroll bar in pixels.
std::shared_ptr< BppImage > BppImageSptr
Definition: BppImage.hpp:1777
Performs a bitwise exclusive-or operation with the destination and source data, and places the result...
Definition: BppImage.hpp:1546
boost::error_info< struct Info_ImageDimensions, ImageDimensions > ImageErrorSourceDimensions
Image dimensions for a source image relevant to the error.
Definition: BppImage.hpp:298
duds::ui::graphics::ConstBppImageSptr selIcon
Icon used to denote a selected menu item.
void render(duds::ui::graphics::BppImageSptr &dest, duds::ui::menu::MenuOutputAccess &mova)
Renders a menu to the given image.
std::uint16_t fracshow
The number of pixels to show of a partially visible menu item.
std::uint16_t valWidth
The width of the value column.
static constexpr Flags DoNotShowText
Only show icons, not text, for the menu items.
void addScrollBar(std::uint16_t width=1, std::uint16_t margin=1, std::uint16_t minsize=4, ScrollBarPlacement place=ScrollRight)
Adds or replaces a scroll bar on the menu.
std::size_t maxVisible() const
Returns the currently set maximum number of visible menu items.
duds::ui::graphics::BppStringCacheSptr cache
Pre-rendered strings for menu text.
static constexpr Flags InternalMask
The second byte (the one next to the least significant byte) is reserved for internal flags...
void recalculateDimensions(duds::ui::graphics::ImageDimensions fitDim)
Recalculates the dimension values needed to render a menu that fits into the given dimensions...
duds::ui::graphics::ImageDimensions destDim
Destination dimensions; used to tell when to recompute dimensions of parts of the menu...
std::unique_ptr< duds::ui::graphics::BppPositionIndicator > posInd
Optional position indicator to show user location within the menu.
duds::ui::graphics::ImageDimensions iconDim
The size of a menu item&#39;s icon.
std::uint16_t valMg
The margin in pixels between the label and value columns,.
duds::general::BitFlags< struct BppMenuFlags > Flags
A type for configuration flags.
The destination image provided for the menu is too small to render a single menu item.
ImageDimensions MaxExtent(const BppImage *i0, const BppImage *i1)
Returns the maximum extent of the dimensions of two bit-per-pixel images.
Definition: BppImage.cpp:797
Flags flgs
Option flags that modify how the menu is rendered.
Represents an option that a user can chose from a menu of options.
Definition: MenuItem.hpp:58
duds::ui::graphics::ImageDimensions itemDim
Computed dimensions of a complete menu item.
std::uint16_t items
Number of items to display.
duds::ui::graphics::ConstBppImageSptr disIcon
Icon used to denote a disabled menu item.
static std::shared_ptr< BppImage > make(const ImageDimensions &id)
Convenience function to make a shared pointer to an image using the BppImage(const ImageDimensions &)...
Definition: BppImage.hpp:1018
std::size_t selectedVisible() const
Returns the index of the currently selected MenuItem within the list of currently visible items...
General graphics related code.
Definition: HD44780.hpp:15
static constexpr Flags AlignLeft
Align each line to the left.
Definition: BppFont.hpp:184
The destination image doesn&#39;t exist.
The menu render is configured to render text, but has no string cache.
General errors.
#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
const duds::ui::graphics::ConstBppImageSptr & icon() const
Returns the item&#39;s icon.
Definition: BppIconItem.hpp:46
boost::error_info< struct Info_FrameDimensions, ImageDimensions > ImageErrorTargetDimensions
Image dimensions for a target image relevant to the error.
Definition: BppImage.hpp:303
Flags flags() const
Returns the configuration flags for this renderer.
Provides access to a MenuOutput for rendering.
ScrollBarPlacement
Values used to control which edge of the menu will host the scroll bar.
std::uint16_t maxVisible() const
Returns the maximum number of visible items to show on the menu; try to avoid calling.
BitFlags setMasked(const BitFlags &bf, const BitFlags &mask)
Changes only the bits in a masked range.
Definition: BitFlags.hpp:278