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 }