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