Index: uspace/app/nav/panel.c
===================================================================
--- uspace/app/nav/panel.c	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/app/nav/panel.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -60,9 +60,11 @@
 };
 
+static void panel_flist_activate_req(ui_file_list_t *, void *);
 static void panel_flist_selected(ui_file_list_t *, void *, const char *);
 
 /** Panel file list callbacks */
 static ui_file_list_cb_t panel_flist_cb = {
-	.selected = panel_flist_selected
+	.activate_req = panel_flist_activate_req,
+	.selected = panel_flist_selected,
 };
 
@@ -216,4 +218,5 @@
 	gfx_coord2_t pos;
 	ui_control_t *ctl;
+	ui_evclaim_t claim;
 
 	pos.x = event->hpos;
@@ -222,9 +225,13 @@
 		return ui_unclaimed;
 
+	ctl = ui_file_list_ctl(panel->flist);
+	claim = ui_control_pos_event(ctl, event);
+	if (claim == ui_claimed)
+		return ui_claimed;
+
 	if (!panel->active && event->type == POS_PRESS)
 		panel_activate_req(panel);
 
-	ctl = ui_file_list_ctl(panel->flist);
-	return ui_control_pos_event(ctl, event);
+	return ui_claimed;
 }
 
@@ -416,4 +423,16 @@
 }
 
+/** File list in panel requests activation.
+ *
+ * @param flist File list
+ * @param arg Argument (panel_t *)
+ */
+static void panel_flist_activate_req(ui_file_list_t *flist, void *arg)
+{
+	panel_t *panel = (panel_t *)arg;
+
+	panel_activate_req(panel);
+}
+
 /** File in panel file list was selected.
  *
Index: uspace/app/uidemo/uidemo.c
===================================================================
--- uspace/app/uidemo/uidemo.c	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/app/uidemo/uidemo.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -44,4 +44,5 @@
 #include <ui/image.h>
 #include <ui/label.h>
+#include <ui/list.h>
 #include <ui/menubar.h>
 #include <ui/menuentry.h>
@@ -598,4 +599,5 @@
 	ui_menu_entry_t *mmodify;
 	ui_menu_entry_t *mabout;
+	ui_list_entry_attr_t eattr;
 	errno_t rc;
 
@@ -1183,4 +1185,83 @@
 
 	ui_tab_add(demo.tbasic, ui_fixed_ctl(demo.bfixed));
+
+	rc = ui_fixed_create(&demo.lfixed);
+	if (rc != EOK) {
+		printf("Error creating fixed layout.\n");
+		return rc;
+	}
+
+	rc = ui_list_create(window, false, &demo.list);
+	if (rc != EOK) {
+		printf("Error creating list.\n");
+		return rc;
+	}
+
+	ui_list_entry_attr_init(&eattr);
+
+	eattr.caption = "One";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Two";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Three";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Four";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Five";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	eattr.caption = "Six";
+	rc = ui_list_entry_append(demo.list, &eattr, NULL);
+	if (rc != EOK) {
+		printf("Error adding list entry.\n");
+		return rc;
+	}
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 4;
+		rect.p0.y = 5;
+		rect.p1.x = 41;
+		rect.p1.y = 10;
+	} else {
+		rect.p0.x = 15;
+		rect.p0.y = 88;
+		rect.p1.x = 245;
+		rect.p1.y = 173;
+	}
+
+	ui_list_set_rect(demo.list, &rect);
+
+	rc = ui_fixed_add(demo.lfixed, ui_list_ctl(demo.list));
+	if (rc != EOK) {
+		printf("Error adding control to layout.\n");
+		return rc;
+	}
+
+	ui_tab_add(demo.tlists, ui_fixed_ctl(demo.lfixed));
 
 	ui_window_add(window, ui_fixed_ctl(demo.fixed));
Index: uspace/app/uidemo/uidemo.h
===================================================================
--- uspace/app/uidemo/uidemo.h	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/app/uidemo/uidemo.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -42,4 +42,5 @@
 #include <ui/fixed.h>
 #include <ui/label.h>
+#include <ui/list.h>
 #include <ui/menu.h>
 #include <ui/menubar.h>
@@ -59,4 +60,5 @@
 	ui_fixed_t *fixed;
 	ui_fixed_t *bfixed;
+	ui_fixed_t *lfixed;
 	ui_menu_bar_t *mbar;
 	ui_menu_t *mfile;
@@ -80,4 +82,5 @@
 	ui_scrollbar_t *hscrollbar;
 	ui_scrollbar_t *vscrollbar;
+	ui_list_t *list;
 } ui_demo_t;
 
Index: uspace/lib/ui/include/types/ui/list.h
===================================================================
--- uspace/lib/ui/include/types/ui/list.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
+++ uspace/lib/ui/include/types/ui/list.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file List control
+ */
+
+#ifndef _UI_TYPES_LIST_H
+#define _UI_TYPES_LIST_H
+
+#include <gfx/color.h>
+
+struct ui_list;
+typedef struct ui_list ui_list_t;
+
+struct ui_list_entry;
+typedef struct ui_list_entry ui_list_entry_t;
+
+/** List entry attributes */
+typedef struct ui_list_entry_attr {
+	/** Entry caption */
+	const char *caption;
+	/** User argument */
+	void *arg;
+	/** Entry color or @c NULL to use default color */
+	gfx_color_t *color;
+	/** Entry background color or @c NULL to use default color */
+	gfx_color_t *bgcolor;
+} ui_list_entry_attr_t;
+
+/** List callbacks */
+typedef struct ui_list_cb {
+	/** Request list activation */
+	void (*activate_req)(ui_list_t *, void *);
+	/** Entry was selected */
+	void (*selected)(ui_list_entry_t *, void *);
+	/** Compare two entries (for sorting) */
+	int (*compare)(ui_list_entry_t *, ui_list_entry_t *);
+} ui_list_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/include/ui/filelist.h
===================================================================
--- uspace/lib/ui/include/ui/filelist.h	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/lib/ui/include/ui/filelist.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -39,6 +39,4 @@
 #include <errno.h>
 #include <gfx/coord.h>
-#include <io/kbd_event.h>
-#include <io/pos_event.h>
 #include <ui/control.h>
 #include <ui/window.h>
Index: uspace/lib/ui/include/ui/list.h
===================================================================
--- uspace/lib/ui/include/ui/list.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
+++ uspace/lib/ui/include/ui/list.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file List
+ */
+
+#ifndef _UI_LIST_H
+#define _UI_LIST_H
+
+#include <errno.h>
+#include <gfx/coord.h>
+#include <ui/control.h>
+#include <ui/window.h>
+#include <stdbool.h>
+#include <types/ui/list.h>
+
+extern errno_t ui_list_create(ui_window_t *, bool, ui_list_t **);
+extern void ui_list_destroy(ui_list_t *);
+extern ui_control_t *ui_list_ctl(ui_list_t *);
+extern void ui_list_set_cb(ui_list_t *, ui_list_cb_t *, void *);
+extern void ui_list_set_rect(ui_list_t *, gfx_rect_t *);
+extern errno_t ui_list_activate(ui_list_t *);
+extern void ui_list_deactivate(ui_list_t *);
+extern ui_list_entry_t *ui_list_get_cursor(ui_list_t *);
+extern void ui_list_entry_attr_init(ui_list_entry_attr_t *);
+extern errno_t ui_list_entry_append(ui_list_t *,
+    ui_list_entry_attr_t *, ui_list_entry_t **);
+extern void ui_list_entry_delete(ui_list_entry_t *);
+extern void *ui_list_entry_get_arg(ui_list_entry_t *);
+extern size_t ui_list_entries_cnt(ui_list_t *);
+extern errno_t ui_list_sort(ui_list_t *);
+extern void ui_list_cursor_center(ui_list_t *, ui_list_entry_t *);
+extern ui_list_entry_t *ui_list_first(ui_list_t *);
+extern ui_list_entry_t *ui_list_last(ui_list_t *);
+extern ui_list_entry_t *ui_list_next(ui_list_entry_t *);
+extern ui_list_entry_t *ui_list_prev(ui_list_entry_t *);
+extern bool ui_list_is_active(ui_list_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/meson.build
===================================================================
--- uspace/lib/ui/meson.build	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/lib/ui/meson.build	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -40,4 +40,5 @@
 	'src/image.c',
 	'src/label.c',
+	'src/list.c',
 	'src/menu.c',
 	'src/menubar.c',
@@ -71,4 +72,5 @@
 	'test/image.c',
 	'test/label.c',
+	'test/list.c',
 	'test/main.c',
 	'test/menu.c',
Index: uspace/lib/ui/private/filelist.h
===================================================================
--- uspace/lib/ui/private/filelist.h	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/lib/ui/private/filelist.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,8 +37,7 @@
 #define _UI_PRIVATE_FILELIST_H
 
-#include <adt/list.h>
 #include <gfx/color.h>
-#include <gfx/coord.h>
 #include <ipc/loc.h>
+#include <ui/list.h>
 #include <ui/window.h>
 #include <stdint.h>
@@ -61,6 +60,6 @@
 	/** Containing file list */
 	struct ui_file_list *flist;
-	/** Link to @c flist->entries */
-	link_t lentries;
+	/** List entry */
+	ui_list_entry_t *entry;
 	/** File name */
 	char *name;
@@ -82,5 +81,8 @@
 
 	/** Containing window */
-	ui_window_t *window;
+	struct ui_window *window;
+
+	/** UI list */
+	ui_list_t *list;
 
 	/** Callbacks */
@@ -90,10 +92,4 @@
 	void *cb_arg;
 
-	/** File list rectangle */
-	gfx_rect_t rect;
-
-	/** Scrollbar */
-	struct ui_scrollbar *scrollbar;
-
 	/** Directory-type entry color */
 	gfx_color_t *dir_color;
@@ -102,39 +98,8 @@
 	gfx_color_t *svc_color;
 
-	/** File list entries (list of ui_file_list_entry_t) */
-	list_t entries;
-
-	/** Number of entries */
-	size_t entries_cnt;
-
-	/** First entry of current page */
-	ui_file_list_entry_t *page;
-
-	/** Index of first entry of current page */
-	size_t page_idx;
-
-	/** Cursor position */
-	ui_file_list_entry_t *cursor;
-
-	/** Index of entry under cursor */
-	size_t cursor_idx;
-
-	/** @c true iff the file list is active */
-	bool active;
-
 	/** Directory */
 	char *dir;
 } ui_file_list_t;
 
-extern gfx_coord_t ui_file_list_entry_height(ui_file_list_t *);
-extern errno_t ui_file_list_entry_paint(ui_file_list_entry_t *, size_t);
-extern errno_t ui_file_list_paint(ui_file_list_t *);
-extern ui_evclaim_t ui_file_list_kbd_event(ui_file_list_t *, kbd_event_t *);
-extern ui_evclaim_t ui_file_list_pos_event(ui_file_list_t *, pos_event_t *);
-extern unsigned ui_file_list_page_size(ui_file_list_t *);
-extern void ui_file_list_inside_rect(ui_file_list_t *, gfx_rect_t *);
-extern void ui_file_list_scrollbar_rect(ui_file_list_t *, gfx_rect_t *);
-extern gfx_coord_t ui_file_list_scrollbar_pos(ui_file_list_t *);
-extern void ui_file_list_scrollbar_update(ui_file_list_t *);
 extern bool ui_file_list_is_active(ui_file_list_t *);
 extern void ui_file_list_entry_delete(ui_file_list_entry_t *);
@@ -142,30 +107,17 @@
 extern errno_t ui_file_list_sort(ui_file_list_t *);
 extern int ui_file_list_entry_ptr_cmp(const void *, const void *);
+extern void ui_file_list_entry_attr_init(ui_file_list_entry_attr_t *);
+extern errno_t ui_file_list_entry_append(ui_file_list_t *,
+    ui_file_list_entry_attr_t *);
 extern ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *);
 extern ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *);
 extern ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *);
 extern ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *);
-extern ui_file_list_entry_t *ui_file_list_page_nth_entry(ui_file_list_t *,
-    size_t, size_t *);
-extern void ui_file_list_entry_attr_init(ui_file_list_entry_attr_t *);
-extern errno_t ui_file_list_entry_append(ui_file_list_t *,
-    ui_file_list_entry_attr_t *);
-extern void ui_file_list_cursor_move(ui_file_list_t *, ui_file_list_entry_t *,
-    size_t);
-extern void ui_file_list_cursor_up(ui_file_list_t *);
-extern void ui_file_list_cursor_down(ui_file_list_t *);
-extern void ui_file_list_cursor_top(ui_file_list_t *);
-extern void ui_file_list_cursor_bottom(ui_file_list_t *);
-extern void ui_file_list_page_up(ui_file_list_t *);
-extern void ui_file_list_page_down(ui_file_list_t *);
-extern void ui_file_list_scroll_up(ui_file_list_t *);
-extern void ui_file_list_scroll_down(ui_file_list_t *);
-extern void ui_file_list_scroll_page_up(ui_file_list_t *);
-extern void ui_file_list_scroll_page_down(ui_file_list_t *);
-extern void ui_file_list_scroll_pos(ui_file_list_t *, size_t);
 extern errno_t ui_file_list_open_dir(ui_file_list_t *, ui_file_list_entry_t *);
 extern errno_t ui_file_list_open_file(ui_file_list_t *, ui_file_list_entry_t *);
 extern void ui_file_list_activate_req(ui_file_list_t *);
 extern void ui_file_list_selected(ui_file_list_t *, const char *);
+extern errno_t ui_file_list_paint(ui_file_list_t *);
+extern int ui_file_list_list_compare(ui_list_entry_t *, ui_list_entry_t *);
 
 #endif
