├── .editorconfig ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docs ├── Lime.html ├── Lime │ ├── Drawables.html │ ├── Drawables │ │ ├── Circle.html │ │ ├── FilledRectangle.html │ │ ├── Pixels.html │ │ ├── Pixels │ │ │ └── Error.html │ │ ├── Rectangle.html │ │ └── Rectangle │ │ │ └── Type.html │ ├── Modules.html │ └── Modules │ │ ├── Cursor.html │ │ ├── Mouse.html │ │ ├── Mouse │ │ ├── Event.html │ │ └── Mode.html │ │ └── Window.html ├── css │ └── style.css ├── index.html ├── index.json ├── js │ └── doc.js ├── search-index.js └── toplevel.html ├── examples ├── README.md ├── image.cr ├── image.png ├── lights.cr ├── mouse.cr ├── pixels.cr └── shapes.cr ├── shard.yml ├── spec ├── drawables_spec.cr ├── image.png ├── lime_spec.cr ├── modules_spec.cr └── spec_helper.cr └── src ├── README.md ├── lime.cr └── lime ├── drawables.cr └── modules.cr /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*.cr] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | indent_style = space 8 | indent_size = 2 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /lib/ 2 | /bin/ 3 | /.shards/ 4 | *.dwarf 5 | 6 | # Libraries don't need dependency lock 7 | # Dependencies will be locked in applications that use them 8 | /shard.lock 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: crystal 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 r00ster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Docs](https://img.shields.io/badge/docs-available-brightgreen.svg)](https://r00ster91.github.io/lime/) 2 | 3 | # lime 4 | 5 | A library for drawing graphics on the console screen 6 | 7 | ## Features 8 | 9 | * Drawing of 10 | * text 11 | * lines 12 | * rectangles 13 | * filled rectangles 14 | * circles 15 | * PNG images 16 | * Management of the window 17 | * Controlling of the console cursor 18 | * Keyboard and mouse pointer input 19 | 20 | ## Installation 21 | 22 | Add the dependency to your `shard.yml`: 23 | 24 | ```yaml 25 | dependencies: 26 | lime: 27 | github: r00ster91/lime 28 | ``` 29 | 30 | Then run `shards install` 31 | 32 | ## Example 33 | 34 | Three lights flashing repeatedly in order: 35 | 36 | ```crystal 37 | require "lime" 38 | 39 | # Create the lights: 40 | # They are red, green and blue rectangles with doubled lines 41 | light1 = Rectangle.new( 42 | x: 2, y: 1, 43 | width: 5, height: 3, 44 | type: Double, color: :red 45 | ) 46 | light2 = Rectangle.new(8, 1, 5, 3, Double, :green) 47 | light3 = Rectangle.new(14, 1, 5, 3, Double, :blue) 48 | 49 | # The light that is currently on 50 | light = 1 51 | 52 | loop do 53 | if light == 3 54 | light = 1 55 | else 56 | light += 1 57 | end 58 | 59 | case light 60 | when 1 61 | light1.color = :light_red 62 | light2.color = :green 63 | light3.color = :blue 64 | when 2 65 | light1.color = :red 66 | light2.color = :light_green 67 | light3.color = :blue 68 | when 3 69 | light1.color = :red 70 | light2.color = :green 71 | light3.color = :light_blue 72 | end 73 | 74 | # Insert the lights into the buffer: 75 | light1.draw 76 | light2.draw 77 | light3.draw 78 | 79 | # Draw the content of the buffer to the screen: 80 | Lime.draw 81 | # Clear the buffer (not the screen) so we have room for the next frame: 82 | Lime.clear 83 | 84 | # You can use `Lime.loop` instead of a normal loop to skip the above two steps 85 | 86 | # A short delay: 87 | sleep 0.5 88 | end 89 | ``` 90 | 91 | In the top left corner we can see: 92 | 93 | ![lights](https://i.imgur.com/hDHDiJB.gif) 94 | 95 | See [`examples`](https://github.com/r00ster91/lime/tree/master/examples) for more examples. 96 | 97 | For technical information about lime, see [`src/README.md`](https://github.com/r00ster91/lime/tree/master/src/README.md). 98 | 99 | ## Contributing 100 | 101 | 1. Fork it () 102 | 2. Create your feature branch (`git checkout -b my-new-feature`) 103 | 3. Install required shards (`shards install`) 104 | 4. Make your changes 105 | 5. Format the code (`crystal tool format`) 106 | 6. Make sure the specs compile (`crystal spec -v`) 107 | 7. Commit your changes (`git commit -am 'Add some feature'`) 108 | 8. Push to the branch (`git push origin my-new-feature`) 109 | 9. Create a new Pull Request 110 | 111 | ## Contributors 112 | 113 | - [r00ster](https://github.com/r00ster91) - creator and maintainer 114 | -------------------------------------------------------------------------------- /docs/Lime/Drawables.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Drawables - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | module Lime::Drawables 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

Drawables that can be inserted into the buffer using the draw method of every drawable.

164 | 165 |

The drawables are available on the top-level by default.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/drawables.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 |
203 | 204 |
205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 |
215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /docs/Lime/Drawables/Circle.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Drawables::Circle - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | struct Lime::Drawables::Circle 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

Overview

164 | 165 |

A drawable circle.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/drawables.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 |

Constructors

195 | 205 | 206 | 207 | 208 | 209 | 210 |

Instance Method Summary

211 | 256 | 257 | 258 | 259 | 260 | 261 |
262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 |
294 | 295 | 296 |

Constructor Detail

297 | 298 |
299 |
300 | 301 | def self.new(x : Int32, y : Int32, radius : Int32, color : Colorize::Color = Colorize::ColorANSI::Default) 302 | 303 | # 304 |
305 | 306 |

Initializes a new Circle.

307 | 308 |
309 |
310 | 311 | [View source] 312 | 313 |
314 |
315 | 316 | 317 | 318 | 319 | 320 | 321 |

Instance Method Detail

322 | 323 |
324 |
325 | 326 | def color : Colorize::Color256 | Colorize::ColorANSI | Colorize::ColorRGB 327 | 328 | # 329 |
330 | 331 |
332 |
333 | 334 | [View source] 335 | 336 |
337 |
338 | 339 |
340 |
341 | 342 | def draw 343 | 344 | # 345 |
346 | 347 |

Inserts the circle into the buffer.

348 | 349 |

This method uses the midpoint circle algorithm.

350 | 351 |
352 |
353 | 354 | [View source] 355 | 356 |
357 |
358 | 359 |
360 |
361 | 362 | def radius : Int32 363 | 364 | # 365 |
366 | 367 |
368 |
369 | 370 | [View source] 371 | 372 |
373 |
374 | 375 |
376 |
377 | 378 | def radius=(radius) 379 | 380 | # 381 |
382 | 383 |
384 |
385 | 386 | [View source] 387 | 388 |
389 |
390 | 391 |
392 |
393 | 394 | def x : Int32 395 | 396 | # 397 |
398 | 399 |
400 |
401 | 402 | [View source] 403 | 404 |
405 |
406 | 407 |
408 |
409 | 410 | def x=(x) 411 | 412 | # 413 |
414 | 415 |
416 |
417 | 418 | [View source] 419 | 420 |
421 |
422 | 423 |
424 |
425 | 426 | def y : Int32 427 | 428 | # 429 |
430 | 431 |
432 |
433 | 434 | [View source] 435 | 436 |
437 |
438 | 439 |
440 |
441 | 442 | def y=(y) 443 | 444 | # 445 |
446 | 447 |
448 |
449 | 450 | [View source] 451 | 452 |
453 |
454 | 455 | 456 | 457 | 458 | 459 |
460 | 461 | 462 | 463 | -------------------------------------------------------------------------------- /docs/Lime/Drawables/Pixels.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Drawables::Pixels - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | struct Lime::Drawables::Pixels 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

Overview

164 | 165 |

A drawable sequence of pixels.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/drawables.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 |

Constructors

195 | 212 | 213 | 214 | 215 | 216 | 217 |

Instance Method Summary

218 | 265 | 266 | 267 | 268 | 269 | 270 |
271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 |
303 | 304 | 305 |

Constructor Detail

306 | 307 |
308 |
309 | 310 | def self.new(path : String, x : Int32, y : Int32) 311 | 312 | # 313 |
314 | 315 |

Initializes new Pixels from an image.

316 | 317 |

path must lead to an PNG-encoded image.

318 | 319 |
320 |
321 | 322 | [View source] 323 | 324 |
325 |
326 | 327 |
328 |
329 | 330 | def self.new(x : Int32, y : Int32, color_characters : String) 331 | 332 | # 333 |
334 | 335 |

Initializes new Pixels from a string.

336 | 337 |

Iterates through every character of color_characters and every time a color character 338 | is found, it's replaced with its color.

339 | 340 |

Available color characters are:

341 | 342 |
  • '1': default
  • '0': black
  • '9': dark gray
  • '6': light gray
  • 'w': white
  • 'r': red
  • 'g': green
  • 'b': blue
  • 'y': yellow
  • 'm': magenta
  • 'c': cyan
  • 'R': light red
  • 'G': light green
  • 'B': light blue
  • 'Y': light yellow
  • 'M': light magenta
  • 'C': light cyan
343 | 344 |

Comments are also allowed in the string.

345 | 346 |

Example string:

347 | 348 |
# A flower:
349 |  RRR
350 | RYYYR # Head
351 |  RRR
352 |   g
353 | G g G
354 | GGgGG # Stem
355 |  GgG
356 |   g
357 | 358 |

becomes:

359 | 360 |

flower

361 | 362 |

Raises Error when an invalid color character is found in color_characters.

363 | 364 |
365 |
366 | 367 | [View source] 368 | 369 |
370 |
371 | 372 | 373 | 374 | 375 | 376 | 377 |

Instance Method Detail

378 | 379 |
380 |
381 | 382 | def draw 383 | 384 | # 385 |
386 | 387 |

Inserts the pixels into the buffer.

388 | 389 |
390 |
391 | 392 | [View source] 393 | 394 |
395 |
396 | 397 |
398 |
399 | 400 | def height : Int32 401 | 402 | # 403 |
404 | 405 |
406 |
407 | 408 | [View source] 409 | 410 |
411 |
412 | 413 |
414 |
415 | 416 | def map(&block) 417 | 418 | # 419 |
420 | 421 |

Invokes the given block for each of the pixels, replacing the pixel with the pixel returned by the block. 422 | The block must return a Tuple(UInt8, UInt8, UInt8).

423 | 424 |
# Invert colors of an image:
425 | image.map { |pixel| {255u8 - pixel.red, 255u8 - pixel.green, 255u8 - pixel.blue} }
426 | 427 |
428 |
429 | 430 | [View source] 431 | 432 |
433 |
434 | 435 |
436 |
437 | 438 | def width : Int32 439 | 440 | # 441 |
442 | 443 |
444 |
445 | 446 | [View source] 447 | 448 |
449 |
450 | 451 |
452 |
453 | 454 | def x : Int32 455 | 456 | # 457 |
458 | 459 |
460 |
461 | 462 | [View source] 463 | 464 |
465 |
466 | 467 |
468 |
469 | 470 | def x=(x) 471 | 472 | # 473 |
474 | 475 |
476 |
477 | 478 | [View source] 479 | 480 |
481 |
482 | 483 |
484 |
485 | 486 | def y : Int32 487 | 488 | # 489 |
490 | 491 |
492 |
493 | 494 | [View source] 495 | 496 |
497 |
498 | 499 |
500 |
501 | 502 | def y=(y) 503 | 504 | # 505 |
506 | 507 |
508 |
509 | 510 | [View source] 511 | 512 |
513 |
514 | 515 | 516 | 517 | 518 | 519 |
520 | 521 | 522 | 523 | -------------------------------------------------------------------------------- /docs/Lime/Drawables/Pixels/Error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Drawables::Pixels::Error - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | class Lime::Drawables::Pixels::Error 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

Overview

164 | 165 |

Raised when an invalid color character is found in a pixel string.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/drawables.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 |
203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 |
235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 |
245 | 246 | 247 | 248 | -------------------------------------------------------------------------------- /docs/Lime/Drawables/Rectangle/Type.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Drawables::Rectangle::Type - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | enum Lime::Drawables::Rectangle::Type 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

The type of a Rectangle.

164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 |

Defined in:

179 | 180 | 181 | 182 | lime/drawables.cr 183 | 184 | 185 |
186 | 187 | 188 | 189 | 190 | 191 |

Enum Members

192 | 193 |
194 | 195 |
196 | Default = 0 197 |
198 | 199 |
200 |

The default rectangle type. It looks like this:

201 | 202 |
┌────┐
203 | │    │
204 | └────┘
205 |
206 | 207 | 208 |
209 | Double = 1 210 |
211 | 212 |
213 |

Like the default type but with doubled lines:

214 | 215 |
╔════╗
216 | ║    ║
217 | ╚════╝
218 |
219 | 220 | 221 |
222 | Round = 2 223 |
224 | 225 |
226 |

Like the default type but with round corners:

227 | 228 |
╭────╮
229 | │    │
230 | ╰────╯
231 |
232 | 233 | 234 |
235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 |

Instance Method Summary

243 | 261 | 262 | 263 | 264 | 265 | 266 |
267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 |
309 | 310 | 311 | 312 | 313 | 314 | 315 |

Instance Method Detail

316 | 317 |
318 |
319 | 320 | def default? 321 | 322 | # 323 |
324 | 325 |
326 |
327 | 328 | [View source] 329 | 330 |
331 |
332 | 333 |
334 |
335 | 336 | def double? 337 | 338 | # 339 |
340 | 341 |
342 |
343 | 344 | [View source] 345 | 346 |
347 |
348 | 349 |
350 |
351 | 352 | def round? 353 | 354 | # 355 |
356 | 357 |
358 |
359 | 360 | [View source] 361 | 362 |
363 |
364 | 365 | 366 | 367 | 368 | 369 |
370 | 371 | 372 | 373 | -------------------------------------------------------------------------------- /docs/Lime/Modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Modules - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | module Lime::Modules 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

Modules for interacting with various system functionalities.

164 | 165 |

