peterbrittain / asciimatics

A cross platform package to do curses-like operations, plus higher level APIs and widgets to create text UIs and ASCII art animations
Apache License 2.0
3.61k stars 238 forks source link

Add support for scroll wheels #345

Open jeffwright13 opened 2 years ago

jeffwright13 commented 2 years ago

As per our conversation 2022-02-06 at https://gitter.im/asciimatics/Lobby, I am requesting scrollwheel support in Asciimatics.

Main technical thrust: Modify Screen to recognize scrollwheel events and handle it in the Frame

I will begin work on this soon and submit a PR when I get it working.

=======================

Jeff Wright @jeffwright13 Feb 06 06:35 @peterbrittain - is there a way to modify the scrolling behavior? When I use the scroll wheel on my mouse, it seems to want to scroll an entire page at a time, instead of what I have come to expect in a normal GUI: N lines at a time. The result is a very jumpy experience. I didn't see anything in the docs about configuring scroll behavior. peterbrittain @peterbrittain Feb 06 06:46 That's because asciimatics doesn't support scroll wheels (due to patchy support in Linux). I suspect what you're seeing is the terminal intercepting the event and scrolling the whole buffer. You'll be able to tell if this is the case, because asciimatics will still only draw on the original screen that was visible when you started the app. Jeff Wright @jeffwright13 Feb 06 06:55 So nothing to be done then? peterbrittain @peterbrittain Feb 06 07:08 Mouse support has improved in ncurses 6. Do you know what version your system uses? We'd also need to see if there is a setting on your terminal to force it to pass the input to the running app (instead of consuming it itself). Jeff Wright @jeffwright13 Feb 06 07:14

I am running (currently) on a Macbook Pro (2019/Intel). Latest version of OSX. I usually run Pytest (and hence Asciimatics) out of iTerm2, although sometimes also from my IDE (VSCode, with Integrated Terminal). My shell is latest zsh.

$ tic -V ncurses 5.7.20081102

BUT:

$ brew upgrade ncurses Running brew update --preinstall... ==> Auto-updated Homebrew! Updated 2 taps (homebrew/core and homebrew/cask). ==> Updated Formulae Updated 84 formulae. ==> Updated Casks Updated 36 casks.

Warning: ncurses 6.3 already installed

peterbrittain @peterbrittain Feb 06 08:02 Ok... with a newer version of ncurses, you then need to enable scroll wheel reporting. Looks like there's a setting in iterm2: https://iterm2.com/documentation-preferences-profiles-terminal.html Finally, we'd need to tweak asciimatics Screen to recognize the events and handle it in the Frame. All pretty self-contained changes, so I can explain them to you if you're up for submitting a PR. Jeff Wright @jeffwright13 Feb 06 09:16 Screen Shot 2022-02-06 at 9.16.13 AM.png Screen Shot 2022-02-06 at 9.16.38 AM.png Looks like I already had that setting enabled ("Enable mouse reporting") Sure, I could submit a PR. Would need some guidance on where in the code to make changes. peterbrittain @peterbrittain Feb 06 14:57 Ok... There are 2 basic changes. First is to _CursesScreen.get_event(). There's already logic to handle other buttons in there. We just need to find what events fire when you scroll the mouse wheel. You could either add some logging to the function, or use something like this: https://stackoverflow.com/questions/11893034/mouse-wheel-in-python-curses. According to the curses maintainer, it should look like mouse buttons 4 and 5. See https://stackoverflow.com/questions/42356352/mouse-scroll-up-ncurses-c Once you have those events translated into equivalent asciimatics MouseEvents, you just need to act on them in the scrollbar. That's in scrollbar.py. simply get the current position and add/subtract a percentage to the position on the relevant button. Jeff Wright @jeffwright13 Feb 06 20:54

I made the following changes to screen.py.

                # Some Linux modes only report clicks, so check for any
                # button down or click events.
                if (bstate & curses.BUTTON1_PRESSED != 0 or
                        bstate & curses.BUTTON1_CLICKED != 0):
                    buttons |= MouseEvent.LEFT_CLICK
                if (bstate & curses.BUTTON3_PRESSED != 0 or
                        bstate & curses.BUTTON3_CLICKED != 0):
                    buttons |= MouseEvent.RIGHT_CLICK
                if bstate & curses.BUTTON1_DOUBLE_CLICKED != 0:
                    buttons |= MouseEvent.DOUBLE_CLICK
                if bstate & curses.BUTTON4_PRESSED != 0:
                    print("BUTTON4_PRESSED")
                if bstate & curses.BUTTON5_PRESSED != 0:
                    print("BUTTON5_PRESSED")
                if bstate & curses.BUTTON4_CLICKED != 0:
                    print("BUTTON4_CLICKED")
                if bstate & curses.BUTTON5_CLICKED != 0:
                    print("BUTTON5_CLICKED")
                return MouseEvent(x, y, buttons)

This resulted in an exception when the scroll wheel was rolled:

Traceback (most recent call last): File "/Users/jwr003/coding/pytest-fold/pytest_fold/tui.py", line 186, in main() File "/Users/jwr003/coding/pytest-fold/pytest_fold/tui.py", line 179, in main Screen.wrapper(demo, catch_interrupt=True, arguments=[last_scene]) File "/Users/jwr003/coding/pytest-fold/venv/lib/python3.9/site-packages/asciimatics/screen.py", line 1393, in wrapper return func(screen, *arguments) File "/Users/jwr003/coding/pytest-fold/pytest_fold/tui.py", line 166, in demo screen.play( File "/Users/jwr003/coding/pytest-fold/venv/lib/python3.9/site-packages/asciimatics/screen.py", line 1566, in play self.draw_next_frame(repeat=repeat) File "/Users/jwr003/coding/pytest-fold/venv/lib/python3.9/site-packages/asciimatics/screen.py", line 1649, in draw_next_frame event = self.get_event() File "/Users/jwr003/coding/pytest-fold/venv/lib/python3.9/site-packages/asciimatics/screen.py", line 2494, in get_event if bstate & curses.BUTTON5_PRESSED != 0: AttributeError: module 'curses' has no attribute 'BUTTON5_PRESSED'

I am on a Mac. I saw the following comment in _curses.py:

# Darwin ncurses doesn't provide BUTTON5_* constants

Maybe that's the issue with the crash above. peterbrittain @peterbrittain Feb 07 00:58 Yeah - I wondered if we'd start hitting issue like this. We can create the constants ourselves if needed. For now, I suggest you just log the bstate in hex and compare it to https://github.com/mirror/ncurses/blob/d30f99439fcc8d4bb4c38e5c4afb4f6555fc6ad4/include/curses.tail Note that print doesn't work in alternate screens (like asciimatics uses). Also, if you don't like doing the bit maths, you can probably use this definition instead: https://github.com/mattn/pdcurses/blob/master/curses.h Jeff Wright @jeffwright13 Feb 07 11:14 The print was meant for initial diagnosis to see what button the system registered when I scrolled. Definitely will remove before issuing a PR! peterbrittain @peterbrittain Feb 07 11:57 :-)

peterbrittain commented 2 years ago

That would be a cool addition. Looking forwards to the PR... Feel free to reach out on gitter if you need more help.