1 module fluid.backend.simpledisplay; 2 3 version (Have_arsd_official_simpledisplay): 4 5 debug (Fluid_BuildMessages) { 6 pragma(msg, "Fluid: Building with arsd.simpledisplay support"); 7 } 8 9 import arsd.simpledisplay; 10 11 import std.algorithm; 12 import std.datetime.stopwatch; 13 14 import fluid.backend; 15 16 17 @safe: 18 19 20 private { 21 alias Rectangle = fluid.backend.Rectangle; 22 alias Color = fluid.backend.Color; 23 alias Image = fluid.backend.Image; 24 alias MouseButton = fluid.backend.MouseButton; 25 } 26 27 class SimpledisplayBackend : FluidBackend { 28 29 SimpleWindow window; 30 31 private enum InputState { 32 up, 33 pressed, 34 down, 35 released, 36 repeated, 37 } 38 39 private { 40 41 Vector2 _mousePosition; 42 int _dpi; 43 float _scale = 1; 44 bool _hasJustResized; 45 StopWatch _stopWatch; 46 float _deltaTime; 47 Rectangle _scissors; 48 bool _scissorsEnabled; 49 FluidMouseCursor _cursor; 50 51 TextureReaper _reaper; 52 53 /// Recent input events for keyboard and mouse. 54 InputState[KeyboardKey.max+1] _keyboardState; 55 InputState[MouseButton.max+1] _mouseState; 56 int _scroll; 57 // gamepads? 58 59 /// Characters typed by the user, awaiting consumption. 60 const(dchar)[] _characterQueue; 61 62 // Missing from simpledisplay at the time of writing 63 extern(C) void function(GLint x, GLint y, GLsizei width, GLsizei height) glScissor; 64 65 } 66 67 // TODO non-openGL backend, maybe... 68 69 /// Initialize the backend using the given window. 70 /// 71 /// Make sure to call `SimpledisplayBackend.poll()` *after* the Fluid `draw` call, and only do it once per frame, 72 /// other Fluid might not be able to keep itself up to date with latest events. 73 /// 74 /// Please note Fluid will register its own event handlers, so if you 75 /// intend to use them, you should make sure to call whatever value was set previously. 76 /// 77 /// --- 78 /// auto oldMouseHandler = window.handleMouseEvent; 79 /// window.handleMouseEvent = (MouseEvent event) { 80 /// oldMouseHandler(event); 81 /// // ... do your stuff ... 82 /// }; 83 /// --- 84 /// 85 /// Gamepad input is not supported for simpledisplay. 86 this(SimpleWindow window) { 87 88 this.window = window; 89 90 () @trusted { 91 this.glScissor = cast(typeof(glScissor)) glbindGetProcAddress("glScissor"); 92 }(); 93 94 updateDPI(); 95 _stopWatch.start(); 96 97 auto oldMouseHandler = window.handleMouseEvent; 98 auto oldKeyHandler = window.handleKeyEvent; 99 auto oldCharHandler = window.handleCharEvent; 100 auto oldWindowResized = window.windowResized; 101 auto oldOnDpiChanged = window.onDpiChanged; 102 103 // Register a mouse handler 104 this.window.handleMouseEvent = (MouseEvent event) { 105 106 if (oldMouseHandler) oldMouseHandler(event); 107 108 // Scroll event 109 if (const scroll = event.button.scrollValue) { 110 111 if (event.type == event.type.buttonPressed) { 112 113 _scroll = scroll; 114 115 } 116 117 } 118 119 // Other events 120 else final switch (event.type) { 121 122 // Update mouse position 123 case event.type.motion: 124 _mousePosition = Vector2(event.x, event.y); 125 return; 126 127 // Update button state 128 case event.type.buttonPressed: 129 _mouseState[event.button.toFluid] = InputState.pressed; 130 return; 131 132 case event.type.buttonReleased: 133 _mouseState[event.button.toFluid] = InputState.released; 134 return; 135 136 } 137 138 }; 139 140 // Register a keyboard handler 141 this.window.handleKeyEvent = (KeyEvent event) { 142 143 if (oldKeyHandler) oldKeyHandler(event); 144 145 const key = event.key.toFluid; 146 147 // Released 148 if (!event.pressed) 149 _keyboardState[key] = InputState.released; 150 151 // Repeat 152 else if (isDown(key)) 153 _keyboardState[key] = InputState.repeated; 154 155 // Pressed 156 else 157 _keyboardState[key] = InputState.pressed; 158 159 }; 160 161 // Register character handler 162 this.window.handleCharEvent = (dchar character) { 163 164 import std.uni; 165 166 if (oldCharHandler) oldCharHandler(character); 167 168 // Ignore control characters 169 if (character.isControl) return; 170 171 // Send new characters 172 _characterQueue ~= character; 173 174 }; 175 176 // Register a resize handler 177 this.window.windowResized = (int width, int height) { 178 179 if (oldWindowResized) oldWindowResized(width, height); 180 181 // Update window size 182 _hasJustResized = true; 183 glViewport(0, 0, width, height); 184 185 }; 186 187 this.window.onDpiChanged = () { 188 189 if (oldOnDpiChanged) oldOnDpiChanged(); 190 191 // Update window size 192 _hasJustResized = true; 193 updateDPI(); 194 195 }; 196 197 } 198 199 bool isPressed(MouseButton button) const { 200 201 return _mouseState[button] == InputState.pressed; 202 203 } 204 205 bool isReleased(MouseButton button) const { 206 207 return _mouseState[button] == InputState.released; 208 209 } 210 211 bool isDown(MouseButton button) const { 212 213 return _mouseState[button].among(InputState.pressed, InputState.down) != 0; 214 215 } 216 217 bool isUp(MouseButton button) const { 218 219 return _mouseState[button].among(InputState.released, InputState.up) != 0; 220 221 } 222 223 bool isPressed(KeyboardKey key) const { 224 225 return _keyboardState[key] == InputState.pressed; 226 227 } 228 229 bool isReleased(KeyboardKey key) const { 230 231 return _keyboardState[key] == InputState.released; 232 233 } 234 235 bool isDown(KeyboardKey key) const { 236 237 return _keyboardState[key].among(InputState.pressed, InputState.repeated, InputState.down) != 0; 238 239 } 240 241 bool isUp(KeyboardKey key) const { 242 243 return _keyboardState[key].among(InputState.released, InputState.up) != 0; 244 245 } 246 247 bool isRepeated(KeyboardKey key) const { 248 249 return _keyboardState[key] == InputState.repeated; 250 251 } 252 253 254 dchar inputCharacter() { 255 256 // No characters in queue 257 if (_characterQueue.length == 0) 258 return '\0'; 259 260 // Pop the first character 261 auto result = _characterQueue[0]; 262 _characterQueue = _characterQueue[1..$]; 263 return result; 264 265 } 266 267 int isPressed(GamepadButton button) const 268 => 0; 269 int isReleased(GamepadButton button) const 270 => 0; 271 int isDown(GamepadButton button) const 272 => 0; 273 int isUp(GamepadButton button) const 274 => 1; 275 int isRepeated(GamepadButton button) const 276 => 0; 277 278 private void updateDPI() @trusted { 279 280 _dpi = either(window.actualDpi, 96); 281 282 } 283 284 /// Update event state. To be called *after* drawing. 285 void poll() { 286 287 // Calculate delta time 288 _deltaTime = _stopWatch.peek.total!"msecs" / 1000f; 289 _stopWatch.reset(); 290 291 // Reset frame state 292 _hasJustResized = false; 293 _characterQueue = null; 294 _scroll = 0; 295 296 foreach (ref state; _keyboardState) { 297 if (state == state.pressed) state = state.down; 298 if (state == state.repeated) state = state.down; 299 if (state == state.released) state = state.up; 300 } 301 302 foreach (ref state; _mouseState) { 303 if (state == state.pressed) state = state.down; 304 if (state == state.released) state = state.up; 305 } 306 307 } 308 309 Vector2 mousePosition(Vector2 position) @trusted { 310 311 auto positionRay = toSdpyCoords(position); 312 window.warpMouse(cast(int) positionRay.x, cast(int) positionRay.y); 313 return _mousePosition = position; 314 315 } 316 317 Vector2 mousePosition() const { 318 319 return toFluidCoords(_mousePosition); 320 321 } 322 323 Vector2 scroll() const { 324 325 return Vector2(0, _scroll); 326 327 } 328 329 float deltaTime() const { 330 331 return _deltaTime; 332 333 } 334 335 bool hasJustResized() const { 336 337 return _hasJustResized; 338 339 } 340 341 Vector2 windowSize(Vector2 size) @trusted { 342 343 auto sizeRay = toSdpyCoords(size); 344 window.resize(cast(int) sizeRay.x, cast(int) sizeRay.y); 345 return size; 346 347 } 348 349 Vector2 windowSize() const { 350 351 return toFluidCoords(Vector2(window.width, window.height)); 352 353 } 354 355 /// Convert window coordinates to OpenGL coordinates; done *after* toSdpyCoords. 356 Vector2 toGL(Vector2 coords) { 357 358 return Vector2( 359 coords.x / window.width * 2 - 1, 360 1 - coords.y / window.height * 2 361 ); 362 363 } 364 365 /// Create a vertex at given screenspace position 366 void vertex(Vector2 coords) @trusted { 367 368 glVertex2f(toGL(coords).tupleof); 369 370 } 371 372 float scale() const { 373 374 return _scale; 375 376 } 377 378 float scale(float value) { 379 380 return _scale = value; 381 382 } 383 384 Vector2 dpi() const @trusted { 385 386 return Vector2(_dpi, _dpi) * scale; 387 388 } 389 390 Vector2 toSdpyCoords(Vector2 position) const @trusted { 391 392 return Vector2(position.x * hidpiScale.x, position.y * hidpiScale.y); 393 394 } 395 396 Rectangle toSdpyCoords(Rectangle rec) const @trusted { 397 398 return Rectangle( 399 rec.x * hidpiScale.x, 400 rec.y * hidpiScale.y, 401 rec.width * hidpiScale.x, 402 rec.height * hidpiScale.y, 403 ); 404 405 } 406 407 Vector2 toFluidCoords(Vector2 position) const @trusted { 408 409 return Vector2(position.x / hidpiScale.x, position.y / hidpiScale.y); 410 411 } 412 413 Vector2 toFluidCoords(float x, float y) const @trusted { 414 415 return Vector2(x / hidpiScale.x, y / hidpiScale.y); 416 417 } 418 419 Rectangle toFluidCoords(Rectangle rec) const @trusted { 420 421 return Rectangle( 422 rec.x / hidpiScale.x, 423 rec.y / hidpiScale.y, 424 rec.width / hidpiScale.x, 425 rec.height / hidpiScale.y, 426 ); 427 428 } 429 430 Rectangle area(Rectangle rect) @trusted { 431 432 auto rectRay = toSdpyCoords(rect); 433 434 glEnable(GL_SCISSOR_TEST); 435 glScissor( 436 cast(int) rectRay.x, 437 cast(int) (window.height - rectRay.y - rectRay.height), 438 cast(int) rectRay.width, 439 cast(int) rectRay.height, 440 ); 441 _scissorsEnabled = true; 442 443 return _scissors = rect; 444 445 } 446 447 Rectangle area() const { 448 449 if (_scissorsEnabled) 450 return _scissors; 451 else 452 return Rectangle(0, 0, windowSize.tupleof); 453 454 } 455 456 void restoreArea() @trusted { 457 458 glDisable(GL_SCISSOR_TEST); 459 _scissorsEnabled = false; 460 461 } 462 463 FluidMouseCursor mouseCursor(FluidMouseCursor cursor) @trusted { 464 465 // Hide the cursor 466 if (cursor.system == cursor.system.none) { 467 window.hideCursor(); 468 } 469 470 // Show the cursor 471 else { 472 window.showCursor(); 473 window.cursor = cursor.toSimpleDisplay; 474 } 475 476 return _cursor = cursor; 477 478 } 479 480 FluidMouseCursor mouseCursor() const { 481 482 return _cursor; 483 484 } 485 486 TextureReaper* reaper() return scope { 487 488 return &_reaper; 489 490 } 491 492 Texture loadTexture(Image image) @system { 493 494 Texture result; 495 result.width = image.width; 496 result.height = image.height; 497 498 // Create an OpenGL texture 499 glGenTextures(1, &result.id); 500 glBindTexture(GL_TEXTURE_2D, result.id); 501 502 // Prepare the tombstone 503 result.tombstone = reaper.makeTombstone(this, result.id); 504 505 // No filtering 506 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 507 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 508 509 // Repeat on 510 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); 511 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); 512 513 // Upload the data 514 glTexImage2D( 515 516 // 2D texture, no mipmaps, four channels 517 GL_TEXTURE_2D, 0, GL_RGBA, 518 519 // Size 520 image.width, image.height, 521 522 // No border 523 0, 524 525 // Formatted as R8B8G8A8 526 GL_RGBA, GL_UNSIGNED_BYTE, image.pixels.ptr, 527 528 ); 529 530 // Unbind the texture 531 glBindTexture(GL_TEXTURE_2D, 0); 532 533 return result; 534 535 } 536 537 Image loadImage(string filename) @system { 538 539 version (Have_arsd_official_image_files) { 540 541 import arsd.image; 542 543 // Load the image 544 auto image = loadImageFromFile(filename).getAsTrueColorImage; 545 546 // Convert to a Fluid image 547 Image result; 548 result.pixels = cast(Color[]) image.imageData.bytes; 549 result.width = image.width; 550 result.height = image.height; 551 return result; 552 553 } 554 555 else assert(false, "arsd-official:image_files is required to load images from files"); 556 557 } 558 559 Texture loadTexture(string filename) @system { 560 561 return loadTexture(loadImage(filename)); 562 563 } 564 565 /// Destroy a texture 566 void unloadTexture(uint id) @system { 567 568 if (id == 0) return; 569 570 glDeleteTextures(1, &id); 571 572 } 573 574 private void openglDraw() @trusted { 575 576 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 577 glEnable(GL_BLEND); 578 glLoadIdentity(); 579 580 // This must be present, otherwise the AMD Linux driver will hang when the window is resized. I don't have the 581 // slightest clue why. 582 if (auto error = glGetError()) { 583 import std.stdio; 584 debug writeln(error); 585 } 586 587 } 588 589 void drawLine(Vector2 start, Vector2 end, Color color) @trusted { 590 591 openglDraw(); 592 glBegin(GL_LINES); 593 594 glColor4ub(color.tupleof); 595 vertex(toSdpyCoords(start)); 596 vertex(toSdpyCoords(end)); 597 598 glEnd(); 599 600 } 601 602 void drawTriangle(Vector2 a, Vector2 b, Vector2 c, Color color) @trusted { 603 604 openglDraw(); 605 glBegin(GL_TRIANGLES); 606 glColor4ub(color.tupleof); 607 vertex(toSdpyCoords(a)); 608 vertex(toSdpyCoords(b)); 609 vertex(toSdpyCoords(c)); 610 glEnd(); 611 612 } 613 614 void drawRectangle(Rectangle rectangle, Color color) @trusted { 615 616 drawRectangleImpl(toSdpyCoords(rectangle), color); 617 618 } 619 620 private void drawRectangleImpl(Rectangle rectangle, Color color) @trusted { 621 622 import fluid.utils; 623 624 openglDraw(); 625 glBegin(GL_TRIANGLES); 626 glColor4ub(color.tupleof); 627 628 // d--c 629 // | /| 630 // |/ | 631 // a--b 632 const a = start(rectangle) + Vector2(0, rectangle.height); 633 const b = end(rectangle); 634 const d = start(rectangle); 635 const c = start(rectangle) + Vector2(rectangle.width, 0); 636 637 // First triangle 638 glTexCoord2f(0, 0); 639 vertex(d); 640 glTexCoord2f(0, 1); 641 vertex(a); 642 glTexCoord2f(1, 0); 643 vertex(c); 644 645 // Second triangle 646 glTexCoord2f(1, 0); 647 vertex(c); 648 glTexCoord2f(0, 1); 649 vertex(a); 650 glTexCoord2f(1, 1); 651 vertex(b); 652 653 glEnd(); 654 655 } 656 657 void drawTexture(Texture texture, Rectangle rectangle, Color tint, string altText) @trusted 658 in (false) 659 do { 660 661 // TODO filtering? 662 drawTextureImpl(texture, rectangle, tint, altText, true); 663 664 } 665 666 void drawTextureAlign(Texture texture, Rectangle rectangle, Color tint, string altText) @trusted 667 in (false) 668 do { 669 670 drawTextureImpl(texture, rectangle, tint, altText, true); 671 672 } 673 674 @trusted 675 private void drawTextureImpl(Texture texture, Rectangle rectangle, Color tint, string altText, bool alignPixels) { 676 677 import std.math; 678 679 rectangle = toSdpyCoords(rectangle); 680 681 if (alignPixels) { 682 rectangle.x = floor(rectangle.x); 683 rectangle.y = floor(rectangle.y); 684 } 685 686 glEnable(GL_TEXTURE_2D); 687 glBindTexture(GL_TEXTURE_2D, texture.id); 688 drawRectangleImpl(rectangle, tint); 689 glBindTexture(GL_TEXTURE_2D, 0); 690 691 } 692 693 } 694 695 int scrollValue(arsd.simpledisplay.MouseButton button) { 696 697 switch (button) { 698 699 case button.wheelUp: return -1; 700 case button.wheelDown: return +1; 701 default: return 0; 702 703 } 704 705 } 706 707 MouseButton toFluid(arsd.simpledisplay.MouseButton button) { 708 709 switch (button) { 710 711 default: 712 case button.wheelUp: 713 case button.wheelDown: 714 case button.none: return MouseButton.none; 715 case button.left: return MouseButton.left; 716 case button.middle: return MouseButton.middle; 717 case button.right: return MouseButton.right; 718 case button.backButton: return MouseButton.back; 719 case button.forwardButton: return MouseButton.forward; 720 721 } 722 723 } 724 725 KeyboardKey toFluid(arsd.simpledisplay.Key key) { 726 727 switch (key) { 728 729 default: return KeyboardKey.none; 730 case key.Escape: return KeyboardKey.escape; 731 case key.Backspace: return KeyboardKey.backspace; 732 case key.F1: return KeyboardKey.f1; 733 case key.F2: return KeyboardKey.f2; 734 case key.F3: return KeyboardKey.f3; 735 case key.F4: return KeyboardKey.f4; 736 case key.F5: return KeyboardKey.f5; 737 case key.F6: return KeyboardKey.f6; 738 case key.F7: return KeyboardKey.f7; 739 case key.F8: return KeyboardKey.f8; 740 case key.F9: return KeyboardKey.f9; 741 case key.F10: return KeyboardKey.f10; 742 case key.F11: return KeyboardKey.f11; 743 case key.F12: return KeyboardKey.f12; 744 case key.PrintScreen: return KeyboardKey.printScreen; 745 case key.ScrollLock: return KeyboardKey.scrollLock; 746 case key.Pause: return KeyboardKey.pause; 747 case key.Grave: return KeyboardKey.grave; 748 case key.N0: return KeyboardKey.digit0; 749 case key.N1: return KeyboardKey.digit1; 750 case key.N2: return KeyboardKey.digit2; 751 case key.N3: return KeyboardKey.digit3; 752 case key.N4: return KeyboardKey.digit4; 753 case key.N5: return KeyboardKey.digit5; 754 case key.N6: return KeyboardKey.digit6; 755 case key.N7: return KeyboardKey.digit7; 756 case key.N8: return KeyboardKey.digit8; 757 case key.N9: return KeyboardKey.digit9; 758 case key.Dash: return KeyboardKey.dash; 759 case key.Equals: return KeyboardKey.equal; 760 case key.Backslash: return KeyboardKey.backslash; 761 case key.Insert: return KeyboardKey.insert; 762 case key.Home: return KeyboardKey.home; 763 case key.PageUp: return KeyboardKey.pageUp; 764 case key.PageDown: return KeyboardKey.pageDown; 765 case key.Delete: return KeyboardKey.del; 766 case key.End: return KeyboardKey.end; 767 case key.Up: return KeyboardKey.up; 768 case key.Down: return KeyboardKey.down; 769 case key.Left: return KeyboardKey.left; 770 case key.Right: return KeyboardKey.right; 771 case key.Tab: return KeyboardKey.tab; 772 case key.Q: return KeyboardKey.q; 773 case key.W: return KeyboardKey.w; 774 case key.E: return KeyboardKey.e; 775 case key.R: return KeyboardKey.r; 776 case key.T: return KeyboardKey.t; 777 case key.Y: return KeyboardKey.y; 778 case key.U: return KeyboardKey.u; 779 case key.I: return KeyboardKey.i; 780 case key.O: return KeyboardKey.o; 781 case key.P: return KeyboardKey.p; 782 case key.LeftBracket: return KeyboardKey.leftBracket; 783 case key.RightBracket: return KeyboardKey.rightBracket; 784 case key.CapsLock: return KeyboardKey.capsLock; 785 case key.A: return KeyboardKey.a; 786 case key.S: return KeyboardKey.s; 787 case key.D: return KeyboardKey.d; 788 case key.F: return KeyboardKey.f; 789 case key.G: return KeyboardKey.g; 790 case key.H: return KeyboardKey.h; 791 case key.J: return KeyboardKey.j; 792 case key.K: return KeyboardKey.k; 793 case key.L: return KeyboardKey.l; 794 case key.Semicolon: return KeyboardKey.semicolon; 795 case key.Apostrophe: return KeyboardKey.apostrophe; 796 case key.Enter: return KeyboardKey.enter; 797 case key.Shift: return KeyboardKey.leftShift; 798 case key.Z: return KeyboardKey.z; 799 case key.X: return KeyboardKey.x; 800 case key.C: return KeyboardKey.c; 801 case key.V: return KeyboardKey.v; 802 case key.B: return KeyboardKey.b; 803 case key.N: return KeyboardKey.n; 804 case key.M: return KeyboardKey.m; 805 case key.Comma: return KeyboardKey.comma; 806 case key.Period: return KeyboardKey.period; 807 case key.Slash: return KeyboardKey.slash; 808 case key.Shift_r: return KeyboardKey.rightShift; 809 case key.Ctrl: return KeyboardKey.leftControl; 810 case key.Windows: return KeyboardKey.leftSuper; 811 case key.Alt: return KeyboardKey.leftAlt; 812 case key.Space: return KeyboardKey.space; 813 case key.Alt_r: return KeyboardKey.rightAlt; 814 case key.Windows_r: return KeyboardKey.rightSuper; 815 case key.Menu: return KeyboardKey.contextMenu; 816 case key.Ctrl_r: return KeyboardKey.rightControl; 817 case key.NumLock: return KeyboardKey.numLock; 818 case key.Divide: return KeyboardKey.keypadDivide; 819 case key.Multiply: return KeyboardKey.keypadMultiply; 820 case key.Minus: return KeyboardKey.keypadSubtract; 821 case key.Plus: return KeyboardKey.keypadSum; 822 case key.PadEnter: return KeyboardKey.keypadEnter; 823 case key.Pad0: return KeyboardKey.keypad0; 824 case key.Pad1: return KeyboardKey.keypad1; 825 case key.Pad2: return KeyboardKey.keypad2; 826 case key.Pad3: return KeyboardKey.keypad3; 827 case key.Pad4: return KeyboardKey.keypad4; 828 case key.Pad5: return KeyboardKey.keypad5; 829 case key.Pad6: return KeyboardKey.keypad6; 830 case key.Pad7: return KeyboardKey.keypad7; 831 case key.Pad8: return KeyboardKey.keypad8; 832 case key.Pad9: return KeyboardKey.keypad9; 833 case key.PadDot: return KeyboardKey.keypadDecimal; 834 835 } 836 837 } 838 839 MouseCursor toSimpleDisplay(FluidMouseCursor cursor) @trusted { 840 841 switch (cursor.system) { 842 843 default: 844 case cursor.system.systemDefault: 845 case cursor.system.none: 846 return GenericCursor.Default; 847 848 case cursor.system.pointer: return GenericCursor.Hand; 849 case cursor.system.crosshair: return GenericCursor.Cross; 850 case cursor.system.text: return GenericCursor.Text; 851 case cursor.system.allScroll: return GenericCursor.Move; 852 case cursor.system.resizeEW: return GenericCursor.SizeWe; 853 case cursor.system.resizeNS: return GenericCursor.SizeNs; 854 case cursor.system.resizeNESW: return GenericCursor.SizeNesw; 855 case cursor.system.resizeNWSE: return GenericCursor.SizeNwse; 856 case cursor.system.notAllowed: return GenericCursor.NotAllowed; 857 858 } 859 860 }