A possible design for pixel-perfect scrolling in TUI regions (new "text group" protocol + other use cases) #9965
LucasPayne
started this conversation in
Ideas
Replies: 2 comments 1 reply
-
|
No need for all this complexity. Simply have a protocol that allows the
client to tell the terminal emulator to scroll in a rectangular region.
There will be a register scroll area, at which time the terminal takes a
snapshot of that area. Then scroll registered area by pixel events at
which time the terminal renders the partially scrolled area on top of
the underlying area, displaced by the specified number of pixels and
cropped to the area. The client simply registers the scroll area, then
updates it with the content it would show after scrolling is finished.
And then sends scroll events to the terminal. The client can then
update top/bottom line (or redraw the entire screen if its easier),
ask terminal to scroll by the pixels till the line is complete. Rinse
and repeat.
…On Mon, May 04, 2026 at 11:46:40PM -0700, Lucas Payne wrote:
I just watched the recent Linkarzu interview where Kovid mentioned he has some ideas on protocols for pixel scrolling inside neovim windows.
Similar discussions:
#9878
#9690
I have thought about the pixel text scrolling in TUIs as well and have an idea. This solution may seem a bit heavy, but is what I have come up with which is robust and artifact-free, crops scroll regions automatically, and allows multiple scroll regions simultaneously. It might not be realistic, though, because it requires adding a new terminal feature called "text groups".
"Text groups" are a new piece of metadata for visible cells. They are basically an invisible SGR style, so when you start a text group any visible text will have that property, and you can reset the "style" so the text has the "default text group" (or no text group). Text groups are a generic feature which can be used for other things (such as image cropping), not just for scroll animations.
So, for example, vim can be modified to render its cells for a particular window with a unique text group. Globally unique text group IDs can be created with the same solution as used for kitty images.
Each text group therefore has a region on the final terminal display. So, for example, vim could have a text group for a window, with a hole in it due to a popup. Vim doesn't have to do any complex cropping, it just has to draw the cells of the window with that text group active (e.g. it is no more complex than what vim does to render a window with a window-local background color).
Something like the synchronized update protocol can be used for a particular text group, to define "frames". Smooth scroll is basically an animation property, you set the scroll amount (X and Y offset in cells), a linear speed or even possibly interpolation curve parameters, and then
on the next synchronized update of this text group from frame A to frame B, the text of frame A is scrolled by X and Y using the speed/curve, and is masked by the text group extent of frame B. Newly appearing text is scrolled in from frame B. (e.g. if a rectangle region is scrolled up one line, only frame A has the bottom line, and only frame B has the top line, so the animation needs to get text from both frames.)
The effect is a smooth, properly masked scroll, which allows arbitrary scroll region geometry (including non-rectangular regions and blocked areas like from vim and multiplexer popups). You can also trivially switch vim or multiplexer tab mid-scroll with no artifacts. You can even have multiple smooth scrolls visible at the same time, such as from a compilation log visible in a neovim terminal while you are scrolling a source file.
It is assumed that the text in frame A and frame B is consistent with being a scroll of the given amount. If it isn't, then the animation will scroll and then change text, which is fine. It is "undefined behaviour" and may use the text from either frame A or frame B at their intersection.
**Summary**
*Protocol additions*
- A generic protocol, "text groups", which allow cells to have an ID. Same semantics as SGR.
- Globally unique text group IDs can be requested in the same way as for kitty images,
if you want multiple text-group-using TUI programs to not clash.
- Synchronized updates for text groups. (Allows the concept of "frame A and B").
- Text group properties: Set interpolation speed/curve and scrolled-amount for current frame.
- Delete text group (this may be needed to avoid leaking text group properties and kitty's internal synchronized update timestamps).
*Possible caveats*
Note that the smooth-scroll experience depends on the TUI app calculating its own frame time-stamps and setting the interpolation speed or curve accordingly. It may be required to allow partial scroll to feel nicer, e.g. if you want an IDE-like experience using the trackpad to browse a file. In that case this can be done by adding a stop parameter which adjusts the animation curve to stop midway.
**Additional use-case for text groups**
This is a heavy feature to add just for pixel scrolling, but there are other possible use cases.
*Image cropping*
I think this is the ideal solution to proper image cropping in TUI programs. Unicode placeholders are nice because they automatically allow cropping to a region of text, including proper cropping of popups. But they don't allow images with arbitrary locations without some trickery, and don't allow images over/under text. A new solution is to add a new image property, a "text group mask", which is a list of text groups. The image is only rendered on cells of those text groups. So, you can have an image in a neovim window which easily crops properly with neovim popups, scrolls properly inside neovim, scrolls properly with kitty scrollback, and also with multiplexer tab switches and tmux popups.
*Multiple fonts*
Set a (assumed metric-compatible) font per text group. Set dynamically with kitty @. Further, could even do this for colors, or even cursor appearance. Totally unnecessary but cool and made possible with text groups.
--
Reply to this email directly or view it on GitHub:
#9965
You are receiving this because you are subscribed to this thread.
Message ID: ***@***.***>
--
_____________________________________
Dr. Kovid Goyal
https://www.kovidgoyal.net
https://calibre-ebook.com
_____________________________________
|
Beta Was this translation helpful? Give feedback.
1 reply
-
|
On Fri, May 15, 2026 at 05:42:58PM -0700, jake-stewart wrote:
I am curious how scroll input would fit alongside this. Currently scroll is
reported as 1 event per cell, which doesn't map well to fine-grained trackpad
scrolling. I imagine this needs a new mode analogous to SGR-Pixels that reports
scroll in pixel deltas rather than cells.
Yes, also on my TODO list is a comprehensive pointer input reporting mode
that fixes the various issues with SGR pixels such as no leave
reporting, no out of window reporting etc. and adds support for scroll
events and touch events.
Though even with current scroll reporting an application could
conceivably just interpolate between the single row scroll events.
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
I just watched the recent Linkarzu interview where Kovid mentioned he has some ideas on protocols for pixel scrolling inside neovim windows.
Similar discussions:
#9878
#9690
I have thought about the pixel text scrolling in TUIs as well and have an idea. This solution may seem a bit heavy, but is what I have come up with which is robust and artifact-free, crops scroll regions automatically, and allows multiple scroll regions simultaneously. It might not be realistic, though, because it requires adding a new terminal feature called "text groups".
"Text groups" are a new piece of metadata for visible cells. They are basically an invisible SGR style, so when you start a text group any visible text will have that property, and you can reset the "style" so the text has the "default text group" (or no text group). Text groups are a generic feature which can be used for other things (such as image cropping), not just for scroll animations.
So, for example, vim can be modified to render its cells for a particular window with a unique text group. Globally unique text group IDs can be created with the same solution as used for kitty images.
Each text group therefore has a region on the final terminal display. So, for example, vim could have a text group for a window, with a hole in it due to a popup. Vim doesn't have to do any complex cropping, it just has to draw the cells of the window with that text group active (e.g. it is no more complex than what vim does to render a window with a window-local background color).
Something like the synchronized update protocol can be used for a particular text group, to define "frames". Smooth scroll is basically an animation property, you set the scroll amount (X and Y offset in cells), a linear speed or even possibly interpolation curve parameters, and then the next synchronized update of this text group from frame A to frame B, the text of frame A is scrolled by X and Y using the speed/curve, and is masked by the text group extent of frame B. Newly appearing text is scrolled in from frame B. (e.g. if a rectangle region is scrolled up one line, only frame A has the bottom line, and only frame B has the top line, so the animation needs to get text from both frames.)
The effect is a smooth, properly masked scroll, which allows arbitrary scroll region geometry (including non-rectangular regions and blocked areas like from vim and multiplexer popups). You can also trivially switch vim or multiplexer tab mid-scroll with no artifacts. You can even have multiple smooth scrolls visible at the same time, such as from a compilation log visible in a neovim terminal while you are scrolling a source file.
It is assumed that the text in frame A and frame B is consistent with being a scroll of the given amount. If it isn't, then the animation will scroll and then change text, which is fine. It is "undefined behaviour" and may use the text from either frame A or frame B at their intersection.
Summary
Protocol additions
if you want multiple text-group-using TUI programs to not clash.
Possible caveats
Note that the smooth-scroll experience depends on the TUI app calculating its own frame time-stamps and setting the interpolation speed or curve accordingly. It may be required to allow partial scroll to feel nicer, e.g. if you want an IDE-like experience using the trackpad to browse a file. In that case this can be done by adding a stop parameter which adjusts the animation curve to stop midway.
Additional use-case for text groups
This is a heavy feature to add just for pixel scrolling, but there are other possible use cases.
Image cropping
I think this is the ideal solution to proper image cropping in TUI programs. Unicode placeholders are nice because they automatically allow cropping to a region of text, including proper cropping of popups. But they don't allow images with arbitrary locations without some trickery, and don't allow images over/under text. A new solution is to add a new image property, a "text group mask", which is a list of text groups. The image is only rendered on cells of those text groups. So, you can have an image in a neovim window which easily crops properly with neovim popups, scrolls properly inside neovim, scrolls properly with kitty scrollback, and also with multiplexer tab switches and tmux popups.
Multiple fonts
Set a (assumed metric-compatible) font per text group. Set dynamically with kitty @. Further, could even do this for colors, or even cursor appearance. Totally unnecessary but cool and made possible with text groups.
Beta Was this translation helpful? Give feedback.
All reactions