Skip to content
Joel de Guzman edited this page Feb 18, 2020 · 3 revisions

The element is the class that deals with the graphic representation of fine-grained elements inside a window which may be static graphics or active controls. Elements are light-weight objects. By design, they are minimal in terms of memory footprint. For example, elements do not have information about their coordinates. That means that the client has to supply the information on where an element is drawn. That is done using context parameters that most of the element's member functions take. For example:

virtual void draw(context const& ctx);

The Context

The draw interface above implies that you need to supply some "context" in order to draw an element. The context class provides information such as the element's bounds, a rectangle that indicates where the element needs to be drawn. The context class inherits from the basic_context, a struct which contains a reference to the view —the main content view inside a window where elements are placed, and a canvas, a drawing surface where the elements are drawn.

struct basic_context
{
   /*...*/

   elements::view&      view;
   elements::canvas&    canvas;
};

The context class inherits from basic_context and additionally includes a pointer to the "current" element being drawn, a pointer to its parent element in the elements hierarchy (that which contains it), and the element's bounds, a rectangle that indicates where the element needs to be drawn.

Only one element member function takes in a basic_context argument, the limits member function (more on that later). Other than that, all the other element member functions take in a context argument. The reason for this is that the at the point where the limits is called, the element's position and parent-child relationships in the elements hierarchy have not been established yet.

class context : public basic_context
{
public:

   /*...*/

   element*          element;
   context const*    parent;
   rect              bounds;
};

The Element Class

class element : public std::enable_shared_from_this<element>
{
public:

   using element_ptr = std::shared_ptr<element>;
   using element_const_ptr = std::shared_ptr<element const>;

   /*...*/

// Image

   virtual view_limits     limits(basic_context const& ctx) const;
   virtual view_stretch    stretch() const;
   virtual element*        hit_test(context const& ctx, point p);
   virtual void            draw(context const& ctx);
   virtual void            layout(context const& ctx);
   virtual void            refresh(context const& ctx, element& element, int outward = 0);
   void                    refresh(context const& ctx, int outward = 0);

// Control

   virtual element*        click(context const& ctx, mouse_button btn);
   virtual void            drag(context const& ctx, mouse_button btn);
   virtual bool            key(context const& ctx, key_info k);
   virtual bool            text(context const& ctx, text_info info);
   virtual bool            cursor(context const& ctx, point p, cursor_tracking status);
   virtual bool            scroll(context const& ctx, point dir, point p);

   virtual bool            focus(focus_request r);
   virtual element const*  focus() const;
   virtual element*        focus();
   virtual bool            is_control() const;

// Receiver

   virtual void            value(bool val);
   virtual void            value(int val);
   virtual void            value(double val);
   virtual void            value(std::string_view val);
};

Element Image Member Functions

These member functions pertain to how an element is drawn as well as providing hints on how an element wants to be laid out.

limits

Determines the minimum and maximum extents of an element.

virtual view_limits     limits(basic_context const& ctx) const;

view_limits is a struct containing the minimum and maximum information.

struct view_limits
{
   point    min;
   point    max;
};

An element has a fixed size if min == max. An element is infinitely resizable if max == full_extent, where full_extent is a constexpr defined by the library.

stretch

Determines how much an element can stretch horizontally and vertically relative to other elements in a column or row. If a row (or column) has more space allocated to it than the minimum required by all elements in that row (or column), as specified by the sum of all the minimum limits of each of the elements, then the resizable elements (see limits above) are stretched to fit the allocated space. The value returned by stretch dictates how much that element can stretch. The default is 1.0. A stretch value of 2.0 means that the element is twice as stretchable compared to all other elements in that row (or column), assuming the others have the default 1.0 stretch value.

virtual view_stretch    stretch() const;

view_stretch is a struct containing the stretchiness information.

struct view_stretch
{
   float    x;
   float    y;
};

hit_test

Returns a pointer to the inner-most element that intersects point p or nullptr if none.

virtual element*        hit_test(context const& ctx, point p);

draw

Draw the element. The context's bounds member specifies where the element should be drawn. The element should use the context's canvas member for drawing. The draw member function should not be called directly or indirectly from other member functions, except from a parent's draw function, drawing its children. If you need to redraw an element from other member functions, call the refresh member function instead.

virtual void            draw(context const& ctx);

layout

As mentioned, by design, elements do not know about their location in the window. However, some elements may make use of the layout member function if it needs to update information necessary for drawing. For example, composite elements, those that have children elements, may cache information about the coordinates of sub elements. The layout member function is called, prior to calling draw, to give the element a chance to prepare its children for drawing. The context's bounds member specifies the outermost bounds of the element. The composite element subdivides this area as appropriate for each of its children.

