1 // Copyright 2022 Garrett D'Amore
2 //
3 // Distributed under the Boost Software License, Version 1.0.
4 // (See accompanying file LICENSE or https://www.boost.org/LICENSE_1_0.txt)
5 
6 /**
7  * Mouse demo (demonstrates input handling)
8  */
9 module mouse;
10 
11 import std.stdio;
12 import std.string;
13 import std.concurrency;
14 import core.stdc.stdlib;
15 
16 import dcell;
17 
18 void emitStr(Screen s, Coord pos, Style style, dstring str)
19 {
20 	while (str != "")
21 	{
22 		s[pos] = Cell(str[0], style, 1);
23 		str = str[1 .. $];
24 		pos.x++;
25 	}
26 }
27 
28 void order(ref int i1, ref int i2)
29 {
30 	if (i2 < i1)
31 	{
32 		int v = i1;
33 		i1 = i2;
34 		i2 = v;
35 	}
36 }
37 
38 void order(ref Coord c1, ref Coord c2)
39 {
40 	order(c1.x, c2.x);
41 	order(c1.y, c2.y);
42 }
43 
44 void drawBox(Screen s, Coord c1, Coord c2, Style style, dchar fill = ' ')
45 {
46 	order(c1, c2);
47 	Coord pos;
48 	for (pos.x = c1.x; pos.x <= c2.x; pos.x++)
49 	{
50 		s[Coord(pos.x, c1.y)] = Cell(Glyph.horizLine, style);
51 		s[Coord(pos.x, c2.y)] = Cell(Glyph.horizLine, style);
52 	}
53 	for (pos.y = c1.y + 1; pos.y < c2.y; pos.y++)
54 	{
55 		s[Coord(c1.x, pos.y)] = Cell(Glyph.vertLine, style);
56 		s[Coord(c2.x, pos.y)] = Cell(Glyph.vertLine, style);
57 	}
58 	if (c1.y != c2.y && c1.x != c2.x)
59 	{
60 		s[Coord(c1.x, c1.y)] = Cell(Glyph.upperLeftCorner, style);
61 		s[Coord(c2.x, c1.y)] = Cell(Glyph.upperRightCorner, style);
62 		s[Coord(c1.x, c2.y)] = Cell(Glyph.lowerLeftCorner, style);
63 		s[Coord(c2.x, c2.y)] = Cell(Glyph.lowerRightCorner, style);
64 	}
65 	for (pos.y = c1.y + 1; pos.y < c2.y; pos.y++)
66 	{
67 		for (pos.x = c1.x + 1; pos.x < c2.x; pos.x++)
68 		{
69 			s[pos] = Cell(fill, style);
70 		}
71 	}
72 }
73 
74 void drawSelect(Screen s, Coord c1, Coord c2, bool sel)
75 {
76 	order(c1, c2);
77 	Coord pos;
78 	for (pos.y = c1.y; pos.y < c2.y; pos.y++)
79 	{
80 		for (pos.x = c1.x; pos.x < c2.x; pos.x++)
81 		{
82 			if (sel)
83 				s[pos].style.attr |= Attr.reverse;
84 			else
85 				s[pos].style.attr &= ~Attr.reverse;
86 		}
87 	}
88 }
89 
90 void main()
91 {
92 	import std.stdio;
93 
94 	auto s = newScreen();
95 	assert(s !is null);
96 
97 	dstring posFmt = "Mouse: %d, %d";
98 	dstring btnFmt = "Buttons: %s";
99 	dstring keyFmt = "Keys: %s";
100 	dstring pasteFmt = "Paste: [%d] %s";
101 	dstring bStr = "";
102 	dstring kStr = "";
103 	dstring pStr = "";
104 	bool pasting = false;
105 
106 	s.start(thisTid());
107 	s.showCursor(Cursor.hidden);
108 	s.enableMouse(MouseEnable.all);
109 	s.enablePaste(true);
110 	Style white;
111 	white.fg = Color.midnightBlue;
112 	white.bg = Color.lightCoral;
113 	Coord mousePos = Coord(-1, -1);
114 	Coord oldTop = Coord(-1, -1);
115 	Coord oldBot = Coord(-1, -1);
116 	Coord bPos;
117 	int esc = 0;
118 	dchar lb;
119 
120 	for (;;)
121 	{
122 		auto ps = pStr;
123 		if (ps.length > 25)
124 			ps = "..." ~ ps[$ - 23 .. $];
125 		drawBox(s, Coord(1, 1), Coord(42, 7), white);
126 		Coord pos = Coord(3, 2);
127 		emitStr(s, pos, white, "Press ESC twice to exit, C to clear.");
128 		pos.y++;
129 		emitStr(s, pos, white, format(posFmt, mousePos.x, mousePos.y));
130 		pos.y++;
131 		emitStr(s, pos, white, format(btnFmt, bStr));
132 		pos.y++;
133 		emitStr(s, pos, white, format(keyFmt, kStr));
134 		pos.y++;
135 		emitStr(s, pos, white, format(pasteFmt, pStr.length, ps));
136 		s.show();
137 		bStr = "";
138 		Event ev;
139 		receive(
140 			(Event ee) { ev = ee; }
141 		);
142 		Style st;
143 		st.bg = Color.red;
144 		Style up;
145 		up.bg = Color.blue;
146 		up.fg = Color.black;
147 		// clear previous selection
148 		if (oldTop.x >= 0 && oldTop.y >= 0 && oldBot.x >= 0)
149 		{
150 			drawSelect(s, oldTop, oldBot, false);
151 		}
152 		pos = s.size();
153 		pos.x--;
154 		pos.y--;
155 
156 		switch (ev.type)
157 		{
158 		case EventType.resize:
159 			s.resize();
160 			s.sync();
161 			break;
162 		case EventType.paste:
163 			pStr = ev.paste.content;
164 			break;
165 		case EventType.key:
166 			pStr = "";
167 			s[pos] = Cell('K', st);
168 			switch (ev.key.key)
169 			{
170 			case Key.esc:
171 				esc++;
172 				if (esc > 1)
173 				{
174 					s.stop();
175 					exit(0);
176 				}
177 				break;
178 			case Key.ctrlL:
179 				s.sync();
180 				esc = 0;
181 				break;
182 			case Key.rune:
183 				if (ev.key.ch == 'C' || ev.key.ch == 'c')
184 				{
185 					s.clear();
186 				}
187 				esc = 0;
188 				s[pos] = Cell('R', st);
189 				break;
190 			default:
191 				break;
192 			}
193 			kStr = ev.key.toString();
194 			break;
195 		case EventType.mouse:
196 			if (ev.mouse.mod & Modifiers.shift)
197 				bStr ~= " S";
198 			if (ev.mouse.mod & Modifiers.ctrl)
199 				bStr ~= " C";
200 			if (ev.mouse.mod & Modifiers.alt)
201 				bStr ~= " A";
202 			if (ev.mouse.mod & Modifiers.meta)
203 				bStr ~= " M";
204 			if (ev.mouse.btn & Buttons.wheelUp)
205 				bStr ~= " WU";
206 			if (ev.mouse.btn & Buttons.wheelDown)
207 				bStr ~= " WD";
208 			if (ev.mouse.btn & Buttons.wheelLeft)
209 				bStr ~= " WL";
210 			if (ev.mouse.btn & Buttons.wheelRight)
211 				bStr ~= " WR";
212 			// we only want buttons, not wheel events
213 			auto button = ev.mouse.btn;
214 			button &= ~Buttons.wheelUp;
215 			button &= ~Buttons.wheelDown;
216 			button &= ~Buttons.wheelLeft;
217 			button &= ~Buttons.wheelRight;
218 			if (button == Buttons.none)
219 			{
220 				if (oldTop.x >= 0)
221 				{
222 					Style ns = up;
223 					ns.bg = (cast(Color)(lb - '0'));
224 					drawBox(s, oldTop, ev.mouse.pos, ns, lb);
225 					oldTop = Coord(-1, -1);
226 					oldBot = Coord(-1, -1);
227 				}
228 			}
229 			else if (oldTop.x < 0)
230 			{
231 				oldTop = ev.mouse.pos;
232 			}
233 			if (button & Buttons.button1)
234 				bStr ~= " B1";
235 			if (button & Buttons.button2)
236 				bStr ~= " B2";
237 			if (button & Buttons.button3)
238 				bStr ~= " B3";
239 			if (button & Buttons.button4)
240 				bStr ~= " B4";
241 			if (button & Buttons.button5)
242 				bStr ~= " B5";
243 			if (button & Buttons.button6)
244 				bStr ~= " B6";
245 			if (button & Buttons.button7)
246 				bStr ~= " B7";
247 			if (button & Buttons.button8)
248 				bStr ~= " B8";
249 			if (bStr.length > 0)
250 				lb = bStr[$ - 1];
251 			mousePos = ev.mouse.pos;
252 			if (button != Buttons.none)
253 				oldBot = ev.mouse.pos;
254 
255 			s[pos] = Cell('M', st);
256 			break;
257 		default:
258 			s[pos] = Cell('X', st);
259 			break;
260 		}
261 		if (oldTop.x >= 0 && oldBot.x >= 0)
262 		{
263 			drawSelect(s, oldTop, oldBot, true);
264 		}
265 	}
266 }