@@ -64,6 +64,7 @@ void Panel_init(Panel* this, int x, int y, int w, int h, const ObjectClass* type
6464 this -> needsRedraw = true;
6565 this -> cursorOn = false;
6666 this -> wasFocus = false;
67+ this -> allowExcessScrollV = false;
6768 RichString_beginAllocated (this -> header );
6869 this -> defaultBar = fuBar ;
6970 this -> currentBar = fuBar ;
@@ -118,6 +119,7 @@ void Panel_prune(Panel* this) {
118119 this -> selected = 0 ;
119120 this -> oldSelected = 0 ;
120121 this -> needsRedraw = true;
122+ this -> allowExcessScrollV = false;
121123}
122124
123125void Panel_add (Panel * this , Object * o ) {
@@ -262,11 +264,20 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
262264 h -- ;
263265 }
264266
265- // ensure scroll area is on screen
266- if (this -> scrollV < 0 ) {
267- this -> scrollV = 0 ;
268- this -> needsRedraw = true;
269- } else if (this -> scrollV > size - h ) {
267+ /* ensure scroll area is on screen
268+ Note: when allowExcessScrollV is set, negative scrollV is allowed to display
269+ empty lines above the first row, and scrollV > size-h is allowed to display
270+ empty lines below the last row. Both are used by stable tree view hard mode
271+ to keep the selected row at a fixed screen position. */
272+ if (!this -> allowExcessScrollV ) {
273+ if (this -> scrollV < 0 ) {
274+ this -> scrollV = 0 ;
275+ this -> needsRedraw = true;
276+ } else if (this -> scrollV > size - h ) {
277+ this -> scrollV = MAXIMUM (size - h , 0 );
278+ this -> needsRedraw = true;
279+ }
280+ } if (!this -> allowExcessScrollV && this -> scrollV > size - h ) {
270281 this -> scrollV = MAXIMUM (size - h , 0 );
271282 this -> needsRedraw = true;
272283 }
@@ -279,8 +290,10 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
279290 this -> needsRedraw = true;
280291 }
281292
282- int first = this -> scrollV ;
283- int upTo = MINIMUM (first + h , size );
293+ // topPad: empty screen lines above the first row (non-zero when scrollV is negative)
294+ int topPad = (this -> scrollV < 0 ) ? - this -> scrollV : 0 ;
295+ int first = this -> scrollV + topPad ;
296+ int upTo = MINIMUM (first + h - topPad , size );
284297
285298 int selectionColor = focus
286299 ? CRT_colors [this -> selectionColorId ]
@@ -289,6 +302,10 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
289302 RichString_begin (item );
290303 if (this -> needsRedraw || force_redraw ) {
291304 int line = 0 ;
305+ while (line < topPad ) {
306+ mvhline (y + line , x , ' ' , this -> w );
307+ line ++ ;
308+ }
292309 for (int i = first ; line < h && i < upTo ; i ++ ) {
293310 const Object * itemObj = Vector_get (this -> items , i );
294311 RichString_rewind (& item , RichString_size (& item ));
@@ -321,9 +338,9 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
321338 RichString_rewind (& item , RichString_size (& item ));
322339 Object_display (oldObj , & item );
323340 int oldLen = RichString_sizeVal (item );
324- mvhline (y + this -> oldSelected - first , x + 0 , ' ' , this -> w );
341+ mvhline (y + this -> oldSelected - this -> scrollV , x + 0 , ' ' , this -> w );
325342 if (scrollH < oldLen )
326- RichString_printoffnVal (item , y + this -> oldSelected - first , x ,
343+ RichString_printoffnVal (item , y + this -> oldSelected - this -> scrollV , x ,
327344 scrollH , MINIMUM (oldLen - scrollH , this -> w ));
328345
329346 const Object * newObj = Vector_get (this -> items , this -> selected );
@@ -333,10 +350,10 @@ void Panel_draw(Panel* this, bool force_redraw, bool focus, bool highlightSelect
333350 int newLen = RichString_sizeVal (item );
334351 this -> selectedLen = newLen ;
335352 attrset (selectionColor );
336- mvhline (y + this -> selected - first , x + 0 , ' ' , this -> w );
353+ mvhline (y + this -> selected - this -> scrollV , x + 0 , ' ' , this -> w );
337354 RichString_setAttr (& item , selectionColor );
338355 if (scrollH < newLen )
339- RichString_printoffnVal (item , y + this -> selected - first , x ,
356+ RichString_printoffnVal (item , y + this -> selected - this -> scrollV , x ,
340357 scrollH , MINIMUM (newLen - scrollH , this -> w ));
341358 attrset (CRT_colors [RESET_COLOR ]);
342359 }
@@ -364,6 +381,8 @@ bool Panel_onKey(Panel* this, int key) {
364381 assert (this != NULL );
365382
366383 const int size = Vector_size (this -> items );
384+ const int availableHeight = this -> h - Panel_headerHeight (this );
385+ const int maxScroll = MAXIMUM (0 , size - availableHeight );
367386
368387 #define PANEL_SCROLL (amount ) \
369388 do { \
@@ -419,6 +438,26 @@ bool Panel_onKey(Panel* this, int key) {
419438 PANEL_SCROLL (+ CRT_scrollWheelVAmount );
420439 break ;
421440
441+ case KEY_SR :
442+ if (this -> scrollV > 0 ) {
443+ // keep selection within the now-visible area
444+ if (this -> selected < this -> scrollV + availableHeight ) {
445+ this -> scrollV -- ;
446+ this -> needsRedraw = true;
447+ }
448+ }
449+ break ;
450+
451+ case KEY_SF :
452+ if (this -> scrollV < maxScroll ) {
453+ // keep selection within the now-visible area
454+ if (this -> selected >= this -> scrollV ) {
455+ this -> scrollV ++ ;
456+ this -> needsRedraw = true;
457+ }
458+ }
459+ break ;
460+
422461 case KEY_HOME :
423462 this -> selected = 0 ;
424463 break ;
0 commit comments