This document is an overview of Views concepts, terminology, and architecture. The target audience is engineers using or working on Views.
Points in this document are written as (x,y)
, and rectangles are written as
[(x,y) wxh]
. For example, the rectangle [(100,100) 50x20]
contains the point
(130,110)
.
Views uses a coordinate system with (0,0)
at the top-left, with increasing
x-coordinates moving rightwards and increasing y-coordinates moving downwards.
This is the same as the Windows and GTK coordinate systems, but different from
the Cocoa coordinate system, which has (0,0)
at the bottom-left. Coordinates
in this document use the Views coordinate system.
Views generally take ownership of objects passed to them even via raw pointers, although there are some exceptions, such as delegates.
A View is a UI element, similar to an HTML DOM element. Each View occupies a
rectangle, called its bounds, which is expressed in the coordinate system of
its parent View. Views may have child Views, which are laid out according to the
View's layout manager, although individual Views may also override
View::Layout
to implement their own layout logic. Each View may also have a
border and/or a background.
Each View can calculate different sizes, which are used by the View's parent View to decide how to position and size it. Views may have any or all of a preferred size, a minimum size, and a maximum size. These may instead be calculated by the View's LayoutManager, and may be used by the parent View's LayoutManager.
It is generally not a good idea to explicitly change the bounds of a View. Typically, bounds are computed by the parent View's Layout method or the parent View's LayoutManager. It is better to build a LayoutManager that does what you want than to hand-layout Views by changing their bounds.
For more details about Views, see view.h.
The border is conventionally drawn around the edges of the View's bounding
rectangle, and also defines the View's content bounds, which are the area
inside which the View's content is drawn. For example, a View that is at
[(0,0) 100x100]
which has a solid border of thickness 2 will have content
bounds of [(2,2) 96x96]
.
For more details, see border.h.
The background is drawn below any other part of the View, including the border. Any View can have a background, but most Views do not. A background is usually responsible for filling the View's entire bounds. Backgrounds are usually a color, but can be a gradient or something else entirely.
For more details, see background.h.
The content is the area inside the content bounds of the View. A View's child Views, if it has any, are also positioned and drawn inside the content bounds of a View. There is no class representing the content area of a View; it only exists as the space enclosed by the View's border, and its shape is defined by the border's insets.
A View's layout manager defines how the View's child views should be laid out within the View's content bounds. There are a few layout managers supplied with Views. The simplest is FillLayout, which lays out all a View's children occupying the View's entire content bounds. FlexLayout provides a CSS-like layout for horizontal and vertical arrangements of views.
Other commonly-used layouts managers are BoxLayout, a predecessor of FlexLayout, and TableLayout, which provides a flexible row-and-column system.
Views are painted by pre-order traversal of the View tree - i.e., a parent View
is painted before its child Views are. Each View paints all its children in
z-order, as determined by View::GetChildrenInZOrder()
, so the last child in
z-order is painted last and therefore over the previous children. The default
z-order is the order in which children were added to the parent View.
Different View subclasses implement their own painting logic inside
View::OnPaint
, which by default simply calls View::OnPaintBackground
and
View::OnPaintBorder
. Generally, subclass implementations of View::OnPaint
begin by calling the superclass View::OnPaint
.
If you need a special background or border for a View subclass, it is better to
create a subclass of Background
or Border
and install that, rather than
overriding ::OnPaintBackground
or ::OnPaintBorder
. Doing this helps preserve
the separation of Views into the three parts described above and makes painting
code easier to understand.
Views need an underlying canvas to paint onto. This has to be supplied by the operating system, usually by creating a native drawing surface of some kind. Views calls these widgets. A Widget is the bridge between a tree of Views and a native window of some sort, which Views calls a native widget. Each Widget has a root view, which is a special subclass of View that helps with this bridging; the root view in turn has a single child view, called the Widget's contents view, which fills the entire root view. All other Views inside a given Widget are children of that Widget's contents view.
Widgets have many responsibilities, including but not limited to:
- Keyboard focus management, via FocusManager
- Window resizing/minimization/maximization
- Window shaping, for non-rectangular windows
- Input event routing
For more details, see widget.h.
The contents view of most Widgets is a Non-Client View, which is either a NonClientView or one of its descendants. The Non-Client View has two children, which are the Non-Client Frame View (a NonClientFrameView) and the Client View (a ClientView). The non-client frame view is responsible for painting window decorations, the Widget's border, the shadow, and so on; the client view is responsible for painting the Widget's contents. The area the client view occupies is sometimes referred to as the Widget's "client area". The non-client frame view may be swapped out as the system theme changes without affecting the client view.
The overall structure of a Widget and its helper Views looks like this:
A commonly-used type of client view is a dialog client view, which has a
contents view, optional buttons on the lower-right, and an optional extra
view on the lower-left. Dialogs are usually created by subclassing
DialogDelegate or DialogDelegateView and then calling
DialogDelegate::CreateDialogWidget
. The dialog's contents view fills the
entire top part of the widget's client view, and the bottom part is taken over
by the dialog's buttons and extra view.
A common type of dialog is a bubble, which is a dialog that is anchored to a
parent View and moves as the parent View moves. Bubbles are usually created by
subclassing BubbleDialogDelegateView and then calling
BubbleDialogDelegateView::CreateBubble
. Bubbles can have a title, which is
drawn alongside the window controls as part of the Bubble's Widget's
NonClientFrameView.