Index: uspace/lib/ui/private/list.h
===================================================================
--- uspace/lib/ui/private/list.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
+++ uspace/lib/ui/private/list.h	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -0,0 +1,142 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/**
+ * @file UI list
+ */
+
+#ifndef _UI_PRIVATE_LIST_H
+#define _UI_PRIVATE_LIST_H
+
+#include <adt/list.h>
+#include <gfx/color.h>
+#include <gfx/coord.h>
+#include <ipc/loc.h>
+#include <ui/window.h>
+#include <stdint.h>
+#include <types/ui/list.h>
+
+/** List entry */
+struct ui_list_entry {
+	/** Containing list */
+	struct ui_list *list;
+	/** Link to @c flist->entries */
+	link_t lentries;
+	/** Entry caption */
+	char *caption;
+	/** User argument */
+	void *arg;
+	/** Custom color or @c NULL to use default color */
+	gfx_color_t *color;
+	/** Custom background color or @c NULL to use default color */
+	gfx_color_t *bgcolor;
+};
+
+/** UI list.
+ *
+ * Displays a scrollable list of entries.
+ */
+typedef struct ui_list {
+	/** Base control object */
+	struct ui_control *control;
+
+	/** Containing window */
+	ui_window_t *window;
+
+	/** Callbacks */
+	struct ui_list_cb *cb;
+
+	/** Callback argument */
+	void *cb_arg;
+
+	/** List rectangle */
+	gfx_rect_t rect;
+
+	/** Scrollbar */
+	struct ui_scrollbar *scrollbar;
+
+	/** List entries (list of ui_list_entry_t) */
+	list_t entries;
+
+	/** Number of entries */
+	size_t entries_cnt;
+
+	/** First entry of current page */
+	ui_list_entry_t *page;
+
+	/** Index of first entry of current page */
+	size_t page_idx;
+
+	/** Cursor position */
+	ui_list_entry_t *cursor;
+
+	/** Index of entry under cursor */
+	size_t cursor_idx;
+
+	/** @c true iff the list is active */
+	bool active;
+} ui_list_t;
+
+extern gfx_coord_t ui_list_entry_height(ui_list_t *);
+extern errno_t ui_list_entry_paint(ui_list_entry_t *, size_t);
+extern errno_t ui_list_paint(ui_list_t *);
+extern ui_evclaim_t ui_list_kbd_event(ui_list_t *, kbd_event_t *);
+extern ui_evclaim_t ui_list_pos_event(ui_list_t *, pos_event_t *);
+extern unsigned ui_list_page_size(ui_list_t *);
+extern void ui_list_inside_rect(ui_list_t *, gfx_rect_t *);
+extern void ui_list_scrollbar_rect(ui_list_t *, gfx_rect_t *);
+extern gfx_coord_t ui_list_scrollbar_pos(ui_list_t *);
+extern void ui_list_scrollbar_update(ui_list_t *);
+extern void ui_list_clear_entries(ui_list_t *);
+extern ui_list_entry_t *ui_list_page_nth_entry(ui_list_t *,
+    size_t, size_t *);
+extern void ui_list_cursor_move(ui_list_t *, ui_list_entry_t *,
+    size_t);
+extern void ui_list_cursor_up(ui_list_t *);
+extern void ui_list_cursor_down(ui_list_t *);
+extern void ui_list_cursor_top(ui_list_t *);
+extern void ui_list_cursor_bottom(ui_list_t *);
+extern void ui_list_page_up(ui_list_t *);
+extern void ui_list_page_down(ui_list_t *);
+extern void ui_list_scroll_up(ui_list_t *);
+extern void ui_list_scroll_down(ui_list_t *);
+extern void ui_list_scroll_page_up(ui_list_t *);
+extern void ui_list_scroll_page_down(ui_list_t *);
+extern void ui_list_scroll_pos(ui_list_t *, size_t);
+extern void ui_list_activate_req(ui_list_t *);
+extern void ui_list_selected(ui_list_entry_t *);
+extern int ui_list_entry_ptr_cmp(const void *, const void *);
+extern size_t ui_list_entry_get_idx(ui_list_entry_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/ui/src/filelist.c
===================================================================
--- uspace/lib/ui/src/filelist.c	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/lib/ui/src/filelist.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -30,5 +30,5 @@
  * @{
  */
-/** @file File list.
+/** @file File list control.
  *
  * Displays a file listing.
@@ -37,13 +37,10 @@
 #include <dirent.h>
 #include <errno.h>
-#include <gfx/render.h>
-#include <gfx/text.h>
 #include <stdlib.h>
 #include <str.h>
 #include <ui/control.h>
 #include <ui/filelist.h>
-#include <ui/paint.h>
+#include <ui/list.h>
 #include <ui/resource.h>
-#include <ui/scrollbar.h>
 #include <vfs/vfs.h>
 #include <qsort.h>
@@ -56,5 +53,5 @@
 static ui_evclaim_t ui_file_list_ctl_pos_event(void *, pos_event_t *);
 
-/** File list control ops */
+/** List control ops */
 ui_control_ops_t ui_file_list_ctl_ops = {
 	.destroy = ui_file_list_ctl_destroy,
@@ -64,24 +61,12 @@
 };
 
-enum {
-	file_list_entry_hpad = 2,
-	file_list_entry_vpad = 2,
-	file_list_entry_hpad_text = 1,
-	file_list_entry_vpad_text = 0,
-};
-
-static void ui_file_list_scrollbar_up(ui_scrollbar_t *, void *);
-static void ui_file_list_scrollbar_down(ui_scrollbar_t *, void *);
-static void ui_file_list_scrollbar_page_up(ui_scrollbar_t *, void *);
-static void ui_file_list_scrollbar_page_down(ui_scrollbar_t *, void *);
-static void ui_file_list_scrollbar_moved(ui_scrollbar_t *, void *, gfx_coord_t);
-
-/** File list scrollbar callbacks */
-static ui_scrollbar_cb_t ui_file_list_scrollbar_cb = {
-	.up = ui_file_list_scrollbar_up,
-	.down = ui_file_list_scrollbar_down,
-	.page_up = ui_file_list_scrollbar_page_up,
-	.page_down = ui_file_list_scrollbar_page_down,
-	.moved = ui_file_list_scrollbar_moved
+static void ui_file_list_list_activate_req(ui_list_t *, void *);
+static void ui_file_list_list_selected(ui_list_entry_t *, void *);
+
+/** List callbacks */
+ui_list_cb_t ui_file_list_list_cb = {
+	.activate_req = ui_file_list_list_activate_req,
+	.selected = ui_file_list_list_selected,
+	.compare = ui_file_list_list_compare,
 };
 
@@ -105,8 +90,6 @@
 	rc = ui_control_new(&ui_file_list_ctl_ops, (void *)flist,
 	    &flist->control);
-	if (rc != EOK) {
-		free(flist);
-		return rc;
-	}
+	if (rc != EOK)
+		goto error;
 
 	rc = gfx_color_new_ega(0x0f, &flist->dir_color);
@@ -118,38 +101,33 @@
 		goto error;
 
-	rc = ui_scrollbar_create(ui_window_get_ui(window), window,
-	    ui_sbd_vert, &flist->scrollbar);
+	rc = ui_list_create(window, active, &flist->list);
 	if (rc != EOK)
 		goto error;
 
-	ui_scrollbar_set_cb(flist->scrollbar, &ui_file_list_scrollbar_cb,
-	    (void *) flist);
+	ui_list_set_cb(flist->list, &ui_file_list_list_cb, (void *)flist);
 
 	flist->window = window;
-	list_initialize(&flist->entries);
-	flist->entries_cnt = 0;
-	flist->active = active;
-
 	*rflist = flist;
 	return EOK;
 error:
+	ui_control_delete(flist->control);
 	if (flist->dir_color != NULL)
 		gfx_color_delete(flist->dir_color);
 	if (flist->svc_color != NULL)
 		gfx_color_delete(flist->svc_color);
+	free(flist);
+	return rc;
+}
+
+/** Destroy file list.
+ *
+ * @param flist File list
+ */
+void ui_file_list_destroy(ui_file_list_t *flist)
+{
+	ui_file_list_clear_entries(flist);
+	ui_list_destroy(flist->list);
 	ui_control_delete(flist->control);
 	free(flist);
-	return rc;
-}
-
-/** Destroy file list.
- *
- * @param flist File list
- */
-void ui_file_list_destroy(ui_file_list_t *flist)
-{
-	ui_file_list_clear_entries(flist);
-	ui_control_delete(flist->control);
-	free(flist);
 }
 
@@ -166,324 +144,4 @@
 }
 
-/** Get height of file list entry.
- *
- * @param flist File list
- * @return Entry height in pixels
- */
-gfx_coord_t ui_file_list_entry_height(ui_file_list_t *flist)
-{
-	ui_resource_t *res;
-	gfx_font_metrics_t metrics;
-	gfx_coord_t height;
-	gfx_coord_t vpad;
-
-	res = ui_window_get_res(flist->window);
-
-	if (res->textmode) {
-		vpad = file_list_entry_vpad_text;
-	} else {
-		vpad = file_list_entry_vpad;
-	}
-
-	/* Normal menu entry */
-	gfx_font_get_metrics(res->font, &metrics);
-	height = metrics.ascent + metrics.descent + 1;
-
-	return height + 2 * vpad;
-}
-
-/** Paint file list entry.
- *
- * @param entry File list entry
- * @param entry_idx Entry index (within list of entries)
- * @return EOK on success or an error code
- */
-errno_t ui_file_list_entry_paint(ui_file_list_entry_t *entry, size_t entry_idx)
-{
-	ui_file_list_t *flist = entry->flist;
-	gfx_context_t *gc = ui_window_get_gc(flist->window);
-	ui_resource_t *res = ui_window_get_res(flist->window);
-	gfx_font_t *font = ui_resource_get_font(res);
-	gfx_text_fmt_t fmt;
-	gfx_coord2_t pos;
-	gfx_rect_t rect;
-	gfx_rect_t lrect;
-	gfx_rect_t crect;
-	gfx_color_t *bgcolor;
-	char *caption;
-	gfx_coord_t hpad, vpad;
-	gfx_coord_t line_height;
-	size_t rows;
-	errno_t rc;
-	int rv;
-
-	line_height = ui_file_list_entry_height(flist);
-	ui_file_list_inside_rect(entry->flist, &lrect);
-
-	gfx_text_fmt_init(&fmt);
-	fmt.font = font;
-	rows = ui_file_list_page_size(flist) + 1;
-
-	/* Do not display entry outside of current page */
-	if (entry_idx < flist->page_idx ||
-	    entry_idx >= flist->page_idx + rows)
-		return EOK;
-
-	if (res->textmode) {
-		hpad = file_list_entry_hpad_text;
-		vpad = file_list_entry_vpad_text;
-	} else {
-		hpad = file_list_entry_hpad;
-		vpad = file_list_entry_vpad;
-	}
-
-	pos.x = lrect.p0.x;
-	pos.y = lrect.p0.y + line_height * (entry_idx - flist->page_idx);
-
-	if (entry == flist->cursor && flist->active) {
-		fmt.color = res->entry_sel_text_fg_color;
-		bgcolor = res->entry_sel_text_bg_color;
-	} else if (entry->isdir) {
-		if (res->textmode) {
-			fmt.color = flist->dir_color;
-			bgcolor = flist->dir_color;
-		} else {
-			fmt.color = res->entry_fg_color;
-			bgcolor = res->entry_bg_color;
-		}
-	} else if (entry->svc != 0) {
-		if (res->textmode) {
-			fmt.color = flist->svc_color;
-			bgcolor = flist->svc_color;
-		} else {
-			fmt.color = res->entry_fg_color;
-			bgcolor = res->entry_bg_color;
-		}
-	} else {
-		fmt.color = res->entry_fg_color;
-		bgcolor = res->entry_bg_color;
-	}
-
-	/* Draw entry background */
-	rect.p0 = pos;
-	rect.p1.x = lrect.p1.x;
-	rect.p1.y = rect.p0.y + line_height;
-
-	/* Clip to file list interior */
-	gfx_rect_clip(&rect, &lrect, &crect);
-
-	rc = gfx_set_color(gc, bgcolor);
-	if (rc != EOK)
-		return rc;
-
-	rc = gfx_fill_rect(gc, &crect);
-	if (rc != EOK)
-		return rc;
-
-	/*
-	 * Make sure name does not overflow the entry rectangle.
-	 *
-	 * XXX We probably want to measure the text width, and,
-	 * if it's too long, use gfx_text_find_pos() to find where
-	 * it should be cut off (and append some sort of overflow
-	 * marker.
-	 */
-	rc = gfx_set_clip_rect(gc, &crect);
-	if (rc != EOK)
-		return rc;
-
-	pos.x += hpad;
-	pos.y += vpad;
-
-	if (res->textmode || !entry->isdir) {
-		rc = gfx_puttext(&pos, &fmt, entry->name);
-		if (rc != EOK) {
-			(void) gfx_set_clip_rect(gc, NULL);
-			return rc;
-		}
-	} else {
-		/*
-		 * XXX This is mostly a hack to distinguish directories
-		 * util a better solution is available. (E.g. a Size
-		 * column where we can put <dir>, an icon.)
-		 */
-		rv = asprintf(&caption, "[%s]", entry->name);
-		if (rv < 0) {
-			(void) gfx_set_clip_rect(gc, NULL);
-			return rc;
-		}
-
-		rc = gfx_puttext(&pos, &fmt, caption);
-		if (rc != EOK) {
-			free(caption);
-			(void) gfx_set_clip_rect(gc, NULL);
-			return rc;
-		}
-
-		free(caption);
-	}
-
-	return gfx_set_clip_rect(gc, NULL);
-}
-
-/** Paint file list.
- *
- * @param flist File list
- */
-errno_t ui_file_list_paint(ui_file_list_t *flist)
-{
-	gfx_context_t *gc = ui_window_get_gc(flist->window);
-	ui_resource_t *res = ui_window_get_res(flist->window);
-	ui_file_list_entry_t *entry;
-	int i, lines;
-	errno_t rc;
-
-	rc = gfx_set_color(gc, res->entry_bg_color);
-	if (rc != EOK)
-		return rc;
-
-	rc = gfx_fill_rect(gc, &flist->rect);
-	if (rc != EOK)
-		return rc;
-
-	if (!res->textmode) {
-		rc = ui_paint_inset_frame(res, &flist->rect, NULL);
-		if (rc != EOK)
-			return rc;
-	}
-
-	lines = ui_file_list_page_size(flist) + 1;
-	i = 0;
-
-	entry = flist->page;
-	while (entry != NULL && i < lines) {
-		rc = ui_file_list_entry_paint(entry, flist->page_idx + i);
-		if (rc != EOK)
-			return rc;
-
-		++i;
-		entry = ui_file_list_next(entry);
-	}
-
-	rc = ui_scrollbar_paint(flist->scrollbar);
-	if (rc != EOK)
-		return rc;
-
-	rc = gfx_update(gc);
-	if (rc != EOK)
-		return rc;
-
-	return EOK;
-}
-
-/** Handle file list keyboard event.
- *
- * @param flist File list
- * @param event Keyboard event
- * @return ui_claimed iff event was claimed
- */
-ui_evclaim_t ui_file_list_kbd_event(ui_file_list_t *flist, kbd_event_t *event)
-{
-	if (!flist->active)
-		return ui_unclaimed;
-
-	if (event->type == KEY_PRESS) {
-		if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
-			switch (event->key) {
-			case KC_UP:
-				ui_file_list_cursor_up(flist);
-				break;
-			case KC_DOWN:
-				ui_file_list_cursor_down(flist);
-				break;
-			case KC_HOME:
-				ui_file_list_cursor_top(flist);
-				break;
-			case KC_END:
-				ui_file_list_cursor_bottom(flist);
-				break;
-			case KC_PAGE_UP:
-				ui_file_list_page_up(flist);
-				break;
-			case KC_PAGE_DOWN:
-				ui_file_list_page_down(flist);
-				break;
-			case KC_ENTER:
-				ui_file_list_open(flist, flist->cursor);
-				break;
-			default:
-				break;
-			}
-		}
-	}
-
-	return ui_claimed;
-}
-
-/** Handle file list position event.
- *
- * @param flist File list
- * @param event Position event
- * @return ui_claimed iff event was claimed
- */
-ui_evclaim_t ui_file_list_pos_event(ui_file_list_t *flist, pos_event_t *event)
-{
-	gfx_coord2_t pos;
-	gfx_rect_t irect;
-	ui_file_list_entry_t *entry;
-	gfx_coord_t line_height;
-	size_t entry_idx;
-	ui_evclaim_t claim;
-	int n;
-
-	claim = ui_scrollbar_pos_event(flist->scrollbar, event);
-	if (claim == ui_claimed)
-		return ui_claimed;
-
-	line_height = ui_file_list_entry_height(flist);
-
-	pos.x = event->hpos;
-	pos.y = event->vpos;
-	if (!gfx_pix_inside_rect(&pos, &flist->rect))
-		return ui_unclaimed;
-
-	if (!flist->active && event->type == POS_PRESS)
-		ui_file_list_activate_req(flist);
-
-	if (event->type == POS_PRESS || event->type == POS_DCLICK) {
-		ui_file_list_inside_rect(flist, &irect);
-
-		/* Did we click on one of the entries? */
-		if (gfx_pix_inside_rect(&pos, &irect)) {
-			/* Index within page */
-			n = (pos.y - irect.p0.y) / line_height;
-
-			/* Entry and its index within entire listing */
-			entry = ui_file_list_page_nth_entry(flist, n, &entry_idx);
-			if (entry == NULL)
-				return ui_claimed;
-
-			if (event->type == POS_PRESS) {
-				/* Move to the entry found */
-				ui_file_list_cursor_move(flist, entry, entry_idx);
-			} else {
-				/* event->type == POS_DCLICK */
-				ui_file_list_open(flist, entry);
-			}
-		} else {
-			/* It's in the border. */
-			if (event->type == POS_PRESS) {
-				/* Top or bottom half? */
-				if (pos.y >= (irect.p0.y + irect.p1.y) / 2)
-					ui_file_list_page_down(flist);
-				else
-					ui_file_list_page_up(flist);
-			}
-		}
-	}
-
-	return ui_claimed;
-}
-
 /** Get base control for file list.
  *
@@ -503,112 +161,5 @@
 void ui_file_list_set_rect(ui_file_list_t *flist, gfx_rect_t *rect)
 {
-	gfx_rect_t srect;
-
-	flist->rect = *rect;
-
-	ui_file_list_scrollbar_rect(flist, &srect);
-	ui_scrollbar_set_rect(flist->scrollbar, &srect);
-}
-
-/** Get file list page size.
- *
- * @param flist File list
- * @return Number of entries that fit in flist at the same time.
- */
-unsigned ui_file_list_page_size(ui_file_list_t *flist)
-{
-	gfx_coord_t line_height;
-	gfx_rect_t irect;
-
-	line_height = ui_file_list_entry_height(flist);
-	ui_file_list_inside_rect(flist, &irect);
-	return (irect.p1.y - irect.p0.y) / line_height;
-}
-
-/** Get file list interior rectangle.
- *
- * @param flist File list
- * @param irect Place to store interior rectangle
- */
-void ui_file_list_inside_rect(ui_file_list_t *flist, gfx_rect_t *irect)
-{
-	ui_resource_t *res = ui_window_get_res(flist->window);
-	gfx_rect_t rect;
-	gfx_coord_t width;
-
-	if (res->textmode) {
-		rect = flist->rect;
-	} else {
-		ui_paint_get_inset_frame_inside(res, &flist->rect, &rect);
-	}
-
-	if (res->textmode) {
-		width = 1;
-	} else {
-		width = 23;
-	}
-
-	irect->p0 = rect.p0;
-	irect->p1.x = rect.p1.x - width;
-	irect->p1.y = rect.p1.y;
-}
-
-/** Get file list scrollbar rectangle.
- *
- * @param flist File list
- * @param irect Place to store interior rectangle
- */
-void ui_file_list_scrollbar_rect(ui_file_list_t *flist, gfx_rect_t *srect)
-{
-	ui_resource_t *res = ui_window_get_res(flist->window);
-	gfx_rect_t rect;
-	gfx_coord_t width;
-
-	if (res->textmode) {
-		rect = flist->rect;
-	} else {
-		ui_paint_get_inset_frame_inside(res, &flist->rect, &rect);
-	}
-
-	if (res->textmode) {
-		width = 1;
-	} else {
-		width = 23;
-	}
-
-	srect->p0.x = rect.p1.x - width;
-	srect->p0.y = rect.p0.y;
-	srect->p1 = rect.p1;
-}
-
-/** Compute new position for file list scrollbar thumb.
- *
- * @param flist File list
- * @return New position
- */
-gfx_coord_t ui_file_list_scrollbar_pos(ui_file_list_t *flist)
-{
-	size_t entries;
-	size_t pglen;
-	size_t sbar_len;
-
-	entries = list_count(&flist->entries);
-	pglen = ui_file_list_page_size(flist);
-	sbar_len = ui_scrollbar_move_length(flist->scrollbar);
-
-	if (entries > pglen)
-		return sbar_len * flist->page_idx / (entries - pglen);
-	else
-		return 0;
-}
-
-/** Update file list scrollbar position.
- *
- * @param flist File list
- */
-void ui_file_list_scrollbar_update(ui_file_list_t *flist)
-{
-	ui_scrollbar_set_pos(flist->scrollbar,
-	    ui_file_list_scrollbar_pos(flist));
+	ui_list_set_rect(flist->list, rect);
 }
 
@@ -620,5 +171,5 @@
 bool ui_file_list_is_active(ui_file_list_t *flist)
 {
-	return flist->active;
+	return ui_list_is_active(flist->list);
 }
 
@@ -639,7 +190,5 @@
 	}
 
-	flist->active = true;
-	(void) ui_file_list_paint(flist);
-	return EOK;
+	return ui_list_activate(flist->list);
 }
 
@@ -650,6 +199,5 @@
 void ui_file_list_deactivate(ui_file_list_t *flist)
 {
-	flist->active = false;
-	(void) ui_file_list_paint(flist);
+	ui_list_deactivate(flist->list);
 }
 
