1 /++ 2 Displays a color-picker dialog box. On Windows, uses the standard system dialog you know from Paint. On X, uses a custom one with hsla and rgba support. 3 4 History: 5 Written April 2017. 6 7 Added to dub on December 9, 2021. 8 +/ 9 module arsd.minigui_addons.color_dialog; 10 11 import arsd.minigui; 12 13 static if(UsingWin32Widgets) 14 pragma(lib, "comdlg32"); 15 16 /++ 17 18 +/ 19 auto showColorDialog(Window owner, Color current, void delegate(Color choice) onOK, void delegate() onCancel = null) { 20 static if(UsingWin32Widgets) { 21 import core.sys.windows.windows; 22 static COLORREF[16] customColors; 23 CHOOSECOLOR cc; 24 cc.lStructSize = cc.sizeof; 25 cc.hwndOwner = owner ? owner.win.impl.hwnd : null; 26 cc.lpCustColors = cast(LPDWORD) customColors.ptr; 27 cc.rgbResult = RGB(current.r, current.g, current.b); 28 cc.Flags = CC_FULLOPEN | CC_RGBINIT; 29 if(ChooseColor(&cc)) { 30 onOK(Color(GetRValue(cc.rgbResult), GetGValue(cc.rgbResult), GetBValue(cc.rgbResult))); 31 } else { 32 if(onCancel) 33 onCancel(); 34 } 35 } else static if(UsingCustomWidgets) { 36 auto cpd = new ColorPickerDialog(current, onOK, owner); 37 cpd.show(); 38 return cpd; 39 } else static assert(0); 40 } 41 42 /* 43 Hue / Saturation picker 44 Lightness Picker 45 46 Text selections 47 48 Graphical representation 49 50 Cancel OK 51 */ 52 53 static if(UsingCustomWidgets) 54 class ColorPickerDialog : Dialog { 55 static arsd.simpledisplay.Sprite hslImage; 56 57 static bool canUseImage; 58 59 void delegate(Color) onOK; 60 61 this(Color current, void delegate(Color) onOK, Window owner) { 62 super(360, 460, "Color picker"); 63 64 this.onOK = onOK; 65 66 67 /* 68 statusBar.parts ~= new StatusBar.Part(140); 69 statusBar.parts ~= new StatusBar.Part(140); 70 statusBar.parts ~= new StatusBar.Part(140); 71 statusBar.parts ~= new StatusBar.Part(140); 72 this.addEventListener("mouseover", (Event ev) { 73 import std.conv; 74 this.statusBar.parts[2].content = to!string(ev.target.minHeight) ~ " - " ~ to!string(ev.target.maxHeight); 75 this.statusBar.parts[3].content = ev.target.toString(); 76 }); 77 */ 78 79 80 static if(UsingSimpledisplayX11) 81 // it is brutally slow over the network if we don't 82 // have xshm, so we've gotta do something else. 83 canUseImage = Image.impl.xshmAvailable; 84 else 85 canUseImage = true; 86 87 if(hslImage is null && canUseImage) { 88 auto img = new TrueColorImage(360, 255); 89 double h = 0.0, s = 1.0, l = 0.5; 90 foreach(y; 0 .. img.height) { 91 foreach(x; 0 .. img.width) { 92 img.imageData.colors[y * img.width + x] = Color.fromHsl(h,s,l); 93 h += 360.0 / img.width; 94 } 95 h = 0.0; 96 s -= 1.0 / img.height; 97 } 98 99 hslImage = new arsd.simpledisplay.Sprite(this.win, Image.fromMemoryImage(img)); 100 } 101 102 auto t = this; 103 104 auto wid = new class Widget { 105 this() { super(t); } 106 override int minHeight() { return hslImage ? hslImage.height : 4; } 107 override int maxHeight() { return hslImage ? hslImage.height : 4; } 108 override int marginBottom() { return 4; } 109 override void paint(WidgetPainter painter) { 110 if(hslImage) 111 hslImage.drawAt(painter, Point(0, 0)); 112 } 113 }; 114 115 auto hs = new HorizontalSlider(0, 1000, 50, t); 116 117 auto hr = new HorizontalLayout(t); 118 119 auto vlRgb = new VerticalLayout(180, hr); 120 auto vlHsl = new VerticalLayout(180, hr); 121 122 h = new LabeledLineEdit("Hue:", TextAlignment.Right, vlHsl); 123 s = new LabeledLineEdit("Saturation:", TextAlignment.Right, vlHsl); 124 l = new LabeledLineEdit("Lightness:", TextAlignment.Right, vlHsl); 125 126 css = new LabeledLineEdit("CSS:", TextAlignment.Right, vlHsl); 127 128 r = new LabeledLineEdit("Red:", TextAlignment.Right, vlRgb); 129 g = new LabeledLineEdit("Green:", TextAlignment.Right, vlRgb); 130 b = new LabeledLineEdit("Blue:", TextAlignment.Right, vlRgb); 131 a = new LabeledLineEdit("Alpha:", TextAlignment.Right, vlRgb); 132 133 import std.conv; 134 import std.format; 135 136 double[3] lastHsl; 137 138 void updateCurrent() { 139 r.content = to!string(current.r); 140 g.content = to!string(current.g); 141 b.content = to!string(current.b); 142 a.content = to!string(current.a); 143 144 auto hsl = current.toHsl; 145 if(hsl[2] == 0.0 || hsl[2] == 1.0) { 146 hsl[0 .. 2] = lastHsl[0 .. 2]; 147 } 148 149 h.content = format("%0.3f", hsl[0]); 150 s.content = format("%0.3f", hsl[1]); 151 l.content = format("%0.3f", hsl[2]); 152 153 hs.setPosition(cast(int) (hsl[2] * 1000)); 154 155 css.content = current.toCssString(); 156 lastHsl = hsl; 157 } 158 159 updateCurrent(); 160 161 r.addEventListener("focus", &r.selectAll); 162 g.addEventListener("focus", &g.selectAll); 163 b.addEventListener("focus", &b.selectAll); 164 a.addEventListener("focus", &a.selectAll); 165 166 h.addEventListener("focus", &h.selectAll); 167 s.addEventListener("focus", &s.selectAll); 168 l.addEventListener("focus", &l.selectAll); 169 170 css.addEventListener("focus", &css.selectAll); 171 172 void convertFromHsl() { 173 try { 174 auto c = Color.fromHsl(h.content.to!double, s.content.to!double, l.content.to!double); 175 c.a = a.content.to!ubyte; 176 current = c; 177 updateCurrent(); 178 } catch(Exception e) { 179 } 180 } 181 182 hs.addEventListener((ChangeEvent!int ce) { 183 // this should only change l, not hs 184 auto ch = h.content; 185 auto cs = s.content; 186 l.content = to!string(ce.value / 1000.0); 187 convertFromHsl(); 188 189 h.content = ch; 190 s.content = cs; 191 }); 192 193 194 h.addEventListener("change", &convertFromHsl); 195 s.addEventListener("change", &convertFromHsl); 196 l.addEventListener("change", &convertFromHsl); 197 198 css.addEventListener("change", () { 199 current = Color.fromString(css.content); 200 updateCurrent(); 201 }); 202 203 void helper(MouseEventBase event) { 204 try { 205 // this should ONLY actually change hue and saturation 206 207 auto h = cast(double) event.clientX / hslImage.width * 360.0; 208 auto s = 1.0 - (cast(double) event.clientY / hslImage.height * 1.0); 209 auto oldl = this.l.content; 210 auto oldhsp = hs.position; 211 auto l = this.l.content.to!double; 212 213 current = Color.fromHsl(h, s, l); 214 // import std.stdio; writeln(current.toHsl, " ", h, " ", s, " ", l); 215 current.a = a.content.to!ubyte; 216 217 updateCurrent(); 218 219 this.l.content = oldl; 220 hs.setPosition(oldhsp); 221 222 auto e2 = new Event("change", this); 223 e2.dispatch(); 224 } catch(Exception e) { 225 } 226 } 227 228 if(hslImage !is null) 229 wid.addEventListener((MouseDownEvent ev) { helper(ev); }); 230 231 if(hslImage !is null) 232 wid.addEventListener((MouseMoveEvent event) { 233 if(event.state & ModifierState.leftButtonDown) 234 helper(event); 235 }); 236 237 this.addEventListener((KeyDownEvent event) { 238 if(event.key == Key.Enter || event.key == Key.PadEnter) 239 OK(); 240 if(event.key == Key.Escape) 241 Cancel(); 242 }); 243 244 this.addEventListener("change", { 245 redraw(); 246 }); 247 248 auto s = this; 249 auto currentColorWidget = new class Widget { 250 this() { 251 super(s); 252 } 253 254 override void paint(WidgetPainter painter) { 255 auto c = currentColor(); 256 257 auto c1 = alphaBlend(c, Color(64, 64, 64)); 258 auto c2 = alphaBlend(c, Color(192, 192, 192)); 259 260 painter.outlineColor = c1; 261 painter.fillColor = c1; 262 painter.drawRectangle(Point(0, 0), this.width / 2, this.height / 2); 263 painter.drawRectangle(Point(this.width / 2, this.height / 2), this.width / 2, this.height / 2); 264 265 painter.outlineColor = c2; 266 painter.fillColor = c2; 267 painter.drawRectangle(Point(this.width / 2, 0), this.width / 2, this.height / 2); 268 painter.drawRectangle(Point(0, this.height / 2), this.width / 2, this.height / 2); 269 } 270 }; 271 272 auto hl = new HorizontalLayout(this); 273 auto cancelButton = new Button("Cancel", hl); 274 auto okButton = new Button("OK", hl); 275 276 recomputeChildLayout(); // FIXME hack 277 278 cancelButton.addEventListener(EventType.triggered, &Cancel); 279 okButton.addEventListener(EventType.triggered, &OK); 280 281 r.focus(); 282 } 283 284 LabeledLineEdit r; 285 LabeledLineEdit g; 286 LabeledLineEdit b; 287 LabeledLineEdit a; 288 289 LabeledLineEdit h; 290 LabeledLineEdit s; 291 LabeledLineEdit l; 292 293 LabeledLineEdit css; 294 295 Color currentColor() { 296 import std.conv; 297 try { 298 return Color(to!int(r.content), to!int(g.content), to!int(b.content), to!int(a.content)); 299 } catch(Exception e) { 300 return Color.transparent; 301 } 302 } 303 304 305 override void OK() { 306 import std.conv; 307 try { 308 onOK(Color(to!int(r.content), to!int(g.content), to!int(b.content), to!int(a.content))); 309 this.close(); 310 } catch(Exception e) { 311 auto mb = new MessageBox("Bad value"); 312 mb.show(); 313 } 314 } 315 } 316 317