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