@@ -661,53 +209,4 @@
 {
 	memset(attr, 0, sizeof(*attr));
-}
-
-/** Destroy file list control.
- *
- * @param arg Argument (ui_file_list_t *)
- */
-void ui_file_list_ctl_destroy(void *arg)
-{
-	ui_file_list_t *flist = (ui_file_list_t *) arg;
-
-	ui_file_list_destroy(flist);
-}
-
-/** Paint file list control.
- *
- * @param arg Argument (ui_file_list_t *)
- * @return EOK on success or an error code
- */
-errno_t ui_file_list_ctl_paint(void *arg)
-{
-	ui_file_list_t *flist = (ui_file_list_t *) arg;
-
-	return ui_file_list_paint(flist);
-}
-
-/** Handle file list control keyboard event.
- *
- * @param arg Argument (ui_file_list_t *)
- * @param kbd_event Keyboard event
- * @return @c ui_claimed iff the event is claimed
- */
-ui_evclaim_t ui_file_list_ctl_kbd_event(void *arg, kbd_event_t *event)
-{
-	ui_file_list_t *flist = (ui_file_list_t *) arg;
-
-	return ui_file_list_kbd_event(flist, event);
-}
-
-/** Handle file list control position event.
- *
- * @param arg Argument (ui_file_list_t *)
- * @param pos_event Position event
- * @return @c ui_claimed iff the event is claimed
- */
-ui_evclaim_t ui_file_list_ctl_pos_event(void *arg, pos_event_t *event)
-{
-	ui_file_list_t *flist = (ui_file_list_t *) arg;
-
-	return ui_file_list_pos_event(flist, event);
 }
 
@@ -721,4 +220,12 @@
 {
 	ui_file_list_entry_t *entry;
+	ui_list_entry_attr_t lattr;
+	ui_list_entry_t *lentry;
+	ui_resource_t *res;
+	char *caption;
+	errno_t rc;
+	int rv;
+
+	res = ui_window_get_res(flist->window);
 
 	entry = calloc(1, sizeof(ui_file_list_entry_t));
@@ -736,7 +243,44 @@
 	entry->isdir = attr->isdir;
 	entry->svc = attr->svc;
-	link_initialize(&entry->lentries);
-	list_append(&entry->lentries, &flist->entries);
-	++flist->entries_cnt;
+
+	if (attr->isdir && !res->textmode) {
+		rv = asprintf(&caption, "[%s]", attr->name);
+		if (rv < 0)
+			caption = NULL;
+	} else {
+		caption = str_dup(attr->name);
+	}
+
+	if (caption == NULL) {
+		free(entry->name);
+		free(entry);
+		return ENOMEM;
+	}
+
+	lattr.caption = caption;
+	lattr.arg = (void *)entry;
+	lattr.color = NULL;
+	lattr.bgcolor = NULL;
+
+	if (res->textmode) {
+		if (attr->isdir) {
+			lattr.color = flist->dir_color;
+			lattr.bgcolor = flist->dir_color;
+		} else if (attr->svc != 0) {
+			lattr.color = flist->svc_color;
+			lattr.bgcolor = flist->svc_color;
+		}
+	}
+
+	rc = ui_list_entry_append(flist->list, &lattr, &lentry);
+	if (rc != EOK) {
+		free(caption);
+		free(entry->name);
+		free(entry);
+		return rc;
+	}
+
+	free(caption);
+	entry->entry = lentry;
 	return EOK;
 }
@@ -748,12 +292,6 @@
 void ui_file_list_entry_delete(ui_file_list_entry_t *entry)
 {
-	if (entry->flist->cursor == entry)
-		entry->flist->cursor = NULL;
-	if (entry->flist->page == entry)
-		entry->flist->page = NULL;
-
-	list_remove(&entry->lentries);
-	--entry->flist->entries_cnt;
-	free((char *) entry->name);
+	ui_list_entry_delete(entry->entry);
+	free(entry->name);
 	free(entry);
 }
@@ -788,10 +326,7 @@
 	char *ndir = NULL;
 	ui_file_list_entry_attr_t attr;
+	ui_file_list_entry_t *cur;
 	ui_file_list_entry_t *next;
-	ui_file_list_entry_t *prev;
 	char *olddn;
-	size_t pg_size;
-	size_t max_idx;
-	size_t i;
 	errno_t rc;
 
@@ -853,9 +388,4 @@
 		goto error;
 
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = ui_file_list_first(flist);
-	flist->page_idx = 0;
-
 	/* Moving up? */
 	if (str_cmp(dirname, "..") == 0) {
@@ -865,41 +395,14 @@
 			/* Find corresponding entry */
 			++olddn;
-			next = ui_file_list_next(flist->cursor);
+			cur = ui_file_list_first(flist);
+			next = ui_file_list_next(cur);
 			while (next != NULL && str_cmp(next->name, olddn) <= 0 &&
 			    next->isdir) {
-				flist->cursor = next;
-				++flist->cursor_idx;
-				next = ui_file_list_next(flist->cursor);
+				cur = next;
+				next = ui_file_list_next(cur);
 			}
 
-			/* Move page so that cursor is in the center */
-			flist->page = flist->cursor;
-			flist->page_idx = flist->cursor_idx;
-
-			pg_size = ui_file_list_page_size(flist);
-
-			for (i = 0; i < pg_size / 2; i++) {
-				prev = ui_file_list_prev(flist->page);
-				if (prev == NULL)
-					break;
-
-				flist->page = prev;
-				--flist->page_idx;
-			}
-
-			/* Make sure page is not beyond the end if possible */
-			if (flist->entries_cnt > pg_size)
-				max_idx = flist->entries_cnt - pg_size;
-			else
-				max_idx = 0;
-
-			while (flist->page_idx > 0 && flist->page_idx > max_idx) {
-				prev = ui_file_list_prev(flist->page);
-				if (prev == NULL)
-					break;
-
-				flist->page = prev;
-				--flist->page_idx;
-			}
+			/* Center on the entry */
+			ui_list_cursor_center(flist->list, cur->entry);
 		}
 	}
@@ -925,52 +428,21 @@
 errno_t ui_file_list_sort(ui_file_list_t *flist)
 {
-	ui_file_list_entry_t **emap;
-	ui_file_list_entry_t *entry;
-	size_t i;
-
-	/* Create an array to hold pointer to each entry */
-	emap = calloc(flist->entries_cnt, sizeof(ui_file_list_entry_t *));
-	if (emap == NULL)
-		return ENOMEM;
-
-	/* Write entry pointers to array */
-	entry = ui_file_list_first(flist);
-	i = 0;
-	while (entry != NULL) {
-		assert(i < flist->entries_cnt);
-		emap[i++] = entry;
-		entry = ui_file_list_next(entry);
-	}
-
-	/* Sort the array of pointers */
-	qsort(emap, flist->entries_cnt, sizeof(ui_file_list_entry_t *),
-	    ui_file_list_entry_ptr_cmp);
-
-	/* Unlink entries from entry list */
-	entry = ui_file_list_first(flist);
-	while (entry != NULL) {
-		list_remove(&entry->lentries);
-		entry = ui_file_list_first(flist);
-	}
-
-	/* Add entries back to entry list sorted */
-	for (i = 0; i < flist->entries_cnt; i++)
-		list_append(&emap[i]->lentries, &flist->entries);
-
-	free(emap);
-	return EOK;
-}
-
-/** Compare two file list entries indirectly referenced by pointers.
- *
- * @param pa Pointer to pointer to first entry
- * @param pb Pointer to pointer to second entry
- * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
- */
-int ui_file_list_entry_ptr_cmp(const void *pa, const void *pb)
-{
-	ui_file_list_entry_t *a = *(ui_file_list_entry_t **)pa;
-	ui_file_list_entry_t *b = *(ui_file_list_entry_t **)pb;
+	return ui_list_sort(flist->list);
+}
+
+/** Compare two list entries within file list entries.
+ *
+ * @param ea First UI list entry
+ * @param eb Second UI list entry
+ * @return <0, =0, >=0 if a < b, a == b, a > b, resp.
+ */
+int ui_file_list_list_compare(ui_list_entry_t *ea, ui_list_entry_t *eb)
+{
+	ui_file_list_entry_t *a;
+	ui_file_list_entry_t *b;
 	int dcmp;
+
+	a = (ui_file_list_entry_t *)ui_list_entry_get_arg(ea);
+	b = (ui_file_list_entry_t *)ui_list_entry_get_arg(eb);
 
 	/* Sort directories first */
@@ -989,11 +461,11 @@
 ui_file_list_entry_t *ui_file_list_first(ui_file_list_t *flist)
 {
-	link_t *link;
-
-	link = list_first(&flist->entries);
-	if (link == NULL)
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_first(flist->list);
+	if (lentry == NULL)
 		return NULL;
 
-	return list_get_instance(link, ui_file_list_entry_t, lentries);
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
 }
 
@@ -1005,11 +477,11 @@
 ui_file_list_entry_t *ui_file_list_last(ui_file_list_t *flist)
 {
-	link_t *link;
-
-	link = list_last(&flist->entries);
-	if (link == NULL)
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_last(flist->list);
+	if (lentry == NULL)
 		return NULL;
 
-	return list_get_instance(link, ui_file_list_entry_t, lentries);
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
 }
 
@@ -1021,11 +493,11 @@
 ui_file_list_entry_t *ui_file_list_next(ui_file_list_entry_t *cur)
 {
-	link_t *link;
-
-	link = list_next(&cur->lentries, &cur->flist->entries);
-	if (link == NULL)
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_next(cur->entry);
+	if (lentry == NULL)
 		return NULL;
 
-	return list_get_instance(link, ui_file_list_entry_t, lentries);
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
 }
 
@@ -1037,421 +509,27 @@
 ui_file_list_entry_t *ui_file_list_prev(ui_file_list_entry_t *cur)
 {
-	link_t *link;
-
-	link = list_prev(&cur->lentries, &cur->flist->entries);
-	if (link == NULL)
+	ui_list_entry_t *lentry;
+
+	lentry = ui_list_prev(cur->entry);
+	if (lentry == NULL)
 		return NULL;
 
-	return list_get_instance(link, ui_file_list_entry_t, lentries);
-}
-
-/** Find the n-th entry of the current file list page.
- *
- * @param flist File list
- * @param n Which entry to get (starting from 0)
- * @param ridx Place to store index (within listing) of the entry
- * @return n-th entry of the page
- */
-ui_file_list_entry_t *ui_file_list_page_nth_entry(ui_file_list_t *flist,
-    size_t n, size_t *ridx)
-{
-	ui_file_list_entry_t *entry;
-	size_t i;
-	size_t idx;
-
-	assert(n <= ui_file_list_page_size(flist));
-
-	entry = flist->page;
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(lentry);
+}
+
+/** Get entry under cursor.
+ *
+ * @param flist File list
+ * @return Current cursor
+ */
+ui_file_list_entry_t *ui_file_list_get_cursor(ui_file_list_t *flist)
+{
+	ui_list_entry_t *entry;
+
+	entry = ui_list_get_cursor(flist->list);
 	if (entry == NULL)
 		return NULL;
 
-	idx = flist->page_idx;
-	for (i = 0; i < n; i++) {
-		entry = ui_file_list_next(entry);
-		if (entry == NULL)
-			return NULL;
-
-		++idx;
-	}
-
-	*ridx = idx;
-	return entry;
-}
-
-/** Get entry under cursor.
- *
- * @param flist File list
- * @return Current cursor
- */
-ui_file_list_entry_t *ui_file_list_get_cursor(ui_file_list_t *flist)
-{
-	return flist->cursor;
-}
-
-/** Move cursor to a new position, possibly scrolling.
- *
- * @param flist File list
- * @param entry New entry under cursor
- * @param entry_idx Index of new entry under cursor
- */
-void ui_file_list_cursor_move(ui_file_list_t *flist,
-    ui_file_list_entry_t *entry, size_t entry_idx)
-{
-	gfx_context_t *gc = ui_window_get_gc(flist->window);
-	ui_file_list_entry_t *old_cursor;
-	size_t old_idx;
-	size_t rows;
-	ui_file_list_entry_t *e;
-	size_t i;
-
-	rows = ui_file_list_page_size(flist);
-
-	old_cursor = flist->cursor;
-	old_idx = flist->cursor_idx;
-
-	flist->cursor = entry;
-	flist->cursor_idx = entry_idx;
-
-	if (entry_idx >= flist->page_idx &&
-	    entry_idx < flist->page_idx + rows) {
-		/*
-		 * If cursor is still on the current page, we're not
-		 * scrolling. Just unpaint old cursor and paint new
-		 * cursor.
-		 */
-		ui_file_list_entry_paint(old_cursor, old_idx);
-		ui_file_list_entry_paint(flist->cursor, flist->cursor_idx);
-
-		(void) gfx_update(gc);
-	} else {
-		/*
-		 * Need to scroll and update all rows.
-		 */
-
-		/* Scrolling up */
-		if (entry_idx < flist->page_idx) {
-			flist->page = entry;
-			flist->page_idx = entry_idx;
-		}
-
-		/* Scrolling down */
-		if (entry_idx >= flist->page_idx + rows) {
-			if (entry_idx >= rows) {
-				flist->page_idx = entry_idx - rows + 1;
-				/* Find first page entry (go back rows - 1) */
-				e = entry;
-				for (i = 0; i < rows - 1; i++) {
-					e = ui_file_list_prev(e);
-				}
-
-				/* Should be valid */
-				assert(e != NULL);
-				flist->page = e;
-			} else {
-				flist->page = ui_file_list_first(flist);
-				flist->page_idx = 0;
-			}
-		}
-
-		ui_file_list_scrollbar_update(flist);
-		(void) ui_file_list_paint(flist);
-	}
-}
-
-/** Move cursor one entry up.
- *
- * @param flist File list
- */
-void ui_file_list_cursor_up(ui_file_list_t *flist)
-{
-	ui_file_list_entry_t *prev;
-	size_t prev_idx;
-
-	prev = ui_file_list_prev(flist->cursor);
-	prev_idx = flist->cursor_idx - 1;
-	if (prev != NULL)
-		ui_file_list_cursor_move(flist, prev, prev_idx);
-}
-
-/** Move cursor one entry down.
- *
- * @param flist File list
- */
-void ui_file_list_cursor_down(ui_file_list_t *flist)
-{
-	ui_file_list_entry_t *next;
-	size_t next_idx;
-
-	next = ui_file_list_next(flist->cursor);
-	next_idx = flist->cursor_idx + 1;
-	if (next != NULL)
-		ui_file_list_cursor_move(flist, next, next_idx);
-}
-
-/** Move cursor to top.
- *
- * @param flist File list
- */
-void ui_file_list_cursor_top(ui_file_list_t *flist)
-{
-	ui_file_list_cursor_move(flist, ui_file_list_first(flist), 0);
-}
-
-/** Move cursor to bottom.
- *
- * @param flist File list
- */
-void ui_file_list_cursor_bottom(ui_file_list_t *flist)
-{
-	ui_file_list_cursor_move(flist, ui_file_list_last(flist),
-	    flist->entries_cnt - 1);
-}
-
-/** Move cursor one page up.
- *
- * @param flist File list
- */
-void ui_file_list_page_up(ui_file_list_t *flist)
-{
-	gfx_context_t *gc = ui_window_get_gc(flist->window);
-	ui_file_list_entry_t *old_page;
-	ui_file_list_entry_t *old_cursor;
-	size_t old_idx;
-	size_t rows;
-	ui_file_list_entry_t *entry;
-	size_t i;
-
-	rows = ui_file_list_page_size(flist);
-
-	old_page = flist->page;
-	old_cursor = flist->cursor;
-	old_idx = flist->cursor_idx;
-
-	/* Move page by rows entries up (if possible) */
-	for (i = 0; i < rows; i++) {
-		entry = ui_file_list_prev(flist->page);
-		if (entry != NULL) {
-			flist->page = entry;
-			--flist->page_idx;
-		}
-	}
-
-	/* Move cursor by rows entries up (if possible) */
-
-	for (i = 0; i < rows; i++) {
-		entry = ui_file_list_prev(flist->cursor);
-		if (entry != NULL) {
-			flist->cursor = entry;
-			--flist->cursor_idx;
-		}
-	}
-
-	if (flist->page != old_page) {
-		/* We have scrolled. Need to repaint all entries */
-		ui_file_list_scrollbar_update(flist);
-		(void) ui_file_list_paint(flist);
-	} else if (flist->cursor != old_cursor) {
-		/* No scrolling, but cursor has moved */
-		ui_file_list_entry_paint(old_cursor, old_idx);
-		ui_file_list_entry_paint(flist->cursor, flist->cursor_idx);
-
-		(void) gfx_update(gc);
-	}
-}
-
-/** Move cursor one page down.
- *
- * @param flist File list
- */
-void ui_file_list_page_down(ui_file_list_t *flist)
-{
-	gfx_context_t *gc = ui_window_get_gc(flist->window);
-	ui_file_list_entry_t *old_page;
-	ui_file_list_entry_t *old_cursor;
-	size_t old_idx;
-	size_t max_idx;
-	size_t rows;
-	ui_file_list_entry_t *entry;
-	size_t i;
-
-	rows = ui_file_list_page_size(flist);
-
-	old_page = flist->page;
-	old_cursor = flist->cursor;
-	old_idx = flist->cursor_idx;
-
-	if (flist->entries_cnt > rows)
-		max_idx = flist->entries_cnt - rows;
-	else
-		max_idx = 0;
-
-	/* Move page by rows entries down (if possible) */
-	for (i = 0; i < rows; i++) {
-		entry = ui_file_list_next(flist->page);
-		/* Do not scroll that results in a short page */
-		if (entry != NULL && flist->page_idx < max_idx) {
-			flist->page = entry;
-			++flist->page_idx;
-		}
-	}
-
-	/* Move cursor by rows entries down (if possible) */
-
-	for (i = 0; i < rows; i++) {
-		entry = ui_file_list_next(flist->cursor);
-		if (entry != NULL) {
-			flist->cursor = entry;
-			++flist->cursor_idx;
-		}
-	}
-
-	if (flist->page != old_page) {
-		/* We have scrolled. Need to repaint all entries */
-		ui_file_list_scrollbar_update(flist);
-		(void) ui_file_list_paint(flist);
-	} else if (flist->cursor != old_cursor) {
-		/* No scrolling, but cursor has moved */
-		ui_file_list_entry_paint(old_cursor, old_idx);
-		ui_file_list_entry_paint(flist->cursor, flist->cursor_idx);
-
-		(void) gfx_update(gc);
-	}
-}
-
-/** Scroll one entry up.
- *
- * @param flist File list
- */
-void ui_file_list_scroll_up(ui_file_list_t *flist)
-{
-	ui_file_list_entry_t *prev;
-
-	prev = ui_file_list_prev(flist->page);
-	if (prev == NULL)
-		return;
-
-	flist->page = prev;
-	assert(flist->page_idx > 0);
-	--flist->page_idx;
-
-	ui_file_list_scrollbar_update(flist);
-	(void) ui_file_list_paint(flist);
-}
-
-/** Scroll one entry down.
- *
- * @param flist File list
- */
-void ui_file_list_scroll_down(ui_file_list_t *flist)
-{
-	ui_file_list_entry_t *next;
-	ui_file_list_entry_t *pgend;
-	size_t i;
-	size_t rows;
-
-	next = ui_file_list_next(flist->page);
-	if (next == NULL)
-		return;
-
-	rows = ui_file_list_page_size(flist);
-
-	/* Find last page entry */
-	pgend = flist->page;
-	for (i = 0; i < rows && pgend != NULL; i++) {
-		pgend = ui_file_list_next(pgend);
-	}
-
-	/* Scroll down by one entry, if the page remains full */
-	if (pgend != NULL) {
-		flist->page = next;
-		++flist->page_idx;
-	}
-
-	ui_file_list_scrollbar_update(flist);
-	(void) ui_file_list_paint(flist);
-}
-
-/** Scroll one page up.
- *
- * @param flist File list
- */
-void ui_file_list_scroll_page_up(ui_file_list_t *flist)
-{
-	ui_file_list_entry_t *prev;
-	size_t i;
-	size_t rows;
-
-	prev = ui_file_list_prev(flist->page);
-	if (prev == NULL)
-		return;
-
-	rows = ui_file_list_page_size(flist);
-
-	for (i = 0; i < rows && prev != NULL; i++) {
-		flist->page = prev;
-		assert(flist->page_idx > 0);
-		--flist->page_idx;
-		prev = ui_file_list_prev(prev);
-	}
-
-	ui_file_list_scrollbar_update(flist);
-	(void) ui_file_list_paint(flist);
-}
-
-/** Scroll one page down.
- *
- * @param flist File list
- */
-void ui_file_list_scroll_page_down(ui_file_list_t *flist)
-{
-	ui_file_list_entry_t *next;
-	ui_file_list_entry_t *pgend;
-	size_t i;
-	size_t rows;
-
-	next = ui_file_list_next(flist->page);
-	if (next == NULL)
-		return;
-
-	rows = ui_file_list_page_size(flist);
-
-	/* Find last page entry */
-	pgend = flist->page;
-	for (i = 0; i < rows && pgend != NULL; i++) {
-		pgend = ui_file_list_next(pgend);
-	}
-
-	/* Scroll by up to 'rows' entries, keeping the page full */
-	for (i = 0; i < rows && pgend != NULL; i++) {
-		flist->page = next;
-		++flist->page_idx;
-		next = ui_file_list_next(next);
-		pgend = ui_file_list_next(pgend);
-	}
-
-	ui_file_list_scrollbar_update(flist);
-	(void) ui_file_list_paint(flist);
-}
-
-/** Scroll to a specific entry
- *
- * @param flist File list
- * @param page_idx New index of first entry on the page
- */
-void ui_file_list_scroll_pos(ui_file_list_t *flist, size_t page_idx)
-{
-	ui_file_list_entry_t *entry;
-	size_t i;
-
-	entry = ui_file_list_first(flist);
-	for (i = 0; i < page_idx; i++) {
-		entry = ui_file_list_next(entry);
-		assert(entry != NULL);
-	}
-
-	flist->page = entry;
-	flist->page_idx = page_idx;
-
-	(void) ui_file_list_paint(flist);
+	return (ui_file_list_entry_t *)ui_list_entry_get_arg(entry);
 }
 
@@ -1487,5 +565,4 @@
     ui_file_list_entry_t *entry)
 {
-	gfx_context_t *gc = ui_window_get_gc(flist->window);
 	char *dirname;
 	errno_t rc;
@@ -1515,5 +592,5 @@
 		return rc;
 
-	return gfx_update(gc);
+	return EOK;
 }
 
