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 hr = new HorizontalLayout(t);
116 
117 		auto vlRgb = new class VerticalLayout {
118 			this() {
119 				super(hr);
120 			}
121 			override int maxWidth() { return 150; };
122 		};
123 
124 		auto vlHsl = new class VerticalLayout {
125 			this() {
126 				super(hr);
127 			}
128 			override int maxWidth() { return 150; };
129 		};
130 
131 		h = new LabeledLineEdit("Hue:", TextAlignment.Right, vlHsl);
132 		s = new LabeledLineEdit("Saturation:", TextAlignment.Right, vlHsl);
133 		l = new LabeledLineEdit("Lightness:", TextAlignment.Right, vlHsl);
134 
135 		css = new LabeledLineEdit("CSS:", TextAlignment.Right, vlHsl);
136 
137 		r = new LabeledLineEdit("Red:", TextAlignment.Right, vlRgb);
138 		g = new LabeledLineEdit("Green:", TextAlignment.Right, vlRgb);
139 		b = new LabeledLineEdit("Blue:", TextAlignment.Right, vlRgb);
140 		a = new LabeledLineEdit("Alpha:", TextAlignment.Right, vlRgb);
141 
142 		import std.conv;
143 		import std.format;
144 
145 		void updateCurrent() {
146 			r.content = to!string(current.r);
147 			g.content = to!string(current.g);
148 			b.content = to!string(current.b);
149 			a.content = to!string(current.a);
150 
151 			auto hsl = current.toHsl;
152 
153 			h.content = format("%0.2f", hsl[0]);
154 			s.content = format("%0.2f", hsl[1]);
155 			l.content = format("%0.2f", hsl[2]);
156 
157 			css.content = current.toCssString();
158 		}
159 
160 		updateCurrent();
161 
162 		r.addEventListener("focus", &r.selectAll);
163 		g.addEventListener("focus", &g.selectAll);
164 		b.addEventListener("focus", &b.selectAll);
165 		a.addEventListener("focus", &a.selectAll);
166 
167 		h.addEventListener("focus", &h.selectAll);
168 		s.addEventListener("focus", &s.selectAll);
169 		l.addEventListener("focus", &l.selectAll);
170 
171 		css.addEventListener("focus", &css.selectAll);
172 
173 		void convertFromHsl() {
174 			try {
175 				auto c = Color.fromHsl(h.content.to!double, s.content.to!double, l.content.to!double);
176 				c.a = a.content.to!ubyte;
177 				current = c;
178 				updateCurrent();
179 			} catch(Exception e) {
180 			}
181 		}
182 
183 		h.addEventListener("change", &convertFromHsl);
184 		s.addEventListener("change", &convertFromHsl);
185 		l.addEventListener("change", &convertFromHsl);
186 
187 		css.addEventListener("change", () {
188 			current = Color.fromString(css.content);
189 			updateCurrent();
190 		});
191 
192 		void helper(MouseEventBase event) {
193 			auto h = cast(double) event.clientX / hslImage.width * 360.0;
194 			auto s = 1.0 - (cast(double) event.clientY / hslImage.height * 1.0);
195 			auto l = this.l.content.to!double;
196 
197 			current = Color.fromHsl(h, s, l);
198 			current.a = a.content.to!ubyte;
199 
200 			updateCurrent();
201 
202 			auto e2 = new Event("change", this);
203 			e2.dispatch();
204 		}
205 
206 		if(hslImage !is null)
207 		wid.addEventListener((MouseDownEvent ev) { helper(ev); });
208 		if(hslImage !is null)
209 		wid.addEventListener((MouseMoveEvent event) {
210 			if(event.state & ModifierState.leftButtonDown)
211 				helper(event);
212 		});
213 
214 		this.addEventListener((KeyDownEvent event) {
215 			if(event.key == Key.Enter || event.key == Key.PadEnter)
216 				OK();
217 			if(event.key == Key.Escape)
218 				Cancel();
219 		});
220 
221 		this.addEventListener("change", {
222 			redraw();
223 		});
224 
225 		auto s = this;
226 		auto currentColorWidget = new class Widget {
227 			this() {
228 				super(s);
229 			}
230 
231 			override void paint(WidgetPainter painter) {
232 				auto c = currentColor();
233 
234 				auto c1 = alphaBlend(c, Color(64, 64, 64));
235 				auto c2 = alphaBlend(c, Color(192, 192, 192));
236 
237 				painter.outlineColor = c1;
238 				painter.fillColor = c1;
239 				painter.drawRectangle(Point(0, 0), this.width / 2, this.height / 2);
240 				painter.drawRectangle(Point(this.width / 2, this.height / 2), this.width / 2, this.height / 2);
241 
242 				painter.outlineColor = c2;
243 				painter.fillColor = c2;
244 				painter.drawRectangle(Point(this.width / 2, 0), this.width / 2, this.height / 2);
245 				painter.drawRectangle(Point(0, this.height / 2), this.width / 2, this.height / 2);
246 			}
247 		};
248 
249 		auto hl = new HorizontalLayout(this);
250 		auto cancelButton = new Button("Cancel", hl);
251 		auto okButton = new Button("OK", hl);
252 
253 		recomputeChildLayout(); // FIXME hack
254 
255 		cancelButton.addEventListener(EventType.triggered, &Cancel);
256 		okButton.addEventListener(EventType.triggered, &OK);
257 
258 		r.focus();
259 	}
260 
261 	LabeledLineEdit r;
262 	LabeledLineEdit g;
263 	LabeledLineEdit b;
264 	LabeledLineEdit a;
265 
266 	LabeledLineEdit h;
267 	LabeledLineEdit s;
268 	LabeledLineEdit l;
269 
270 	LabeledLineEdit css;
271 
272 	Color currentColor() {
273 		import std.conv;
274 		try {
275 			return Color(to!int(r.content), to!int(g.content), to!int(b.content), to!int(a.content));
276 		} catch(Exception e) {
277 			return Color.transparent;
278 		}
279 	}
280 
281 
282 	override void OK() {
283 		import std.conv;
284 		try {
285 			onOK(Color(to!int(r.content), to!int(g.content), to!int(b.content), to!int(a.content)));
286 			this.close();
287 		} catch(Exception e) {
288 			auto mb = new MessageBox("Bad value");
289 			mb.show();
290 		}
291 	}
292 }
293 
294