The modules are available on the top-level by default.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/modules.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 |
203 | 204 |
205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 |
215 | 216 | 217 | 218 | -------------------------------------------------------------------------------- /docs/Lime/Modules/Cursor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Modules::Cursor - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | module Lime::Modules::Cursor 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

The cursor of the console.

164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |

Extended Modules

172 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 |

Defined in:

186 | 187 | 188 | 189 | lime/modules.cr 190 | 191 | 192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 |

Instance Method Summary

204 | 249 | 250 | 251 | 252 | 253 | 254 |
255 | 256 |
257 | 258 | 259 | 260 | 261 | 262 | 263 |

Instance Method Detail

264 | 265 |
266 |
267 | 268 | def move_down(cells = 1) 269 | 270 | # 271 |
272 | 273 |

Moves the cursor down by cells.

274 | 275 |
276 |
277 | 278 | [View source] 279 | 280 |
281 |
282 | 283 |
284 |
285 | 286 | def move_left(cells = 1) 287 | 288 | # 289 |
290 | 291 |

Moves the cursor left by cells.

292 | 293 |
294 |
295 | 296 | [View source] 297 | 298 |
299 |
300 | 301 |
302 |
303 | 304 | def move_right(cells = 1) 305 | 306 | # 307 |
308 | 309 |

Moves the cursor right by cells.

310 | 311 |
312 |
313 | 314 | [View source] 315 | 316 |
317 |
318 | 319 |
320 |
321 | 322 | def move_up(cells = 1) 323 | 324 | # 325 |
326 | 327 |

Moves the cursor up by cells.

328 | 329 |
330 |
331 | 332 | [View source] 333 | 334 |
335 |
336 | 337 |
338 |
339 | 340 | def position=(position : Tuple(Int32, Int32)) 341 | 342 | # 343 |
344 | 345 |

Sets the position of the cursor to position.

346 | 347 |
Cursor.position = {5, 5}
348 | 
349 | # The cursor is now at position 5, 5
350 | 351 |
352 |
353 | 354 | [View source] 355 | 356 |
357 |
358 | 359 |
360 |
361 | 362 | def visible=(visible : Bool) 363 | 364 | # 365 |
366 | 367 |

Shows or hides the cursor.

368 | 369 |
370 |
371 | 372 | [View source] 373 | 374 |
375 |
376 | 377 | 378 | 379 | 380 | 381 |
382 | 383 | 384 | 385 | -------------------------------------------------------------------------------- /docs/Lime/Modules/Mouse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Modules::Mouse - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | module Lime::Modules::Mouse 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

The mouse pointer of the system.

164 | 165 |

NOTE The default mouse event report mode is Mode::Off which won't report any mouse events. 166 | To set a mouse event report mode, use #mode=.

167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 |

Extended Modules

175 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 |

Defined in:

189 | 190 | 191 | 192 | lime/modules.cr 193 | 194 | 195 |
196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 |

Class Method Summary

205 | 222 | 223 | 224 | 225 |

Instance Method Summary

226 | 257 | 258 | 259 | 260 | 261 | 262 |
263 | 264 |
265 | 266 | 267 | 268 | 269 |

Class Method Detail

270 | 271 |
272 |
273 | 274 | def self.extended 275 | 276 | # 277 |
278 | 279 |

Returns true if the mouse event report range is extended, otherwise false.

280 | 281 |
282 |
283 | 284 | [View source] 285 | 286 |
287 |
288 | 289 |
290 |
291 | 292 | def self.mode : Mode 293 | 294 | # 295 |
296 | 297 |

Returns the current mouse event report mode.

298 | 299 |
300 |
301 | 302 | [View source] 303 | 304 |
305 |
306 | 307 | 308 | 309 | 310 |

Instance Method Detail

311 | 312 |
313 |
314 | 315 | def extend(bool = true) 316 | 317 | # 318 |
319 | 320 |

Extends the mouse event report range if bool is true, otherwise resets the range.

321 | 322 |

The default X10 mouse protocol doesn't support mouse event reporting with 323 | x, y coordinates greater than 94. 324 | This extends the range to be greater than 94 by using the 1006 SGR (Select Graphic Rendition) mouse protocol.

325 | 326 |
327 |
328 | 329 | [View source] 330 | 331 |
332 |
333 | 334 |
335 |
336 | 337 | def get : Event? 338 | 339 | # 340 |
341 | 342 |

Waits for input and returns Event if the input is a mouse event, 343 | nil if the input is not a mouse event or if .mode is Mode::Off.

344 | 345 |

NOTE Ctrl+C is caught by this method and will not be handled by the system.

346 | 347 |
348 |
349 | 350 | [View source] 351 | 352 |
353 |
354 | 355 |
356 |
357 | 358 | def mode=(mode : Mode) 359 | 360 | # 361 |
362 | 363 |

Sets the mouse event report mode to mode.

364 | 365 |
366 |
367 | 368 | [View source] 369 | 370 |
371 |
372 | 373 |
374 |
375 | 376 | def peek : Event? 377 | 378 | # 379 |
380 | 381 |

Returns the mouse event happening in the moment this method is called as Event, 382 | nil if the input is not a mouse event or if .mode is Mode::Off.

383 | 384 |

NOTE Ctrl+C can be caught by this method and will not be handled by the system.

385 | 386 |
387 |
388 | 389 | [View source] 390 | 391 |
392 |
393 | 394 | 395 | 396 | 397 | 398 |
399 | 400 | 401 | 402 | -------------------------------------------------------------------------------- /docs/Lime/Modules/Mouse/Event.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Modules::Mouse::Event - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | struct Lime::Modules::Mouse::Event 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 |

Overview

164 | 165 |

A mouse event.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/modules.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 | 194 |

Constructors

195 | 203 | 204 | 205 | 206 | 207 | 208 |

Instance Method Summary

209 | 246 | 247 | 248 | 249 | 250 | 251 |
252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 |
284 | 285 | 286 |

Constructor Detail

287 | 288 |
289 |
290 | 291 | def self.new(x : Int32, y : Int32, type : Symbol) 292 | 293 | # 294 |
295 | 296 |
297 |
298 | 299 | [View source] 300 | 301 |
302 |
303 | 304 | 305 | 306 | 307 | 308 | 309 |

Instance Method Detail

310 | 311 |
312 |
313 | 314 | def clone 315 | 316 | # 317 |
318 | 319 |
320 |
321 | 322 | [View source] 323 | 324 |
325 |
326 | 327 |
328 |
329 | 330 | def copy_with(x _x = @x, y _y = @y, type _type = @type) 331 | 332 | # 333 |
334 | 335 |
336 |
337 | 338 | [View source] 339 | 340 |
341 |
342 | 343 |
344 |
345 | 346 | def kind : Symbol 347 | 348 | # 349 |
350 | 351 |

Returns the kind of mouse event based on #type:

352 | 353 |
  • :release if a mouse button has been released.
  • :click if a mouse button has been pressed.
  • :wheel if the wheel has been scrolled.
  • :drag if mode is Mode::Drag or Mode::All and a mouse button has been pressed and the mouse has been moved.
  • :move if mode is Mode::All and the mouse has been moved.
354 | 355 |
356 |
357 | 358 | [View source] 359 | 360 |
361 |
362 | 363 |
364 |
365 | 366 | def type : Symbol 367 | 368 | # 369 |
370 | 371 |

Returns the type of mouse event:

372 | 373 |
  • :release if a mouse button has been released.
374 | 375 |
  • :left if the left mouse button has been pressed.
  • :wheel if the mouse wheel button has been pressed.
  • :right if the right mouse button has been pressed.
376 | 377 |
  • :wheel_up if the mouse wheel has been scrolled up.
  • :wheel_down if the mouse wheel has been scrolled down.
378 | 379 |

Only if mode is Mode::Drag or Mode::All:

380 | 381 |
  • :left_drag if the left mouse button has been pressed and the mouse has been moved.
  • :wheel_drag if the mouse wheel button has been pressed and the mouse has been moved.
  • :right_drag if the right mouse button has been pressed and the mouse has been moved.
382 | 383 |

Only if mode is Mode::All:

384 | 385 |
  • :move if the mouse has been moved.
386 | 387 |
388 |
389 | 390 | [View source] 391 | 392 |
393 |
394 | 395 |
396 |
397 | 398 | def x : Int32 399 | 400 | # 401 |
402 | 403 |
404 |
405 | 406 |
407 |
408 | 409 |
410 |
411 | 412 | def y : Int32 413 | 414 | # 415 |
416 | 417 |
418 |
419 | 420 |
421 |
422 | 423 | 424 | 425 | 426 | 427 |
428 | 429 | 430 | 431 | -------------------------------------------------------------------------------- /docs/Lime/Modules/Mouse/Mode.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Modules::Mouse::Mode - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | enum Lime::Modules::Mouse::Mode 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

The mouse event report mode.

164 | 165 |

The default mode is Off.

166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 |

Defined in:

181 | 182 | 183 | 184 | lime/modules.cr 185 | 186 | 187 |
188 | 189 | 190 | 191 | 192 | 193 |

Enum Members

194 | 195 |
196 | 197 |
198 | Off = 0 199 |
200 | 201 |
202 |

Reports no mouse events.

203 |
204 | 205 | 206 |
207 | Click = 1 208 |
209 | 210 |
211 |

Reports only mouse click events.

212 |
213 | 214 | 215 |
216 | Drag = 2 217 |
218 | 219 |
220 |

Reports only mouse drag events.

221 |
222 | 223 | 224 |
225 | All = 3 226 |
227 | 228 |
229 |

Reports all mouse events: movements, clicks and drags.

230 |
231 | 232 | 233 |
234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 |

Instance Method Summary

242 | 265 | 266 | 267 | 268 | 269 | 270 |
271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 |
313 | 314 | 315 | 316 | 317 | 318 | 319 |

Instance Method Detail

320 | 321 |
322 |
323 | 324 | def all? 325 | 326 | # 327 |
328 | 329 |
330 |
331 | 332 | [View source] 333 | 334 |
335 |
336 | 337 |
338 |
339 | 340 | def click? 341 | 342 | # 343 |
344 | 345 |
346 |
347 | 348 | [View source] 349 | 350 |
351 |
352 | 353 |
354 |
355 | 356 | def drag? 357 | 358 | # 359 |
360 | 361 |
362 |
363 | 364 | [View source] 365 | 366 |
367 |
368 | 369 |
370 |
371 | 372 | def off? 373 | 374 | # 375 |
376 | 377 |
378 |
379 | 380 | [View source] 381 | 382 |
383 |
384 | 385 | 386 | 387 | 388 | 389 |
390 | 391 | 392 | 393 | -------------------------------------------------------------------------------- /docs/Lime/Modules/Window.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Lime::Modules::Window - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | module Lime::Modules::Window 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 |

Overview

162 | 163 |

The window of the console.

164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 |

Extended Modules

172 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 |

Defined in:

186 | 187 | 188 | 189 | lime/modules.cr 190 | 191 | 192 |
193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 |

Class Method Summary

202 | 247 | 248 | 249 | 250 |

Instance Method Summary

251 | 275 | 276 | 277 | 278 | 279 | 280 |
281 | 282 |
283 | 284 | 285 | 286 | 287 |

Class Method Detail

288 | 289 |
290 |
291 | 292 | def self.height : Int32 293 | 294 | # 295 |
296 | 297 |

Returns the height of the window in cells, zero-based.

298 | 299 |
300 |
301 | 302 | [View source] 303 | 304 |
305 |
306 | 307 |
308 |
309 | 310 | def self.height_cells : Int32 311 | 312 | # 313 |
314 | 315 |

Returns the width of the window in cells, one-based.

316 | 317 |
318 |
319 | 320 | [View source] 321 | 322 |
323 |
324 | 325 |
326 |
327 | 328 | def self.height_pixels : Int32 329 | 330 | # 331 |
332 | 333 |

Returns the height of the window in pixels.

334 | 335 |
336 |
337 | 338 | [View source] 339 | 340 |
341 |
342 | 343 |
344 |
345 | 346 | def self.width : Int32 347 | 348 | # 349 |
350 | 351 |

Returns the width of the window in cells, zero-based.

352 | 353 |
354 |
355 | 356 | [View source] 357 | 358 |
359 |
360 | 361 |
362 |
363 | 364 | def self.width_cells : Int32 365 | 366 | # 367 |
368 | 369 |

Returns the height of the window in cells, one-based.

370 | 371 |
372 |
373 | 374 | [View source] 375 | 376 |
377 |
378 | 379 |
380 |
381 | 382 | def self.width_pixels 383 | 384 | # 385 |
386 | 387 |

Returns the width of the window in pixels.

388 | 389 |
390 |
391 | 392 | [View source] 393 | 394 |
395 |
396 | 397 | 398 | 399 | 400 |

Instance Method Detail

401 | 402 |
403 |
404 | 405 | def scrollbar=(scrollbar : Bool) 406 | 407 | # 408 |
409 | 410 |

Disables or enables the alternative screen buffer for the console which doesn't have a scrollbar.

411 | 412 |
413 |
414 | 415 | [View source] 416 | 417 |
418 |
419 | 420 |
421 |
422 | 423 | def title=(title) 424 | 425 | # 426 |
427 | 428 |

Sets the title of the window to title.