virtual void            layout(context const& ctx);

refresh

Send a refresh request for a particular element to redraw it. This function essentially invalidates the bounds of the element and triggers a subsequent redraw of that area in the window. The outward argument specifies which of the element's parents should be refreshed. If for example, a margin element encloses an image element, and you want to refresh the image, including its margin, then specify an outward argument of 1. The default outward argument is 0.

virtual void            refresh(context const& ctx, element& element, int outward = 0);
void                    refresh(context const& ctx, int outward = 0);

The second refresh member function simply forwards to the first, passing in a reference to this element.

void element::refresh(context const& ctx, int outward = 0) 
{ 
   refresh(ctx, *this, outward); 
}

Element Control Member Functions

click

Respond to a mouse button event. Returns the innermost element being clicked or nullptr if none. The innermost element is the one that actually acts on the click event.

virtual element*        click(context const& ctx, mouse_button btn);

mouse_button is a struct that provides the relevant information.

struct mouse_button
{
   enum what { left, middle, right };

   bool     down;
   int      num_clicks;
   what     state;
   int      modifiers;
   point    pos;
};
  • down: True if the mouse button is pressed, otherwise it is released.
  • num_clicks: The number of clicks (single click, double click, etc.)
  • state: Indicates whether the left, middle or right button is clicked.
  • modifiers: A bit mask that specifies which relevant keys are being pressed at the same time (see below).
  • pos: The current mouse position

The modifiers member is a bit mask with these possible values:

enum : uint16_t
{
   mod_shift         = 0x0001,
   mod_control       = 0x0002,

   // mod_alt maps to the Alt key on PC keyboards
   // and maps to the Option key on MacOS
   mod_alt           = 0x0004,

   // mod_super maps to the Windows key on PC keyboards
   // and maps to the Command key on MacOS
   mod_super         = 0x0008,

   // mod_action maps to mod_control on Windows and Linux
   // and maps to mod_super on MacOS
   mod_action        = 0x0010,
};

drag

This member function is called after a mouse click, when the user drags the mouse. This will be called continuously while the mouse is being dragged.

virtual void            drag(context const& ctx, mouse_button btn);

See mouse_button above for information about the btn argument.

key

Handle raw key events.

virtual bool            key(context const& ctx, key_info k);

key_info is a struct that provides the relevant information about the key event:

struct key_info
{
   key_code          key;
   key_action        action;
   int               modifiers;
};

key_code are enumerations about the pressed key:

enum class key_code : int16_t
{
   unknown           = -1,

   // Printable keys
   space             = 32,
   apostrophe        = 39,    // '
   comma             = 44,    // ,
   minus             = 45,    // -
   period            = 46,    // .
   slash             = 47,    // /
   _0                = 48,
   _1                = 49,
   _2                = 50,
   _3                = 51,
   _4                = 52,
   _5                = 53,
   _6                = 54,
   _7                = 55,
   _8                = 56,
   _9                = 57,
   semicolon         = 59,    // ;
   equal             = 61,    // =
   a                 = 65,
   b                 = 66,
   c                 = 67,
   d                 = 68,
   e                 = 69,
   f                 = 70,
   g                 = 71,
   h                 = 72,
   i                 = 73,
   j                 = 74,
   k                 = 75,
   l                 = 76,
   m                 = 77,
   n                 = 78,
   o                 = 79,
   p                 = 80,
   q                 = 81,
   r                 = 82,
   s                 = 83,
   t                 = 84,
   u                 = 85,
   v                 = 86,
   w                 = 87,
   x                 = 88,
   y                 = 89,
   z                 = 90,
   left_bracket      = 91,    // [
   backslash         = 92,    // \ (back-slash)
   right_bracket     = 93,    // ]
   grave_accent      = 96,    // `
   world_1           = 161,   // non-US #1
   world_2           = 162,   // non-US #2