@@ -1557,73 +634,85 @@
 }
 
-/** File list scrollbar up button pressed.
- *
- * @param scrollbar Scrollbar
+/** Paint file list.
+ *
+ * @param flist File list
+ * @return EOK on success or an error code.
+ */
+errno_t ui_file_list_paint(ui_file_list_t *flist)
+{
+	return ui_control_paint(ui_list_ctl(flist->list));
+}
+
+/** Destroy file list control.
+ *
+ * @param arg Argument (ui_list_t *)
+ */
+void ui_file_list_ctl_destroy(void *arg)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	ui_file_list_destroy(flist);
+}
+
+/** Paint file list control.
+ *
  * @param arg Argument (ui_file_list_t *)
- */
-static void ui_file_list_scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
+ * @return EOK on success or an error code
+ */
+errno_t ui_file_list_ctl_paint(void *arg)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	return ui_file_list_paint(flist);
+}
+
+/** Handle file list control keyboard event.
+ *
+ * @param arg Argument (ui_file_list_t *)
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_file_list_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	return ui_control_kbd_event(ui_list_ctl(flist->list), event);
+}
+
+/** Handle file list control position event.
+ *
+ * @param arg Argument (ui_file_list_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_file_list_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_file_list_t *flist = (ui_file_list_t *) arg;
+
+	return ui_control_pos_event(ui_list_ctl(flist->list), event);
+}
+
+/** Activate request callback handler for UI list within file list.
+ *
+ * @param list UI list
+ * @param arg Argument (File list)
+ */
+static void ui_file_list_list_activate_req(ui_list_t *list, void *arg)
 {
 	ui_file_list_t *flist = (ui_file_list_t *)arg;
-	ui_file_list_scroll_up(flist);
-}
-
-/** File list scrollbar down button pressed.
- *
- * @param scrollbar Scrollbar
- * @param arg Argument (ui_file_list_t *)
- */
-static void ui_file_list_scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
-{
-	ui_file_list_t *flist = (ui_file_list_t *)arg;
-	ui_file_list_scroll_down(flist);
-}
-
-/** File list scrollbar page up pressed.
- *
- * @param scrollbar Scrollbar
- * @param arg Argument (ui_file_list_t *)
- */
-static void ui_file_list_scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
-{
-	ui_file_list_t *flist = (ui_file_list_t *)arg;
-	ui_file_list_scroll_page_up(flist);
-}
-
-/** File list scrollbar page down pressed.
- *
- * @param scrollbar Scrollbar
- * @param arg Argument (ui_file_list_t *)
- */
-static void ui_file_list_scrollbar_page_down(ui_scrollbar_t *scrollbar,
-    void *arg)
-{
-	ui_file_list_t *flist = (ui_file_list_t *)arg;
-	ui_file_list_scroll_page_down(flist);
-}
-
-/** File list scrollbar moved.
- *
- * @param scrollbar Scrollbar
- * @param arg Argument (ui_file_list_t *)
- */
-static void ui_file_list_scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg,
-    gfx_coord_t pos)
-{
-	ui_file_list_t *flist = (ui_file_list_t *)arg;
-	size_t entries;
-	size_t pglen;
-	size_t sbar_len;
-	size_t pgstart;
-
-	entries = list_count(&flist->entries);
-	pglen = ui_file_list_page_size(flist);
-	sbar_len = ui_scrollbar_move_length(flist->scrollbar);
-
-	if (entries > pglen)
-		pgstart = (entries - pglen) * pos / (sbar_len - 1);
-	else
-		pgstart = 0;
-
-	ui_file_list_scroll_pos(flist, pgstart);
+
+	ui_file_list_activate_req(flist);
+}
+
+/** Entry selected callback handler for UI list within file list.
+ *
+ * @param entr Activated UI list entry
+ * @param arg Argument (File list entry)
+ */
+static void ui_file_list_list_selected(ui_list_entry_t *entry, void *arg)
+{
+	ui_file_list_entry_t *fentry = (void *)arg;
+
+	(void) ui_file_list_open(fentry->flist, fentry);
 }
 
