1 module awebview.gui.widgets.select;
2 
3 
4 import awebview.gui.html;
5 import awebview.gui.activity;
6 import awebview.gui.application;
7 import awebview.wrapper;
8 
9 import std..string;
10 import std.array;
11 import std.typecons;
12 
13 import carbon.nonametype;
14 
15 
16 interface ISelect
17 {
18     @property
19     string selected();
20 
21     @property
22     void selected(string s);
23 }
24 
25 
26 class Select(alias attrs = null)
27 : DefineSignals!(DeclareSignals!(TemplateHTMLElement!(HTMLElement, `<select id="%[id%]" ` ~ buildHTMLTagAttr(attrs) ~ `>%s</select>`),
28                                  "onChange", "onClick"),
29                 "onChange"),
30   ISelect
31 {
32     this(string id)
33     {
34         super(id, true);
35     }
36 
37 
38     @property
39     void showAllOptions(bool bShowAllOption)
40     {
41         if(!_bShowAllOptions){
42             bShowAllOption = true;
43             this.staticProps["size"] = this._opts.length;
44         }
45     }
46 
47 
48     @property
49     void size(uint s)
50     {
51         _bShowAllOptions = false;
52         this.staticProps["size"] = s;
53     }
54 
55 
56     final
57     @property
58     auto options() pure nothrow @safe @nogc
59     {
60         static struct Result
61         {
62             void opOpAssign(string op : "~")(string[2] kv)
63             {
64                 _this.appendOption(kv[0], kv[1]);
65             }
66 
67 
68             string[2] opIndex(size_t idx) { return _this._opts[idx]; }
69 
70             string opIndex(string str)
71             {
72                 foreach(i, ref e; _this._opts)
73                     if(e[0] == str)
74                         return e[1];
75 
76                 return _this._opts[$][1];  // throw error
77             }
78 
79             void opIndexAssign(string text, size_t idx)
80             {
81                 _this._opts[idx] = text;
82             }
83 
84             void opIndexAssign(string text, string str)
85             {
86                 foreach(i, ref e; _this._opts)
87                     if(e[0] == str)
88                         e[1] = str;
89 
90                 _this._opts[$] = str;   // throw error
91             }
92 
93           private:
94             Select!attrs _this;
95         }
96 
97 
98         return Result(this);
99     }
100 
101 
102     @property
103     void options(string[2][] options)
104     {
105         _opts = options;
106 
107         if(_bShowAllOptions)
108             this.staticProps["size"] = this._opts.length;
109     }
110 
111 
112     @property
113     void options(string[string] opts)
114     {
115         _opts.length = 0;
116         foreach(k, v; opts)
117             _opts ~= [k, v];
118 
119         if(_bShowAllOptions)
120             this.staticProps["size"] = this._opts.length;
121     }
122 
123 
124     @property
125     void appendOption(string key, string text)
126     {
127         _opts ~= [key, text];
128 
129         if(_bShowAllOptions)
130             this.staticProps["size"] = this._opts.length;
131     }
132 
133 
134     @property
135     uint selectedIndex()
136     {
137         return this["selectedIndex"].get!uint;
138     }
139 
140 
141     override
142     @property
143     string selected()
144     {
145         uint idx = this.selectedIndex;
146         return _opts[idx][0];
147     }
148 
149 
150     @property
151     string selectedText()
152     {
153         uint idx = this.selectedIndex;
154         return _opts[idx][1];
155     }
156 
157 
158     override
159     @property
160     void selected(string key)
161     {
162         foreach(i, e; _opts)
163             if(e[0] == key){
164                 this["selectedIndex"] = i;
165                 return;
166             }
167 
168         assert(0);
169     }
170 
171 
172     @property
173     void selectedIndex(size_t idx)
174     {
175         this["selectedIndex"] = idx;
176     }
177 
178 
179     void makeOption(size_t i, string key, string text, scope void delegate(const(char)[]) sink) const
180     {
181         sink.formattedWrite(`<option value="%s">%s</option>`, key, text);
182     }
183 
184 
185     HTMLPage makePopupMenu() @property
186     {
187         static class Result : HTMLPage
188         {
189             this(Select sel)
190             {
191                 _sel = sel;
192                 super(_sel.id ~ "_contextmenu_");
193 
194                 auto tag = new AssumeImplemented!(DeclDefSignals!(TagOnlyElement, "onClick"))(_sel.id);
195                 tag.doJSInitialize = false;
196                 tag.onClick.connect!"onClick"(this);
197                 _elems[tag.id] = tag;
198             }
199 
200 
201             override
202             inout(HTMLElement[string]) elements() inout @property { return _elems; }
203 
204 
205             override
206             string html() const @property
207             {
208                 string genRows()
209                 {
210                     auto app = appender!string();
211                     foreach(i, e; _sel._opts)
212                         app.formattedWrite(q{<tr onclick="%3$s.onClick(%1$s)"><td>%2$s</td></tr>}, i, e[1], _sel.id);
213                     return app.data;
214                 }
215 
216                 auto app = appender!string();
217                 app.put(`<html><head><title>select</title></head><style>html,body { width: 100%; } tr:hover { background-color: #007bff; color: #000000; min-width:100%; width:100%; }</style>`);
218                 app.put(`<body style="margin:0px; padding:0px;">`);
219                 app.formattedWrite(`<table id="%1$s" style="border-collapse: collapse; border: inset 1px black;">%2$s</table>`, _sel.id, genRows());
220                 app.put(`</body></html>`);
221                 return app.data;
222             }
223 
224 
225             void onClick(FiredContext ctx, WeakRef!(const(JSArrayCpp)) args)
226             {
227                 assert(args.length == 1);
228                 auto idx = args[0].get!uint;
229                 _sel.selectedIndex = idx;
230                 activity.detach();
231             }
232 
233 
234           private:
235             HTMLElement[string] _elems;
236             Select _sel;
237         }
238 
239         return new Result(this);
240     }
241 
242 
243     override
244     @property
245     string html()
246     {
247         auto str = super.html;
248 
249         auto app = appender!string();
250         foreach(i, e; _opts)
251             makeOption(i, e[0], e[1], delegate(const(char)[] cs){ app.put(cs); });
252 
253         return format(str, app.data);
254     }
255 
256 
257     override
258     void onClick(WeakRef!(const(JSArrayCpp)) args)
259     {
260         if(_popupMenu is null)
261             _popupMenu = makePopupMenu();
262 
263         auto rec = this.boundingClientRect;
264 
265         _popupMenu.elements[this.id].staticProps["style.width"] = rec.width;
266 
267         if(auto a = cast(SDLPopupActivity)activity){
268             a.popupChild(_popupMenu, rec.x, rec.y + rec.height, rec.width, 0);
269         }else{
270             auto a = cast(SDLApplication)application;
271             a.popupActivity.popupAtRel(_popupMenu, cast(SDLActivity)this.activity, rec.x, rec.y + rec.height, rec.width, 0);
272         }
273     }
274 
275 
276   private:
277     string[2][] _opts;
278     bool _bShowAllOptions;
279 
280     HTMLPage _popupMenu;
281 }