   // Function keys
   escape            = 256,
   enter             = 257,
   tab               = 258,
   backspace         = 259,
   insert            = 260,
   _delete           = 261,
   right             = 262,
   left              = 263,
   down              = 264,
   up                = 265,
   page_up           = 266,
   page_down         = 267,
   home              = 268,
   end               = 269,
   caps_lock         = 280,
   scroll_lock       = 281,
   num_lock          = 282,
   print_screen      = 283,
   pause             = 284,
   f1                = 290,
   f2                = 291,
   f3                = 292,
   f4                = 293,
   f5                = 294,
   f6                = 295,
   f7                = 296,
   f8                = 297,
   f9                = 298,
   f10               = 299,
   f11               = 300,
   f12               = 301,
   f13               = 302,
   f14               = 303,
   f15               = 304,
   f16               = 305,
   f17               = 306,
   f18               = 307,
   f19               = 308,
   f20               = 309,
   f21               = 310,
   f22               = 311,
   f23               = 312,
   f24               = 313,
   f25               = 314,
   kp_0              = 320,
   kp_1              = 321,
   kp_2              = 322,
   kp_3              = 323,
   kp_4              = 324,
   kp_5              = 325,
   kp_6              = 326,
   kp_7              = 327,
   kp_8              = 328,
   kp_9              = 329,
   kp_decimal        = 330,
   kp_divide         = 331,
   kp_multiply       = 332,
   kp_subtract       = 333,
   kp_add            = 334,
   kp_enter          = 335,
   kp_equal          = 336,
   left_shift        = 340,
   left_control      = 341,
   left_alt          = 342,
   left_super        = 343,
   right_shift       = 344,
   right_control     = 345,
   right_alt         = 346,
   right_super       = 347,
   menu              = 348
};

key_action is another enumeration about the state of the key, whether it is being pressed, released or repeatedly pressed:

enum class key_action
{
   unknown           = -1,
   release           = 0,
   press             = 1,
   repeat            = 2,
};

modifiers is the same as that for click: a bit mask with these possible values:

enum : uint16_t
{
   mod_shift         = 0x0001,
   mod_control       = 0x0002,

   // mod_alt maps to the Alt key on PC keyboards
   // and maps to the Option key on MacOS
   mod_alt           = 0x0004,

   // mod_super maps to the Windows key on PC keyboards
   // and maps to the Command key on MacOS
   mod_super         = 0x0008,

   // mod_action maps to mod_control on Windows and Linux
   // and maps to mod_super on MacOS
   mod_action        = 0x0010,
};

Returns true if the element handled the key event.

text

While key handles raw key events, text handles processed text entry, possibly managing multiple key presses and multi-language inputs. The prepared text information is UCS4 code point.

virtual bool            text(context const& ctx, text_info info);

text_info is a struct that contains the UCS4 code point and the same modifier bit mask as that of key (see key above):

struct text_info
{
   uint32_t codepoint;
   int      modifiers;
};

Returns true if the element handled the text entry.

cursor

Handles cursor move events. This is called when the cursor hovers over an element.

virtual bool            cursor(context const& ctx, point p, cursor_tracking status);

The point, p, specifies where the cursor position is. cursor_tracking is an enumeration that tells the element if the cursor is either entering, hovering, or leaving the element:

enum class cursor_tracking
{
   entering,   // Sent when the cursor is entering the element
   hovering,   // Sent when the cursor is hovering over the element
   leaving     // Sent when the cursor is leaving the element
};

Returns true if the element handled the cursor event.

scroll

Handle scroll-wheel or scroll gesture events.

virtual bool            scroll(context const& ctx, point dir, point p);

The point, p, specifies where the cursor position is. The point, dir, specifies the horizontal (x) and vertical (y) direction, how many pixels to move in the x and y axis. Negative means go up or left. positive means go down or right. Scrolling can be fractional for HDPI and also to allow inertial scrolling.

Returns true if the element handled the scroll event.

focus

Some elements such as text entry boxes can become the focus. When an element becomes the focus, it will be the one receiving key and text inputs.

virtual bool            focus(focus_request r);
virtual element const*  focus() const;
virtual element*        focus();

Elements wanting to be focus should implement the first focus overload. Focus negoiations proceeds as follows:

  1. The element is asked if it can become the focus (wants_focus).
  2. The element begins focus (begin_focus).
  3. When the client clicks on something else that also wants to become focus, the current focus is asked to end focus (end_focus).

This is specified in the focus_request enumeration below:

enum class focus_request
{
   wants_focus,
   begin_focus,
   end_focus
};

The second and third focus overrides return the current focus. This is typically implemented by composite elements.

is_control

Return true if the element is a control element. A control element is an element that requires user interaction and implements one or more of the element control member functions.

virtual bool            is_control() const;

Element Receiver Member Functions

Elements such as buttons and sliders may have values. These member functions respond to value changes. For example, a slider accepts normalized values from 0.0 to 1.0. Assigning the slider a value of 0.5 will move its thumb to the middle position. Doing an element refresh (see refresh member function above) will ask elements to redraw the slider with the updated position.

virtual void            value(bool val);
virtual void            value(int val);
virtual void            value(double val);
virtual void            value(std::string_view val);