Fix scrolling when application or category list too long #8

This can be tested with `XDGCTL_DEV=100 xdgctl`.

Author Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-26 12:47:19 +0100
Committer Mitja Felicijan <mitja.felicijan@gmail.com> 2026-01-26 12:47:19 +0100
Commit 7f75293b2a10a94933f63e095b23af0d3ac60cbc (patch)
-rw-r--r-- main.c 42
1 files changed, 33 insertions, 9 deletions
diff --git a/main.c b/main.c
...
30
typedef struct {
30
typedef struct {
31
	int category_idx;
31
	int category_idx;
32
	int app_idx;
32
	int app_idx;
  
33
	int category_offset;
  
34
	int app_offset;
33
	int col; // 0 for category, 1 for app
35
	int col; // 0 for category, 1 for app
34
	char message[512];
36
	char message[512];
35
	GList *cached_apps;
37
	GList *cached_apps;
...
105
  
107
  
106
void draw_categories(State *state) {
108
void draw_categories(State *state) {
107
	int count = get_category_count(state);
109
	int count = get_category_count(state);
108
	for (int i = 0; i < count; ++i) {
110
	int height = tb_height() - Y_OFF_START - 1;
  
111
	for (int i = 0; i < height && (i + state->category_offset) < count; ++i) {
  
112
		int idx = i + state->category_offset;
109
		uint16_t fg = COLOR_DEFAULT;
113
		uint16_t fg = COLOR_DEFAULT;
110
		uint16_t bg = TB_DEFAULT;
114
		uint16_t bg = TB_DEFAULT;
111
		if (state->col == 0 && state->category_idx == i) {
115
		if (state->col == 0 && state->category_idx == idx) {
112
			bg = COLOR_SELECTED;
116
			bg = COLOR_SELECTED;
113
		} else if (state->category_idx == i) {
117
		} else if (state->category_idx == idx) {
114
			fg = COLOR_SELECTED;
118
			fg = COLOR_SELECTED;
115
			bg = COLOR_DEFAULT;
119
			bg = COLOR_DEFAULT;
116
		}
120
		}
117
		tb_print(X_OFF_CATEGORIES, Y_OFF_START + i, fg, bg, get_category_name(state, i));
121
		tb_print(X_OFF_CATEGORIES, Y_OFF_START + i, fg, bg, get_category_name(state, idx));
118
	}
122
	}
119
}
123
}
120
  
124
  
121
void draw_apps_list(State *state) {
125
void draw_apps_list(State *state) {
  
126
	int height = tb_height() - Y_OFF_START - 1;
122
	if (state->is_dev_mode) {
127
	if (state->is_dev_mode) {
123
		int i = 0;
128
		int i = 0;
124
		for (GList *l = state->cached_apps; l != NULL; l = l->next, ++i) {
129
		GList *l = g_list_nth(state->cached_apps, state->app_offset);
  
130
		for (; l != NULL && i < height; l = l->next, ++i) {
  
131
			int idx = i + state->app_offset;
125
			char *app_name = (char *)l->data;
132
			char *app_name = (char *)l->data;
126
			uint16_t fg = COLOR_DEFAULT;
133
			uint16_t fg = COLOR_DEFAULT;
127
			uint16_t bg = TB_DEFAULT;
134
			uint16_t bg = TB_DEFAULT;
128
			if (state->col == 1 && state->app_idx == i) {
135
			if (state->col == 1 && state->app_idx == idx) {
129
				bg = COLOR_SELECTED;
136
				bg = COLOR_SELECTED;
130
			}
137
			}
131
			char name[256];
138
			char name[256];
...
154
	}
161
	}
155
  
162
  
156
	int i = 0;
163
	int i = 0;
157
	for (GList *l = state->cached_apps; l != NULL; l = l->next, ++i) {
164
	GList *l = g_list_nth(state->cached_apps, state->app_offset);
  
165
	for (; l != NULL && i < height; l = l->next, ++i) {
  
166
		int idx = i + state->app_offset;
158
		GAppInfo *app = (GAppInfo *)l->data;
167
		GAppInfo *app = (GAppInfo *)l->data;
159
		uint16_t fg = COLOR_DEFAULT;
168
		uint16_t fg = COLOR_DEFAULT;
160
		uint16_t bg = TB_DEFAULT;
169
		uint16_t bg = TB_DEFAULT;
161
		if (state->col == 1 && state->app_idx == i) {
170
		if (state->col == 1 && state->app_idx == idx) {
162
			bg = COLOR_SELECTED;
171
			bg = COLOR_SELECTED;
163
		}
172
		}
164
  
173
  
...
206
		return 1;
215
		return 1;
207
	}
216
	}
208
  
217
  
209
	State state = {0, 0, 0, {0}, NULL, 0, 0};
218
	State state = {0, 0, 0, 0, 0, {0}, NULL, 0, 0};
210
	char *dev_env = getenv("XDGCTL_DEV");
219
	char *dev_env = getenv("XDGCTL_DEV");
211
	if (dev_env) {
220
	if (dev_env) {
212
		state.is_dev_mode = 1;
221
		state.is_dev_mode = 1;
...
219
  
228
  
220
	struct tb_event ev;
229
	struct tb_event ev;
221
	while (1) {
230
	while (1) {
  
231
		int visible_height = tb_height() - Y_OFF_START - 1;
  
232
		if (state.category_idx < state.category_offset) {
  
233
			state.category_offset = state.category_idx;
  
234
		} else if (state.category_idx >= state.category_offset + visible_height) {
  
235
			state.category_offset = state.category_idx - visible_height + 1;
  
236
		}
  
237
		if (state.app_idx < state.app_offset) {
  
238
			state.app_offset = state.app_idx;
  
239
		} else if (state.app_idx >= state.app_offset + visible_height) {
  
240
			state.app_offset = state.app_idx - visible_height + 1;
  
241
		}
  
242
  
222
		draw(&state);
243
		draw(&state);
223
		tb_poll_event(&ev);
244
		tb_poll_event(&ev);
224
  
245
  
...
232
					if (state.category_idx > 0) {
253
					if (state.category_idx > 0) {
233
						state.category_idx--;
254
						state.category_idx--;
234
						update_cached_apps(&state);
255
						update_cached_apps(&state);
  
256
						state.app_offset = 0;
235
						state.message[0] = '\0';
257
						state.message[0] = '\0';
236
					}
258
					}
237
				} else {
259
				} else {
...
246
					if (state.category_idx < count - 1) {
268
					if (state.category_idx < count - 1) {
247
						state.category_idx++;
269
						state.category_idx++;
248
						update_cached_apps(&state);
270
						update_cached_apps(&state);
  
271
						state.app_offset = 0;
249
						state.message[0] = '\0';
272
						state.message[0] = '\0';
250
					}
273
					}
251
				} else {
274
				} else {
...
259
				if (state.col == 0) {
282
				if (state.col == 0) {
260
					state.col = 1;
283
					state.col = 1;
261
					state.app_idx = 0;
284
					state.app_idx = 0;
  
285
					state.app_offset = 0;
262
				}
286
				}
263
			} else if (ev.key == TB_KEY_ARROW_LEFT) {
287
			} else if (ev.key == TB_KEY_ARROW_LEFT) {
264
				if (state.col == 1) {
288
				if (state.col == 1) {
...