]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/item/modalcontroller.qc
Header police
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / item / modalcontroller.qc
1 #include "modalcontroller.qh"
2 #ifndef ITEM_MODALCONTROLLER_H
3         #define ITEM_MODALCONTROLLER_H
4         #include "container.qc"
5         CLASS(ModalController, Container)
6                 METHOD(ModalController, resizeNotify, void(entity, vector, vector, vector, vector));
7                 METHOD(ModalController, draw, void(entity));
8                 METHOD(ModalController, showChild, void(entity, entity, vector, vector, float));
9                 METHOD(ModalController, hideChild, void(entity, entity, float));
10                 METHOD(ModalController, hideAll, void(entity, float));
11                 METHOD(ModalController, addItem, void(entity, entity, vector, vector, float));
12                 METHOD(ModalController, addTab, void(entity, entity, entity));
13
14                 METHOD(ModalController, initializeDialog, void(entity, entity));
15
16                 METHOD(ModalController, switchState, void(entity, entity, float, float));
17                 ATTRIB(ModalController, origin, vector, '0 0 0')
18                 ATTRIB(ModalController, size, vector, '0 0 0')
19                 ATTRIB(ModalController, previousButton, entity, NULL)
20                 ATTRIB(ModalController, fadedAlpha, float, 0.3)
21         ENDCLASS(ModalController)
22
23         .entity tabSelectingButton;
24         .vector origin;
25         .vector size;
26         void TabButton_Click(entity button, entity tab);         // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
27         void DialogOpenButton_Click(entity button, entity tab);  // assumes a button has set the above fields to its own absolute origin, its size, and the tab to activate
28         void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize);
29         void DialogCloseButton_Click(entity button, entity tab); // assumes a button has set the above fields to the tab to close
30 #endif
31
32 #ifdef IMPLEMENTATION
33
34 // modal dialog controller
35 // handles a stack of dialog elements
36 // each element can have one of the following states:
37 //   0: hidden (fading out)
38 //   1: visible (zooming in)
39 //   2: greyed out (inactive)
40 // While an animation is running, no item has focus. When an animation is done,
41 // the topmost item gets focus.
42 // The items are assumed to be added in overlapping order, that is, the lowest
43 // window must get added first.
44 //
45 // Possible uses:
46 // - to control a modal dialog:
47 //   - show modal dialog: me.showChild(me, childItem, buttonAbsOrigin, buttonAbsSize, 0) // childItem also gets focus
48 //   - dismiss modal dialog: me.hideChild(me, childItem, 0) // childItem fades out and relinquishes focus
49 //   - show first screen in m_show: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
50 // - to show a temporary dialog instead of the menu (teamselect): me.hideAll(me, 1); me.showChild(me, teamSelectDialog, '0 0 0', '0 0 0', 1);
51 // - as a tabbed dialog control:
52 //   - to initialize: me.hideAll(me, 1); me.showChild(me, me.firstChild, '0 0 0', '0 0 0', 1);
53 //   - to show a tab: me.hideChild(me, currentTab, 0); me.showChild(me, newTab, buttonAbsOrigin, buttonAbsSize, 0);
54
55         .vector ModalController_initialSize;
56         .vector ModalController_initialOrigin;
57         .vector ModalController_initialFontScale;
58         .float ModalController_initialAlpha;
59         .vector ModalController_buttonSize;
60         .vector ModalController_buttonOrigin;
61         .float ModalController_state;
62         .float ModalController_factor;
63         .entity ModalController_controllingButton;
64
65         void ModalController_initializeDialog(entity me, entity root)
66         {
67                 me.hideAll(me, 1);
68                 me.showChild(me, root, '0 0 0', '0 0 0', 1);  // someone else animates for us
69         }
70
71         void TabButton_Click(entity button, entity tab)
72         {
73                 if (tab.ModalController_state == 1) return;
74                 tab.parent.hideAll(tab.parent, 0);
75                 button.forcePressed = 1;
76                 tab.ModalController_controllingButton = button;
77                 tab.parent.showChild(tab.parent, tab, button.origin, button.size, 0);
78         }
79
80         void DialogOpenButton_Click(entity button, entity tab)
81         {
82                 DialogOpenButton_Click_withCoords(button, tab, button.origin, button.size);
83         }
84
85         void DialogOpenButton_Click_withCoords(entity button, entity tab, vector theOrigin, vector theSize)
86         {
87                 if (tab.ModalController_state) return;
88                 if (button) button.forcePressed = 1;
89                 if (tab.parent.focusedChild) tab.parent.focusedChild.saveFocus(tab.parent.focusedChild);
90                 tab.ModalController_controllingButton = button;
91                 tab.parent.showChild(tab.parent, tab, theOrigin, theSize, 0);
92         }
93
94         void DialogCloseButton_Click(entity button, entity tab)
95         {
96                 tab.parent.hideChild(tab.parent, tab, 0);
97         }
98
99         void ModalController_resizeNotify(entity me, vector relOrigin, vector relSize, vector absOrigin, vector absSize)
100         {
101                 me.resizeNotifyLie(me, relOrigin, relSize, absOrigin, absSize, ModalController_initialOrigin, ModalController_initialSize, ModalController_initialFontScale);
102         }
103
104         void ModalController_switchState(entity me, entity other, float state, float skipAnimation)
105         {
106                 float previousState;
107                 previousState = other.ModalController_state;
108                 if (state == previousState && !skipAnimation) return;
109                 other.ModalController_state = state;
110                 switch (state)
111                 {
112                         case 0:
113                                 other.ModalController_factor = 1 - other.Container_alpha / other.ModalController_initialAlpha;
114                                 // fading out
115                                 break;
116                         case 1:
117                                 other.ModalController_factor = other.Container_alpha / other.ModalController_initialAlpha;
118                                 if (previousState == 0 && !skipAnimation)
119                                 {
120                                         other.Container_origin = other.ModalController_buttonOrigin;
121                                         other.Container_size = other.ModalController_buttonSize;
122                                 }
123                                 // zooming in
124                                 break;
125                         case 2:
126                                 other.ModalController_factor = bound(0, (1 - other.Container_alpha / other.ModalController_initialAlpha) / me.fadedAlpha, 1);
127                                 // fading out halfway
128                                 break;
129                 }
130                 if (skipAnimation) other.ModalController_factor = 1;
131         }
132
133         void ModalController_draw(entity me)
134         {
135                 entity e;
136                 entity front;
137                 float animating;
138                 float f;  // animation factor
139                 float df; // animation step size
140                 float prevFactor, targetFactor;
141                 vector targetOrigin, targetSize;
142                 float targetAlpha;
143                 vector fs;
144                 animating = 0;
145
146                 front = NULL;
147                 for (e = me.firstChild; e; e = e.nextSibling)
148                         if (e.ModalController_state)
149                         {
150                                 if (front) me.switchState(me, front, 2, 0);
151                                 front = e;
152                         }
153                 if (front) me.switchState(me, front, 1, 0);
154
155                 df = frametime * 3;  // animation speed
156
157                 for (e = me.firstChild; e; e = e.nextSibling)
158                 {
159                         if (e.ModalController_state == 2)
160                         {
161                                 // fading out partially
162                                 targetOrigin = e.Container_origin; // stay as is
163                                 targetSize = e.Container_size;     // stay as is
164                                 targetAlpha = me.fadedAlpha * e.ModalController_initialAlpha;
165                         }
166                         else if (e.ModalController_state == 1)
167                         {
168                                 // zooming in
169                                 targetOrigin = e.ModalController_initialOrigin;
170                                 targetSize = e.ModalController_initialSize;
171                                 targetAlpha = e.ModalController_initialAlpha;
172                         }
173                         else
174                         {
175                                 // fading out
176                                 targetOrigin = e.Container_origin; // stay as is
177                                 targetSize = e.Container_size;     // stay as is
178                                 targetAlpha = 0;
179                         }
180
181                         f = (e.ModalController_factor = min(1, e.ModalController_factor + df));
182                         if (f == 1)
183                         {
184                                 prevFactor = 0;
185                                 targetFactor = 1;
186                                 e.Container_origin = targetOrigin;
187                                 e.Container_size = targetSize;
188                                 me.setAlphaOf(me, e, targetAlpha);
189                         }
190                         else
191                         {
192                                 prevFactor = (1 - f) / (1 - f + df);
193                                 if (!e.ModalController_state)  // optimize code and avoid precision errors
194                                 {
195                                         me.setAlphaOf(me, e, e.Container_alpha  * prevFactor);
196                                 }
197                                 else
198                                 {
199                                         animating = 1;
200                                         targetFactor = df / (1 - f + df);
201
202                                         if (e.ModalController_state == 1)
203                                         {
204                                                 e.Container_origin = e.Container_origin * prevFactor + targetOrigin * targetFactor;
205                                                 e.Container_size   = e.Container_size   * prevFactor + targetSize   * targetFactor;
206                                         }
207                                         me.setAlphaOf(me, e, e.Container_alpha  * prevFactor + targetAlpha  * targetFactor);
208                                 }
209                         }
210                         // assume: o == to * f_prev + X * (1 - f_prev)
211                         // make:   o' = to * f  + X * (1 - f)
212                         // -->
213                         // X == (o - to * f_prev) / (1 - f_prev)
214                         // o' = to * f + (o - to * f_prev) / (1 - f_prev) * (1 - f)
215                         // --> (maxima)
216                         // o' = (to * (f - f_prev) + o * (1 - f)) / (1 - f_prev)
217
218                         if (e.ModalController_state == 1)
219                         {
220                                 fs = globalToBoxSize(e.Container_size, e.ModalController_initialSize);
221                                 e.Container_fontscale_x = fs.x * e.ModalController_initialFontScale.x;
222                                 e.Container_fontscale_y = fs.y * e.ModalController_initialFontScale.y;
223                         }
224                 }
225
226                 if (animating || !me.focused) me.setFocus(me, NULL);
227                 else me.setFocus(me, front);
228                 SUPER(ModalController).draw(me);
229         }
230
231         void ModalController_addTab(entity me, entity other, entity tabButton)
232         {
233                 me.addItem(me, other, '0 0 0', '1 1 1', 1);
234                 tabButton.onClick = TabButton_Click;
235                 tabButton.onClickEntity = other;
236                 other.tabSelectingButton = tabButton;
237                 if (other == me.firstChild)
238                 {
239                         tabButton.forcePressed = 1;
240                         other.ModalController_controllingButton = tabButton;
241                         me.showChild(me, other, '0 0 0', '0 0 0', 1);
242                 }
243         }
244
245         void ModalController_addItem(entity me, entity other, vector theOrigin, vector theSize, float theAlpha)
246         {
247                 SUPER(ModalController).addItem(me, other, theOrigin, theSize, (other == me.firstChild) ? theAlpha : 0);
248                 other.ModalController_initialFontScale = other.Container_fontscale;
249                 other.ModalController_initialSize = other.Container_size;
250                 other.ModalController_initialOrigin = other.Container_origin;
251                 other.ModalController_initialAlpha = theAlpha;  // hope Container never modifies this
252                 if (other.ModalController_initialFontScale == '0 0 0') other.ModalController_initialFontScale = '1 1 0';
253         }
254
255         void ModalController_showChild(entity me, entity other, vector theOrigin, vector theSize, float skipAnimation)
256         {
257                 if (other.ModalController_state == 0 || skipAnimation)
258                 {
259                         me.setFocus(me, NULL);
260                         if (!skipAnimation)
261                         {
262                                 other.ModalController_buttonOrigin = globalToBox(theOrigin, me.origin, me.size);
263                                 other.ModalController_buttonSize = globalToBoxSize(theSize, me.size);
264                         }
265                         me.switchState(me, other, 1, skipAnimation);
266                 }  // zoom in from button (factor increases)
267         }
268
269         void ModalController_hideAll(entity me, float skipAnimation)
270         {
271                 entity e;
272                 for (e = me.firstChild; e; e = e.nextSibling)
273                         me.hideChild(me, e, skipAnimation);
274         }
275
276         void ModalController_hideChild(entity me, entity other, float skipAnimation)
277         {
278                 if (other.ModalController_state || skipAnimation)
279                 {
280                         me.setFocus(me, NULL);
281                         me.switchState(me, other, 0, skipAnimation);
282                         if (other.ModalController_controllingButton)
283                         {
284                                 other.ModalController_controllingButton.forcePressed = 0;
285                                 other.ModalController_controllingButton = NULL;
286                         }
287                 }  // just alpha fade out (factor increases and decreases alpha)
288         }
289 #endif