1+ #include "animation.h"
12#include "output.h"
23#include "scroller.h"
34#include "toplevel.h"
@@ -19,6 +20,9 @@ bool edge_scroller_pointer_focus = true;
1920float * scroller_proportion_preset = NULL ;
2021int scroller_proportion_preset_count = 0 ;
2122
23+ int scroller_center_focused_column = SCROLLER_CENTER_ON_OVERFLOW ;
24+ bool scroller_always_center_single_column = true;
25+
2226static int max_i (int a , int b ) {
2327 return a > b ? a : b ;
2428}
@@ -27,6 +31,9 @@ static double max_d(double a, double b) {
2731 return a > b ? a : b ;
2832}
2933
34+ static double compute_view_offset (scroller_state_t * s , struct wlr_box available );
35+ static void scroller_start_view_anim (scroller_state_t * s , double target_offset );
36+
3037static scroller_tile_t * tile_grow (scroller_column_t * col ) {
3138 if (col -> tile_count >= col -> capacity ) {
3239 int new_cap = col -> capacity ? col -> capacity * 2 : 4 ;
@@ -371,6 +378,12 @@ void scroller_arrange(struct output_t *m, desktop_t *d, struct wlr_box available
371378 }
372379 col_xs [s -> column_count ] = wx ; // sentinel
373380
381+ // auto-compute view offset when active column changes
382+ if (s -> view_offset_dirty ) {
383+ scroller_start_view_anim (s , compute_view_offset (s , available ));
384+ s -> view_offset_dirty = false;
385+ }
386+
374387 // view position (camera in world space)
375388 double active_col_x = col_xs [s -> active_column_idx ];
376389 double view_pos = active_col_x + s -> view_offset ;
@@ -439,6 +452,91 @@ static void apply_active_focus(desktop_t *d) {
439452 }
440453}
441454
455+ static double compute_view_offset (scroller_state_t * s , struct wlr_box available ) {
456+ scroller_column_t * col = & s -> columns [s -> active_column_idx ];
457+ double col_width = col -> resolved_width ;
458+ double avail_width = (double )available .width ;
459+
460+ if (scroller_always_center_single_column && s -> column_count == 1 && col_width < avail_width )
461+ return (col_width - avail_width ) / 2.0 ;
462+
463+ switch (scroller_center_focused_column ) {
464+ case SCROLLER_CENTER_NEVER :
465+ return 0.0 ;
466+ case SCROLLER_CENTER_ALWAYS :
467+ return (col_width - avail_width ) / 2.0 ;
468+ case SCROLLER_CENTER_ON_OVERFLOW :
469+ default :
470+ if (col_width > avail_width )
471+ return (col_width - avail_width ) / 2.0 ;
472+ return 0.0 ;
473+ }
474+ }
475+
476+ static void scroller_apply_positions (output_t * m , scroller_state_t * s ) {
477+ desktop_t * d = m -> desk ;
478+ int bw = (int )effective_border_width (d );
479+
480+ for (int i = 0 ; i < s -> column_count ; i ++ ) {
481+ scroller_column_t * col = & s -> columns [i ];
482+ for (int j = 0 ; j < col -> tile_count ; j ++ ) {
483+ client_t * c = col -> tiles [j ].client ;
484+ if (!c || !c -> toplevel || !c -> toplevel -> node )
485+ continue ;
486+
487+ struct wlr_scene_tree * st = client_get_scene_tree (c );
488+ if (st )
489+ wlr_scene_node_set_position (& st -> node , c -> tiled_rectangle .x , c -> tiled_rectangle .y );
490+
491+ struct wlr_scene_tree * bt = client_border_tree (c );
492+ if (bt )
493+ wlr_scene_node_set_position (& bt -> node , c -> tiled_rectangle .x - bw , c -> tiled_rectangle .y - bw );
494+ }
495+ }
496+ }
497+
498+ static void scroller_start_view_anim (scroller_state_t * s , double target_offset ) {
499+ if (enable_animations ) {
500+ s -> view_anim .active = true;
501+ s -> view_anim .from = s -> view_offset ;
502+ s -> view_anim .target = target_offset ;
503+ clock_gettime (CLOCK_MONOTONIC , & s -> view_anim .start );
504+ s -> view_anim .duration_ms = animation_get_duration ();
505+ } else {
506+ s -> view_offset = target_offset ;
507+ s -> view_offset_dirty = false;
508+ }
509+ }
510+
511+ bool scroller_tick (output_t * output , struct timespec now ) {
512+ if (!output || !output -> desk || output -> desk -> layout != LAYOUT_SCROLLER )
513+ return false;
514+
515+ scroller_state_t * s = output -> desk -> scroller_state ;
516+ if (!s || !s -> view_anim .active )
517+ return false;
518+
519+ long elapsed_ms = (now .tv_sec - s -> view_anim .start .tv_sec ) * 1000 + (now .tv_nsec -
520+ s -> view_anim .start .tv_nsec ) / 1000000 ;
521+
522+ if (elapsed_ms >= (long )s -> view_anim .duration_ms ) {
523+ s -> view_offset = s -> view_anim .target ;
524+ s -> view_anim .active = false;
525+ arrange (output , output -> desk , true);
526+ return false;
527+ }
528+
529+ double t = (double )elapsed_ms / (double )s -> view_anim .duration_ms ;
530+ // ease-out cubic (no pow to avoid -lm)
531+ double eased = 1.0 - (1.0 - t ) * (1.0 - t ) * (1.0 - t );
532+ s -> view_offset = s -> view_anim .from + (s -> view_anim .target - s -> view_anim .from ) * eased ;
533+
534+ arrange (output , output -> desk , false);
535+ scroller_apply_positions (output , s );
536+
537+ return true;
538+ }
539+
442540bool scroller_focus_next (desktop_t * d ) {
443541 scroller_state_t * s = d -> scroller_state ;
444542 if (!s || s -> column_count == 0 )
@@ -450,7 +548,7 @@ bool scroller_focus_next(desktop_t *d) {
450548 } else {
451549 s -> active_column_idx ++ ;
452550 }
453- s -> view_offset = 0.0 ; // reset scroll for now
551+ s -> view_offset_dirty = true;
454552 apply_active_focus (d );
455553 return true;
456554}
@@ -466,7 +564,7 @@ bool scroller_focus_prev(desktop_t *d) {
466564 } else {
467565 s -> active_column_idx -- ;
468566 }
469- s -> view_offset = 0.0 ;
567+ s -> view_offset_dirty = true ;
470568 apply_active_focus (d );
471569 return true;
472570}
@@ -514,7 +612,7 @@ void scroller_center_window(desktop_t *d, client_t *client) {
514612
515613 s -> active_column_idx = col_idx ;
516614 s -> columns [col_idx ].active_tile_idx = tile_idx ;
517- s -> view_offset = 0.0 ;
615+ s -> view_offset_dirty = true ;
518616 apply_active_focus (d );
519617}
520618
0 commit comments