--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+#include <inttypes.h>
+#include <string.h>
+#include <sys/ttydefaults.h>
+
+#include "ui/browser.h"
+#include "ui/helpline.h"
+#include "ui/keysyms.h"
+#include "ui/ui.h"
+#include "util/annotate.h"
+#include "util/annotate-data.h"
+#include "util/evsel.h"
+#include "util/sort.h"
+
+struct annotated_data_browser {
+       struct ui_browser b;
+       struct list_head entries;
+};
+
+struct browser_entry {
+       struct list_head node;
+       struct annotated_member *data;
+       struct type_hist_entry hists;
+       int indent;
+};
+
+static void update_hist_entry(struct type_hist_entry *dst,
+                             struct type_hist_entry *src)
+{
+       dst->nr_samples += src->nr_samples;
+       dst->period += src->period;
+}
+
+static int get_member_overhead(struct annotated_data_type *adt,
+                              struct browser_entry *entry,
+                              struct evsel *evsel)
+{
+       struct annotated_member *member = entry->data;
+       int i;
+
+       for (i = 0; i < member->size; i++) {
+               struct type_hist *h;
+               int offset = member->offset + i;
+
+               h = adt->histograms[evsel->core.idx];
+               update_hist_entry(&entry->hists, &h->addr[offset]);
+       }
+       return 0;
+}
+
+static int add_child_entries(struct annotated_data_browser *browser,
+                            struct annotated_data_type *adt,
+                            struct annotated_member *member,
+                            struct evsel *evsel, int indent)
+{
+       struct annotated_member *pos;
+       struct browser_entry *entry;
+       int nr_entries = 0;
+
+       entry = zalloc(sizeof(*entry));
+       if (entry == NULL)
+               return -1;
+
+       entry->data = member;
+       entry->indent = indent;
+       if (get_member_overhead(adt, entry, evsel) < 0) {
+               free(entry);
+               return -1;
+       }
+
+       list_add_tail(&entry->node, &browser->entries);
+       nr_entries++;
+
+       list_for_each_entry(pos, &member->children, node) {
+               int nr = add_child_entries(browser, adt, pos, evsel, indent + 1);
+
+               if (nr < 0)
+                       return nr;
+
+               nr_entries += nr;
+       }
+
+       /* add an entry for the closing bracket ("}") */
+       if (!list_empty(&member->children)) {
+               entry = zalloc(sizeof(*entry));
+               if (entry == NULL)
+                       return -1;
+
+               entry->indent = indent;
+               list_add_tail(&entry->node, &browser->entries);
+               nr_entries++;
+       }
+
+       return nr_entries;
+}
+
+static int annotated_data_browser__collect_entries(struct annotated_data_browser *browser)
+{
+       struct hist_entry *he = browser->b.priv;
+       struct annotated_data_type *adt = he->mem_type;
+       struct evsel *evsel = hists_to_evsel(he->hists);
+
+       INIT_LIST_HEAD(&browser->entries);
+       browser->b.entries = &browser->entries;
+       browser->b.nr_entries = add_child_entries(browser, adt, &adt->self,
+                                                 evsel, /*indent=*/0);
+       return 0;
+}
+
+static void annotated_data_browser__delete_entries(struct annotated_data_browser *browser)
+{
+       struct browser_entry *pos, *tmp;
+
+       list_for_each_entry_safe(pos, tmp, &browser->entries, node) {
+               list_del_init(&pos->node);
+               free(pos);
+       }
+}
+
+static unsigned int browser__refresh(struct ui_browser *uib)
+{
+       return ui_browser__list_head_refresh(uib);
+}
+
+static int browser__show(struct ui_browser *uib)
+{
+       struct hist_entry *he = uib->priv;
+       struct annotated_data_type *adt = he->mem_type;
+       const char *help = "Press 'h' for help on key bindings";
+       char title[256];
+
+       snprintf(title, sizeof(title), "Annotate type: '%s' (%d samples)",
+                adt->self.type_name, he->stat.nr_events);
+
+       if (ui_browser__show(uib, title, help) < 0)
+               return -1;
+
+       /* second line header */
+       ui_browser__gotorc_title(uib, 0, 0);
+       ui_browser__set_color(uib, HE_COLORSET_ROOT);
+
+       if (symbol_conf.show_total_period)
+               strcpy(title, "Period");
+       else if (symbol_conf.show_nr_samples)
+               strcpy(title, "Samples");
+       else
+               strcpy(title, "Percent");
+
+       ui_browser__printf(uib, " %10s %10s %10s  %s",
+                          title, "Offset", "Size", "Field");
+       ui_browser__write_nstring(uib, "", uib->width);
+       return 0;
+}
+
+static void browser__write_overhead(struct ui_browser *uib,
+                                   struct type_hist *total,
+                                   struct type_hist_entry *hist, int row)
+{
+       u64 period = hist->period;
+       double percent = total->period ? (100.0 * period / total->period) : 0;
+       bool current = ui_browser__is_current_entry(uib, row);
+       int nr_samples = 0;
+
+       ui_browser__set_percent_color(uib, percent, current);
+
+       if (symbol_conf.show_total_period)
+               ui_browser__printf(uib, " %10" PRIu64, period);
+       else if (symbol_conf.show_nr_samples)
+               ui_browser__printf(uib, " %10d", nr_samples);
+       else
+               ui_browser__printf(uib, " %10.2f", percent);
+
+       ui_browser__set_percent_color(uib, 0, current);
+}
+
+static void browser__write(struct ui_browser *uib, void *entry, int row)
+{
+       struct browser_entry *be = entry;
+       struct annotated_member *member = be->data;
+       struct hist_entry *he = uib->priv;
+       struct annotated_data_type *adt = he->mem_type;
+       struct evsel *evsel = hists_to_evsel(he->hists);
+
+       if (member == NULL) {
+               bool current = ui_browser__is_current_entry(uib, row);
+
+               /* print the closing bracket */
+               ui_browser__set_percent_color(uib, 0, current);
+               ui_browser__write_nstring(uib, "", 11);
+               ui_browser__printf(uib, " %10s %10s  %*s};",
+                                  "", "", be->indent * 4, "");
+               ui_browser__write_nstring(uib, "", uib->width);
+               return;
+       }
+
+       /* print the number */
+       browser__write_overhead(uib, adt->histograms[evsel->core.idx],
+                               &be->hists, row);
+
+       /* print type info */
+       if (be->indent == 0 && !member->var_name) {
+               ui_browser__printf(uib, " %10d %10d  %s%s",
+                                  member->offset, member->size,
+                                  member->type_name,
+                                  list_empty(&member->children) ? ";" : " {");
+       } else {
+               ui_browser__printf(uib, " %10d %10d  %*s%s\t%s%s",
+                                  member->offset, member->size,
+                                  be->indent * 4, "", member->type_name,
+                                  member->var_name ?: "",
+                                  list_empty(&member->children) ? ";" : " {");
+       }
+       /* fill the rest */
+       ui_browser__write_nstring(uib, "", uib->width);
+}
+
+static int annotated_data_browser__run(struct annotated_data_browser *browser,
+                                      struct evsel *evsel __maybe_unused,
+                                      struct hist_browser_timer *hbt)
+{
+       int delay_secs = hbt ? hbt->refresh : 0;
+       int key;
+
+       if (browser__show(&browser->b) < 0)
+               return -1;
+
+       while (1) {
+               key = ui_browser__run(&browser->b, delay_secs);
+
+               switch (key) {
+               case K_TIMER:
+                       if (hbt)
+                               hbt->timer(hbt->arg);
+                       continue;
+               case K_F1:
+               case 'h':
+                       ui_browser__help_window(&browser->b,
+               "UP/DOWN/PGUP\n"
+               "PGDN/SPACE    Navigate\n"
+               "</>           Move to prev/next symbol\n"
+               "q/ESC/CTRL+C  Exit\n\n");
+                       continue;
+               case K_LEFT:
+               case '<':
+               case '>':
+               case K_ESC:
+               case 'q':
+               case CTRL('c'):
+                       goto out;
+               default:
+                       continue;
+               }
+       }
+out:
+       ui_browser__hide(&browser->b);
+       return key;
+}
+
+int hist_entry__annotate_data_tui(struct hist_entry *he, struct evsel *evsel,
+                                 struct hist_browser_timer *hbt)
+{
+       struct annotated_data_browser browser = {
+               .b = {
+                       .refresh = browser__refresh,
+                       .seek    = ui_browser__list_head_seek,
+                       .write   = browser__write,
+                       .priv    = he,
+                       .extra_title_lines = 1,
+               },
+       };
+       int ret;
+
+       ui_helpline__push("Press ESC to exit");
+
+       ret = annotated_data_browser__collect_entries(&browser);
+       if (ret == 0)
+               ret = annotated_data_browser__run(&browser, evsel, hbt);
+
+       annotated_data_browser__delete_entries(&browser);
+
+       return ret;
+}