429 | 430 |
431 |
432 | 433 | [View source] 434 | 435 |
436 |
437 | 438 |
439 |
440 | 441 | def update 442 | 443 | # 444 |
445 | 446 | 448 | 449 |
450 |
451 | 452 | [View source] 453 | 454 |
455 |
456 | 457 | 458 | 459 | 460 | 461 |
462 | 463 | 464 | 465 | -------------------------------------------------------------------------------- /docs/css/style.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | background: #FFFFFF; 3 | position: relative; 4 | margin: 0; 5 | padding: 0; 6 | width: 100%; 7 | height: 100%; 8 | overflow: hidden; 9 | } 10 | 11 | body { 12 | font-family: "Avenir", "Tahoma", "Lucida Sans", "Lucida Grande", Verdana, Arial, sans-serif; 13 | color: #333; 14 | line-height: 1.5; 15 | } 16 | 17 | a { 18 | color: #263F6C; 19 | } 20 | 21 | a:visited { 22 | color: #112750; 23 | } 24 | 25 | h1, h2, h3, h4, h5, h6 { 26 | margin: 35px 0 25px; 27 | color: #444444; 28 | } 29 | 30 | h1.type-name { 31 | color: #47266E; 32 | margin: 20px 0 30px; 33 | background-color: #F8F8F8; 34 | padding: 10px 12px; 35 | border: 1px solid #EBEBEB; 36 | border-radius: 2px; 37 | } 38 | 39 | h2 { 40 | border-bottom: 1px solid #E6E6E6; 41 | padding-bottom: 5px; 42 | } 43 | 44 | body { 45 | display: flex; 46 | } 47 | 48 | .sidebar, .main-content { 49 | overflow: auto; 50 | } 51 | 52 | .sidebar { 53 | width: 30em; 54 | color: #F8F4FD; 55 | background-color: #2E1052; 56 | padding: 0 0 30px; 57 | box-shadow: inset -3px 0 4px rgba(0,0,0,.35); 58 | line-height: 1.2; 59 | } 60 | 61 | .sidebar .search-box { 62 | padding: 8px 9px; 63 | } 64 | 65 | .sidebar input { 66 | display: block; 67 | box-sizing: border-box; 68 | margin: 0; 69 | padding: 5px; 70 | font: inherit; 71 | font-family: inherit; 72 | line-height: 1.2; 73 | width: 100%; 74 | border: 0; 75 | outline: 0; 76 | border-radius: 2px; 77 | box-shadow: 0px 3px 5px rgba(0,0,0,.25); 78 | transition: box-shadow .12s; 79 | } 80 | 81 | .sidebar input:focus { 82 | box-shadow: 0px 5px 6px rgba(0,0,0,.5); 83 | } 84 | 85 | .sidebar input::-webkit-input-placeholder { /* Chrome/Opera/Safari */ 86 | color: #C8C8C8; 87 | font-size: 14px; 88 | text-indent: 2px; 89 | } 90 | 91 | .sidebar input::-moz-placeholder { /* Firefox 19+ */ 92 | color: #C8C8C8; 93 | font-size: 14px; 94 | text-indent: 2px; 95 | } 96 | 97 | .sidebar input:-ms-input-placeholder { /* IE 10+ */ 98 | color: #C8C8C8; 99 | font-size: 14px; 100 | text-indent: 2px; 101 | } 102 | 103 | .sidebar input:-moz-placeholder { /* Firefox 18- */ 104 | color: #C8C8C8; 105 | font-size: 14px; 106 | text-indent: 2px; 107 | } 108 | 109 | .sidebar ul { 110 | margin: 0; 111 | padding: 0; 112 | list-style: none outside; 113 | } 114 | 115 | .sidebar li { 116 | display: block; 117 | position: relative; 118 | } 119 | 120 | .types-list li.hide { 121 | display: none; 122 | } 123 | 124 | .sidebar a { 125 | text-decoration: none; 126 | color: inherit; 127 | transition: color .14s; 128 | } 129 | .types-list a { 130 | display: block; 131 | padding: 5px 15px 5px 30px; 132 | } 133 | 134 | .types-list { 135 | display: block; 136 | } 137 | 138 | .sidebar a:focus { 139 | outline: 1px solid #D1B7F1; 140 | } 141 | 142 | .types-list a { 143 | padding: 5px 15px 5px 30px; 144 | } 145 | 146 | .sidebar .current > a, 147 | .sidebar a:hover { 148 | color: #866BA6; 149 | } 150 | 151 | .repository-links { 152 | padding: 5px 15px 5px 30px; 153 | } 154 | 155 | .types-list li ul { 156 | overflow: hidden; 157 | height: 0; 158 | max-height: 0; 159 | transition: 1s ease-in-out; 160 | } 161 | 162 | .types-list li.parent { 163 | padding-left: 30px; 164 | } 165 | 166 | .types-list li.parent::before { 167 | box-sizing: border-box; 168 | content: "▼"; 169 | display: block; 170 | width: 30px; 171 | height: 30px; 172 | position: absolute; 173 | top: 0; 174 | left: 0; 175 | text-align: center; 176 | color: white; 177 | font-size: 8px; 178 | line-height: 30px; 179 | transform: rotateZ(-90deg); 180 | cursor: pointer; 181 | transition: .2s linear; 182 | } 183 | 184 | 185 | .types-list li.parent > a { 186 | padding-left: 0; 187 | } 188 | 189 | .types-list li.parent.open::before { 190 | transform: rotateZ(0); 191 | } 192 | 193 | .types-list li.open > ul { 194 | height: auto; 195 | max-height: 1000em; 196 | } 197 | 198 | .main-content { 199 | padding: 0 30px 30px 30px; 200 | width: 100%; 201 | } 202 | 203 | .kind { 204 | font-size: 60%; 205 | color: #866BA6; 206 | } 207 | 208 | .superclass-hierarchy { 209 | margin: -15px 0 30px 0; 210 | padding: 0; 211 | list-style: none outside; 212 | font-size: 80%; 213 | } 214 | 215 | .superclass-hierarchy .superclass { 216 | display: inline-block; 217 | margin: 0 7px 0 0; 218 | padding: 0; 219 | } 220 | 221 | .superclass-hierarchy .superclass + .superclass::before { 222 | content: "<"; 223 | margin-right: 7px; 224 | } 225 | 226 | .other-types-list li { 227 | display: inline-block; 228 | } 229 | 230 | .other-types-list, 231 | .list-summary { 232 | margin: 0 0 30px 0; 233 | padding: 0; 234 | list-style: none outside; 235 | } 236 | 237 | .entry-const { 238 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 239 | } 240 | 241 | .entry-const code { 242 | white-space: pre-wrap; 243 | } 244 | 245 | .entry-summary { 246 | padding-bottom: 4px; 247 | } 248 | 249 | .superclass-hierarchy .superclass a, 250 | .other-type a, 251 | .entry-summary .signature { 252 | padding: 4px 8px; 253 | margin-bottom: 4px; 254 | display: inline-block; 255 | background-color: #f8f8f8; 256 | color: #47266E; 257 | border: 1px solid #f0f0f0; 258 | text-decoration: none; 259 | border-radius: 3px; 260 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 261 | transition: background .15s, border-color .15s; 262 | } 263 | 264 | .superclass-hierarchy .superclass a:hover, 265 | .other-type a:hover, 266 | .entry-summary .signature:hover { 267 | background: #D5CAE3; 268 | border-color: #624288; 269 | } 270 | 271 | .entry-summary .summary { 272 | padding-left: 32px; 273 | } 274 | 275 | .entry-summary .summary p { 276 | margin: 12px 0 16px; 277 | } 278 | 279 | .entry-summary a { 280 | text-decoration: none; 281 | } 282 | 283 | .entry-detail { 284 | padding: 30px 0; 285 | } 286 | 287 | .entry-detail .signature { 288 | position: relative; 289 | padding: 5px 15px; 290 | margin-bottom: 10px; 291 | display: block; 292 | border-radius: 5px; 293 | background-color: #f8f8f8; 294 | color: #47266E; 295 | border: 1px solid #f0f0f0; 296 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 297 | transition: .2s ease-in-out; 298 | } 299 | 300 | .entry-detail:target .signature { 301 | background-color: #D5CAE3; 302 | border: 1px solid #624288; 303 | } 304 | 305 | .entry-detail .signature .method-permalink { 306 | position: absolute; 307 | top: 0; 308 | left: -35px; 309 | padding: 5px 15px; 310 | text-decoration: none; 311 | font-weight: bold; 312 | color: #624288; 313 | opacity: .4; 314 | transition: opacity .2s; 315 | } 316 | 317 | .entry-detail .signature .method-permalink:hover { 318 | opacity: 1; 319 | } 320 | 321 | .entry-detail:target .signature .method-permalink { 322 | opacity: 1; 323 | } 324 | 325 | .methods-inherited { 326 | padding-right: 10%; 327 | line-height: 1.5em; 328 | } 329 | 330 | .methods-inherited h3 { 331 | margin-bottom: 4px; 332 | } 333 | 334 | .methods-inherited a { 335 | display: inline-block; 336 | text-decoration: none; 337 | color: #47266E; 338 | } 339 | 340 | .methods-inherited a:hover { 341 | text-decoration: underline; 342 | color: #6C518B; 343 | } 344 | 345 | .methods-inherited .tooltip>span { 346 | background: #D5CAE3; 347 | padding: 4px 8px; 348 | border-radius: 3px; 349 | margin: -4px -8px; 350 | } 351 | 352 | .methods-inherited .tooltip * { 353 | color: #47266E; 354 | } 355 | 356 | pre { 357 | padding: 10px 20px; 358 | margin-top: 4px; 359 | border-radius: 3px; 360 | line-height: 1.45; 361 | overflow: auto; 362 | color: #333; 363 | background: #fdfdfd; 364 | font-size: 14px; 365 | border: 1px solid #eee; 366 | } 367 | 368 | code { 369 | font-family: Menlo, Monaco, Consolas, 'Courier New', Courier, monospace; 370 | } 371 | 372 | :not(pre) > code { 373 | background-color: rgba(40,35,30,0.05); 374 | padding: 0.2em 0.4em; 375 | font-size: 85%; 376 | border-radius: 3px; 377 | } 378 | 379 | span.flag { 380 | padding: 2px 4px 1px; 381 | border-radius: 3px; 382 | margin-right: 3px; 383 | font-size: 11px; 384 | border: 1px solid transparent; 385 | } 386 | 387 | span.flag.orange { 388 | background-color: #EE8737; 389 | color: #FCEBDD; 390 | border-color: #EB7317; 391 | } 392 | 393 | span.flag.yellow { 394 | background-color: #E4B91C; 395 | color: #FCF8E8; 396 | border-color: #B69115; 397 | } 398 | 399 | span.flag.green { 400 | background-color: #469C14; 401 | color: #E2F9D3; 402 | border-color: #34700E; 403 | } 404 | 405 | span.flag.red { 406 | background-color: #BF1919; 407 | color: #F9ECEC; 408 | border-color: #822C2C; 409 | } 410 | 411 | span.flag.purple { 412 | background-color: #2E1052; 413 | color: #ECE1F9; 414 | border-color: #1F0B37; 415 | } 416 | 417 | .tooltip>span { 418 | position: absolute; 419 | opacity: 0; 420 | display: none; 421 | pointer-events: none; 422 | } 423 | 424 | .tooltip:hover>span { 425 | display: inline-block; 426 | opacity: 1; 427 | } 428 | 429 | .c { 430 | color: #969896; 431 | } 432 | 433 | .n { 434 | color: #0086b3; 435 | } 436 | 437 | .t { 438 | color: #0086b3; 439 | } 440 | 441 | .s { 442 | color: #183691; 443 | } 444 | 445 | .i { 446 | color: #7f5030; 447 | } 448 | 449 | .k { 450 | color: #a71d5d; 451 | } 452 | 453 | .o { 454 | color: #a71d5d; 455 | } 456 | 457 | .m { 458 | color: #795da3; 459 | } 460 | 461 | .hidden { 462 | display: none; 463 | } 464 | .search-results { 465 | font-size: 90%; 466 | line-height: 1.3; 467 | } 468 | 469 | .search-results mark { 470 | color: inherit; 471 | background: transparent; 472 | font-weight: bold; 473 | } 474 | .search-result { 475 | padding: 5px 8px 5px 5px; 476 | cursor: pointer; 477 | border-left: 5px solid transparent; 478 | transform: translateX(-3px); 479 | transition: all .2s, background-color 0s, border .02s; 480 | min-height: 3.2em; 481 | } 482 | .search-result.current { 483 | border-left-color: #ddd; 484 | background-color: rgba(200,200,200,0.4); 485 | transform: translateX(0); 486 | transition: all .2s, background-color .5s, border 0s; 487 | } 488 | .search-result.current:hover, 489 | .search-result.current:focus { 490 | border-left-color: #866BA6; 491 | } 492 | .search-result:not(.current):nth-child(2n) { 493 | background-color: rgba(255,255,255,.06); 494 | } 495 | .search-result__title { 496 | font-size: 105%; 497 | word-break: break-all; 498 | line-height: 1.1; 499 | padding: 3px 0; 500 | } 501 | .search-result__title strong { 502 | font-weight: normal; 503 | } 504 | .search-results .search-result__title > a { 505 | padding: 0; 506 | display: block; 507 | } 508 | .search-result__title > a > .args { 509 | color: #dddddd; 510 | font-weight: 300; 511 | transition: inherit; 512 | font-size: 88%; 513 | line-height: 1.2; 514 | letter-spacing: -.02em; 515 | } 516 | .search-result__title > a > .args * { 517 | color: inherit; 518 | } 519 | 520 | .search-result a, 521 | .search-result a:hover { 522 | color: inherit; 523 | } 524 | .search-result:not(.current):hover .search-result__title > a, 525 | .search-result:not(.current):focus .search-result__title > a, 526 | .search-result__title > a:focus { 527 | color: #866BA6; 528 | } 529 | .search-result:not(.current):hover .args, 530 | .search-result:not(.current):focus .args { 531 | color: #6a5a7d; 532 | } 533 | 534 | .search-result__type { 535 | color: #e8e8e8; 536 | font-weight: 300; 537 | } 538 | .search-result__doc { 539 | color: #bbbbbb; 540 | font-size: 90%; 541 | } 542 | .search-result__doc p { 543 | margin: 0; 544 | text-overflow: ellipsis; 545 | display: -webkit-box; 546 | -webkit-box-orient: vertical; 547 | -webkit-line-clamp: 2; 548 | overflow: hidden; 549 | line-height: 1.2em; 550 | max-height: 2.4em; 551 | } 552 | 553 | .js-modal-visible .modal-background { 554 | display: flex; 555 | } 556 | .main-content { 557 | position: relative; 558 | } 559 | .modal-background { 560 | position: absolute; 561 | display: none; 562 | height: 100%; 563 | width: 100%; 564 | background: rgba(120,120,120,.4); 565 | z-index: 100; 566 | align-items: center; 567 | justify-content: center; 568 | } 569 | .usage-modal { 570 | max-width: 90%; 571 | background: #fff; 572 | border: 2px solid #ccc; 573 | border-radius: 9px; 574 | padding: 5px 15px 20px; 575 | min-width: 50%; 576 | color: #555; 577 | position: relative; 578 | transform: scale(.5); 579 | transition: transform 200ms; 580 | } 581 | .js-modal-visible .usage-modal { 582 | transform: scale(1); 583 | } 584 | .usage-modal > .close-button { 585 | position: absolute; 586 | right: 15px; 587 | top: 8px; 588 | color: #aaa; 589 | font-size: 27px; 590 | cursor: pointer; 591 | } 592 | .usage-modal > .close-button:hover { 593 | text-shadow: 2px 2px 2px #ccc; 594 | color: #999; 595 | } 596 | .modal-title { 597 | margin: 0; 598 | text-align: center; 599 | font-weight: normal; 600 | color: #666; 601 | border-bottom: 2px solid #ddd; 602 | padding: 10px; 603 | } 604 | .usage-list { 605 | padding: 0; 606 | margin: 13px; 607 | } 608 | .usage-list > li { 609 | padding: 5px 2px; 610 | overflow: auto; 611 | padding-left: 100px; 612 | min-width: 12em; 613 | } 614 | .usage-modal kbd { 615 | background: #eee; 616 | border: 1px solid #ccc; 617 | border-bottom-width: 2px; 618 | border-radius: 3px; 619 | padding: 3px 8px; 620 | font-family: monospace; 621 | margin-right: 2px; 622 | display: inline-block; 623 | } 624 | .usage-key { 625 | float: left; 626 | clear: left; 627 | margin-left: -100px; 628 | margin-right: 12px; 629 | } 630 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | README - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

