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