Index: uspace/lib/ui/src/list.c
===================================================================
--- uspace/lib/ui/src/list.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
+++ uspace/lib/ui/src/list.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -0,0 +1,1462 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/** @addtogroup libui
+ * @{
+ */
+/** @file List.
+ *
+ * Simple list control.
+ */
+
+#include <errno.h>
+#include <gfx/render.h>
+#include <gfx/text.h>
+#include <stdlib.h>
+#include <str.h>
+#include <ui/control.h>
+#include <ui/list.h>
+#include <ui/paint.h>
+#include <ui/resource.h>
+#include <ui/scrollbar.h>
+#include "../private/list.h"
+#include "../private/resource.h"
+
+static void ui_list_ctl_destroy(void *);
+static errno_t ui_list_ctl_paint(void *);
+static ui_evclaim_t ui_list_ctl_kbd_event(void *, kbd_event_t *);
+static ui_evclaim_t ui_list_ctl_pos_event(void *, pos_event_t *);
+
+/** List control ops */
+ui_control_ops_t ui_list_ctl_ops = {
+	.destroy = ui_list_ctl_destroy,
+	.paint = ui_list_ctl_paint,
+	.kbd_event = ui_list_ctl_kbd_event,
+	.pos_event = ui_list_ctl_pos_event
+};
+
+enum {
+	list_entry_hpad = 2,
+	list_entry_vpad = 2,
+	list_entry_hpad_text = 1,
+	list_entry_vpad_text = 0,
+};
+
+static void ui_list_scrollbar_up(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_down(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_page_up(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_page_down(ui_scrollbar_t *, void *);
+static void ui_list_scrollbar_moved(ui_scrollbar_t *, void *, gfx_coord_t);
+
+/** List scrollbar callbacks */
+static ui_scrollbar_cb_t ui_list_scrollbar_cb = {
+	.up = ui_list_scrollbar_up,
+	.down = ui_list_scrollbar_down,
+	.page_up = ui_list_scrollbar_page_up,
+	.page_down = ui_list_scrollbar_page_down,
+	.moved = ui_list_scrollbar_moved
+};
+
+/** Create UI list.
+ *
+ * @param window Containing window
+ * @param active @c true iff list should be active
+ * @param rlist Place to store pointer to new list
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_create(ui_window_t *window, bool active,
+    ui_list_t **rlist)
+{
+	ui_list_t *list;
+	errno_t rc;
+
+	list = calloc(1, sizeof(ui_list_t));
+	if (list == NULL)
+		return ENOMEM;
+
+	rc = ui_control_new(&ui_list_ctl_ops, (void *)list,
+	    &list->control);
+	if (rc != EOK) {
+		free(list);
+		return rc;
+	}
+
+	rc = ui_scrollbar_create(ui_window_get_ui(window), window,
+	    ui_sbd_vert, &list->scrollbar);
+	if (rc != EOK)
+		goto error;
+
+	ui_scrollbar_set_cb(list->scrollbar, &ui_list_scrollbar_cb,
+	    (void *) list);
+
+	list->window = window;
+	list_initialize(&list->entries);
+	list->entries_cnt = 0;
+	list->active = active;
+
+	*rlist = list;
+	return EOK;
+error:
+	ui_control_delete(list->control);
+	free(list);
+	return rc;
+}
+
+/** Destroy UI list.
+ *
+ * @param list UI list
+ */
+void ui_list_destroy(ui_list_t *list)
+{
+	ui_list_clear_entries(list);
+	ui_control_delete(list->control);
+	free(list);
+}
+
+/** Set UI list callbacks.
+ *
+ * @param list UI list
+ * @param cb Callbacks
+ * @param arg Argument to callback functions
+ */
+void ui_list_set_cb(ui_list_t *list, ui_list_cb_t *cb, void *arg)
+{
+	list->cb = cb;
+	list->cb_arg = arg;
+}
+
+/** Get height of list entry.
+ *
+ * @param list UI list
+ * @return Entry height in pixels
+ */
+gfx_coord_t ui_list_entry_height(ui_list_t *list)
+{
+	ui_resource_t *res;
+	gfx_font_metrics_t metrics;
+	gfx_coord_t height;
+	gfx_coord_t vpad;
+
+	res = ui_window_get_res(list->window);
+
+	if (res->textmode) {
+		vpad = list_entry_vpad_text;
+	} else {
+		vpad = list_entry_vpad;
+	}
+
+	/* Normal menu entry */
+	gfx_font_get_metrics(res->font, &metrics);
+	height = metrics.ascent + metrics.descent + 1;
+
+	return height + 2 * vpad;
+}
+
+/** Paint list entry.
+ *
+ * @param entry List entry
+ * @param entry_idx Entry index (within list of entries)
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_entry_paint(ui_list_entry_t *entry, size_t entry_idx)
+{
+	ui_list_t *list = entry->list;
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_resource_t *res = ui_window_get_res(list->window);
+	gfx_font_t *font = ui_resource_get_font(res);
+	gfx_text_fmt_t fmt;
+	gfx_coord2_t pos;
+	gfx_rect_t rect;
+	gfx_rect_t lrect;
+	gfx_rect_t crect;
+	gfx_color_t *bgcolor;
+	gfx_coord_t hpad, vpad;
+	gfx_coord_t line_height;
+	size_t rows;
+	errno_t rc;
+
+	line_height = ui_list_entry_height(list);
+	ui_list_inside_rect(entry->list, &lrect);
+
+	gfx_text_fmt_init(&fmt);
+	fmt.font = font;
+	rows = ui_list_page_size(list) + 1;
+
+	/* Do not display entry outside of current page */
+	if (entry_idx < list->page_idx ||
+	    entry_idx >= list->page_idx + rows)
+		return EOK;
+
+	if (res->textmode) {
+		hpad = list_entry_hpad_text;
+		vpad = list_entry_vpad_text;
+	} else {
+		hpad = list_entry_hpad;
+		vpad = list_entry_vpad;
+	}
+
+	pos.x = lrect.p0.x;
+	pos.y = lrect.p0.y + line_height * (entry_idx - list->page_idx);
+
+	if (entry == list->cursor && list->active) {
+		fmt.color = res->entry_sel_text_fg_color;
+		bgcolor = res->entry_sel_text_bg_color;
+	} else {
+		if (entry->color != NULL)
+			fmt.color = entry->color;
+		else
+			fmt.color = res->entry_fg_color;
+
+		if (entry->bgcolor != NULL)
+			bgcolor = entry->bgcolor;
+		else
+			bgcolor = res->entry_bg_color;
+	}
+
+	/* Draw entry background */
+	rect.p0 = pos;
+	rect.p1.x = lrect.p1.x;
+	rect.p1.y = rect.p0.y + line_height;
+
+	/* Clip to list interior */
+	gfx_rect_clip(&rect, &lrect, &crect);
+
+	rc = gfx_set_color(gc, bgcolor);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(gc, &crect);
+	if (rc != EOK)
+		return rc;
+
+	/*
+	 * Make sure caption does not overflow the entry rectangle.
+	 *
+	 * XXX We probably want to measure the text width, and,
+	 * if it's too long, use gfx_text_find_pos() to find where
+	 * it should be cut off (and append some sort of overflow
+	 * marker.
+	 */
+	rc = gfx_set_clip_rect(gc, &crect);
+	if (rc != EOK)
+		return rc;
+
+	pos.x += hpad;
+	pos.y += vpad;
+
+	rc = gfx_puttext(&pos, &fmt, entry->caption);
+	if (rc != EOK) {
+		(void) gfx_set_clip_rect(gc, NULL);
+		return rc;
+	}
+
+	return gfx_set_clip_rect(gc, NULL);
+}
+
+/** Paint UI list.
+ *
+ * @param list UI list
+ */
+errno_t ui_list_paint(ui_list_t *list)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_resource_t *res = ui_window_get_res(list->window);
+	ui_list_entry_t *entry;
+	int i, lines;
+	errno_t rc;
+
+	rc = gfx_set_color(gc, res->entry_bg_color);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_fill_rect(gc, &list->rect);
+	if (rc != EOK)
+		return rc;
+
+	if (!res->textmode) {
+		rc = ui_paint_inset_frame(res, &list->rect, NULL);
+		if (rc != EOK)
+			return rc;
+	}
+
+	lines = ui_list_page_size(list) + 1;
+	i = 0;
+
+	entry = list->page;
+	while (entry != NULL && i < lines) {
+		rc = ui_list_entry_paint(entry, list->page_idx + i);
+		if (rc != EOK)
+			return rc;
+
+		++i;
+		entry = ui_list_next(entry);
+	}
+
+	rc = ui_scrollbar_paint(list->scrollbar);
+	if (rc != EOK)
+		return rc;
+
+	rc = gfx_update(gc);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+/** Handle list keyboard event.
+ *
+ * @param list UI list
+ * @param event Keyboard event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t ui_list_kbd_event(ui_list_t *list, kbd_event_t *event)
+{
+	if (!list->active)
+		return ui_unclaimed;
+
+	if (event->type == KEY_PRESS) {
+		if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
+			switch (event->key) {
+			case KC_UP:
+				ui_list_cursor_up(list);
+				break;
+			case KC_DOWN:
+				ui_list_cursor_down(list);
+				break;
+			case KC_HOME:
+				ui_list_cursor_top(list);
+				break;
+			case KC_END:
+				ui_list_cursor_bottom(list);
+				break;
+			case KC_PAGE_UP:
+				ui_list_page_up(list);
+				break;
+			case KC_PAGE_DOWN:
+				ui_list_page_down(list);
+				break;
+			case KC_ENTER:
+				ui_list_selected(list->cursor);
+				break;
+			default:
+				break;
+			}
+		}
+	}
+
+	return ui_claimed;
+}
+
+/** Handle UI list position event.
+ *
+ * @param list UI list
+ * @param event Position event
+ * @return ui_claimed iff event was claimed
+ */
+ui_evclaim_t ui_list_pos_event(ui_list_t *list, pos_event_t *event)
+{
+	gfx_coord2_t pos;
+	gfx_rect_t irect;
+	ui_list_entry_t *entry;
+	gfx_coord_t line_height;
+	size_t entry_idx;
+	ui_evclaim_t claim;
+	int n;
+
+	claim = ui_scrollbar_pos_event(list->scrollbar, event);
+	if (claim == ui_claimed)
+		return ui_claimed;
+
+	line_height = ui_list_entry_height(list);
+
+	pos.x = event->hpos;
+	pos.y = event->vpos;
+	if (!gfx_pix_inside_rect(&pos, &list->rect))
+		return ui_unclaimed;
+
+	if (event->type == POS_PRESS || event->type == POS_DCLICK) {
+		ui_list_inside_rect(list, &irect);
+
+		/* Did we click on one of the entries? */
+		if (gfx_pix_inside_rect(&pos, &irect)) {
+			/* Index within page */
+			n = (pos.y - irect.p0.y) / line_height;
+
+			/* Entry and its index within entire listing */
+			entry = ui_list_page_nth_entry(list, n, &entry_idx);
+			if (entry == NULL)
+				return ui_claimed;
+
+			if (event->type == POS_PRESS) {
+				/* Move to the entry found */
+				ui_list_cursor_move(list, entry, entry_idx);
+			} else {
+				/* event->type == POS_DCLICK */
+				ui_list_selected(entry);
+			}
+		} else {
+			/* It's in the border. */
+			if (event->type == POS_PRESS) {
+				/* Top or bottom half? */
+				if (pos.y >= (irect.p0.y + irect.p1.y) / 2)
+					ui_list_page_down(list);
+				else
+					ui_list_page_up(list);
+			}
+		}
+	}
+
+	if (!list->active && event->type == POS_PRESS)
+		ui_list_activate_req(list);
+
+	return ui_claimed;
+}
+
+/** Get base control for UI list.
+ *
+ * @param list UI list
+ * @return Base UI control
+ */
+ui_control_t *ui_list_ctl(ui_list_t *list)
+{
+	return list->control;
+}
+
+/** Set UI list rectangle.
+ *
+ * @param list UI list
+ * @param rect Rectangle
+ */
+void ui_list_set_rect(ui_list_t *list, gfx_rect_t *rect)
+{
+	gfx_rect_t srect;
+
+	list->rect = *rect;
+
+	ui_list_scrollbar_rect(list, &srect);
+	ui_scrollbar_set_rect(list->scrollbar, &srect);
+}
+
+/** Get UI list page size.
+ *
+ * @param list UI list
+ * @return Number of entries that fit in list at the same time.
+ */
+unsigned ui_list_page_size(ui_list_t *list)
+{
+	gfx_coord_t line_height;
+	gfx_rect_t irect;
+
+	line_height = ui_list_entry_height(list);
+	ui_list_inside_rect(list, &irect);
+	return (irect.p1.y - irect.p0.y) / line_height;
+}
+
+/** Get UI list interior rectangle.
+ *
+ * @param list UI list
+ * @param irect Place to store interior rectangle
+ */
+void ui_list_inside_rect(ui_list_t *list, gfx_rect_t *irect)
+{
+	ui_resource_t *res = ui_window_get_res(list->window);
+	gfx_rect_t rect;
+	gfx_coord_t width;
+
+	if (res->textmode) {
+		rect = list->rect;
+	} else {
+		ui_paint_get_inset_frame_inside(res, &list->rect, &rect);
+	}
+
+	if (res->textmode) {
+		width = 1;
+	} else {
+		width = 23;
+	}
+
+	irect->p0 = rect.p0;
+	irect->p1.x = rect.p1.x - width;
+	irect->p1.y = rect.p1.y;
+}
+
+/** Get UI list scrollbar rectangle.
+ *
+ * @param list UI list
+ * @param irect Place to store interior rectangle
+ */
+void ui_list_scrollbar_rect(ui_list_t *list, gfx_rect_t *srect)
+{
+	ui_resource_t *res = ui_window_get_res(list->window);
+	gfx_rect_t rect;
+	gfx_coord_t width;
+
+	if (res->textmode) {
+		rect = list->rect;
+	} else {
+		ui_paint_get_inset_frame_inside(res, &list->rect, &rect);
+	}
+
+	if (res->textmode) {
+		width = 1;
+	} else {
+		width = 23;
+	}
+
+	srect->p0.x = rect.p1.x - width;
+	srect->p0.y = rect.p0.y;
+	srect->p1 = rect.p1;
+}
+
+/** Compute new position for UI list scrollbar thumb.
+ *
+ * @param list UI list
+ * @return New position
+ */
+gfx_coord_t ui_list_scrollbar_pos(ui_list_t *list)
+{
+	size_t entries;
+	size_t pglen;
+	size_t sbar_len;
+
+	entries = list_count(&list->entries);
+	pglen = ui_list_page_size(list);
+	sbar_len = ui_scrollbar_move_length(list->scrollbar);
+
+	if (entries > pglen)
+		return sbar_len * list->page_idx / (entries - pglen);
+	else
+		return 0;
+}
+
+/** Update UI list scrollbar position.
+ *
+ * @param list UI list
+ */
+void ui_list_scrollbar_update(ui_list_t *list)
+{
+	ui_scrollbar_set_pos(list->scrollbar,
+	    ui_list_scrollbar_pos(list));
+}
+
+/** Determine if UI list is active.
+ *
+ * @param list UI list
+ * @return @c true iff UI list is active
+ */
+bool ui_list_is_active(ui_list_t *list)
+{
+	return list->active;
+}
+
+/** Activate UI list.
+ *
+ * @param list UI list
+ *
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_activate(ui_list_t *list)
+{
+	list->active = true;
+	(void) ui_list_paint(list);
+	return EOK;
+}
+
+/** Deactivate UI list.
+ *
+ * @param list UI list
+ */
+void ui_list_deactivate(ui_list_t *list)
+{
+	list->active = false;
+	(void) ui_list_paint(list);
+}
+
+/** Initialize UI list entry attributes.
+ *
+ * @param attr Attributes
+ */
+void ui_list_entry_attr_init(ui_list_entry_attr_t *attr)
+{
+	memset(attr, 0, sizeof(*attr));
+}
+
+/** Destroy UI list control.
+ *
+ * @param arg Argument (ui_list_t *)
+ */
+void ui_list_ctl_destroy(void *arg)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	ui_list_destroy(list);
+}
+
+/** Paint UI list control.
+ *
+ * @param arg Argument (ui_list_t *)
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_ctl_paint(void *arg)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	return ui_list_paint(list);
+}
+
+/** Handle UI list control keyboard event.
+ *
+ * @param arg Argument (ui_list_t *)
+ * @param kbd_event Keyboard event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_list_ctl_kbd_event(void *arg, kbd_event_t *event)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	return ui_list_kbd_event(list, event);
+}
+
+/** Handle UI list control position event.
+ *
+ * @param arg Argument (ui_list_t *)
+ * @param pos_event Position event
+ * @return @c ui_claimed iff the event is claimed
+ */
+ui_evclaim_t ui_list_ctl_pos_event(void *arg, pos_event_t *event)
+{
+	ui_list_t *list = (ui_list_t *) arg;
+
+	return ui_list_pos_event(list, event);
+}
+
+/** Append new UI list entry.
+ *
+ * @param list UI list
+ * @param attr Entry attributes
+ * @param rentry Place to store pointer to new entry or @c NULL if not
+ *               interested
+ * @return EOK on success or an error code
+ */
+errno_t ui_list_entry_append(ui_list_t *list, ui_list_entry_attr_t *attr,
+    ui_list_entry_t **rentry)
+{
+	ui_list_entry_t *entry;
+
+	entry = calloc(1, sizeof(ui_list_entry_t));
+	if (entry == NULL)
+		return ENOMEM;
+
+	entry->list = list;
+	entry->caption = str_dup(attr->caption);
+	if (entry->caption == NULL) {
+		free(entry);
+		return ENOMEM;
+	}
+
+	entry->arg = attr->arg;
+	entry->color = attr->color;
+	entry->bgcolor = attr->bgcolor;
+	link_initialize(&entry->lentries);
+	list_append(&entry->lentries, &list->entries);
+
+	if (list->entries_cnt == 0) {
+		/* Adding first entry - need to set the cursor */
+		list->cursor = entry;
+		list->cursor_idx = 0;
+		list->page = entry;
+		list->page_idx = 0;
+	}
+
+	++list->entries_cnt;
+
+	if (rentry != NULL)
+		*rentry = entry;
+	return EOK;
+}
+
+/** Delete UI list entry.
+ *
+ * @param entry UI list entry
+ */
+void ui_list_entry_delete(ui_list_entry_t *entry)
+{
+	if (entry->list->cursor == entry)
+		entry->list->cursor = NULL;
+	if (entry->list->page == entry)
+		entry->list->page = NULL;
+
+	list_remove(&entry->lentries);
+	--entry->list->entries_cnt;
+	free((char *) entry->caption);
+	free(entry);
+}
+
+/** Get entry argument.
+ *
+ * @param entry UI list entry
+ * @return User argument
+ */
+void *ui_list_entry_get_arg(ui_list_entry_t *entry)
+{
+	return entry->arg;
+}
+
+/** Clear UI list entry list.
+ *
+ * @param list UI list
+ */
+void ui_list_clear_entries(ui_list_t *list)
+{
+	ui_list_entry_t *entry;
+
+	entry = ui_list_first(list);
+	while (entry != NULL) {
+		ui_list_entry_delete(entry);
+		entry = ui_list_first(list);
+	}
+}
+
+/** Get number of UI list entries.
+ *
+ * @param list UI list
+ * @return Number of entries
+ */
+size_t ui_list_entries_cnt(ui_list_t *list)
+{
+	return list->entries_cnt;
+}
+
+/** Return first UI list entry.
+ *
+ * @param list UI list
+ * @return First UI list entry or @c NULL if there are no entries
+ */
+ui_list_entry_t *ui_list_first(ui_list_t *list)
+{
+	link_t *link;
+
+	link = list_first(&list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Return last UI list entry.
+ *
+ * @param list UI list
+ * @return Last UI list entry or @c NULL if there are no entries
+ */
+ui_list_entry_t *ui_list_last(ui_list_t *list)
+{
+	link_t *link;
+
+	link = list_last(&list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Return next UI list entry.
+ *
+ * @param cur Current entry
+ * @return Next entry or @c NULL if @a cur is the last entry
+ */
+ui_list_entry_t *ui_list_next(ui_list_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_next(&cur->lentries, &cur->list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Return previous UI list entry.
+ *
+ * @param cur Current entry
+ * @return Previous entry or @c NULL if @a cur is the first entry
+ */
+ui_list_entry_t *ui_list_prev(ui_list_entry_t *cur)
+{
+	link_t *link;
+
+	link = list_prev(&cur->lentries, &cur->list->entries);
+	if (link == NULL)
+		return NULL;
+
+	return list_get_instance(link, ui_list_entry_t, lentries);
+}
+
+/** Find the n-th entry of the current UI list page.
+ *
+ * @param list UI list
+ * @param n Which entry to get (starting from 0)
+ * @param ridx Place to store index (within listing) of the entry
+ * @return n-th entry of the page
+ */
+ui_list_entry_t *ui_list_page_nth_entry(ui_list_t *list,
+    size_t n, size_t *ridx)
+{
+	ui_list_entry_t *entry;
+	size_t i;
+	size_t idx;
+
+	assert(n <= ui_list_page_size(list));
+
+	entry = list->page;
+	if (entry == NULL)
+		return NULL;
+
+	idx = list->page_idx;
+	for (i = 0; i < n; i++) {
+		entry = ui_list_next(entry);
+		if (entry == NULL)
+			return NULL;
+
+		++idx;
+	}
+
+	*ridx = idx;
+	return entry;
+}
+
+/** Get entry under cursor.
+ *
+ * @param list UI list
+ * @return Current cursor
+ */
+ui_list_entry_t *ui_list_get_cursor(ui_list_t *list)
+{
+	return list->cursor;
+}
+
+/** Move cursor to a new position, possibly scrolling.
+ *
+ * @param list UI list
+ * @param entry New entry under cursor
+ * @param entry_idx Index of new entry under cursor
+ */
+void ui_list_cursor_move(ui_list_t *list,
+    ui_list_entry_t *entry, size_t entry_idx)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_list_entry_t *old_cursor;
+	size_t old_idx;
+	size_t rows;
+	ui_list_entry_t *e;
+	size_t i;
+
+	rows = ui_list_page_size(list);
+
+	old_cursor = list->cursor;
+	old_idx = list->cursor_idx;
+
+	list->cursor = entry;
+	list->cursor_idx = entry_idx;
+
+	if (entry_idx >= list->page_idx &&
+	    entry_idx < list->page_idx + rows) {
+		/*
+		 * If cursor is still on the current page, we're not
+		 * scrolling. Just unpaint old cursor and paint new
+		 * cursor.
+		 */
+		ui_list_entry_paint(old_cursor, old_idx);
+		ui_list_entry_paint(list->cursor, list->cursor_idx);
+
+		(void) gfx_update(gc);
+	} else {
+		/*
+		 * Need to scroll and update all rows.
+		 */
+
+		/* Scrolling up */
+		if (entry_idx < list->page_idx) {
+			list->page = entry;
+			list->page_idx = entry_idx;
+		}
+
+		/* Scrolling down */
+		if (entry_idx >= list->page_idx + rows) {
+			if (entry_idx >= rows) {
+				list->page_idx = entry_idx - rows + 1;
+				/* Find first page entry (go back rows - 1) */
+				e = entry;
+				for (i = 0; i < rows - 1; i++) {
+					e = ui_list_prev(e);
+				}
+
+				/* Should be valid */
+				assert(e != NULL);
+				list->page = e;
+			} else {
+				list->page = ui_list_first(list);
+				list->page_idx = 0;
+			}
+		}
+
+		ui_list_scrollbar_update(list);
+		(void) ui_list_paint(list);
+	}
+}
+
+/** Move cursor one entry up.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_up(ui_list_t *list)
+{
+	ui_list_entry_t *prev;
+	size_t prev_idx;
+
+	prev = ui_list_prev(list->cursor);
+	prev_idx = list->cursor_idx - 1;
+	if (prev != NULL)
+		ui_list_cursor_move(list, prev, prev_idx);
+}
+
+/** Move cursor one entry down.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_down(ui_list_t *list)
+{
+	ui_list_entry_t *next;
+	size_t next_idx;
+
+	next = ui_list_next(list->cursor);
+	next_idx = list->cursor_idx + 1;
+	if (next != NULL)
+		ui_list_cursor_move(list, next, next_idx);
+}
+
+/** Move cursor to top.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_top(ui_list_t *list)
+{
+	ui_list_cursor_move(list, ui_list_first(list), 0);
+}
+
+/** Move cursor to bottom.
+ *
+ * @param list UI list
+ */
+void ui_list_cursor_bottom(ui_list_t *list)
+{
+	ui_list_cursor_move(list, ui_list_last(list),
+	    list->entries_cnt - 1);
+}
+
+/** Move cursor one page up.
+ *
+ * @param list UI list
+ */
+void ui_list_page_up(ui_list_t *list)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_list_entry_t *old_page;
+	ui_list_entry_t *old_cursor;
+	size_t old_idx;
+	size_t rows;
+	ui_list_entry_t *entry;
+	size_t i;
+
+	rows = ui_list_page_size(list);
+
+	old_page = list->page;
+	old_cursor = list->cursor;
+	old_idx = list->cursor_idx;
+
+	/* Move page by rows entries up (if possible) */
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_prev(list->page);
+		if (entry != NULL) {
+			list->page = entry;
+			--list->page_idx;
+		}
+	}
+
+	/* Move cursor by rows entries up (if possible) */
+
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_prev(list->cursor);
+		if (entry != NULL) {
+			list->cursor = entry;
+			--list->cursor_idx;
+		}
+	}
+
+	if (list->page != old_page) {
+		/* We have scrolled. Need to repaint all entries */
+		ui_list_scrollbar_update(list);
+		(void) ui_list_paint(list);
+	} else if (list->cursor != old_cursor) {
+		/* No scrolling, but cursor has moved */
+		ui_list_entry_paint(old_cursor, old_idx);
+		ui_list_entry_paint(list->cursor, list->cursor_idx);
+
+		(void) gfx_update(gc);
+	}
+}
+
+/** Move cursor one page down.
+ *
+ * @param list UI list
+ */
+void ui_list_page_down(ui_list_t *list)
+{
+	gfx_context_t *gc = ui_window_get_gc(list->window);
+	ui_list_entry_t *old_page;
+	ui_list_entry_t *old_cursor;
+	size_t old_idx;
+	size_t max_idx;
+	size_t rows;
+	ui_list_entry_t *entry;
+	size_t i;
+
+	rows = ui_list_page_size(list);
+
+	old_page = list->page;
+	old_cursor = list->cursor;
+	old_idx = list->cursor_idx;
+
+	if (list->entries_cnt > rows)
+		max_idx = list->entries_cnt - rows;
+	else
+		max_idx = 0;
+
+	/* Move page by rows entries down (if possible) */
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_next(list->page);
+		/* Do not scroll that results in a short page */
+		if (entry != NULL && list->page_idx < max_idx) {
+			list->page = entry;
+			++list->page_idx;
+		}
+	}
+
+	/* Move cursor by rows entries down (if possible) */
+
+	for (i = 0; i < rows; i++) {
+		entry = ui_list_next(list->cursor);
+		if (entry != NULL) {
+			list->cursor = entry;
+			++list->cursor_idx;
+		}
+	}
+
+	if (list->page != old_page) {
+		/* We have scrolled. Need to repaint all entries */
+		ui_list_scrollbar_update(list);
+		(void) ui_list_paint(list);
+	} else if (list->cursor != old_cursor) {
+		/* No scrolling, but cursor has moved */
+		ui_list_entry_paint(old_cursor, old_idx);
+		ui_list_entry_paint(list->cursor, list->cursor_idx);
+
+		(void) gfx_update(gc);
+	}
+}
+
+/** Scroll one entry up.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_up(ui_list_t *list)
+{
+	ui_list_entry_t *prev;
+
+	prev = ui_list_prev(list->page);
+	if (prev == NULL)
+		return;
+
+	list->page = prev;
+	assert(list->page_idx > 0);
+	--list->page_idx;
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll one entry down.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_down(ui_list_t *list)
+{
+	ui_list_entry_t *next;
+	ui_list_entry_t *pgend;
+	size_t i;
+	size_t rows;
+
+	next = ui_list_next(list->page);
+	if (next == NULL)
+		return;
+
+	rows = ui_list_page_size(list);
+
+	/* Find last page entry */
+	pgend = list->page;
+	for (i = 0; i < rows && pgend != NULL; i++) {
+		pgend = ui_list_next(pgend);
+	}
+
+	/* Scroll down by one entry, if the page remains full */
+	if (pgend != NULL) {
+		list->page = next;
+		++list->page_idx;
+	}
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll one page up.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_page_up(ui_list_t *list)
+{
+	ui_list_entry_t *prev;
+	size_t i;
+	size_t rows;
+
+	prev = ui_list_prev(list->page);
+	if (prev == NULL)
+		return;
+
+	rows = ui_list_page_size(list);
+
+	for (i = 0; i < rows && prev != NULL; i++) {
+		list->page = prev;
+		assert(list->page_idx > 0);
+		--list->page_idx;
+		prev = ui_list_prev(prev);
+	}
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll one page down.
+ *
+ * @param list UI list
+ */
+void ui_list_scroll_page_down(ui_list_t *list)
+{
+	ui_list_entry_t *next;
+	ui_list_entry_t *pgend;
+	size_t i;
+	size_t rows;
+
+	next = ui_list_next(list->page);
+	if (next == NULL)
+		return;
+
+	rows = ui_list_page_size(list);
+
+	/* Find last page entry */
+	pgend = list->page;
+	for (i = 0; i < rows && pgend != NULL; i++) {
+		pgend = ui_list_next(pgend);
+	}
+
+	/* Scroll by up to 'rows' entries, keeping the page full */
+	for (i = 0; i < rows && pgend != NULL; i++) {
+		list->page = next;
+		++list->page_idx;
+		next = ui_list_next(next);
+		pgend = ui_list_next(pgend);
+	}
+
+	ui_list_scrollbar_update(list);
+	(void) ui_list_paint(list);
+}
+
+/** Scroll to a specific entry
+ *
+ * @param list UI list
+ * @param page_idx New index of first entry on the page
+ */
+void ui_list_scroll_pos(ui_list_t *list, size_t page_idx)
+{
+	ui_list_entry_t *entry;
+	size_t i;
+
+	entry = ui_list_first(list);
+	for (i = 0; i < page_idx; i++) {
+		entry = ui_list_next(entry);
+		assert(entry != NULL);
+	}
+
+	list->page = entry;
+	list->page_idx = page_idx;
+
+	(void) ui_list_paint(list);
+}
+
+/** Request UI list activation.
+ *
+ * Call back to request UI list activation.
+ *
+ * @param list UI list
+ */
+void ui_list_activate_req(ui_list_t *list)
+{
+	if (list->cb != NULL && list->cb->activate_req != NULL) {
+		list->cb->activate_req(list, list->cb_arg);
+	} else {
+		/*
+		 * If there is no callback for activation request,
+		 * just activate the list.
+		 */
+		ui_list_activate(list);
+	}
+}
+
+/** Sort list entries.
+ *
+ * @param list UI list
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t ui_list_sort(ui_list_t *list)
+{
+	ui_list_entry_t **emap;
+	ui_list_entry_t *entry;
+	size_t i;
+
+	/* Create an array to hold pointer to each entry */
+	emap = calloc(list->entries_cnt, sizeof(ui_list_entry_t *));
+	if (emap == NULL)
+		return ENOMEM;
+
+	/* Write entry pointers to array */
+	entry = ui_list_first(list);
+	i = 0;
+	while (entry != NULL) {
+		assert(i < list->entries_cnt);
+		emap[i++] = entry;
+		entry = ui_list_next(entry);
+	}
+
+	/* Sort the array of pointers */
+	qsort(emap, list->entries_cnt, sizeof(ui_list_entry_t *),
+	    ui_list_entry_ptr_cmp);
+
+	/* Unlink entries from entry list */
+	entry = ui_list_first(list);
+	while (entry != NULL) {
+		list_remove(&entry->lentries);
+		entry = ui_list_first(list);
+	}
+
+	/* Add entries back to entry list sorted */
+	for (i = 0; i < list->entries_cnt; i++)
+		list_append(&emap[i]->lentries, &list->entries);
+
+	free(emap);
+
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	return EOK;
+}
+
+/** Determine list entry index.
+ *
+ * @param entry List entry
+ * @return List entry index
+ */
+size_t ui_list_entry_get_idx(ui_list_entry_t *entry)
+{
+	ui_list_entry_t *ep;
+	size_t idx;
+
+	idx = 0;
+	ep = ui_list_prev(entry);
+	while (ep != NULL) {
+		++idx;
+		ep = ui_list_prev(ep);
+	}
+
+	return idx;
+}
+
+/** Center list cursor on entry.
+ *
+ * @param list UI list
+ * @param entry Entry
+ */
+void ui_list_cursor_center(ui_list_t *list, ui_list_entry_t *entry)
+{
+	ui_list_entry_t *prev;
+	size_t idx;
+	size_t max_idx;
+	size_t pg_size;
+	size_t i;
+
+	idx = ui_list_entry_get_idx(entry);
+	list->cursor = entry;
+	list->cursor_idx = idx;
+
+	/* Move page so that cursor is in the center */
+	list->page = list->cursor;
+	list->page_idx = list->cursor_idx;
+
+	pg_size = ui_list_page_size(list);
+
+	for (i = 0; i < pg_size / 2; i++) {
+		prev = ui_list_prev(list->page);
+		if (prev == NULL)
+			break;
+
+		list->page = prev;
+		--list->page_idx;
+	}
+
+	/* Make sure page is not beyond the end if possible */
+	if (list->entries_cnt > pg_size)
+		max_idx = list->entries_cnt - pg_size;
+	else
+		max_idx = 0;
+
+	while (list->page_idx > 0 && list->page_idx > max_idx) {
+		prev = ui_list_prev(list->page);
+		if (prev == NULL)
+			break;
+
+		list->page = prev;
+		--list->page_idx;
+	}
+}
+
+/** Call back when an entry is selected.
+ *
+ * @param entry UI list entry
+ * @param fname File name
+ */
+void ui_list_selected(ui_list_entry_t *entry)
+{
+	if (entry->list->cb != NULL && entry->list->cb->selected != NULL)
+		entry->list->cb->selected(entry, entry->arg);
+}
+
+/** UI list scrollbar up button pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_up(list);
+}
+
+/** UI list scrollbar down button pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_down(ui_scrollbar_t *scrollbar, void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_down(list);
+}
+
+/** UI list scrollbar page up pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_page_up(ui_scrollbar_t *scrollbar, void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_page_up(list);
+}
+
+/** UI list scrollbar page down pressed.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_page_down(ui_scrollbar_t *scrollbar,
+    void *arg)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	ui_list_scroll_page_down(list);
+}
+
+/** UI list scrollbar moved.
+ *
+ * @param scrollbar Scrollbar
+ * @param arg Argument (ui_list_t *)
+ */
+static void ui_list_scrollbar_moved(ui_scrollbar_t *scrollbar, void *arg,
+    gfx_coord_t pos)
+{
+	ui_list_t *list = (ui_list_t *)arg;
+	size_t entries;
+	size_t pglen;
+	size_t sbar_len;
+	size_t pgstart;
+
+	entries = list_count(&list->entries);
+	pglen = ui_list_page_size(list);
+	sbar_len = ui_scrollbar_move_length(list->scrollbar);
+
+	if (entries > pglen)
+		pgstart = (entries - pglen) * pos / (sbar_len - 1);
+	else
+		pgstart = 0;
+
+	ui_list_scroll_pos(list, pgstart);
+}
+
+/** Compare two list entries indirectly referenced by pointers.
+ *
+ * @param pa Pointer to pointer to first entry
+ * @param pb Pointer to pointer to second entry
+ * @return <0, =0, >=0 if pa < b, pa == pb, pa > pb, resp.
+ */
+int ui_list_entry_ptr_cmp(const void *pa, const void *pb)
+{
+	ui_list_entry_t *a = *(ui_list_entry_t **)pa;
+	ui_list_entry_t *b = *(ui_list_entry_t **)pb;
+
+	return a->list->cb->compare(a, b);
+}
+
+/** @}
+ */
Index: uspace/lib/ui/test/filelist.c
===================================================================
--- uspace/lib/ui/test/filelist.c	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/lib/ui/test/filelist.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2022 Jiri Svoboda
+ * Copyright (c) 2023 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,4 +37,5 @@
 #include <vfs/vfs.h>
 #include "../private/filelist.h"
+#include "../private/list.h"
 
 PCUT_INIT;
@@ -117,72 +118,4 @@
 }
 
-/** ui_file_list_entry_height() gives the correct height */
-PCUT_TEST(entry_height)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	errno_t rc;
-	gfx_coord_t height;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Font height is 13, padding: 2 (top) + 2 (bottom) */
-	height = ui_file_list_entry_height(flist);
-	PCUT_ASSERT_INT_EQUALS(17, height);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Test ui_file_list_entry_paint() */
-PCUT_TEST(entry_paint)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_file_list_entry_attr_init(&attr);
-	attr.name = "a";
-	attr.size = 1;
-
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_entry_paint(ui_file_list_first(flist), 0);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
 /** Test ui_file_list_paint() */
 PCUT_TEST(paint)
@@ -238,149 +171,4 @@
 	control = ui_file_list_ctl(flist);
 	PCUT_ASSERT_NOT_NULL(control);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Test ui_file_list_kbd_event() */
-PCUT_TEST(kbd_event)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_evclaim_t claimed;
-	kbd_event_t event;
-	errno_t rc;
-
-	/* Active file list should claim events */
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	event.type = KEY_PRESS;
-	event.key = KC_ESCAPE;
-	event.mods = 0;
-	event.c = '\0';
-
-	claimed = ui_file_list_kbd_event(flist, &event);
-	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
-
-	ui_file_list_destroy(flist);
-
-	/* Inactive file list should not claim events */
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, false, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	event.type = KEY_PRESS;
-	event.key = KC_ESCAPE;
-	event.mods = 0;
-	event.c = '\0';
-
-	claimed = ui_file_list_kbd_event(flist, &event);
-	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** Test ui_file_list_pos_event() */
-PCUT_TEST(pos_event)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_evclaim_t claimed;
-	pos_event_t event;
-	gfx_rect_t rect;
-	ui_file_list_entry_attr_t attr;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 10;
-	rect.p0.y = 20;
-	rect.p1.x = 50;
-	rect.p1.y = 220;
-
-	ui_file_list_set_rect(flist, &rect);
-
-	ui_file_list_entry_attr_init(&attr);
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = ui_file_list_first(flist);
-	flist->page_idx = 0;
-
-	event.pos_id = 0;
-	event.type = POS_PRESS;
-	event.btn_num = 1;
-
-	/* Clicking on the middle entry should select it */
-	event.hpos = 20;
-	event.vpos = 40;
-
-	claimed = ui_file_list_pos_event(flist, &event);
-	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
-
-	PCUT_ASSERT_NOT_NULL(flist->cursor);
-	PCUT_ASSERT_STR_EQUALS("b", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor->size);
-
-	/* Clicking on the top edge should do a page-up */
-	event.hpos = 20;
-	event.vpos = 20;
-	claimed = ui_file_list_pos_event(flist, &event);
-	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
-
-	PCUT_ASSERT_NOT_NULL(flist->cursor);
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
 
 	ui_file_list_destroy(flist);
@@ -417,165 +205,220 @@
 
 	ui_file_list_set_rect(flist, &rect);
-	PCUT_ASSERT_INT_EQUALS(rect.p0.x, flist->rect.p0.x);
-	PCUT_ASSERT_INT_EQUALS(rect.p0.y, flist->rect.p0.y);
-	PCUT_ASSERT_INT_EQUALS(rect.p1.x, flist->rect.p1.x);
-	PCUT_ASSERT_INT_EQUALS(rect.p1.y, flist->rect.p1.y);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_page_size() returns correct size */
-PCUT_TEST(page_size)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 10;
-	rect.p0.y = 20;
-	rect.p1.x = 50;
-	rect.p1.y = 220;
-
-	ui_file_list_set_rect(flist, &rect);
-
-	/* NOTE If page size changes, we have problems elsewhere in the tests */
-	PCUT_ASSERT_INT_EQUALS(11, ui_file_list_page_size(flist));
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_inside_rect() gives correct interior rectangle */
-PCUT_TEST(inside_rect)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	gfx_rect_t rect;
-	gfx_rect_t irect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 10;
-	rect.p0.y = 20;
-	rect.p1.x = 50;
-	rect.p1.y = 220;
-
-	ui_file_list_set_rect(flist, &rect);
-
-	ui_file_list_inside_rect(flist, &irect);
-	PCUT_ASSERT_INT_EQUALS(10 + 2, irect.p0.x);
-	PCUT_ASSERT_INT_EQUALS(20 + 2, irect.p0.y);
-	PCUT_ASSERT_INT_EQUALS(50 - 2 - 23, irect.p1.x);
-	PCUT_ASSERT_INT_EQUALS(220 - 2, irect.p1.y);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_scrollbar_rect() gives correct scrollbar rectangle */
-PCUT_TEST(scrollbar_rect)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	gfx_rect_t rect;
-	gfx_rect_t srect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 10;
-	rect.p0.y = 20;
-	rect.p1.x = 50;
-	rect.p1.y = 220;
-
-	ui_file_list_set_rect(flist, &rect);
-
-	ui_file_list_scrollbar_rect(flist, &srect);
-	PCUT_ASSERT_INT_EQUALS(50 - 2 - 23, srect.p0.x);
-	PCUT_ASSERT_INT_EQUALS(20 + 2, srect.p0.y);
-	PCUT_ASSERT_INT_EQUALS(50 - 2, srect.p1.x);
-	PCUT_ASSERT_INT_EQUALS(220 - 2, srect.p1.y);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_scrollbar_update() updates scrollbar position */
-PCUT_TEST(scrollbar_update)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	gfx_rect_t rect;
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, flist->list->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, flist->list->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, flist->list->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, flist->list->rect.p1.y);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_is_active() returns file list activity state */
+PCUT_TEST(is_active)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
+	ui_file_list_destroy(flist);
+
+	rc = ui_file_list_create(window, false, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_activate() activates file list */
+PCUT_TEST(activate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, false, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
+	rc = ui_file_list_activate(flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_deactivate() deactivates file list */
+PCUT_TEST(deactivate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
+	ui_file_list_deactivate(flist);
+	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_entry_append() appends new entry */
+PCUT_TEST(entry_append)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
 	ui_file_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(flist->list));
+
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_entry_delete() deletes entry */
+PCUT_TEST(entry_delete)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
 	ui_file_list_entry_t *entry;
-	gfx_coord_t pos;
-	gfx_coord_t move_len;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 50;
-	rect.p1.y = 38;
-
-	ui_file_list_set_rect(flist, &rect);
+	ui_file_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.name = "b";
+	attr.size = 2;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
+
+	entry = ui_file_list_first(flist);
+	ui_file_list_entry_delete(entry);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(flist->list));
+
+	entry = ui_file_list_first(flist);
+	ui_file_list_entry_delete(entry);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(flist->list));
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_file_list_clear_entries() removes all entries from file list */
+PCUT_TEST(clear_entries)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
 	ui_file_list_entry_attr_init(&attr);
@@ -585,246 +428,4 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	entry = ui_file_list_next(ui_file_list_first(flist));
-
-	flist->cursor = entry;
-	flist->cursor_idx = 1;
-	flist->page = entry;
-	flist->page_idx = 1;
-
-	ui_file_list_scrollbar_update(flist);
-
-	/* Now scrollbar thumb should be all the way down */
-	move_len = ui_scrollbar_move_length(flist->scrollbar);
-	pos = ui_scrollbar_get_pos(flist->scrollbar);
-	PCUT_ASSERT_INT_EQUALS(move_len, pos);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_is_active() returns file list activity state */
-PCUT_TEST(is_active)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
-	ui_file_list_destroy(flist);
-
-	rc = ui_file_list_create(window, false, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_activate() activates file list */
-PCUT_TEST(activate)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, false, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
-	rc = ui_file_list_activate(flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_deactivate() deactivates file list */
-PCUT_TEST(deactivate)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	PCUT_ASSERT_TRUE(ui_file_list_is_active(flist));
-	ui_file_list_deactivate(flist);
-	PCUT_ASSERT_FALSE(ui_file_list_is_active(flist));
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_entry_append() appends new entry */
-PCUT_TEST(entry_append)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	PCUT_ASSERT_INT_EQUALS(1, list_count(&flist->entries));
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	PCUT_ASSERT_INT_EQUALS(2, list_count(&flist->entries));
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_entry_delete() deletes entry */
-PCUT_TEST(entry_delete)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_t *entry;
-	ui_file_list_entry_attr_t attr;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	PCUT_ASSERT_INT_EQUALS(2, list_count(&flist->entries));
-
-	entry = ui_file_list_first(flist);
-	ui_file_list_entry_delete(entry);
-
-	PCUT_ASSERT_INT_EQUALS(1, list_count(&flist->entries));
-
-	entry = ui_file_list_first(flist);
-	ui_file_list_entry_delete(entry);
-
-	PCUT_ASSERT_INT_EQUALS(0, list_count(&flist->entries));
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_clear_entries() removes all entries from file list */
-PCUT_TEST(clear_entries)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_file_list_entry_attr_init(&attr);
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
 	attr.name = "a";
 	attr.size = 2;
@@ -832,8 +433,8 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	PCUT_ASSERT_INT_EQUALS(2, list_count(&flist->entries));
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
 
 	ui_file_list_clear_entries(flist);
-	PCUT_ASSERT_INT_EQUALS(0, list_count(&flist->entries));
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(flist->list));
 
 	ui_file_list_destroy(flist);
@@ -892,5 +493,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	PCUT_ASSERT_INT_EQUALS(2, list_count(&flist->entries));
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
 
 	entry = ui_file_list_first(flist);
@@ -979,6 +580,6 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
-	PCUT_ASSERT_NOT_NULL(flist->cursor);
-	PCUT_ASSERT_STR_EQUALS("b", flist->cursor->name);
+	PCUT_ASSERT_NOT_NULL(ui_file_list_get_cursor(flist));
+	PCUT_ASSERT_STR_EQUALS("b", ui_file_list_get_cursor(flist)->name);
 
 	ui_file_list_destroy(flist);
@@ -1064,6 +665,6 @@
 }
 
-/** ui_file_list_entry_ptr_cmp compares two indirectly referenced entries */
-PCUT_TEST(entry_ptr_cmp)
+/** ui_file_list_list_compare compares two file list entries */
+PCUT_TEST(list_compare)
 {
 	ui_t *ui;
@@ -1106,13 +707,13 @@
 
 	/* a < b */
-	rel = ui_file_list_entry_ptr_cmp(&a, &b);
+	rel = ui_file_list_list_compare(a->entry, b->entry);
 	PCUT_ASSERT_TRUE(rel < 0);
 
 	/* b > a */
-	rel = ui_file_list_entry_ptr_cmp(&b, &a);
+	rel = ui_file_list_list_compare(b->entry, a->entry);
 	PCUT_ASSERT_TRUE(rel > 0);
 
 	/* a == a */
-	rel = ui_file_list_entry_ptr_cmp(&a, &a);
+	rel = ui_file_list_list_compare(a->entry, a->entry);
 	PCUT_ASSERT_INT_EQUALS(0, rel);
 
@@ -1120,4 +721,16 @@
 	ui_window_destroy(window);
 	ui_destroy(ui);
+}
+
+/** ui_file_list_entry_attr_init() initializes file list attribute structure */
+PCUT_TEST(entry_attr_init)
+{
+	ui_file_list_entry_attr_t attr;
+
+	ui_file_list_entry_attr_init(&attr);
+	PCUT_ASSERT_NULL(attr.name);
+	PCUT_ASSERT_INT_EQUALS(0, attr.size);
+	PCUT_ASSERT_EQUALS(false, attr.isdir);
+	PCUT_ASSERT_INT_EQUALS(0, attr.svc);
 }
 
@@ -1354,1164 +967,4 @@
 }
 
