This repository has been archived by the owner on May 28, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Readme.md.html
226 lines (152 loc) · 12.9 KB
/
Readme.md.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
<meta charset="utf-8">
**Ngs.Engine.Framework**
# Wrapper (facade) API
The wrapper types provide an API directly faced to the application developer. They are meant to have more stable interfaces compared to those of the raw interop API.
# GUI framework
The `Ngs.Engine.UI` namespace provides a framework for creating a graphical user interface based on NgsPF.
This framework is not thread-safe.
## Concepts
### Organization of an user interface
*Workspaces* are ...
!!! TODO: Yet to be written about...
Workspaces
*Windows* are the root-level components of GUI and act as a boundary between a window manager (provided by an operating system) and an application.
*Views* (`Ngs.Engine.UI.View`) are the functional basic-blocks of GUI. Views are capable of hosting zero or more *subviews*, forming a tree structure (*view tree*). Subviews are positioned using a *layout* object associated with their superview.
!!! Tip
It's impossible to make a view without a layout have subviews. Subviews must be positioned somehow. Furthermore, subviews are managed by a layout at the implementation level.
Every window has exactly one *contents view*, which serves as a root of the view tree.
Views are finally converted to *layers*. Layers are purely presentational objects that are used to instruct the underlying compositing engine what to display on the screen.
!!! Tip
Layers can be thought of as a series of render commands. In fact, layers are generated in this style through method calls done by an implementation of `Ngs.Engine.UI.View.Render`.
However, they are represented using the retained mode graphics model (à la scene graph) in the composition engine, so they must first be converted to this form before being displayed.
The framework provides guidance for application developers about the API usage that minimizes the number of operations required to modify the layer tree.
!!! TODO: Yet to be written about...
Ports
### Standard view components
The framework provides the implementation of the following commonly used view types:
*Forms* are a special type of views that provides a data binding facility. Data binding is done through automatic management of the lifetimes and properties of their subviews.
!!! TODO: Yet to be written about...
Containers
## Layouting
The layouting process is based around the following things:
- The *inherent layout properties* of each view. They describe layout constraints and preferred values regarding the size computation of the view on which they are defined. They are derived from the information available locally within each view. Especially, they are considered invariant to translation.
*********************************************
* .--------+---+----+---> x
* |░░░░░░░░| ┊ |░░░░
* minimum size +--------* ┊ |░░░░
* | ┊ |░░░░
* preferred size +┄┄┄┄┄┄┄┄┄┄┄┄* |░░░░
* | |░░░░
* maximum size +-----------------*░░░░
* v░░░░░░░░░░░░░░░░░░░░░░
* y
* ░░ disallowed size
*********************************************
- An optional *layout* object associated with each view. A layout object decides the locations of subviews within their superview by accounting both of their inherent layout properties and a layout-specific set of properties.
***************************************************
* .-----+-----.
* *------. |(0,0)|(0,1)|
* | | *------. +-----+-----+
* '------* | | |(1,0)~(1,1)|
* '------* '-----------'
*
* absolute layout table layout
***************************************************
Layouting loosely follows the general principles shown below:
- Preferred values might propagate throughout the hierarchy unless limited elsewhere. When a conflict occurs, we prefer the values from lower-level (closer to the root) views.
- In layouts where views compete for a region (such as a table layout), the subviews' preferred values are used to determine how much area is given to each view.
- The result is undefined when constraints are unsatisfiable.
Layouting is done in two steps:
1. *Measurement*. The minimum/maximum/prefered sizes (called *measured sizes*) of each view are calculated in a bottom-up fashion.
2. *Arrangement*. The *actual size* of each view is calculated based on its measured sizes in a top-down fashion. The *actual position* of each view is also determined during this process.
## Rendering
!!! TODO: Yet to be written about...
Rendering
## Performance optimizations
The framework attempts to skip the re-layout of views that did not change since the previous layout operation. To accomplish this, each view maintains flags indicating which layout step should be repeated before the next rendering. These flags are set via the following routes:
- View implementations call `InvalidateInherentLayoutProps` when at least one of their inherent layout properties might have changed. Calling it provokes the measurement step.
- Layout implementations call `InvalidateLayout` when some of layout-specific layout properties might have changed. Calling it provokes both of the measurement and positioning steps.
A change in the actual size and position triggers re-rendering of the corresponding view. Conversely, re-rendering can be skipped if there is no change in the actual size or position.
Similarly, the framework also attempts to minimize the number of re-rendered views.
!!! TODO: Yet to be written about...
Optimization of rendering
## Mounted/unmounted events
Views can opt in to receive *mounted/unmounted events* whenever they are added to or removed from visible windows of a workspace. Views can make use of this opportunity, for example, to register and unregister event handlers that listen for external data sources (e.g., vertical sync). A more precise definition of these events is shown below:
- The *mounted* event occurs on a view when a new path from a visible window to the view was created, and before the layouting algorithm involving the view is executed.
- The *unmounted* event occurs on a view when it becomes no-longer reachable from any visible windows.
The process of tracking the changes in the mounted state of each view is called *visibility tracking*.
## Input event handling
The input event handling is integrated into the core components.
!!! Note: Design intention
For whatever the reason, it has been a common practice in popular GUI toolkits to integrate the mouse/touch event handling into their core functionalities. So, does this framework too. Although this leads to a bloat of the core components, implementing such a common feature as a separate, pluggable functionality, albeit being feasible, would make the framework harder to use in common use cases.
### Focus management
Each window maintains a variable pointing the currently focused view.
Each view has the following properties that control its behavior regarding the focus management:
- `AcceptsFocus` indicates the view's ability to accept a focus.
- `DeniesFocus` is used to prohibit its descendant views from receiving a focus at all. This is useful, for example, for disabling a group of form controls.
Each view can provide a set of event handlers to get notifications about the changes in the focus state of them and their descendants.
Changes in the view hierarchy can affect the focus state. There are many ways this can happen and tracking the focus state precisely and calling the appropriate event handlers would be both hard and inefficient (because each view has to traverse the view tree to calculate the relationship between itself). For this reason, calling the event handlers (done by `Workspace.UpdateFocus`) is deferred until the next update. This design has an additional advantage of enabling the event handler calling order to be consistent even in complicated cases.
The following lists the events (in a broader sense) that can observed from each view:
1. The changes in the values of `HasFocus` and `Focused`
2. `OnEnter`
3. `OnGotFocus`
4. `OnLostFocus`
5. `OnLeave`
The framework guarantees that the order in which these events are observed by each view exactly follows the regular expression `(1*(2((31*4)|1*)5)*)*`, except in the cases where some views interfere this process, for example, by manipulating the focus state from their event handlers.
!!! Tip
A regular expression is an alternative way to describe a state machine.
`OnEnter` and `OnLeave` have the corresponding events `Enter` and `Leave`, respectively, which can be added handlers to by the owner of a view. `OnGotFocus` and `OnLostFocus` are not exposed to the outside because doing so would defeat the encapsulation.
### Mouse and touch inputs
The framework handles both of mouse and touch inputs through a partly-unified interface.
A *mouse device* object represents either a system mouse device or otherwise virtualized mouse device (e.g., a virtual mouse device emulated by translating the touch input).
*Mouse capture*. A view can temporarily enter the state where all events generated by a specific mouse device. This state is commonly referred to as *mouse capture* or *mouse grab* by many existing GUI systems. Each view can have up to one capturing mouse device associated at any time point.
In contrast to most of the existing implementations, a mouse device object directly calls the target view's method in order to request to acquire a mouse capture on it. This request is rejected if there already exists a mouse device that is associated with the view. The mouse capture is released as soon as the user stops interacting a view using a mouse device (e.g., when all mouse buttons are released).
*Mouse emulation*. To reduce boiler codes and code duplication, touch events are translated to mouse events by default so the view implementors only have to care about mouse events. A view implementation can optionally claim the support for touch inputs (i.e., disable the mouse emulation) to receive the raw touch events.
*Hot tracking*. In contrast to the mouse move event which is managed by a mouse device implementation, hot tracking (triggering the *mouse enter/leave events*) is implemented by `Window`. The mouse enter/leave events (`OnMouseEnter`/`OnMouseLeave`) work similarly to the focus events (`OnEnter`/`OnLeave`).
****************************************
* |
* v
* .--------------.
* | Mouse motion |
* '-+----------+-'
* | If no |
* | buttons +-------+
* v are pressed |
* .--------------. |
* | Hot tracking | |
* '--------------' |
* v
* .------------------------.
* | Does SystemMouseDevice |
* | have a captured view? |
* '---+----------------+---'
* No | | Yes
* v |
* .-------+--------. |
* | Capture a view | |
* | temporarily | |
* '-------+--------' |
* | |
* v v
* .---------------------.
* | Trigger OnMouseMove |
* '---------------------'
*
****************************************
!!! WARNING: Non-public API
Although classes around the mouse input handling are designed in a way that encourages extensibility (except for the hot tracking part), they are currently not exposed to the outside of the framework as there is no strong incentive to stabilize its API.
### Keyboard inputs
*Key events* (*key down/up events*) received by the window are simply forwarded to the currently focused view. If no views are focused at the moment, it is forwarded to the root content view instead.
Key events implement *bubbling*; if a view does not handle a given key event, the event is automatically propagated to its superview. This process is repeated until one of the ancestor views indicates that the key event was handled (by setting `KeyEventArgs.Handled` to `true`), or it reaches the root content view.
!!! WARNING: Text input
Since key events disregard the influence of the user's system keyboard layout settings and input methods, key events cannot be used for text input.
They still can be used to implement, for example, caret navigation within a text box, however.
!!! TODO: Yet to be written about...
Hot keys, tab stop, and text input
## Known issues
- It is impossible to create a wrapping text block that adapts to the container width and changes its height dynamically.
<!-- Markdeep footer -->
<style class="fallback">body{visibility:hidden;white-space:pre;font-family:monospace}</style>
<script src="markdeep.min.js"></script>
<script src="https://casual-effects.com/markdeep/latest/markdeep.min.js"></script>
<script>window.alreadyProcessedMarkdeep||(document.body.style.visibility="visible")</script>