Docs

152 | 153 |

lime

154 | 155 |

A library for drawing graphics on the console screen

156 | 157 |

Features

158 | 159 | 160 | 161 |

Installation

162 | 163 |

Add the dependency to your shard.yml:

164 | 165 |
dependencies:
166 |   lime:
167 |     github: r00ster91/lime
168 | 169 |

Then run shards install

170 | 171 |

Example

172 | 173 |

Three lights flashing repeatedly in order:

174 | 175 |
require "lime"
176 | 
177 | # Create the lights:
178 | # They are red, green and blue rectangles with doubled lines
179 | light1 = Rectangle.new(
180 |   x: 2, y: 1,
181 |   width: 5, height: 3,
182 |   type: Double, color: :red
183 | )
184 | light2 = Rectangle.new(8, 1, 5, 3, Double, :green)
185 | light3 = Rectangle.new(14, 1, 5, 3, Double, :blue)
186 | 
187 | # The light that is currently on
188 | active = 1
189 | 
190 | loop do
191 |   if active == 3
192 |     active = 1
193 |   else
194 |     active += 1
195 |   end
196 | 
197 |   case active
198 |   when 1
199 |     light1.color = :light_red
200 |     light2.color = :green
201 |     light3.color = :blue
202 |   when 2
203 |     light1.color = :red
204 |     light2.color = :light_green
205 |     light3.color = :blue
206 |   when 3
207 |     light1.color = :red
208 |     light2.color = :green
209 |     light3.color = :light_blue
210 |   end
211 | 
212 |   # Insert the lights into the buffer:
213 |   light1.draw
214 |   light2.draw
215 |   light3.draw
216 | 
217 |   # Draw the content of the buffer to the screen:
218 |   Lime.draw
219 |   # Clear the buffer so we have room for new stuff:
220 |   Lime.clear
221 | 
222 |   # You can use `Lime.loop` instead of a normal loop to skip the above two steps
223 | 
224 |   # A short delay:
225 |   sleep 0.5
226 | end
227 | 228 |

In the top left corner we can see:

229 | 230 |

lights

231 | 232 |

See examples for more examples.

233 | 234 |

For technical information about lime, see src/README.md.

235 | 236 |

Contributing

