egra: splitted widgets to package with separate submodules
[iv.d.git] / egra / gui / widgets / root.d
blob2b477a749f8e5511552accdf396017302645e5dc
1 /*
2 * Simple Framebuffer Gfx/GUI lib
4 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
5 * Understanding is not required. Only obedience.
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation, version 3 of the License ONLY.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 module iv.egra.gui.widgets.root /*is aliced*/;
20 private:
22 import arsd.simpledisplay;
24 import iv.egra.gfx;
26 import iv.alice;
27 import iv.cmdcon;
28 import iv.dynstring;
29 import iv.strex;
30 import iv.utfutil;
32 import iv.egra.gui.subwindows;
33 import iv.egra.gui.widgets.base;
34 import iv.egra.gui.widgets.buttons : BaseButtonWidget;
37 // ////////////////////////////////////////////////////////////////////////// //
38 // this is root widget that is used for subwindow client area
39 public class RootWidget : Widget {
40 SubWindow owner;
41 uint mButtons; // used for grab
43 override EgraStyledClass getParent () nothrow @trusted @nogc { return owner; }
45 @disable this ();
47 this (SubWindow aowner) {
48 childDir = GxDir.Vert;
49 super(null);
50 owner = aowner;
51 if (aowner !is null) aowner.setRoot(this);
54 override bool isOwnerFocused () nothrow @safe @nogc {
55 if (owner is null) return false;
56 return owner.active;
59 override bool isOwnerInWindowList () nothrow @safe @nogc {
60 if (owner is null) return false;
61 return owner.inWindowList;
64 // can be called by the owner
65 // this also resets pressed mouse buttons
66 override void releaseGrab () {
67 mButtons = 0;
70 override GxPoint getGlobalOffset () nothrow @safe {
71 if (owner is null) return super.getGlobalOffset();
72 return GxPoint(owner.x0+owner.clientOffsetX, owner.y0+owner.clientOffsetY)+rect.pos;
75 void checkGrab () {
76 if (mButtons && !isOwnerFocused) releaseGrab();
79 bool hasGrab () {
80 checkGrab();
81 return (mButtons != 0);
84 enum EventPhase {
85 Sink,
86 Mine,
87 Bubble,
90 // dispatch event to this widget
91 // it implements sink/bubble model
92 // delegate is:
93 // bool delegate (Widget curr, Widget dest, EventPhase phase);
94 // returns "event eaten" flag (which stops propagation)
95 // `dest` must be a good child, and cannot be `null`
96 final bool dispatchTo(DG) (Widget dest, scope DG dg)
97 if (is(typeof((inout int=0) { DG dg = void; Widget w; EventPhase ph; immutable bool res = dg(w, w, ph); })))
99 // as we're walking from the bottom, first call it recursively, and then call delegate
100 bool sinkPhase() (Widget curr) {
101 if (curr is null) return false;
102 if (sinkPhase(curr.parent)) return true;
103 return dg(curr, dest, EventPhase.Sink);
106 if (dest is null || !isMyChild(dest)) return false;
108 if (sinkPhase(dest.parent)) return true;
109 if (dg(dest, dest, EventPhase.Mine)) return true;
110 for (Widget w = dest.parent; w !is null; w = w.parent) {
111 if (dg(w, dest, EventPhase.Bubble)) return true;
114 return false;
117 void doShiftTab () {
118 Widget pf = null;
119 Widget fc = focusedWidget;
120 foreach (Widget w; allVisualDepth) {
121 if (w is fc) break;
122 if (w.canAcceptFocus()) pf = w;
125 if (pf is null) {
126 // find last
127 foreach (Widget w; allVisualDepth) {
128 if (w.canAcceptFocus()) pf = w;
132 if (pf !is null) pf.focus();
135 void doTab () {
136 Widget fc = focusedWidget;
137 bool seenFC = (fc is null);
138 Widget nf = null;
139 foreach (Widget w; allVisualDepth) {
140 if (!seenFC) {
141 seenFC = (w is fc);
142 } else {
143 if (w.canAcceptFocus()) { nf = w; break; }
147 if (nf is null) {
148 // find first
149 foreach (Widget w; allVisualDepth) {
150 if (w.canAcceptFocus()) { nf = w; break; }
154 if (nf !is null) nf.focus();
157 override bool onKeyBubble (Widget dest, KeyEvent event) {
158 if (event.pressed) {
159 if (event == "Tab" || event == "C-Tab") { doTab(); return true; }
160 if (event == "S-Tab" || event == "C-S-Tab") { doShiftTab(); return true; }
162 Widget def;
163 immutable bool isDefAccept = (event == "Enter");
164 immutable bool isDefCancel = (event == "Escape");
166 // check hotkeys
167 Widget hk = null;
168 foreach (Widget w; allVisualDepth) {
169 if (w.isMyHotkey(event) && w.hotkeyActivated()) {
170 def = null;
171 hk = w;
172 break;
174 if (def is null) {
175 if (isDefAccept || isDefCancel) {
176 if (auto btn = cast(BaseButtonWidget)w) {
177 if ((isDefAccept && btn.deftype == BaseButtonWidget.Default.Accept) ||
178 (isDefCancel && btn.deftype == BaseButtonWidget.Default.Cancel))
180 if (btn.canAcceptFocus()) def = w;
187 if (hk !is null) return true;
188 if (def !is null && def.hotkeyActivated()) return true;
191 return super.onKeyBubble(dest, event);
194 bool dispatchKey (KeyEvent event) {
195 checkGrab();
196 return dispatchTo(focusedWidget, delegate bool (Widget curr, Widget dest, EventPhase phase) {
197 if (curr.nonVisual) return false;
198 final switch (phase) {
199 case EventPhase.Sink: return curr.onKeySink(dest, event);
200 case EventPhase.Mine: return curr.onKey(event);
201 case EventPhase.Bubble: return curr.onKeyBubble(dest, event);
203 assert(0); // just in case
207 // this is quite complicated...
208 protected Widget getMouseDestination (in MouseEvent event) {
209 checkGrab();
211 // still has a grab?
212 if (mButtons) {
213 // if some mouse buttons are still down, route everything to the focused widget
214 // also, release a grab here if we can (grab flag is not used anywhere else)
215 if (event.type == MouseEventType.buttonReleased) mButtons &= ~cast(uint)event.button;
216 return focusedWidget;
219 Widget dest = childAt(event.x, event.y);
220 assert(dest !is null);
222 // if mouse button is pressed, and there were no pressed buttons before,
223 // find the child, and check if it can grab events
224 if (event.type == MouseEventType.buttonPressed) {
225 if (dest !is focusedWidget) dest.focus();
226 if (dest is focusedWidget) {
227 // this activates the grab
228 mButtons = cast(uint)event.button;
229 } else {
230 releaseGrab();
232 } else {
233 // release grab, if necessary (it shouldn't be necessary here, but just in case...)
234 if (mButtons && event.type == MouseEventType.buttonReleased) {
235 mButtons &= ~cast(uint)event.button;
239 // route to the proper child
240 return dest;
243 // mouse event coords should be relative to our rect
244 bool dispatchMouse (MouseEvent event) {
245 Widget dest = getMouseDestination(event);
246 assert(dest !is null);
247 // convert event to global
248 immutable GxRect grect = globalRect;
249 event.x += grect.pos.x;
250 event.y += grect.pos.y;
251 return dispatchTo(dest, delegate bool (Widget curr, Widget dest, EventPhase phase) {
252 if (curr.nonVisual) return false;
253 // fix coordinates
254 immutable GxRect wrect = curr.globalRect;
255 MouseEvent ev = event;
256 ev.x -= wrect.pos.x;
257 ev.y -= wrect.pos.y;
258 final switch (phase) {
259 case EventPhase.Sink: return curr.onMouseSink(dest, ev);
260 case EventPhase.Mine: return curr.onMouse(ev);
261 case EventPhase.Bubble: return curr.onMouseBubble(dest, ev);
263 assert(0); // just in case
267 bool dispatchChar (dchar ch) {
268 checkGrab();
269 return dispatchTo(focusedWidget, delegate bool (Widget curr, Widget dest, EventPhase phase) {
270 if (curr.nonVisual) return false;
271 final switch (phase) {
272 case EventPhase.Sink: return curr.onCharSink(dest, ch);
273 case EventPhase.Mine: return curr.onChar(ch);
274 case EventPhase.Bubble: return curr.onCharBubble(dest, ch);
276 assert(0); // just in case