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 }