237 | 238 |
  1. Fork it (<https://github.com/r00ster91/lime/fork>)
  2. Create your feature branch (git checkout -b my-new-feature)
  3. Make your changes
  4. Format the code (crystal tool format)
  5. Make sure the specs compile (crystal spec -v)
  6. Commit your changes (git commit -am 'Add some feature')
  7. Push to the branch (git push origin my-new-feature)
  8. Create a new Pull Request
239 | 240 |

Contributors

241 | 242 | 243 |
244 | 245 | 246 | -------------------------------------------------------------------------------- /docs/toplevel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | Top Level Namespace - github.com/r00ster91/lime 18 | 19 | 20 | 21 | 148 | 149 | 150 |
151 |

152 | 153 | Top Level Namespace 154 | 155 |

156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 |

Included Modules

166 | 173 | 174 | 175 | 176 |

Extended Modules

177 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 |

Defined in:

193 | 194 | 195 | 196 | 197 | 198 |

Constant Summary

199 | 200 |
201 | 202 |
203 | Default = Rectangle::Type::Default 204 |
205 | 206 |
207 |

The default rectangle type: Rectangle::Type::Default.

208 |
209 | 210 | 211 |
212 | Double = Rectangle::Type::Double 213 |
214 | 215 |
216 |

The double rectangle type: Rectangle::Type::Double.

217 |
218 | 219 | 220 |
221 | Round = Rectangle::Type::Round 222 |
223 | 224 |
225 |

The round rectangle type: Rectangle::Type::Round.

226 |
227 | 228 | 229 |
230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 |
242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 |
264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 |
274 | 275 | 276 | 277 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | The examples should be run outside of this directory like this: `crystal examples/example.cr`. -------------------------------------------------------------------------------- /examples/image.cr: -------------------------------------------------------------------------------- 1 | require "../lime" 2 | 3 | # Load the image: 4 | image_path = "#{__DIR__}/image.png" 5 | image = Pixels.new(image_path, 5, 5) 6 | 7 | # Insert the image pixels into the buffer: 8 | image.draw 9 | 10 | # Draw the content of the buffer to the screen: 11 | Lime.draw 12 | -------------------------------------------------------------------------------- /examples/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooster0/lime/f0bf53fc20e50f8333ae9ee5e4b8c2c0f45e1fc1/examples/image.png -------------------------------------------------------------------------------- /examples/lights.cr: -------------------------------------------------------------------------------- 1 | require "../lime" 2 | 3 | # Create the lights: 4 | # They are red, green and blue rectangles with doubled lines 5 | light1 = Rectangle.new( 6 | x: 2, y: 1, 7 | width: 5, height: 3, 8 | type: Double, color: :red 9 | ) 10 | light2 = Rectangle.new(8, 1, 5, 3, Double, :green) 11 | light3 = Rectangle.new(14, 1, 5, 3, Double, :blue) 12 | 13 | # The light that is currently on 14 | light = 1 15 | 16 | loop do 17 | if light == 3 18 | light = 1 19 | else 20 | light += 1 21 | end 22 | 23 | case light 24 | when 1 25 | light1.color = :light_red 26 | light2.color = :green 27 | light3.color = :blue 28 | when 2 29 | light1.color = :red 30 | light2.color = :light_green 31 | light3.color = :blue 32 | when 3 33 | light1.color = :red 34 | light2.color = :green 35 | light3.color = :light_blue 36 | end 37 | 38 | # Insert the lights into the buffer: 39 | light1.draw 40 | light2.draw 41 | light3.draw 42 | 43 | # Draw the content of the buffer to the screen: 44 | Lime.draw 45 | # Clear the buffer (not the screen) so we have room for the next frame: 46 | Lime.clear 47 | 48 | # You can use `Lime.loop` instead of a normal loop to skip the above two steps 49 | 50 | # A short delay: 51 | sleep 0.5 52 | end 53 | -------------------------------------------------------------------------------- /examples/mouse.cr: -------------------------------------------------------------------------------- 1 | require "../lime" 2 | 3 | # This will be used to detect if there was a collision between the mouse 4 | # and the target's hitbox 5 | def collision?(event, hitbox) 6 | event_size = 1 7 | event.x < hitbox.x + hitbox.width && 8 | event.x + event_size > hitbox.x && 9 | event.y < hitbox.y + hitbox.height && 10 | event.y + event_size > hitbox.y 11 | end 12 | 13 | record Hitbox, x : Int32, y : Int32 do 14 | getter height = 2, width = 3 15 | setter x, y 16 | end 17 | 18 | # A random position for the target and its hitbox 19 | x, y = rand(1..20), rand(10) 20 | target = Rectangle.new(x, y, 3, 2, color: :white) 21 | hitbox = Hitbox.new(x, y) 22 | 23 | # Set the mouse mode to Click which will report all clicks: 24 | Mouse.mode = Mouse::Mode::Click 25 | 26 | puts "Press any mouse button to start." 27 | 28 | loop do 29 | # Wait for an event: 30 | event = Mouse.get 31 | 32 | # Exit if the event is not a mouse event: 33 | abort("Event was not a mouse event.", 0) if event.nil? 34 | 35 | # Place the target at a new random position if the target has been hit: 36 | if collision?(event, hitbox) 37 | x, y = rand(1..50), rand(25) 38 | 39 | target.x = x 40 | target.y = y 41 | 42 | hitbox.x = x 43 | hitbox.y = y 44 | end 45 | 46 | # Insert the target into the buffer: 47 | target.draw 48 | 49 | # Draw the content of the buffer to the screen: 50 | Lime.draw 51 | # Clear the buffer so we have room for new stuff: 52 | Lime.clear 53 | 54 | # You can use `Lime.loop` instead of a normal loop to skip the above two steps 55 | end 56 | -------------------------------------------------------------------------------- /examples/pixels.cr: -------------------------------------------------------------------------------- 1 | require "../lime" 2 | 3 | # Captain Viridian from the game VVVVVV 4 | VIRIDIAN = <<-VIRIDIAN 5 | CCCCCCCC 6 | CCCCCCCCCC 7 | CCC00CC00C 8 | CCC00CC00C 9 | CCCCCCCCCC 10 | CCCCCCCCCC 11 | CCC000000C 12 | CCCC0000CC 13 | CCCCCCCC 14 | CCCC 15 | CCCCCCCC 16 | CCCCCCCCCC 17 | CCCCCCCCCC 18 | CCCCCCCCCC 19 | CC CCCC CC 20 | CC CCCC CC 21 | CCCCCC 22 | CC CC 23 | CCC CCC 24 | CCC CCC 25 | CCC CCC 26 | VIRIDIAN 27 | 28 | # Convert the color characters to pixels: 29 | viridian = Pixels.new(5, 5, VIRIDIAN) 30 | 31 | # Insert Viridian into the buffer: 32 | viridian.draw 33 | 34 | # Draw Viridian to the screen: 35 | Lime.draw 36 | -------------------------------------------------------------------------------- /examples/shapes.cr: -------------------------------------------------------------------------------- 1 | require "../lime" 2 | 3 | # Create the shapes: 4 | rectangle1 = Rectangle.new(2, 1, 10, 5, Default, :light_green) 5 | rectangle2 = Rectangle.new(13, 1, 10, 5, Double, :light_yellow) 6 | rectangle3 = Rectangle.new(24, 1, 10, 5, Round, :light_magenta) 7 | filled_rectangle = FilledRectangle.new(2, 7, 10, 5, color: :light_red) 8 | circle = Circle.new(12, 13, 11, :light_blue) 9 | 10 | # Insert the shapes into the buffer: 11 | rectangle1.draw 12 | rectangle2.draw 13 | rectangle3.draw 14 | filled_rectangle.draw 15 | circle.draw 16 | 17 | # Draw the content of the buffer to the screen: 18 | Lime.draw 19 | -------------------------------------------------------------------------------- /shard.yml: -------------------------------------------------------------------------------- 1 | name: lime 2 | version: 0.9.7 3 | 4 | authors: 5 | - r00ster91 6 | 7 | description: 8 | A library for drawing graphics on the console screen 9 | 10 | dependencies: 11 | stumpy_png: 12 | github: stumpycr/stumpy_png 13 | 14 | license: MIT 15 | -------------------------------------------------------------------------------- /spec/drawables_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | describe Rectangle do 3 | describe ".initialize" do 4 | it "initializes" do 5 | rectangle = Rectangle.new(0, 0, 5, 5, Default, :red) 6 | rectangle.x.should eq(0) 7 | rectangle.y.should eq(0) 8 | rectangle.width.should eq(5) 9 | rectangle.height.should eq(5) 10 | rectangle.type.should eq(Default) 11 | rectangle.color.should eq(Colorize::ColorANSI::Red) 12 | 13 | rectangle.@rectangle.to_s.should eq( 14 | <<-RECTANGLE 15 | \e[31m┌───┐ 16 | │ │ 17 | │ │ 18 | │ │ 19 | └───┘\e[0m 20 | RECTANGLE 21 | ) 22 | 23 | rectangle = Rectangle.new(0, 0, 1, 1) 24 | rectangle.@rectangle.should eq("█") 25 | 26 | rectangle = Rectangle.new(0, 0, 0, 1) 27 | rectangle.@rectangle.should eq("") 28 | 29 | rectangle = Rectangle.new(0, 0, 1, 0) 30 | rectangle.@rectangle.should eq("") 31 | 32 | rectangle = Rectangle.new(0, 0, 0, 0) 33 | rectangle.@rectangle.should eq("") 34 | 35 | rectangle = Rectangle.new(0, 0, 3, 1) 36 | rectangle.@rectangle.should eq("███") 37 | 38 | rectangle = Rectangle.new(0, 0, 1, 3) 39 | rectangle.@rectangle.should eq( 40 | <<-RECTANGLE 41 | █ 42 | █ 43 | █ 44 | RECTANGLE 45 | ) 46 | end 47 | end 48 | 49 | it "draws" do 50 | rectangle = Rectangle.new(1, 1, 5, 5, Default) 51 | rectangle.draw 52 | buffer.should eq( 53 | <<-RECTANGLE 54 | 55 | ┌───┐ 56 | │ │ 57 | │ │ 58 | │ │ 59 | └───┘ 60 | RECTANGLE 61 | ) 62 | end 63 | end 64 | 65 | describe FilledRectangle do 66 | it "initializes" do 67 | filled_rectangle = FilledRectangle.new(0, 0, 5, 5, "#", :red) 68 | filled_rectangle.x.should eq(0) 69 | filled_rectangle.y.should eq(0) 70 | filled_rectangle.width.should eq(5) 71 | filled_rectangle.height.should eq(5) 72 | filled_rectangle.material.should eq("#") 73 | filled_rectangle.color.should eq(Colorize::ColorANSI::Red) 74 | filled_rectangle.@tile.to_s.should eq("\e[31m#####\e[0m") 75 | 76 | filled_rectangle = FilledRectangle.new(0, 0, 0, 10) 77 | filled_rectangle.@tile.to_s.should eq("") 78 | 79 | filled_rectangle = FilledRectangle.new(0, 0, 10, 0) 80 | filled_rectangle.@tile.to_s.should eq("") 81 | 82 | filled_rectangle = FilledRectangle.new(0, 0, 2, 1) 83 | filled_rectangle.@tile.to_s.should eq("██") 84 | 85 | filled_rectangle = FilledRectangle.new(0, 0, 1, 2) 86 | filled_rectangle.@tile.to_s.should eq("█") 87 | end 88 | 89 | it "draws" do 90 | filled_rectangle = FilledRectangle.new(1, 1, 2, 2, "O", :yellow) 91 | filled_rectangle.draw 92 | buffer.should eq( 93 | <<-FILLED_RECTANGLE 94 | 95 | \e[33mO\e[0m\e[33mO\e[0m 96 | \e[33mO\e[0m\e[33mO\e[0m 97 | FILLED_RECTANGLE 98 | ) 99 | end 100 | end 101 | 102 | describe Circle do 103 | it "initializes" do 104 | circle = Circle.new(1, 1, 5, :green) 105 | circle.x.should eq(1) 106 | circle.y.should eq(1) 107 | circle.radius.should eq(5) 108 | circle.color.should eq(Colorize::ColorANSI::Green) 109 | end 110 | 111 | it "draws" do 112 | circle = Circle.new(2, 2, 3, :yellow) 113 | circle.draw 114 | buffer.should eq( 115 | <<-CIRCLE 116 | 117 | \e[33m▄\e[0m\e[33m▄\e[0m\e[33m▄\e[0m 118 | \e[33;43m▄\e[0m \e[33;43m▄\e[0m 119 | \e[33m▀\e[0m\e[33m▄\e[0m\e[33m▄\e[0m\e[33m▄\e[0m\e[33m▀\e[0m 120 | CIRCLE 121 | ) 122 | end 123 | end 124 | 125 | describe Pixels do 126 | describe ".initialize" do 127 | it "initializes color characters" do 128 | pixels = Pixels.new(5, 5, 129 | <<-COLOR_CHARACTERS 130 | RGBRGB 131 | COLOR_CHARACTERS 132 | ) 133 | pixels.x.should eq(5) 134 | pixels.y.should eq(5) 135 | pixels.@pixels.should eq( 136 | [Colorize::ColorANSI::LightRed, 137 | Colorize::ColorANSI::LightGreen, 138 | Colorize::ColorANSI::LightBlue, 139 | Colorize::ColorANSI::LightRed, 140 | Colorize::ColorANSI::LightGreen, 141 | Colorize::ColorANSI::LightBlue] 142 | ) 143 | end 144 | 145 | it "raises when there's an invalid color character" do 146 | expect_raises(Pixels::Error, "Invalid color character") do 147 | pixels = Pixels.new(5, 5, "I") 148 | end 149 | end 150 | 151 | it "handles comments" do 152 | pixels = Pixels.new(0, 0, 153 | <<-COMMENTS 154 | R # #Comment 155 | # Comment 156 | G # #Comment 157 | # Comment 158 | B # #Comment 159 | # Comment 160 | COMMENTS 161 | ) 162 | end 163 | 164 | it "initializes an image" do 165 | image_path = "#{__DIR__}/image.png" 166 | image = Pixels.new(image_path, 0, 0) 167 | image.@pixels.should eq( 168 | [Colorize::ColorRGB.new(237_u8, 28_u8, 36_u8), 169 | Colorize::ColorRGB.new(0_u8, 255_u8, 0_u8), 170 | Colorize::ColorRGB.new(0_u8, 0_u8, 255_u8), :newline, 171 | Colorize::ColorRGB.new(0_u8, 255_u8, 0_u8), 172 | Colorize::ColorRGB.new(237_u8, 28_u8, 36_u8), 173 | Colorize::ColorRGB.new(0_u8, 255_u8, 0_u8), :newline, 174 | Colorize::ColorRGB.new(0_u8, 0_u8, 255_u8), 175 | Colorize::ColorRGB.new(0_u8, 255_u8, 0_u8), 176 | Colorize::ColorRGB.new(237_u8, 28_u8, 36_u8)]) 177 | end 178 | end 179 | 180 | describe "#map" do 181 | it "modifies an image" do 182 | image_path = "#{__DIR__}/image.png" 183 | image = Pixels.new(image_path, 5, 5) 184 | image.map { |pixel| {255u8 - pixel.red, 255u8 - pixel.green, 255u8 - pixel.blue} } 185 | image.@pixels.should eq( 186 | [Colorize::ColorRGB.new(18_u8, 227_u8, 219_u8), 187 | Colorize::ColorRGB.new(255_u8, 0_u8, 255_u8), 188 | Colorize::ColorRGB.new(255_u8, 255_u8, 0_u8), :newline, 189 | Colorize::ColorRGB.new(255_u8, 0_u8, 255_u8), 190 | Colorize::ColorRGB.new(18_u8, 227_u8, 219_u8), 191 | Colorize::ColorRGB.new(255_u8, 0_u8, 255_u8), :newline, 192 | Colorize::ColorRGB.new(255_u8, 255_u8, 0_u8), 193 | Colorize::ColorRGB.new(255_u8, 0_u8, 255_u8), 194 | Colorize::ColorRGB.new(18_u8, 227_u8, 219_u8)] 195 | ) 196 | end 197 | 198 | it "modifies color characters" do 199 | pixels = Pixels.new(5, 5, 200 | <<-COLOR_CHARACTERS 201 | R G B 202 | G R G 203 | B G R 204 | COLOR_CHARACTERS 205 | ) 206 | pixels.@pixels.should eq( 207 | [Colorize::ColorANSI::LightRed, :skip, 208 | Colorize::ColorANSI::LightGreen, :skip, 209 | Colorize::ColorANSI::LightBlue, :newline, 210 | Colorize::ColorANSI::LightGreen, :skip, 211 | Colorize::ColorANSI::LightRed, :skip, 212 | Colorize::ColorANSI::LightGreen, :newline, 213 | Colorize::ColorANSI::LightBlue, :skip, 214 | Colorize::ColorANSI::LightGreen, :skip, 215 | Colorize::ColorANSI::LightRed] 216 | ) 217 | end 218 | end 219 | 220 | describe "#draw" do 221 | it "draws an image" do 222 | image_path = "#{__DIR__}/image.png" 223 | image = Pixels.new(image_path, 5, 5) 224 | image.draw 225 | buffer.should eq( 226 | <<-IMAGE 227 | 228 | 229 | \e[38;2;237;28;36m▄\e[0m\e[38;2;0;255;0m▄\e[0m\e[38;2;0;0;255m▄\e[0m 230 | \e[38;2;0;255;0;48;2;0;0;255m▀\e[0m\e[38;2;237;28;36;48;2;0;255;0m▀\e[0m\e[38;2;0;255;0;48;2;237;28;36m▀\e[0m 231 | IMAGE 232 | ) 233 | end 234 | 235 | it "draws color characters" do 236 | pixels = Pixels.new(5, 5, 237 | <<-COLOR_CHARACTERS 238 | R G B 239 | G R G 240 | B G R 241 | COLOR_CHARACTERS 242 | ) 243 | pixels.draw 244 | buffer.should eq( 245 | <<-PIXELS 246 | 247 | 248 | \e[91m▄\e[0m \e[92m▄\e[0m \e[94m▄\e[0m 249 | \e[92;104m▀\e[0m \e[91;102m▀\e[0m \e[92;101m▀\e[0m 250 | PIXELS 251 | ) 252 | end 253 | end 254 | end 255 | -------------------------------------------------------------------------------- /spec/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wooster0/lime/f0bf53fc20e50f8333ae9ee5e4b8c2c0f45e1fc1/spec/image.png -------------------------------------------------------------------------------- /spec/lime_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | require "../src/lime" 3 | 4 | describe Lime do 5 | describe ".print" do 6 | it "prints a character" do 7 | Lime.print('c', 1, 1) 8 | buffer.should eq("\n c") 9 | end 10 | 11 | it "prints a colored character" do 12 | Lime.print('c'.colorize(:green), 1, 1) 13 | buffer.should eq("\n \e[32mc\e[0m") 14 | end 15 | 16 | it "prints a string" do 17 | Lime.print("string", 1, 1) 18 | buffer.should eq("\n string") 19 | end 20 | 21 | it "prints a colored string" do 22 | Lime.print("red".colorize(:red), 1, 1) 23 | buffer.should eq("\n \e[31mr\e[0m\e[31me\e[0m\e[31md\e[0m") 24 | end 25 | 26 | it "prints a default string" do 27 | Lime.print("default".colorize(:default), 1, 1) 28 | buffer.should eq("\n default") 29 | end 30 | end 31 | 32 | describe ".printf" do 33 | it "printfs a string with a newline" do 34 | Lime.printf("formatted\nstring", 2, 2) 35 | buffer.should eq("\n\n formatted\n string") 36 | end 37 | 38 | it "printfs a colored string with a newline" do 39 | Lime.printf("bl\nue".colorize(:blue), 2, 2) 40 | buffer.should eq( 41 | <<-BLUE 42 | 43 | 44 | \e[34mb\e[0m\e[34ml\e[0m 45 | \e[34mu\e[0m\e[34me\e[0m 46 | BLUE 47 | ) 48 | end 49 | end 50 | 51 | describe ".pixel" do 52 | it "inserts colored pixels into the buffer" do 53 | Lime.pixel(1, 1, :red) 54 | buffer.should eq(" \e[31m▄\e[0m") 55 | 56 | Lime.pixel(1, 0, :green) 57 | buffer.should eq(" \e[32m▀\e[0m") 58 | 59 | Lime.pixel(1, 1, :yellow) 60 | Lime.pixel(1, 0, :magenta) 61 | buffer.should eq(" \e[33;45m▄\e[0m") 62 | 63 | Lime.pixel(1, 0, :magenta) 64 | Lime.pixel(1, 1, :yellow) 65 | buffer.should eq(" \e[35;43m▀\e[0m") 66 | 67 | Lime.pixel(1, 0, :magenta) 68 | Lime.pixel(1, 1, :yellow) 69 | Lime.pixel(1, 0, :magenta) 70 | Lime.pixel(1, 1, :yellow) 71 | buffer.should eq(" \e[35;43m▀\e[0m") 72 | 73 | Lime.pixel(1, 1) 74 | Lime.pixel(1, 0) 75 | Lime.pixel(1, 1, :red) 76 | Lime.pixel(1, 0, :green) 77 | buffer.should eq(" \e[32;41m▀\e[0m") 78 | 79 | Lime.pixel(0, 0) 80 | Lime.pixel(0, 0, :red) 81 | buffer.should eq("\e[31m▀\e[0m") 82 | 83 | Lime.pixel(0, 0) 84 | Lime.pixel(0, 0, :red) 85 | Lime.pixel(0, 0, :red) 86 | buffer.should eq("\e[31m▀\e[0m") 87 | 88 | Lime.pixel(0, 1) 89 | Lime.pixel(0, 1, :red) 90 | buffer.should eq("\e[31m▄\e[0m") 91 | 92 | Lime.pixel(0, 1) 93 | Lime.pixel(0, 1, :red) 94 | Lime.pixel(0, 1, :red) 95 | buffer.should eq("\e[31m▄\e[0m") 96 | 97 | Lime.pixel(0, 1) 98 | Lime.pixel(0, 1) 99 | Lime.pixel(0, 1, :red) 100 | Lime.pixel(0, 1, :red) 101 | buffer.should eq("\e[31m▄\e[0m") 102 | 103 | Lime.pixel(0, 1) 104 | Lime.pixel(0, 1) 105 | Lime.pixel(0, 0, :red) 106 | Lime.pixel(0, 0, :red) 107 | buffer.should eq("\e[41m▄\e[0m") 108 | end 109 | 110 | it "inserts default pixels into the buffer" do 111 | Lime.pixel(1, 1) 112 | buffer.should eq(" ▄") 113 | 114 | Lime.pixel(1, 0) 115 | buffer.should eq(" ▀") 116 | 117 | Lime.pixel(1, 1) 118 | Lime.pixel(1, 0) 119 | buffer.should eq(" █") 120 | 121 | Lime.pixel(1, 0) 122 | Lime.pixel(1, 1) 123 | buffer.should eq(" █") 124 | 125 | Lime.pixel(1, 1) 126 | Lime.pixel(1, 0, :red) 127 | Lime.pixel(1, 0) 128 | buffer.should eq(" █") 129 | 130 | Lime.pixel(1, 0) 131 | Lime.pixel(1, 1, :red) 132 | Lime.pixel(1, 1) 133 | buffer.should eq(" █") 134 | 135 | Lime.pixel(1, 1, :red) 136 | Lime.pixel(1, 0, :green) 137 | Lime.pixel(1, 1) 138 | Lime.pixel(1, 0) 139 | buffer.should eq(" █") 140 | 141 | Lime.pixel(1, 0) 142 | Lime.pixel(1, 0) 143 | buffer.should eq(" ▀") 144 | 145 | Lime.pixel(1, 1) 146 | Lime.pixel(1, 1) 147 | buffer.should eq(" ▄") 148 | end 149 | 150 | it "inserts default and colored pixels into the buffer" do 151 | Lime.pixel(1, 1, :red) 152 | Lime.pixel(1, 0) 153 | buffer.should eq(" \e[41m▀\e[0m") 154 | 155 | Lime.pixel(1, 1) 156 | Lime.pixel(1, 0, :red) 157 | buffer.should eq(" \e[41m▄\e[0m") 158 | 159 | Lime.pixel(1, 1, :red) 160 | Lime.pixel(1, 2) 161 | buffer.should eq(" \e[31m▄\e[0m\n ▀") 162 | 163 | Lime.pixel(1, 1) 164 | Lime.pixel(1, 2, :red) 165 | buffer.should eq(" ▄\n \e[31m▀\e[0m") 166 | 167 | Lime.pixel(1, 0) 168 | Lime.pixel(1, 1) 169 | Lime.pixel(1, 1, :red) 170 | buffer.should eq(" \e[41m▀\e[0m") 171 | 172 | Lime.pixel(1, 0) 173 | Lime.pixel(1, 1) 174 | Lime.pixel(1, 0, :red) 175 | buffer.should eq(" \e[41m▄\e[0m") 176 | 177 | Lime.pixel(1, 0) 178 | Lime.pixel(1, 0, :red) 179 | Lime.pixel(1, 1) 180 | buffer.should eq(" \e[41m▄\e[0m") 181 | end 182 | end 183 | 184 | describe ".line" do 185 | it "inserts a colored line into the buffer" do 186 | Lime.line(0, 0, 5, 5, :red) 187 | buffer.should eq( 188 | <<-LINE 189 | \e[31m▀\e[0m\e[31m▄\e[0m 190 | \e[31m▀\e[0m\e[31m▄\e[0m 191 | \e[31m▀\e[0m\e[31m▄\e[0m 192 | LINE 193 | ) 194 | 195 | Lime.line(0, 0, 5, 10, :red) 196 | buffer.should eq( 197 | <<-LINE 198 | \e[31;41m▀\e[0m 199 | \e[31;41m▀\e[0m 200 | \e[31;41m▀\e[0m 201 | \e[31;41m▀\e[0m 202 | \e[31;41m▀\e[0m 203 | \e[31m▀\e[0m 204 | LINE 205 | ) 206 | end 207 | 208 | it "inserts a default line into the buffer" do 209 | Lime.line(0, 0, 5, 5) 210 | buffer.should eq( 211 | <<-LINE 212 | ▀▄ 213 | ▀▄ 214 | ▀▄ 215 | LINE 216 | ) 217 | 218 | Lime.line(0, 0, 5, 10) 219 | buffer.should eq( 220 | <<-LINE 221 | █ 222 | █ 223 | █ 224 | █ 225 | █ 226 | ▀ 227 | LINE 228 | ) 229 | end 230 | end 231 | 232 | describe ".peek_key_raw" do 233 | it "returns nil if no key has been pressed" do 234 | Lime.peek_key.should eq(nil) 235 | end 236 | end 237 | 238 | describe ".peek_key" do 239 | it "returns nil if no key has been pressed" do 240 | Lime.peek_key.should eq(nil) 241 | end 242 | end 243 | end 244 | -------------------------------------------------------------------------------- /spec/modules_spec.cr: -------------------------------------------------------------------------------- 1 | require "./spec_helper" 2 | require "../src/lime/modules" 3 | 4 | describe Window do 5 | describe ".width" do 6 | it "returns the width zero-based" do 7 | Window.width.should eq(Window.width_cells - 1) 8 | end 9 | end 10 | 11 | describe ".height" do 12 | it "returns the height zero-based" do 13 | Window.height.should eq(Window.height_cells - 1) 14 | end 15 | end 16 | 17 | describe ".width_pixels" do 18 | it "returns the width in pixels" do 19 | Window.width_pixels.should eq(Window.width) 20 | end 21 | end 22 | 23 | describe ".height_pixels" do 24 | it "returns the height in pixels" do 25 | Window.height_pixels.should eq(Window.height*2) 26 | end 27 | end 28 | end 29 | 30 | describe Mouse do 31 | describe ".extend" do 32 | it "extends the range" do 33 | Mouse.extend 34 | Mouse.extended.should eq(true) 35 | end 36 | 37 | it "resets the range" do 38 | Mouse.extend(false) 39 | Mouse.extended.should eq(false) 40 | end 41 | end 42 | 43 | describe ".mode" do 44 | it "returns and sets the mode" do 45 | Mouse.mode = Mouse::Mode::Off 46 | Mouse.mode.should eq(Mouse::Mode::Off) 47 | 48 | Mouse.mode = Mouse::Mode::Click 49 | Mouse.mode.should eq(Mouse::Mode::Click) 50 | end 51 | end 52 | 53 | describe ".peek" do 54 | it "returns nil if the mode is off" do 55 | Mouse.mode = Mouse::Mode::Off 56 | Mouse.peek.should eq(nil) 57 | end 58 | 59 | it "returns nil if no mouse event is happening" do 60 | Mouse.mode = Mouse::Mode::Click 61 | Mouse.peek.should eq(nil) 62 | end 63 | end 64 | 65 | describe Mouse::Event do 66 | describe "#type" do 67 | it "returns the type" do 68 | Mouse::Event.new(5, 5, :move).type.should eq(:move) 69 | Mouse::Event.new(5, 5, :left).type.should eq(:left) 70 | end 71 | end 72 | 73 | describe "#kind" do 74 | it "returns the kind" do 75 | Mouse::Event.new(5, 5, :release).kind.should eq(:release) 76 | Mouse::Event.new(5, 5, :move).kind.should eq(:move) 77 | Mouse::Event.new(5, 5, :left).kind.should eq(:click) 78 | Mouse::Event.new(5, 5, :wheel_up).kind.should eq(:wheel) 79 | Mouse::Event.new(5, 5, :left_drag).kind.should eq(:drag) 80 | end 81 | end 82 | 83 | it "returns the position" do 84 | event = Mouse::Event.new(2, 5, :left) 85 | event.x.should eq(2) 86 | event.y.should eq(5) 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /spec/spec_helper.cr: -------------------------------------------------------------------------------- 1 | require "spec" 2 | 3 | class String 4 | class Builder < IO 5 | def clear 6 | @buffer = GC.malloc_atomic(capacity.to_u32).as(UInt8*) 7 | @bytesize = 0 8 | @finished = false 9 | end 10 | end 11 | 12 | # Cuts `self` down to multiple pieces of size *size* and returns them as an array. 13 | # 14 | # ``` 15 | # "hello world".pieces(2) # => ["he", "ll", "o ", "wo", "rl", "d"] 16 | # ``` 17 | def pieces(of size) 18 | pieces = [] of String 19 | io = String::Builder.new 20 | i = 0 21 | self.each_char do |char| 22 | if i == size 23 | pieces << io.to_s 24 | io.clear 25 | i = 0 26 | end 27 | io << char 28 | i += 1 29 | end 30 | pieces << io.to_s if !io.empty? 31 | pieces 32 | end 33 | end 34 | 35 | def buffer 36 | string = String.build do |io| 37 | Lime.buffer.each do |char| 38 | io << char 39 | end 40 | end 41 | 42 | buffer = string.pieces(Window.width_cells) 43 | Lime.clear 44 | 45 | skip = true 46 | buffer.compact_map do |line| 47 | line = line.rstrip(' ') 48 | skip = false if !line.empty? 49 | next line if skip || !line.empty? 50 | end.join('\n') 51 | end 52 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # How it works exactly 2 | 3 | After you required lime, a buffer will be automatically created. 4 | The buffer is a one-dimensional array of spaces. The amount of spaces is determined by the window width of the console in cells multiplied by the window height in cells. 5 | 6 | So let's say we want to print a single `a` character at the x-axis **10** and at the y-axis **5**: 7 | 8 | ```crystal 9 | Lime.print('a', 10, 5) 10 | ``` 11 | 12 | now the buffer will be accessed and the `a` will be inserted into it at a specific index. The formula for calculating the correct index is: 13 | 14 | ```crystal 15 | x-axis + window width * y-axis 16 | ``` 17 | 18 | (Let's say your window width is **168**) 19 | 20 | so the result is: 21 | 22 | ```crystal 23 | 10 + 168 * 5 = 850 24 | ``` 25 | 26 | which means that the **850**th empty space in the buffer will be replaced by an `a`. But this isn't enough yet to see the `a` on the screen. 27 | 28 | The next step is to call `Lime.draw` which iterates through every single character in the buffer and builds one big string which is then being printed. That's of course much faster than printing every character seperately. 29 | 30 | Usually if you use `Lime.draw` in a loop, you put a `Lime.clear` after it which clears the buffer so you have room for drawing new stuff. `Lime.clear` replaces every element of the buffer by a space. Now the buffer is containing spaces again and when the buffer is drawn, the screen will appear as empty. 31 | -------------------------------------------------------------------------------- /src/lime.cr: -------------------------------------------------------------------------------- 1 | require "colorize" 2 | require "./lime/drawables" 3 | require "./lime/modules" 4 | 5 | # The main module of lime. 6 | # 7 | # All `x` and `y` arguments are zero-based. 8 | # 9 | # An `IndexError` will be raised if: 10 | # * An `x` argument is greater than `Window.width`. 11 | # * An `x` argument is lower than `-Window.width_cells`. 12 | # * A `y` argument is greater than `Window.height`. 13 | # * A `y` argument is lower than `-Window.height_cells`. 14 | # 15 | # NOTE: Sometimes you might come across the terms "cell" and "pixel": 16 | # * A "cell" refers to one place of a character on the console: `█`. 17 | # * A "pixel" **does not** refer to a pixel of a display. 18 | # It refers to the **half** of a "cell": `▀` and `▄`. 19 | module Lime 20 | extend self 21 | 22 | @@buffer = Array(Char | Colorize::Object(Char)).new(Window.width_cells*Window.height_cells) { ' ' } 23 | 24 | # Returns the buffer as an array of characters. 25 | def buffer : Array(Char | Colorize::Object(Char)) 26 | @@buffer 27 | end 28 | 29 | # Sets the buffer to *buffer*. 30 | def buffer=(@@buffer : Array(Char | Colorize::Object(Char))) 31 | end 32 | 33 | # Returns the character of the buffer at *x*, *y*. 34 | def buffer(x, y) : Char | Colorize::Object(Char) 35 | @@buffer[x + Window.width_cells * y] 36 | end 37 | 38 | @@io = IO::Memory.new(@@buffer.size) 39 | 40 | # Draws the content of the buffer to the screen. 41 | # 42 | # NOTE: This should not be called concurrently. 43 | def draw 44 | @@buffer.each do |char| 45 | @@io << char 46 | end 47 | STDOUT.write(@@io.to_slice) 48 | @@io.clear 49 | end 50 | 51 | # Clears the buffer. 52 | def clear 53 | @@buffer.fill { ' ' } 54 | end 55 | 56 | # Sets the height of the buffer to *height*. 57 | def buffer_height=(height) 58 | count = Window.width_cells*height 59 | if height > @@buffer.size/Window.width_cells 60 | count.times do 61 | @@buffer << ' ' 62 | end 63 | else 64 | @@buffer.shift(count) 65 | end 66 | end 67 | 68 | # Waits until a key has been pressed and returns it. 69 | # 70 | # NOTE: Ctrl+C is caught by this method and will not be handled by the system. 71 | def get_key_raw : String 72 | STDIN.raw do |io| 73 | bytes = Bytes.new(3) 74 | String.new(bytes[0, io.read(bytes)]) 75 | end 76 | end 77 | 78 | # Returns the key that is down in the moment this method is called or `nil` if no key is down. 79 | # 80 | # NOTE: Ctrl+C is caught by this method and will not be handled by the system. 81 | def peek_key_raw : String? 82 | STDIN.read_timeout = 0.01 83 | get_key_raw 84 | rescue IO::Timeout 85 | ensure 86 | STDIN.read_timeout = nil 87 | end 88 | 89 | private KEYS = { 90 | {:up, "up arrow"}, {:down, "down arrow"}, 91 | {:left, "left arrow"}, {:right, "right arrow"}, 92 | {:enter, "enter"}, 93 | {:tab, "tab"}, 94 | {:backspace, "backspace"}, 95 | {:escape, "escape"}, 96 | {:ctrl_c, "Ctrl+C"}, 97 | } 98 | 99 | private KEY_BODY = <<-KEY_BODY 100 | when "\\e[A" 101 | :up 102 | when "\\e[B" 103 | :down 104 | when "\\e[C" 105 | :right 106 | when "\\e[D" 107 | :left 108 | when "\\r", "\\n" 109 | :enter 110 | when "\t" 111 | :tab 112 | when "\\u{7f}", "\\b" 113 | :backspace 114 | when "\\e" 115 | :escape 116 | when "\\u{3}" 117 | :ctrl_c 118 | else 119 | key 120 | end 121 | KEY_BODY 122 | 123 | {% begin %} 124 | # Waits until a key has been pressed and returns it compactly as a symbol. 125 | # 126 | {% for value in KEYS %} 127 | # * `{{value[0]}}` if {{value[1].id}} has been pressed. 128 | {% end %} 129 | # 130 | # If none of the above keys are pressed, the key is returned as-is. 131 | # 132 | # NOTE: Ctrl+C is caught by this method and will not be handled by the system. 133 | def get_key : Symbol | String 134 | case key = get_key_raw 135 | {{KEY_BODY.id}} 136 | end 137 | 138 | # Returns the key that is down in the moment this method is called compactly as a symbol or `nil` if no key is down. 139 | # 140 | {% for value in KEYS %} 141 | # * `{{value[0]}}` if {{value[1].id}} is down. 142 | {% end %} 143 | # 144 | # If none of the above keys are down, the key is returned as-is. 145 | # 146 | # NOTE: Ctrl+C is caught by this method and will not be handled by the system. 147 | def peek_key : Symbol | String | Nil 148 | case key = peek_key_raw 149 | {{KEY_BODY.id}} 150 | end 151 | {% end %} 152 | 153 | # Inserts *char* into the buffer at *x*, *y*. 154 | def print(char : Char | Colorize::Object(Char), x : Int32, y : Int32) 155 | @@buffer[x + Window.width_cells * y] = char 156 | end 157 | 158 | # Inserts *string* into the buffer at *x*, *y*. 159 | def print(string : String, x : Int32, y : Int32) 160 | string.each_char_with_index do |char, i| 161 | Lime.print(char, x + i, y) 162 | end 163 | end 164 | 165 | # :ditto: 166 | def print(string : Colorize::Object(String), x : Int32, y : Int32) 167 | fore = string.@fore 168 | back = string.@back 169 | object = string.@object.to_s 170 | 171 | object.each_char_with_index do |char, i| 172 | Lime.print(char.colorize(fore).back(back), x + i, y) 173 | end 174 | end 175 | 176 | # Inserts *string* formatted into the buffer at *x*, *y*. 177 | # 178 | # This method properly aligns each line in the string beneath each other. 179 | # 180 | # ``` 181 | # Lime.printf("hello\nworld", 2, 2) 182 | # ``` 183 | # ```text 184 | # 185 | # 186 | # hello 187 | # world 188 | # ``` 189 | def printf(string : String, x : Int32, y : Int32) 190 | i = 0 191 | string.each_line do |line| 192 | Lime.print(line, x, y + i) 193 | i += 1 194 | end 195 | end 196 | 197 | # :ditto: 198 | def printf(string : Colorize::Object(String), x : Int32, y : Int32) 199 | fore = string.@fore 200 | back = string.@back 201 | object = string.@object.to_s 202 | 203 | i = 0 204 | object.each_line do |line| 205 | Lime.print(line.colorize.fore(fore).back(back), x, y + i) 206 | i += 1 207 | end 208 | end 209 | 210 | # The order of the loop is as follows: 211 | # 212 | # **1.** Executes the given block. 213 | # 214 | # **2.** Draws the content of the buffer to the screen. 215 | # 216 | # **3.** Clears the buffer. 217 | def loop 218 | while true 219 | yield 220 | Lime.draw 221 | Lime.clear 222 | end 223 | end 224 | 225 | # Inserts a pixel at *x*, *y* with *color* into the buffer. 226 | # 227 | # ``` 228 | # Lime.pixel(2, 2) 229 | # ``` 230 | # ```text 231 | # 232 | # ▀ 233 | # ``` 234 | # 235 | # ``` 236 | # Lime.pixel(2, 2) 237 | # Lime.pixel(2, 3) 238 | # ``` 239 | # ```text 240 | # 241 | # █ 242 | # ``` 243 | def pixel(x : Int32, y : Int32, color : Colorize::Color = Colorize::ColorANSI::Default) 244 | char = Lime.buffer(x, y//2) 245 | 246 | # This code was an absolute nightmare 247 | pixel = if y.even? 248 | if char.to_s == "▀" 249 | if color == Colorize::ColorANSI::Default 250 | '▀' 251 | else 252 | '▀'.colorize(color) 253 | end 254 | elsif char.to_s == "█" 255 | if color == Colorize::ColorANSI::Default 256 | '█' 257 | else 258 | '▄'.colorize.back(color) 259 | end 260 | elsif char.to_s.includes?('▄') 261 | if color == Colorize::ColorANSI::Default && char.as(Colorize::Object(Char)).@fore == Colorize::ColorANSI::Default 262 | '█' 263 | elsif char.is_a?(Colorize::Object(Char)) 264 | if color == Colorize::ColorANSI::Default 265 | '▀'.colorize.back(char.@fore) 266 | else 267 | char.back(color) 268 | end 269 | else 270 | char.as(Char).colorize.back(color) 271 | end 272 | else 273 | if char.is_a?(Colorize::Object(Char)) 274 | '▀'.colorize(color).back(char.@back) 275 | else 276 | '▀'.colorize(color) 277 | end 278 | end 279 | else 280 | if char.to_s == "▀" 281 | if color == Colorize::ColorANSI::Default 282 | '█' 283 | else 284 | '▀'.colorize.back(color) 285 | end 286 | elsif char.to_s == "█" 287 | if color == Colorize::ColorANSI::Default 288 | '█' 289 | else 290 | '▀'.colorize.back(color) 291 | end 292 | elsif char.to_s.includes?('▀') 293 | if color == Colorize::ColorANSI::Default && char.as(Colorize::Object(Char)).@fore == Colorize::ColorANSI::Default 294 | '█' 295 | elsif char.is_a?(Colorize::Object(Char)) 296 | if color == Colorize::ColorANSI::Default 297 | '▄'.colorize.back(char.@fore) 298 | else 299 | char.back(color) 300 | end 301 | else 302 | char.as(Char).colorize.back(color) 303 | end 304 | else 305 | if char.is_a?(Colorize::Object(Char)) 306 | '▄'.colorize(color).back(char.@back) 307 | else 308 | '▄'.colorize(color) 309 | end 310 | end 311 | end 312 | 313 | Lime.print(pixel, x, y//2) 314 | end 315 | 316 | # Inserts a line from *x1*, *y1* to *x2*, *y2* with *color* into the buffer. 317 | # 318 | # ``` 319 | # Lime.line(0, 0, 5, 5) 320 | # ``` 321 | # ```text 322 | # ▀▄ 323 | # ▀▄ 324 | # ▀▄ 325 | # ``` 326 | # 327 | # ``` 328 | # Lime.line(0, 0, 10, 5) 329 | # ``` 330 | # ```text 331 | # ▀▀▄▄ 332 | # ▀▀▄▄ 333 | # ▀▀▄ 334 | # ``` 335 | # 336 | # This method uses [Bresenham's line algorithm](https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm). 337 | def line(x1 : Int32, y1 : Int32, 338 | x2 : Int32, y2 : Int32, color : Colorize::Color = Colorize::ColorANSI::Default) 339 | if (y2 - y1).abs < (x2 - x1).abs 340 | if x2 > x2 341 | Lime.line_low(x2, y2, x1, y1, color) 342 | else 343 | Lime.line_low(x1, y1, x2, y2, color) 344 | end 345 | else 346 | if y1 > y2 347 | Lime.line_high(x2, y2, x1, y1, color) 348 | else 349 | Lime.line_high(x1, y1, x2, y2, color) 350 | end 351 | end 352 | end 353 | 354 | # :nodoc: 355 | def line_low(x1, y1, x2, y2, color) 356 | dx = x2 - x1 357 | dy = y2 - y1 358 | if dy < 0 359 | yi = -1 360 | dy = -dy 361 | else 362 | yi = 1 363 | end 364 | d = 2*dy - dx 365 | y = y1 366 | (x1..x2).each do |x| 367 | Lime.pixel(x, y, color) 368 | if d > 0 369 | y += yi 370 | d -= 2*dx 371 | end 372 | d += 2*dy 373 | end 374 | end 375 | 376 | # :nodoc: 377 | def line_high(x1, y1, x2, y2, color) 378 | dx = x2 - x1 379 | dy = y2 - y1 380 | if dx < 0 381 | xi = -1 382 | dx = -dx 383 | else 384 | xi = 1 385 | end 386 | d = 2*dx - dy 387 | x = x1 388 | (y1..y2).each do |y| 389 | Lime.pixel(x, y, color) 390 | if d > 0 391 | x += xi 392 | d -= 2*dy 393 | end 394 | d += 2*dx 395 | end 396 | end 397 | end 398 | -------------------------------------------------------------------------------- /src/lime/drawables.cr: -------------------------------------------------------------------------------- 1 | require "stumpy_png" 2 | 3 | module Lime 4 | # Drawables that can be inserted into the buffer using the `draw` method of every drawable. 5 | # 6 | # The drawables are available on the top-level by default. 7 | # 8 | # Simple example of drawing a `Drawable`: 9 | # 10 | # ``` 11 | # rectangle = Rectangle.new(x: 0, y: 0, width: 20, height: 10, type: Default, color: :light_blue) 12 | # 13 | # Insert the rectangle into the buffer. 14 | # rectangle.draw 15 | # 16 | # Show the buffer and thus the rectangle. 17 | # Lime.draw 18 | # ``` 19 | module Drawables 20 | # A drawable rectangle. 21 | struct Rectangle 22 | property x, y 23 | getter width, height, type, color 24 | 25 | # The type of a `Rectangle`. 26 | enum Type 27 | # The default rectangle type. It looks like this: 28 | # 29 | # ```text 30 | # ┌────┐ 31 | # │ │ 32 | # └────┘ 33 | # ``` 34 | Default 35 | 36 | # Like the default type but with doubled lines: 37 | # 38 | # ```text 39 | # ╔════╗ 40 | # ║ ║ 41 | # ╚════╝ 42 | # ``` 43 | Double 44 | 45 | # Like the default type but with round corners: 46 | # 47 | # ```text 48 | # ╭────╮ 49 | # │ │ 50 | # ╰────╯ 51 | # ``` 52 | Round 53 | end 54 | 55 | def color=(@color : Colorize::Color) 56 | init 57 | end 58 | 59 | def width=(@width) 60 | init 61 | end 62 | 63 | def height=(@height) 64 | init 65 | end 66 | 67 | def type=(@type) 68 | init 69 | end 70 | 71 | @rectangle : Colorize::Object(String) | String = "" 72 | 73 | private macro init 74 | if @width == 1 75 | @rectangle = "█\n"*@height 76 | elsif @height == 1 77 | @rectangle = "█"*@width 78 | elsif @width == 0 || @height == 0 79 | else 80 | width, height = @width-2, @height-2 81 | spaces = " "*width 82 | case @type 83 | when .default? 84 | middle = "─"*width 85 | @rectangle = "┌#{middle}┐\n#{"│#{spaces}│\n"*height}└#{middle}┘" 86 | when .double? 87 | middle = "═"*width 88 | @rectangle = "╔#{middle}╗\n#{"║#{spaces}║\n"*height}╚#{middle}╝" 89 | when .round? 90 | middle = "─"*width 91 | @rectangle = "╭#{middle}╮\n#{"│#{spaces}│\n"*height}╰#{middle}╯" 92 | end 93 | end 94 | @rectangle = @rectangle.as(String).rchop('\n') 95 | if @color != Colorize::ColorANSI::Default 96 | @rectangle = @rectangle.as(String).colorize(color) 97 | end 98 | end 99 | 100 | # Initializes a new `Rectangle`. 101 | # 102 | # For specifying the type, you can use the top-level constants 103 | # `Default`, `Double` and `Round`. 104 | def initialize(@x : Int32, @y : Int32, @width : Int32, @height : Int32, @type : Type = Default, @color : Colorize::Color = Colorize::ColorANSI::Default) 105 | init 106 | end 107 | 108 | # Inserts the rectangle into the buffer. 109 | def draw 110 | Lime.printf(@rectangle, @x, @y) 111 | end 112 | end 113 | 114 | # A drawable filled rectangle. 115 | struct FilledRectangle 116 | property x, y, height 117 | getter width, material, color 118 | 119 | def width=(@width) 120 | initialize(@x, @y, @width, @height, @material, @color) 121 | end 122 | 123 | def material=(@material) 124 | initialize(@x, @y, @width, @height, @material, @color) 125 | end 126 | 127 | @tile : Colorize::Object(String) | String 128 | 129 | # Initializes a new `FilledRectangle`. 130 | # 131 | # *material* is what the filled rectangle will be built with: 132 | # 133 | # ``` 134 | # FilledRectangle.new(0, 0, 5, 5, "#").draw 135 | # ``` 136 | # ````text 137 | # ##### 138 | # ##### 139 | # ##### 140 | # ##### 141 | # ##### 142 | # ``` 143 | def initialize(@x : Int32, @y : Int32, @width : Int32, @height : Int32, @material : String = "█", @color : Colorize::Color = Colorize::ColorANSI::Default) 144 | if @height > 0 145 | @tile = @material*@width 146 | if !color.default? 147 | @tile = @tile.as(String).colorize(@color) 148 | end 149 | else 150 | @tile = "" 151 | end 152 | end 153 | 154 | # Inserts the filled rectangle into the buffer. 155 | def draw 156 | @height.times do |i| 157 | Lime.print(@tile, @x, @y + i) 158 | end 159 | end 160 | end 161 | 162 | # A drawable circle. 163 | struct Circle 164 | getter x, y, radius, color 165 | 166 | def radius=(@radius) 167 | @internal_radius = @radius - 1 168 | end 169 | 170 | def x=(@x) 171 | @internal_x = @x + @radius 172 | end 173 | 174 | def y=(@y) 175 | @internal_y = @y + @radius 176 | end 177 | 178 | @internal_x : Int32 179 | @internal_y : Int32 180 | @internal_radius : Int32 181 | 182 | # Initializes a new `Circle`. 183 | def initialize(@x : Int32, @y : Int32, @radius : Int32, @color : Colorize::Color = Colorize::ColorANSI::Default) 184 | @internal_radius = @radius - 1 185 | @internal_x = @x + @radius 186 | @internal_y = @y + @radius 187 | end 188 | 189 | # Inserts the circle into the buffer. 190 | # 191 | # This method uses the [midpoint circle algorithm](https://en.wikipedia.org/wiki/Midpoint_circle_algorithm). 192 | def draw 193 | x1 = @internal_radius 194 | y1 = 0 195 | err = -@internal_radius 196 | while x1 >= y1 197 | Lime.pixel(@internal_x + x1, @internal_y + y1, @color) 198 | Lime.pixel(@internal_x + y1, @internal_y + x1, @color) 199 | Lime.pixel(@internal_x - y1, @internal_y + x1, @color) 200 | Lime.pixel(@internal_x - x1, @internal_y + y1, @color) 201 | Lime.pixel(@internal_x - x1, @internal_y - y1, @color) 202 | Lime.pixel(@internal_x - y1, @internal_y - x1, @color) 203 | Lime.pixel(@internal_x + y1, @internal_y - x1, @color) 204 | Lime.pixel(@internal_x + x1, @internal_y - y1, @color) 205 | 206 | if err <= 0 207 | y1 += 1 208 | err += y1*2 209 | end 210 | 211 | if err > 0 212 | x1 -= 1 213 | err -= x1*2 214 | end 215 | end 216 | end 217 | end 218 | 219 | # A drawable sequence of pixels. 220 | struct Pixels 221 | property x, y 222 | getter width : Int32, height : Int32 223 | 224 | # Raised when an invalid color character is found in a pixel string. 225 | class Error < Exception 226 | end 227 | 228 | @pixels = [] of Colorize::ColorRGB | Colorize::ColorANSI | Symbol 229 | 230 | # Initializes new `Pixels` from an image. 231 | # 232 | # *path* must lead to an PNG-encoded image. 233 | def initialize(path : String, @x : Int32, @y : Int32) 234 | canvas = StumpyPNG.read(path) 235 | @width, @height = canvas.width, canvas.height 236 | 237 | i = 1 238 | canvas.pixels.each do |pixel| 239 | color = Colorize::ColorRGB.new(*pixel.to_rgb8) 240 | 241 | if pixel.alpha == 0 242 | @pixels << :skip 243 | else 244 | @pixels << color 245 | end 246 | if i == @width 247 | @pixels << :newline 248 | i = 0 249 | end 250 | i += 1 251 | end 252 | @pixels.pop 253 | end 254 | 255 | private COLOR_CHARACTERS = { 256 | {'1', "default"}, 257 | {'0', "black"}, 258 | {'9', "dark_gray"}, 259 | {'6', "light_gray"}, 260 | {'w', "white"}, 261 | {'r', "red"}, 262 | {'g', "green"}, 263 | {'b', "blue"}, 264 | {'y', "yellow"}, 265 | {'m', "magenta"}, 266 | {'c', "cyan"}, 267 | {'R', "light_red"}, 268 | {'G', "light_green"}, 269 | {'B', "light_blue"}, 270 | {'Y', "light_yellow"}, 271 | {'M', "light_magenta"}, 272 | {'C', "light_cyan"}, 273 | } 274 | 275 | {% begin %} 276 | # Initializes new `Pixels` from a string. 277 | # 278 | # Iterates through every character of *color_characters* and every time a color character 279 | # is found, it's replaced with its color. 280 | # 281 | # Available color characters are: 282 | {% for char in COLOR_CHARACTERS %} 283 | # * `'{{char[0].id}}'`: {{char[1].gsub(/_/, " ").id}} 284 | {% end %} 285 | # 286 | # Comments are also allowed in the string. 287 | # 288 | # Example string: 289 | # 290 | # ``` 291 | # # A flower: 292 | # RRR 293 | # RYYYR # Head 294 | # RRR 295 | # g 296 | # G g G 297 | # GGgGG # Stem 298 | # GgG 299 | # g 300 | # ``` 301 | # 302 | # becomes: 303 | # 304 | # ![flower](https://i.imgur.com/XaxqEjB.png) 305 | # 306 | # Raises `Error` when an invalid color character is found in *color_characters*. 307 | def initialize(@x : Int32, @y : Int32, color_characters : String) 308 | # Remove comments: 309 | lines = color_characters.lines.map do |line| 310 | next "" if line[0] == '#' 311 | 312 | line[0..(line.index('#') || 0)-1] 313 | end 314 | 315 | @width = lines.max_by { |line| line.size }.size 316 | @height = lines.size 317 | color_characters = lines.join('\n') 318 | 319 | color_characters.each_char do |char| 320 | @pixels << (case char 321 | when '\n' 322 | :newline 323 | when .whitespace? 324 | :skip 325 | {% for char in COLOR_CHARACTERS %} 326 | when {{char[0]}} then Colorize::ColorANSI::{{char[1].camelcase.id}} 327 | {% end %} 328 | else 329 | raise Error.new("Invalid color character: #{char}") 330 | end) 331 | end 332 | end 333 | {% end %} 334 | 335 | # Invokes the given block for each of the pixels, replacing the pixel with the pixel returned by the block. 336 | # The block must return a `Tuple(UInt8, UInt8, UInt8)`. 337 | # ``` 338 | # # Invert colors of an image: 339 | # image.map { |pixel| {255u8 - pixel.red, 255u8 - pixel.green, 255u8 - pixel.blue} } 340 | # ``` 341 | def map 342 | @pixels.map! do |pixel| 343 | if pixel.is_a?(Colorize::ColorRGB) 344 | Colorize::ColorRGB.new(*yield pixel) 345 | else 346 | pixel 347 | end 348 | end 349 | end 350 | 351 | # Inserts the pixels into the buffer. 352 | def draw 353 | x, y = 0, 0 354 | @pixels.each do |pixel| 355 | case pixel 356 | when :newline 357 | y += 1 358 | x = 0 359 | when :skip 360 | x += 1 361 | else 362 | Lime.pixel(@x + x, @y + y, pixel.as(Colorize::ColorRGB | Colorize::ColorANSI)) 363 | x += 1 364 | end 365 | end 366 | end 367 | end 368 | end 369 | end 370 | 371 | include Lime::Drawables 372 | 373 | # The default rectangle type: `Rectangle::Type::Default`. 374 | Default = Rectangle::Type::Default 375 | 376 | # The double rectangle type: `Rectangle::Type::Double`. 377 | Double = Rectangle::Type::Double 378 | 379 | # The round rectangle type: `Rectangle::Type::Round`. 380 | Round = Rectangle::Type::Round 381 | -------------------------------------------------------------------------------- /src/lime/modules.cr: -------------------------------------------------------------------------------- 1 | module Lime 2 | # Modules for interacting with various system functionalities. 3 | # 4 | # The modules are available on the top-level by default. 5 | module Modules 6 | # The window of the console. 7 | module Window 8 | extend self 9 | 10 | # Returns the height of the window in cells, one-based. 11 | class_getter width_cells : Int32 = `tput cols`.to_i 12 | 13 | # Returns the width of the window in cells, one-based. 14 | class_getter height_cells : Int32 = `tput lines`.to_i 15 | 16 | # Returns the width of the window in cells, zero-based. 17 | class_getter width : Int32 = @@width_cells - 1 18 | 19 | # Returns the height of the window in cells, zero-based. 20 | class_getter height : Int32 = @@height_cells - 1 21 | 22 | # Returns the width of the window in pixels. 23 | def self.width_pixels 24 | @@width 25 | end 26 | 27 | # Returns the height of the window in pixels. 28 | class_getter height_pixels : Int32 = @@height*2 29 | 30 | # Updates the values of `width`, `height`, `width_cells`, `height_cells` and 31 | # `width_pixels`, `height_pixels`. 32 | def update 33 | @@width_cells = `tput cols`.to_i 34 | @@height_cells = `tput lines`.to_i 35 | @@width = @@width_cells - 1 36 | @@height = @@height_cells - 1 37 | @@height_pixels = @@height*2 38 | end 39 | 40 | # Sets the title of the window to *title*. 41 | def title=(title) 42 | print "\e]0;#{title}\a" 43 | end 44 | 45 | # Disables or enables the alternative screen buffer for the console which doesn't have a scrollbar. 46 | def scrollbar=(scrollbar : Bool) 47 | system(scrollbar ? "tput rmcup" : "tput smcup") 48 | end 49 | end 50 | 51 | # The cursor of the console. 52 | module Cursor 53 | extend self 54 | 55 | # Moves the cursor up by *cells*. 56 | def move_up(cells = 1) 57 | print "\e[#{cells}A" 58 | end 59 | 60 | # Moves the cursor down by *cells*. 61 | def move_down(cells = 1) 62 | print "\e[#{cells}B" 63 | end 64 | 65 | # Moves the cursor right by *cells*. 66 | def move_right(cells = 1) 67 | print "\e[#{cells}C" 68 | end 69 | 70 | # Moves the cursor left by *cells*. 71 | def move_left(cells = 1) 72 | print "\e[#{cells}D" 73 | end 74 | 75 | # Shows or hides the cursor. 76 | def visible=(visible : Bool) 77 | print(visible ? "\e[?25h" : "\e[?25l") 78 | end 79 | 80 | # Sets the position of the cursor to *position*. 81 | # ``` 82 | # Cursor.position = {5, 5} 83 | # 84 | # # The cursor is now at position 5, 5 85 | # ``` 86 | def position=(position : Tuple(Int32, Int32)) 87 | # Row and column for the ANSI escape sequence need to be one-based 88 | print "\e[#{position[1] + 1};#{position[0] + 1}H" 89 | end 90 | end 91 | 92 | # The mouse pointer of the system. 93 | # 94 | # NOTE: The default mouse event report mode is `Mode::Off` which won't report any mouse events. 95 | # To set a mouse event report mode, use `mode=`. 96 | module Mouse 97 | extend self 98 | 99 | # Returns `true` if the mouse event report range is extended, otherwise `false`. 100 | class_getter extended = false 101 | 102 | # Returns the current mouse event report mode. 103 | class_getter mode : Mode = Mouse::Mode::Off 104 | 105 | # The mouse event report mode. 106 | # 107 | # The default mode is `Off`. 108 | enum Mode 109 | # Reports no mouse events. 110 | Off 111 | 112 | # Reports only mouse click events. 113 | Click 114 | 115 | # Reports only mouse drag events. 116 | Drag 117 | 118 | # Reports all mouse events: movements, clicks and drags. 119 | All 120 | end 121 | 122 | # Sets the mouse event report mode to *mode*. 123 | def mode=(@@mode : Mode) 124 | case @@mode 125 | when .off? 126 | off 127 | when .click? 128 | print "\e[?1000h" 129 | when .drag? 130 | print "\e[?1002h" 131 | when .all? 132 | print "\e[?1003h" 133 | end 134 | end 135 | 136 | private def off 137 | case @@mode 138 | when .click? 139 | print "\e[?1000l" 140 | when .drag? 141 | print "\e[?1002l" 142 | when .all? 143 | print "\e[?1003l" 144 | end 145 | end 146 | 147 | at_exit do 148 | off 149 | Mouse.extend(false) if @@extended 150 | end 151 | 152 | # Extends the mouse event report range if *bool* is `true`, otherwise resets the range. 153 | # 154 | # The default X10 mouse protocol doesn't support mouse event reporting with 155 | # x, y coordinates greater than 94. 156 | # This extends the range to be greater than 94 by using the 1006 SGR (Select Graphic Rendition) mouse protocol. 157 | def extend(bool = true) # The argument can't be called `extend` because it's a keyword 158 | if bool 159 | print "\e[?1006h" 160 | @@extended = true 161 | else 162 | print "\e[?1006l" 163 | @@extended = false 164 | end 165 | end 166 | 167 | # A mouse event. 168 | record Event, x : Int32, y : Int32, type : Symbol do 169 | # Returns the type of mouse event: 170 | # 171 | # * `:release` if a mouse button has been released. 172 | # 173 | # * `:left` if the left mouse button has been pressed. 174 | # * `:wheel` if the mouse wheel button has been pressed. 175 | # * `:right` if the right mouse button has been pressed. 176 | # 177 | # * `:wheel_up` if the mouse wheel has been scrolled up. 178 | # * `:wheel_down` if the mouse wheel has been scrolled down. 179 | # 180 | # Only if `mode` is `Mode::Drag` or `Mode::All`: 181 | # 182 | # * `:left_drag` if the left mouse button has been pressed and the mouse has been moved. 183 | # * `:wheel_drag` if the mouse wheel button has been pressed and the mouse has been moved. 184 | # * `:right_drag` if the right mouse button has been pressed and the mouse has been moved. 185 | # 186 | # Only if `mode` is `Mode::All`: 187 | # 188 | # * `:move` if the mouse has been moved. 189 | getter type : Symbol 190 | 191 | # Returns the kind of mouse event based on `type`: 192 | # 193 | # * `:release` if a mouse button has been released. 194 | # * `:click` if a mouse button has been pressed. 195 | # * `:wheel` if the wheel has been scrolled. 196 | # * `:drag` if `mode` is `Mode::Drag` or `Mode::All` and a mouse button has been pressed and the mouse has been moved. 197 | # * `:move` if `mode` is `Mode::All` and the mouse has been moved. 198 | def kind : Symbol 199 | case type 200 | when :left, :wheel, :right 201 | :click 202 | when :left_drag, :wheel_drag, :right_drag 203 | :drag 204 | when :wheel_up, :wheel_down 205 | :wheel 206 | when :release 207 | :release 208 | else 209 | :move 210 | end 211 | end 212 | end 213 | 214 | # Waits for input and returns `Event` if the input is a mouse event, 215 | # `nil` if the input is not a mouse event or if `mode` is `Mode::Off`. 216 | # 217 | # NOTE: Ctrl+C is caught by this method and will not be handled by the system. 218 | def get : Event? 219 | return nil if mode.off? 220 | 221 | return get_1006 if @@extended 222 | 223 | input = STDIN.raw do |io| 224 | string = String.new(io.peek) 225 | io.skip(string.size) 226 | string 227 | end 228 | 229 | return nil if input.count('\e') == 0 || input.size < 6 230 | 231 | type = case input[3] 232 | when ' ' then :left 233 | when '!' then :wheel 234 | when '"' then :right 235 | when '#' then :release 236 | when '@' then :left_drag 237 | when 'A' then :wheel_drag 238 | when 'B' then :right_drag 239 | when 'C' then :move 240 | when '`' then :wheel_up 241 | when 'a' then :wheel_down 242 | else 243 | return nil 244 | end 245 | 246 | # 32 to decode the character, 1 more to make the numbers zero-based 247 | Event.new(input[4].ord - 33, input[5].ord - 33, type) 248 | end 249 | 250 | # :nodoc: 251 | def get_1006 : Event? 252 | input = STDIN.raw do |io| 253 | string = String.new(io.peek) 254 | io.skip(string.size) 255 | string 256 | end 257 | 258 | if input 259 | return nil if input.count('\e') == 0 || input.size < 9 260 | else 261 | return nil 262 | end 263 | 264 | input = input.lchop("\e[<").rchop.split(';') 265 | 266 | type = case input[0] 267 | when "0" then :left 268 | when "1" then :wheel 269 | when "2" then :right 270 | when "3" then :release 271 | when "32" then :left_move 272 | when "33" then :wheel_move 273 | when "34" then :right_move 274 | when "35" then :move 275 | when "64" then :wheel_up 276 | when "65" then :wheel_down 277 | else 278 | return nil 279 | end 280 | 281 | Event.new(input[1].to_i - 1, input[2].to_i - 1, type) 282 | end 283 | 284 | # Returns the mouse event happening in the moment this method is called as `Event`, 285 | # `nil` if the input is not a mouse event or if `mode` is `Mode::Off`. 286 | # 287 | # NOTE: Ctrl+C can be caught by this method and will not be handled by the system. 288 | def peek : Event? 289 | STDIN.read_timeout = 0.01 290 | Mouse.get 291 | rescue IO::Timeout 292 | ensure 293 | STDIN.read_timeout = nil 294 | end 295 | end 296 | end 297 | end 298 | 299 | include Lime::Modules 300 | --------------------------------------------------------------------------------