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