-/** ui_file_list_page_nth_entry() .. */
-PCUT_TEST(page_nth_entry)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_t *entry;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	size_t idx;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_file_list_entry_attr_init(&attr);
-
-	/* Add some entries */
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	flist->page = ui_file_list_next(ui_file_list_first(flist));
-	flist->page_idx = 1;
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 100;
-	rect.p1.y = 100;
-	ui_file_list_set_rect(flist, &rect);
-
-	entry = ui_file_list_page_nth_entry(flist, 0, &idx);
-	PCUT_ASSERT_NOT_NULL(entry);
-	PCUT_ASSERT_STR_EQUALS("b", entry->name);
-	PCUT_ASSERT_INT_EQUALS(1, idx);
-
-	entry = ui_file_list_page_nth_entry(flist, 1, &idx);
-	PCUT_ASSERT_NOT_NULL(entry);
-	PCUT_ASSERT_STR_EQUALS("c", entry->name);
-	PCUT_ASSERT_INT_EQUALS(2, idx);
-
-	entry = ui_file_list_page_nth_entry(flist, 2, &idx);
-	PCUT_ASSERT_NULL(entry);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_cursor_move() moves cursor and scrolls */
-PCUT_TEST(cursor_move)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor to the last entry and page start to the next-to-last entry */
-	flist->cursor = ui_file_list_last(flist);
-	flist->cursor_idx = 2;
-	flist->page = ui_file_list_prev(flist->cursor);
-	flist->page_idx = 1;
-
-	/* Move cursor one entry up */
-	ui_file_list_cursor_move(flist, ui_file_list_prev(flist->cursor),
-	    flist->cursor_idx - 1);
-
-	/* Cursor and page start should now both be at the second entry */
-	PCUT_ASSERT_STR_EQUALS("b", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Move cursor to the first entry. This should scroll up. */
-	ui_file_list_cursor_move(flist, ui_file_list_first(flist), 0);
-
-	/* Cursor and page start should now both be at the first entry */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	/* Move cursor to the last entry. */
-	ui_file_list_cursor_move(flist, ui_file_list_last(flist), 2);
-
-	/* Cursor should be on the last entry and page on the next to last */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_cursor_up() moves cursor one entry up */
-PCUT_TEST(cursor_up)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor to the last entry and page start to the next-to-last entry */
-	flist->cursor = ui_file_list_last(flist);
-	flist->cursor_idx = 2;
-	flist->page = ui_file_list_prev(flist->cursor);
-	flist->page_idx = 1;
-
-	/* Move cursor one entry up */
-	ui_file_list_cursor_up(flist);
-
-	/* Cursor and page start should now both be at the second entry */
-	PCUT_ASSERT_STR_EQUALS("b", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Move cursor one entry up. This should scroll up. */
-	ui_file_list_cursor_up(flist);
-
-	/* Cursor and page start should now both be at the first entry */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	/* Moving further up should do nothing (we are at the top). */
-	ui_file_list_cursor_up(flist);
-
-	/* Cursor and page start should still be at the first entry */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_cursor_down() moves cursor one entry down */
-PCUT_TEST(cursor_down)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor and page start to the first entry */
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = flist->cursor;
-	flist->page_idx = 0;
-
-	/* Move cursor one entry down */
-	ui_file_list_cursor_down(flist);
-
-	/* Cursor should now be at the second entry, page stays the same */
-	PCUT_ASSERT_STR_EQUALS("b", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(ui_file_list_first(flist), flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	/* Move cursor one entry down. This should scroll down. */
-	ui_file_list_cursor_down(flist);
-
-	/* Cursor should now be at the third and page at the second entry. */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Moving further down should do nothing (we are at the bottom). */
-	ui_file_list_cursor_down(flist);
-
-	/* Cursor should still be at the third and page at the second entry. */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_cursor_top() moves cursor to the first entry */
-PCUT_TEST(cursor_top)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor to the last entry and page start to the next-to-last entry */
-	flist->cursor = ui_file_list_last(flist);
-	flist->cursor_idx = 2;
-	flist->page = ui_file_list_prev(flist->cursor);
-	flist->page_idx = 1;
-
-	/* Move cursor to the top. This should scroll up. */
-	ui_file_list_cursor_top(flist);
-
-	/* Cursor and page start should now both be at the first entry */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_cursor_bottom() moves cursor to the last entry */
-PCUT_TEST(cursor_bottom)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor and page start to the first entry */
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = flist->cursor;
-	flist->page_idx = 0;
-
-	/* Move cursor to the bottom. This should scroll down. */
-	ui_file_list_cursor_bottom(flist);
-
-	/* Cursor should now be at the third and page at the second entry. */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_page_up() moves one page up */
-PCUT_TEST(page_up)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add five entries (2 full pages, one partial) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "d";
-	attr.size = 4;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "e";
-	attr.size = 5;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor to the last entry and page start to the next-to-last entry */
-	flist->cursor = ui_file_list_last(flist);
-	flist->cursor_idx = 4;
-	flist->page = ui_file_list_prev(flist->cursor);
-	flist->page_idx = 3;
-
-	/* Move one page up */
-	ui_file_list_page_up(flist);
-
-	/* Page should now start at second entry and cursor at third */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Move one page up again. */
-	ui_file_list_page_up(flist);
-
-	/* Cursor and page start should now both be at the first entry */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	/* Moving further up should do nothing (we are at the top). */
-	ui_file_list_page_up(flist);
-
-	/* Cursor and page start should still be at the first entry */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_EQUALS(flist->cursor, flist->page);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_page_up() moves one page down */
-PCUT_TEST(page_down)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add five entries (2 full pages, one partial) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "d";
-	attr.size = 4;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "e";
-	attr.size = 5;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor and page to the first entry */
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = flist->cursor;
-	flist->page_idx = 0;
-
-	/* Move one page down */
-	ui_file_list_page_down(flist);
-
-	/* Page and cursor should point to the third entry */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("c", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page_idx);
-
-	/* Move one page down again. */
-	ui_file_list_page_down(flist);
-
-	/* Cursor should point to last and page to next-to-last entry */
-	PCUT_ASSERT_STR_EQUALS("e", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(5, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(4, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("d", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(4, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page_idx);
-
-	/* Moving further down should do nothing (we are at the bottom). */
-	ui_file_list_page_down(flist);
-
-	/* Cursor should still point to last and page to next-to-last entry */
-	PCUT_ASSERT_STR_EQUALS("e", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(5, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(4, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("d", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(4, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_scroll_up() scrolls up by one row */
-PCUT_TEST(scroll_up)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor to the last entry, page to the second */
-	flist->cursor = ui_file_list_last(flist);
-	flist->cursor_idx = 2;
-	flist->page = ui_file_list_prev(flist->cursor);
-	flist->page_idx = 1;
-
-	/* Scroll one entry up */
-	ui_file_list_scroll_up(flist);
-
-	/* Page should start on the first entry, cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("a", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	/* Try scrolling one more entry up */
-	ui_file_list_scroll_up(flist);
-
-	/* We were at the beginning, so nothing should have changed  */
-	PCUT_ASSERT_STR_EQUALS("c", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("a", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_scroll_down() scrolls down by one row */
-PCUT_TEST(scroll_down)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add tree entries (more than page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor and page start to the first entry */
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = flist->cursor;
-	flist->page_idx = 0;
-
-	/* Scroll one entry down */
-	ui_file_list_scroll_down(flist);
-
-	/* Page should start on the second entry, cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Try scrolling one more entry down */
-	ui_file_list_scroll_down(flist);
-
-	/* We were at the end, so nothing should have changed  */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_scroll_page_up() scrolls up by one page */
-PCUT_TEST(scroll_page_up)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add five entries (more than twice the page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "d";
-	attr.size = 4;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "e";
-	attr.size = 5;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor to the last entry, page to the second last */
-	flist->cursor = ui_file_list_last(flist);
-	flist->cursor_idx = 4;
-	flist->page = ui_file_list_prev(flist->cursor);
-	flist->page_idx = 3;
-
-	/* Scroll one page up */
-	ui_file_list_scroll_page_up(flist);
-
-	/* Page should start on 'b', cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("e", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(5, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(4, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Page up again */
-	ui_file_list_scroll_page_up(flist);
-
-	/* Page should now be at the beginning, cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("e", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(5, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(4, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("a", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	/* Page up again */
-	ui_file_list_scroll_page_up(flist);
-
-	/* We were at the beginning, nothing should have changed */
-	PCUT_ASSERT_STR_EQUALS("e", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(5, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(4, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("a", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-/** ui_file_list_scroll_page_up() scrolls down by one page */
-PCUT_TEST(scroll_page_down)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add five entries (more than twice the page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "d";
-	attr.size = 4;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "e";
-	attr.size = 5;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor and page to the first entry */
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = ui_file_list_first(flist);
-	flist->page_idx = 0;
-
-	/* Scroll one page down */
-	ui_file_list_scroll_page_down(flist);
-
-	/* Page should start on 'c', cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("c", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page_idx);
-
-	/* Page down again */
-	ui_file_list_scroll_page_down(flist);
-
-	/* Page should now start at 'd', cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("d", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(4, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page_idx);
-
-	/* Page down again */
-	ui_file_list_scroll_page_down(flist);
-
-	/* We were at the end, nothing should have changed */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("d", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(4, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
-PCUT_TEST(scroll_pos)
-{
-	ui_t *ui;
-	ui_window_t *window;
-	ui_wnd_params_t params;
-	ui_file_list_t *flist;
-	ui_file_list_entry_attr_t attr;
-	gfx_rect_t rect;
-	errno_t rc;
-
-	rc = ui_create_disp(NULL, &ui);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	ui_wnd_params_init(&params);
-	params.caption = "Test";
-
-	rc = ui_window_create(ui, &params, &window);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = ui_file_list_create(window, true, &flist);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rect.p0.x = 0;
-	rect.p0.y = 0;
-	rect.p1.x = 10;
-	rect.p1.y = 38; /* Assuming this makes page size 2 */
-	ui_file_list_set_rect(flist, &rect);
-
-	PCUT_ASSERT_INT_EQUALS(2, ui_file_list_page_size(flist));
-
-	/* Add five entries (more than twice the page size, which is 2) */
-
-	ui_file_list_entry_attr_init(&attr);
-
-	attr.name = "a";
-	attr.size = 1;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "b";
-	attr.size = 2;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "c";
-	attr.size = 3;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "d";
-	attr.size = 4;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	attr.name = "e";
-	attr.size = 5;
-	rc = ui_file_list_entry_append(flist, &attr);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	/* Cursor and page to the first entry */
-	flist->cursor = ui_file_list_first(flist);
-	flist->cursor_idx = 0;
-	flist->page = ui_file_list_first(flist);
-	flist->page_idx = 0;
-
-	/* Scroll to entry 1 (one down) */
-	ui_file_list_scroll_pos(flist, 1);
-
-	/* Page should start on 'b', cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("b", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(2, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(1, flist->page_idx);
-
-	/* Scroll to entry 3 (i.e. the end) */
-	ui_file_list_scroll_pos(flist, 3);
-
-	/* Page should now start at 'd', cursor unchanged */
-	PCUT_ASSERT_STR_EQUALS("a", flist->cursor->name);
-	PCUT_ASSERT_INT_EQUALS(1, flist->cursor->size);
-	PCUT_ASSERT_INT_EQUALS(0, flist->cursor_idx);
-	PCUT_ASSERT_STR_EQUALS("d", flist->page->name);
-	PCUT_ASSERT_INT_EQUALS(4, flist->page->size);
-	PCUT_ASSERT_INT_EQUALS(3, flist->page_idx);
-
-	ui_file_list_destroy(flist);
-	ui_window_destroy(window);
-	ui_destroy(ui);
-}
-
 /** ui_file_list_open_dir() opens a directory entry */
 PCUT_TEST(open_dir)
@@ -2559,5 +1012,5 @@
 	PCUT_ASSERT_STR_EQUALS(p, flist->dir);
 
-	PCUT_ASSERT_INT_EQUALS(2, list_count(&flist->entries));
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(flist->list));
 
 	entry = ui_file_list_first(flist);
@@ -2706,4 +1159,48 @@
 }
 
+/** ui_file_list_get_cursor() returns the current cursor position */
+PCUT_TEST(get_cursor)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_file_list_t *flist;
+	ui_file_list_entry_attr_t attr;
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_t *cursor;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_file_list_create(window, true, &flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_file_list_entry_attr_init(&attr);
+
+	/* Append entry */
+	attr.name = "a";
+	attr.size = 1;
+	rc = ui_file_list_entry_append(flist, &attr);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_file_list_first(flist);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	/* Cursor should be at the only entry */
+	cursor = ui_file_list_get_cursor(flist);
+	PCUT_ASSERT_EQUALS(entry, cursor);
+
+	ui_file_list_destroy(flist);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
 static void test_file_list_activate_req(ui_file_list_t *flist, void *arg)
 {
Index: uspace/lib/ui/test/list.c
===================================================================
--- uspace/lib/ui/test/list.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
+++ uspace/lib/ui/test/list.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -0,0 +1,2747 @@
+/*
+ * Copyright (c) 2023 Jiri Svoboda
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <errno.h>
+#include <io/kbd_event.h>
+#include <io/pos_event.h>
+#include <pcut/pcut.h>
+#include <stdio.h>
+#include <str.h>
+#include <ui/ui.h>
+#include <ui/list.h>
+#include <ui/scrollbar.h>
+#include <vfs/vfs.h>
+#include "../private/list.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(list);
+
+/** Test response */
+typedef struct {
+	bool activate_req;
+	ui_list_t *activate_req_list;
+
+	bool selected;
+	ui_list_entry_t *selected_entry;
+} test_resp_t;
+
+static void test_list_activate_req(ui_list_t *, void *);
+static void test_list_selected(ui_list_entry_t *, void *);
+static int test_list_compare(ui_list_entry_t *, ui_list_entry_t *);
+
+static ui_list_cb_t test_cb = {
+	.activate_req = test_list_activate_req,
+	.selected = test_list_selected,
+	.compare = test_list_compare
+};
+
+/** Create and destroy file list. */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_set_cb() sets callback */
+PCUT_TEST(set_cb)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	test_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+	PCUT_ASSERT_EQUALS(&test_cb, list->cb);
+	PCUT_ASSERT_EQUALS(&resp, list->cb_arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_height() gives the correct height */
+PCUT_TEST(entry_height)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	gfx_coord_t height;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Font height is 13, padding: 2 (top) + 2 (bottom) */
+	height = ui_list_entry_height(list);
+	PCUT_ASSERT_INT_EQUALS(17, height);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_entry_paint() */
+PCUT_TEST(entry_paint)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_entry_paint(ui_list_first(list), 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_paint() */
+PCUT_TEST(paint)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_paint(list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_ctl() returns a valid UI control */
+PCUT_TEST(ctl)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_control_t *control;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	control = ui_list_ctl(list);
+	PCUT_ASSERT_NOT_NULL(control);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_kbd_event() */
+PCUT_TEST(kbd_event)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_evclaim_t claimed;
+	kbd_event_t event;
+	errno_t rc;
+
+	/* Active list should claim events */
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ESCAPE;
+	event.mods = 0;
+	event.c = '\0';
+
+	claimed = ui_list_kbd_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	ui_list_destroy(list);
+
+	/* Inactive list should not claim events */
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, false, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	event.type = KEY_PRESS;
+	event.key = KC_ESCAPE;
+	event.mods = 0;
+	event.c = '\0';
+
+	claimed = ui_list_kbd_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_unclaimed, claimed);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** Test ui_list_pos_event() */
+PCUT_TEST(pos_event)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_evclaim_t claimed;
+	pos_event_t event;
+	gfx_rect_t rect;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+
+	event.pos_id = 0;
+	event.type = POS_PRESS;
+	event.btn_num = 1;
+
+	/* Clicking on the middle entry should select it */
+	event.hpos = 20;
+	event.vpos = 40;
+
+	claimed = ui_list_pos_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	PCUT_ASSERT_NOT_NULL(list->cursor);
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+
+	/* Clicking on the top edge should do a page-up */
+	event.hpos = 20;
+	event.vpos = 20;
+	claimed = ui_list_pos_event(list, &event);
+	PCUT_ASSERT_EQUALS(ui_claimed, claimed);
+
+	PCUT_ASSERT_NOT_NULL(list->cursor);
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_set_rect() sets internal field */
+PCUT_TEST(set_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 1;
+	rect.p0.y = 2;
+	rect.p1.x = 3;
+	rect.p1.y = 4;
+
+	ui_list_set_rect(list, &rect);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.x, list->rect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p0.y, list->rect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.x, list->rect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(rect.p1.y, list->rect.p1.y);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_size() returns correct size */
+PCUT_TEST(page_size)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	/* NOTE If page size changes, we have problems elsewhere in the tests */
+	PCUT_ASSERT_INT_EQUALS(11, ui_list_page_size(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_inside_rect() gives correct interior rectangle */
+PCUT_TEST(inside_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	gfx_rect_t irect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_inside_rect(list, &irect);
+	PCUT_ASSERT_INT_EQUALS(10 + 2, irect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20 + 2, irect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(50 - 2 - 23, irect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(220 - 2, irect.p1.y);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scrollbar_rect() gives correct scrollbar rectangle */
+PCUT_TEST(scrollbar_rect)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	gfx_rect_t srect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 220;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_scrollbar_rect(list, &srect);
+	PCUT_ASSERT_INT_EQUALS(50 - 2 - 23, srect.p0.x);
+	PCUT_ASSERT_INT_EQUALS(20 + 2, srect.p0.y);
+	PCUT_ASSERT_INT_EQUALS(50 - 2, srect.p1.x);
+	PCUT_ASSERT_INT_EQUALS(220 - 2, srect.p1.y);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scrollbar_update() updates scrollbar position */
+PCUT_TEST(scrollbar_update)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	gfx_rect_t rect;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	gfx_coord_t pos;
+	gfx_coord_t move_len;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 50;
+	rect.p1.y = 38;
+
+	ui_list_set_rect(list, &rect);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_list_next(ui_list_first(list));
+
+	list->cursor = entry;
+	list->cursor_idx = 1;
+	list->page = entry;
+	list->page_idx = 1;
+
+	ui_list_scrollbar_update(list);
+
+	/* Now scrollbar thumb should be all the way down */
+	move_len = ui_scrollbar_move_length(list->scrollbar);
+	pos = ui_scrollbar_get_pos(list->scrollbar);
+	PCUT_ASSERT_INT_EQUALS(move_len, pos);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_is_active() returns list activity state */
+PCUT_TEST(is_active)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_list_is_active(list));
+	ui_list_destroy(list);
+
+	rc = ui_list_create(window, false, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_FALSE(ui_list_is_active(list));
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_activate() activates list */
+PCUT_TEST(activate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, false, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_FALSE(ui_list_is_active(list));
+	rc = ui_list_activate(list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_TRUE(ui_list_is_active(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_deactivate() deactivates list */
+PCUT_TEST(deactivate)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(ui_list_is_active(list));
+	ui_list_deactivate(list);
+	PCUT_ASSERT_FALSE(ui_list_is_active(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_get_cursor() returns the current cursor position */
+PCUT_TEST(get_cursor)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	ui_list_entry_t *cursor;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	/* Cursor should be at the only entry */
+	cursor = ui_list_get_cursor(list);
+	PCUT_ASSERT_EQUALS(entry, cursor);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_attr_init() initializes entry attribute structure */
+PCUT_TEST(entry_attr_init)
+{
+	ui_list_entry_attr_t attr;
+
+	ui_list_entry_attr_init(&attr);
+	PCUT_ASSERT_NULL(attr.caption);
+	PCUT_ASSERT_NULL(attr.arg);
+	PCUT_ASSERT_NULL(attr.color);
+	PCUT_ASSERT_NULL(attr.bgcolor);
+}
+
+/** ui_list_entry_append() appends new entry */
+PCUT_TEST(entry_append)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry without retrieving pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, list_count(&list->entries));
+
+	/* Append entry and get pointer to it */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_EQUALS(attr.arg, entry->arg);
+
+	PCUT_ASSERT_INT_EQUALS(2, list_count(&list->entries));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_delete() deletes entry */
+PCUT_TEST(entry_delete)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list_count(&list->entries));
+
+	entry = ui_list_first(list);
+	ui_list_entry_delete(entry);
+
+	PCUT_ASSERT_INT_EQUALS(1, list_count(&list->entries));
+
+	entry = ui_list_first(list);
+	ui_list_entry_delete(entry);
+
+	PCUT_ASSERT_INT_EQUALS(0, list_count(&list->entries));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_get_arg() gets entry argument */
+PCUT_TEST(entry_get_arg)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	void *arg;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry and get pointer to it */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	entry = NULL;
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	arg = ui_list_entry_get_arg(entry);
+	PCUT_ASSERT_EQUALS(attr.arg, arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entries_cnt() returns the number of entries */
+PCUT_TEST(entries_cnt)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entries_cnt(list));
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Append entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entries_cnt(list));
+
+	/* Append another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_entries_cnt(list));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_sort() sorts UI list entries */
+PCUT_TEST(sort)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	test_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "b";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_sort(list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	entry = ui_list_first(list);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_EQUALS((void *)2, entry->arg);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_EQUALS((void *)1, entry->arg);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_STR_EQUALS("c", entry->caption);
+	PCUT_ASSERT_EQUALS((void *)3, entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_center()...XXX */
+PCUT_TEST(cursor_center)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *a, *b, *c, *d, *e;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	test_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	rect.p0.x = 10;
+	rect.p0.y = 20;
+	rect.p1.x = 50;
+	rect.p1.y = 80;
+
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(3, ui_list_page_size(list));
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &a);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &b);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We only have two entries, but three fit onto the page */
+	ui_list_cursor_center(list, b);
+	PCUT_ASSERT_EQUALS(b, list->cursor);
+	/* Page should start at the beginning */
+	PCUT_ASSERT_EQUALS(a, list->page);
+	PCUT_ASSERT_EQUALS(0, list->page_idx);
+
+	/* Add more entries */
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, &c);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, &d);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, &e);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_cursor_center(list, c);
+	PCUT_ASSERT_EQUALS(c, list->cursor);
+	/*
+	 * We have enough entries, c should be in the middle of the three
+	 * entries on the page, i.e., page should start on 'b'.
+	 */
+	PCUT_ASSERT_EQUALS(b, list->page);
+	PCUT_ASSERT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_clear_entries() removes all entries from list */
+PCUT_TEST(clear_entries)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(2, list_count(&list->entries));
+
+	ui_list_clear_entries(list);
+	PCUT_ASSERT_INT_EQUALS(0, list_count(&list->entries));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_first() returns valid entry or @c NULL as appropriate */
+PCUT_TEST(first)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting it */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We should still get the first entry */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_last() returns valid entry or @c NULL as appropriate */
+PCUT_TEST(last)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting it */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* We should get new entry now */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_next() returns the next entry or @c NULL as appropriate */
+PCUT_TEST(next)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting its successor */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Try getting the successor of first entry again */
+	entry = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_next(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_prev() returns the previous entry or @c NULL as appropriate */
+PCUT_TEST(prev)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Add one entry */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Now try getting its predecessor */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_prev(entry);
+	PCUT_ASSERT_NULL(entry);
+
+	/* Add another entry */
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Try getting the predecessor of the new entry */
+	entry = ui_list_last(list);
+	PCUT_ASSERT_NOT_NULL(entry);
+
+	entry = ui_list_prev(entry);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("a", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)entry->arg);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_nth_entry() .. */
+PCUT_TEST(page_nth_entry)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *entry;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	size_t idx;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_entry_attr_init(&attr);
+
+	/* Add some entries */
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	list->page = ui_list_next(ui_list_first(list));
+	list->page_idx = 1;
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 100;
+	rect.p1.y = 100;
+	ui_list_set_rect(list, &rect);
+
+	entry = ui_list_page_nth_entry(list, 0, &idx);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("b", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(1, idx);
+
+	entry = ui_list_page_nth_entry(list, 1, &idx);
+	PCUT_ASSERT_NOT_NULL(entry);
+	PCUT_ASSERT_STR_EQUALS("c", entry->caption);
+	PCUT_ASSERT_INT_EQUALS(2, idx);
+
+	entry = ui_list_page_nth_entry(list, 2, &idx);
+	PCUT_ASSERT_NULL(entry);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_move() moves cursor and scrolls */
+PCUT_TEST(cursor_move)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Move cursor one entry up */
+	ui_list_cursor_move(list, ui_list_prev(list->cursor),
+	    list->cursor_idx - 1);
+
+	/* Cursor and page start should now both be at the second entry */
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Move cursor to the first entry. This should scroll up. */
+	ui_list_cursor_move(list, ui_list_first(list), 0);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Move cursor to the last entry. */
+	ui_list_cursor_move(list, ui_list_last(list), 2);
+
+	/* Cursor should be on the last entry and page on the next to last */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_up() moves cursor one entry up */
+PCUT_TEST(cursor_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Move cursor one entry up */
+	ui_list_cursor_up(list);
+
+	/* Cursor and page start should now both be at the second entry */
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Move cursor one entry up. This should scroll up. */
+	ui_list_cursor_up(list);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Moving further up should do nothing (we are at the top). */
+	ui_list_cursor_up(list);
+
+	/* Cursor and page start should still be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_down() moves cursor one entry down */
+PCUT_TEST(cursor_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page start to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Move cursor one entry down */
+	ui_list_cursor_down(list);
+
+	/* Cursor should now be at the second entry, page stays the same */
+	PCUT_ASSERT_STR_EQUALS("b", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(ui_list_first(list), list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Move cursor one entry down. This should scroll down. */
+	ui_list_cursor_down(list);
+
+	/* Cursor should now be at the third and page at the second entry. */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Moving further down should do nothing (we are at the bottom). */
+	ui_list_cursor_down(list);
+
+	/* Cursor should still be at the third and page at the second entry. */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_top() moves cursor to the first entry */
+PCUT_TEST(cursor_top)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Move cursor to the top. This should scroll up. */
+	ui_list_cursor_top(list);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_cursor_bottom() moves cursor to the last entry */
+PCUT_TEST(cursor_bottom)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page start to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Move cursor to the bottom. This should scroll down. */
+	ui_list_cursor_bottom(list);
+
+	/* Cursor should now be at the third and page at the second entry. */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_up() moves one page up */
+PCUT_TEST(page_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (2 full pages, one partial) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry and page start to the next-to-last entry */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 4;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 3;
+
+	/* Move one page up */
+	ui_list_page_up(list);
+
+	/* Page should now start at second entry and cursor at third */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Move one page up again. */
+	ui_list_page_up(list);
+
+	/* Cursor and page start should now both be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Moving further up should do nothing (we are at the top). */
+	ui_list_page_up(list);
+
+	/* Cursor and page start should still be at the first entry */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_EQUALS(list->cursor, list->page);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_page_up() moves one page down */
+PCUT_TEST(page_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (2 full pages, one partial) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Move one page down */
+	ui_list_page_down(list);
+
+	/* Page and cursor should point to the third entry */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("c", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->page_idx);
+
+	/* Move one page down again. */
+	ui_list_page_down(list);
+
+	/* Cursor should point to last and page to next-to-last entry */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	/* Moving further down should do nothing (we are at the bottom). */
+	ui_list_page_down(list);
+
+	/* Cursor should still point to last and page to next-to-last entry */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_up() scrolls up by one row */
+PCUT_TEST(scroll_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry, page to the second */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 2;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 1;
+
+	/* Scroll one entry up */
+	ui_list_scroll_up(list);
+
+	/* Page should start on the first entry, cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Try scrolling one more entry up */
+	ui_list_scroll_up(list);
+
+	/* We were at the beginning, so nothing should have changed  */
+	PCUT_ASSERT_STR_EQUALS("c", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_down() scrolls down by one row */
+PCUT_TEST(scroll_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add tree entries (more than page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page start to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = list->cursor;
+	list->page_idx = 0;
+
+	/* Scroll one entry down */
+	ui_list_scroll_down(list);
+
+	/* Page should start on the second entry, cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Try scrolling one more entry down */
+	ui_list_scroll_down(list);
+
+	/* We were at the end, so nothing should have changed  */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_page_up() scrolls up by one page */
+PCUT_TEST(scroll_page_up)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (more than twice the page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor to the last entry, page to the second last */
+	list->cursor = ui_list_last(list);
+	list->cursor_idx = 4;
+	list->page = ui_list_prev(list->cursor);
+	list->page_idx = 3;
+
+	/* Scroll one page up */
+	ui_list_scroll_page_up(list);
+
+	/* Page should start on 'b', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Page up again */
+	ui_list_scroll_page_up(list);
+
+	/* Page should now be at the beginning, cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	/* Page up again */
+	ui_list_scroll_page_up(list);
+
+	/* We were at the beginning, nothing should have changed */
+	PCUT_ASSERT_STR_EQUALS("e", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(5, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(4, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("a", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_page_up() scrolls down by one page */
+PCUT_TEST(scroll_page_down)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (more than twice the page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+
+	/* Scroll one page down */
+	ui_list_scroll_page_down(list);
+
+	/* Page should start on 'c', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("c", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(3, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(2, list->page_idx);
+
+	/* Page down again */
+	ui_list_scroll_page_down(list);
+
+	/* Page should now start at 'd', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	/* Page down again */
+	ui_list_scroll_page_down(list);
+
+	/* We were at the end, nothing should have changed */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_scroll_pos() scrolls to a particular entry */
+PCUT_TEST(scroll_pos)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	gfx_rect_t rect;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rect.p0.x = 0;
+	rect.p0.y = 0;
+	rect.p1.x = 10;
+	rect.p1.y = 38; /* Assuming this makes page size 2 */
+	ui_list_set_rect(list, &rect);
+
+	PCUT_ASSERT_INT_EQUALS(2, ui_list_page_size(list));
+
+	/* Add five entries (more than twice the page size, which is 2) */
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "c";
+	attr.arg = (void *)3;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "d";
+	attr.arg = (void *)4;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "e";
+	attr.arg = (void *)5;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Cursor and page to the first entry */
+	list->cursor = ui_list_first(list);
+	list->cursor_idx = 0;
+	list->page = ui_list_first(list);
+	list->page_idx = 0;
+
+	/* Scroll to entry 1 (one down) */
+	ui_list_scroll_pos(list, 1);
+
+	/* Page should start on 'b', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("b", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(2, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(1, list->page_idx);
+
+	/* Scroll to entry 3 (i.e. the end) */
+	ui_list_scroll_pos(list, 3);
+
+	/* Page should now start at 'd', cursor unchanged */
+	PCUT_ASSERT_STR_EQUALS("a", list->cursor->caption);
+	PCUT_ASSERT_INT_EQUALS(1, (intptr_t)list->cursor->arg);
+	PCUT_ASSERT_INT_EQUALS(0, list->cursor_idx);
+	PCUT_ASSERT_STR_EQUALS("d", list->page->caption);
+	PCUT_ASSERT_INT_EQUALS(4, (intptr_t)list->page->arg);
+	PCUT_ASSERT_INT_EQUALS(3, list->page_idx);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_activate_req() sends activation request */
+PCUT_TEST(activate_req)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	errno_t rc;
+	test_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	resp.activate_req = false;
+	resp.activate_req_list = NULL;
+
+	ui_list_activate_req(list);
+	PCUT_ASSERT_TRUE(resp.activate_req);
+	PCUT_ASSERT_EQUALS(list, resp.activate_req_list);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_selected() runs selected callback */
+PCUT_TEST(selected)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_attr_t attr;
+	ui_list_entry_t *entry;
+	errno_t rc;
+	test_resp_t resp;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+	attr.caption = "Hello";
+	attr.arg = &resp;
+
+	rc = ui_list_entry_append(list, &attr, &entry);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	resp.selected = false;
+	resp.selected_entry = NULL;
+
+	ui_list_selected(entry);
+	PCUT_ASSERT_TRUE(resp.selected);
+	PCUT_ASSERT_EQUALS(entry, resp.selected_entry);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_ptr_cmp compares two indirectly referenced entries */
+PCUT_TEST(entry_ptr_cmp)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *a, *b;
+	ui_list_entry_attr_t attr;
+	test_resp_t resp;
+	int rel;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	a = ui_list_first(list);
+	PCUT_ASSERT_NOT_NULL(a);
+	b = ui_list_next(a);
+	PCUT_ASSERT_NOT_NULL(b);
+
+	/* a < b */
+	rel = ui_list_entry_ptr_cmp(&a, &b);
+	PCUT_ASSERT_TRUE(rel < 0);
+
+	/* b > a */
+	rel = ui_list_entry_ptr_cmp(&b, &a);
+	PCUT_ASSERT_TRUE(rel > 0);
+
+	/* a == a */
+	rel = ui_list_entry_ptr_cmp(&a, &a);
+	PCUT_ASSERT_INT_EQUALS(0, rel);
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+/** ui_list_entry_get_idx() returns entry index */
+PCUT_TEST(entry_get_idx)
+{
+	ui_t *ui;
+	ui_window_t *window;
+	ui_wnd_params_t params;
+	ui_list_t *list;
+	ui_list_entry_t *a, *b;
+	ui_list_entry_attr_t attr;
+	test_resp_t resp;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_wnd_params_init(&params);
+	params.caption = "Test";
+
+	rc = ui_window_create(ui, &params, &window);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = ui_list_create(window, true, &list);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	ui_list_set_cb(list, &test_cb, &resp);
+
+	ui_list_entry_attr_init(&attr);
+
+	attr.caption = "a";
+	attr.arg = (void *)2;
+	rc = ui_list_entry_append(list, &attr, &a);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	attr.caption = "b";
+	attr.arg = (void *)1;
+	rc = ui_list_entry_append(list, &attr, &b);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_INT_EQUALS(0, ui_list_entry_get_idx(a));
+	PCUT_ASSERT_INT_EQUALS(1, ui_list_entry_get_idx(b));
+
+	ui_list_destroy(list);
+	ui_window_destroy(window);
+	ui_destroy(ui);
+}
+
+static void test_list_activate_req(ui_list_t *list, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->activate_req = true;
+	resp->activate_req_list = list;
+}
+
+static void test_list_selected(ui_list_entry_t *entry, void *arg)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	resp->selected = true;
+	resp->selected_entry = entry;
+}
+
+static int test_list_compare(ui_list_entry_t *a, ui_list_entry_t *b)
+{
+	return str_cmp(a->caption, b->caption);
+}
+
+PCUT_EXPORT(list);
Index: uspace/lib/ui/test/main.c
===================================================================
--- uspace/lib/ui/test/main.c	(revision bea6233f6403a8326263792f1a531eb1cb564552)
+++ uspace/lib/ui/test/main.c	(revision 7cf5ddbad0929896f7de7dcfdf23d42fe47929b3)
@@ -41,4 +41,5 @@
 PCUT_IMPORT(image);
 PCUT_IMPORT(label);
+PCUT_IMPORT(list);
 PCUT_IMPORT(menu);
 PCUT_IMPORT(menubar);
