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