Skip to content

Scrolling Containers ​

When layouts contain lists or large blocks of content, they can overflow. RTXUI supports full horizontal and vertical scrolling with visual scrollbars.

Enabling Scroll ​

By adding overflow-y: scroll (or overflow-x: scroll) and restricting the dimensions (height or width), RTXUI automatically crops overflowing elements and displays a modern, responsive scrollbar.

cpp
class ScrollBox : public Component<ScrollBox> {
 public:
  std::string_view view = R"html(
      <div class="scroll-container">
        <div>Item 1</div>
        <div>Item 2</div>
        <div>Item 3</div>
        <!-- ... -->
      </div>
      <style>
        .scroll-container {
          display: block;
          height: 5;
          overflow-y: scroll;
          scrollbar-width: auto;
          border: wide;
          border-color: rgb(29, 78, 216);
        }
      </style>
    )html";
};

Event Bubbling ​

Scroll events automatically bubble up to nested containers when boundaries are reached, allowing for natural feeling nested scroll areas.

Fullscreen Demo
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// Nested scroll containers and scroll chaining.
//
// An inner container consumes wheel events until it reaches its end, then the
// event propagates to its parent.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class NestedScrollDemo : public Component<NestedScrollDemo> {
 public:
  std::string_view view = R"html(
      <h1>RTXUI Nested Scrolling & Chaining Demo</h1>
      <p class="desc">
        This demo showcases nested scrollable elements. Reaching the boundaries (top or bottom)
        of an inner container will bubble the scroll event (mouse wheel or keyboard) to the outer container.
      </p>

      <div class="section-title">[Outer Start] Beginning of Outer List</div>
      <div>[Outer Item 1] Scroll down to discover inner containers...</div>
      <div>[Outer Item 2] Outer container can scroll independently.</div>
      <div>[Outer Item 3] Scroll speed configured in CSS.</div>
      <div>[Outer Item 4] Scrollbar dynamically renders on the right.</div>
      <div>[Outer Item 5] Preparing to load Inner Box A...</div>

      <div class="inner-title">Nested Scrollbox A (Light Blue Border)</div>
      <div class="inner-scroll box-a">
        <div>[Box A - Item 1] Focus me & press ArrowDown</div>
        <div>[Box A - Item 2] Or scroll with mouse wheel</div>
        <div>[Box A - Item 3] Standard scroll chaining is active</div>
        <div>[Box A - Item 4] Item 4</div>
        <div>[Box A - Item 5] Item 5</div>
        <div>[Box A - Item 6] Item 6</div>
        <div>[Box A - Item 7] Item 7</div>
        <div>[Box A - Item 8] Item 8</div>
        <div>[Box A - Item 9] Item 9</div>
        <div>[Box A - Item 10] Item 10</div>
        <div>[Box A - Item 11] Item 11</div>
        <div>[Box A - Item 12] Item 12</div>
        <div>[Box A - Item 13] Item 13</div>
        <div>[Box A - Item 14] Item 14</div>
        <div>[Box A - Item 15] Box A Bottom - Scroll down again to bubble up</div>
      </div>

      <div class="section-title">[Outer Middle] Content between inner containers</div>
      <div>[Outer Item 6] This is intermediate content.</div>
      <div>[Outer Item 7] Layout handles arbitrary nesting level.</div>
      <div>[Outer Item 8] Ready for Inner Box B...</div>

      <div class="inner-title">Nested Scrollbox B (Steel Blue Border)</div>
      <div class="inner-scroll box-b">
        <div>[Box B - Item 1] Welcome to Box B!</div>
        <div>[Box B - Item 2] Focus me & scroll to end</div>
        <div>[Box B - Item 3] Item 3</div>
        <div>[Box B - Item 4] Item 4</div>
        <div>[Box B - Item 5] Item 5</div>
        <div>[Box B - Item 6] Item 6</div>
        <div>[Box B - Item 7] Item 7</div>
        <div>[Box B - Item 8] Item 8</div>
        <div>[Box B - Item 9] Item 9</div>
        <div>[Box B - Item 10] Item 10</div>
        <div>[Box B - Item 11] Item 11</div>
        <div>[Box B - Item 12] Item 12</div>
        <div>[Box B - Item 13] Item 13</div>
        <div>[Box B - Item 14] Item 14</div>
        <div>[Box B - Item 15] Box B Bottom - Scroll down again to bubble up</div>
      </div>

      <div class="section-title">[Outer End] End of Outer List</div>
      <div>[Outer Item 9] Both nested boxes can bubble scroll events up.</div>
      <div>[Outer Item 10] Thank you for using RTXUI!</div>

      <style>
        self {
          --surface: rgb(22, 27, 34);
          --accent-bright: rgb(121, 192, 255);

          display: block;
          width: 100%;
          height: 100%;
          overflow-y: scroll;
          scroll-speed: 1;
          background-color: rgb(13, 17, 23);
          color: rgb(230, 237, 243);
          padding: 1;
        }
        h1 {
          color: var(--accent-bright);
          font-weight: bold;
          margin-bottom: 1;
        }
        .desc {
          color: rgb(139, 148, 158);
          margin-bottom: 1;
        }
        .inner-scroll {
          display: block;
          height: 5;
          overflow-y: scroll;
          scroll-speed: 1;
          margin-top: 1;
          margin-bottom: 1;
        }
        .box-a {
          border: tall;
          border-color: var(--accent-bright);
          background-color: var(--surface);
        }
        .box-b {
          border: tall;
          border-color: rgb(88, 166, 255);
          background-color: var(--surface);
        }
        .section-title {
          color: var(--accent-bright);
          font-weight: bold;
          margin-top: 1;
          margin-bottom: 1;
        }
        .inner-title {
          color: rgb(191, 219, 254);
          font-weight: bold;
          margin-top: 1;
        }
      </style>
    )html";
};

int main() {
  auto app = Ref<NestedScrollDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

Scroll-Into-View on Keyboard Focus ​

RTXUI automatically scrolls containers to keep focused elements visible when users navigate using the keyboard (Tab and Shift+Tab).

Fullscreen Demo
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// Scroll-into-view on keyboard focus.
//
// Tabbing to an element that is outside its scroll container scrolls it into
// view automatically.
//
// Try it: hold Tab and watch the list follow the focus ring.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class FocusScrollDemo : public Component<FocusScrollDemo> {
 public:
  std::string_view view = R"html(
    <div class="container">
      <h2>Keyboard Focus Scroll-Into-View Demo</h2>
      <p class="description">
        Press <b>Tab</b> or <b>Shift-Tab</b> to cycle focus through the items below.
        The scrollable container will automatically scroll to keep the focused element in view.
      </p>

      <div class="scroll-window">
        <div class="list-item" tabindex="0">Item 1 (Start)</div>
        <div class="list-item" tabindex="0">Item 2</div>
        <div class="list-item" tabindex="0">Item 3</div>
        <div class="list-item" tabindex="0">Item 4</div>
        <div class="list-item" tabindex="0">Item 5</div>
        <div class="list-item" tabindex="0">Item 6</div>
        <div class="list-item" tabindex="0">Item 7</div>
        <div class="list-item" tabindex="0">Item 8</div>
        <div class="list-item" tabindex="0">Item 9</div>
        <div class="list-item" tabindex="0">Item 10 (End)</div>
      </div>
    </div>

    <style>
      self {
        --accent: rgb(88, 166, 255);

        display: block;
        padding: 1 2;
        background-color: rgb(13, 17, 23); /* Deep dark slate background */
        color: rgb(230, 237, 243);
      }
      h2 {
        color: var(--accent); /* Bright blue */
        margin-bottom: 0;
      }
      .description {
        color: rgb(139, 148, 158); /* Muted gray text */
        margin-bottom: 2;
      }
      .scroll-window {
        display: block;
        height: 6;
        border: tall;
        border-color: rgb(48, 54, 61);
        overflow-y: scroll;
        scroll-speed: 1;
        width: 40;
      }
      .list-item {
        display: block;
        padding: 0 1;
        border: solid;
        border-color: rgb(22, 27, 34);
        background-color: rgb(30, 41, 59, 0.4);
        margin: 0;
      }
      .list-item:hover {
        background-color: rgb(30, 41, 59, 0.8);
      }
      .list-item:focus {
        border-color: var(--accent); /* Blue focus */
        background-color: rgb(30, 58, 138, 0.5); /* Blue background tint */
        color: rgb(255, 255, 255);
      }
    </style>
  )html";
};

int main() {
  auto app = Ref<FocusScrollDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

Horizontal Scrolling ​

Fullscreen Demo
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// Horizontal overflow.
//
// overflow-x on a container that is narrower than its content produces a
// horizontal scrollbar.
//
// Try it: scroll with Shift+wheel, or drag the scrollbar.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class HorizontalScrollDemo : public Component<HorizontalScrollDemo> {
 public:
  std::string_view view = R"html(
      <div class="header">
        <h1>RTXUI Horizontal Scrolling Demo</h1>
        <p class="desc">
          This demo showcases horizontal scrollable elements. Scroll horizontally using Shift + mouse wheel,
          WheelLeft/Right events, or ArrowLeft/ArrowRight keys when focused.
        </p>
      </div>

      <div class="item first">[Outer Start]</div>
      <div class="item">[Scroll right to find nested scrollboxes...]</div>
      <div class="item">[Item A]</div>
      <div class="item">[Item B]</div>

      <div class="nested-container">
        <div class="inner-title">Nested Box A (Light Blue Border)</div>
        <div class="inner-scroll box-a">
          <div class="inner-item first">[A-1]</div>
          <div class="inner-item">[A-2]</div>
          <div class="inner-item">[A-3]</div>
          <div class="inner-item">[A-4]</div>
          <div class="inner-item">[A-5]</div>
          <div class="inner-item">[A-6]</div>
          <div class="inner-item">[A-7]</div>
          <div class="inner-item">[A-8]</div>
          <div class="inner-item">[A-9]</div>
          <div class="inner-item last">[A-End]</div>
        </div>
      </div>

      <div class="item">[Middle Item]</div>

      <div class="nested-container">
        <div class="inner-title">Nested Box B (Steel Blue Border)</div>
        <div class="inner-scroll box-b">
          <div class="inner-item first">[B-1]</div>
          <div class="inner-item">[B-2]</div>
          <div class="inner-item">[B-3]</div>
          <div class="inner-item">[B-4]</div>
          <div class="inner-item">[B-5]</div>
          <div class="inner-item">[B-6]</div>
          <div class="inner-item">[B-7]</div>
          <div class="inner-item">[B-8]</div>
          <div class="inner-item">[B-9]</div>
          <div class="inner-item last">[B-End]</div>
        </div>
      </div>

      <div class="item last">[Outer End]</div>

      <style>
        self {
          --surface: rgb(22, 27, 34);
          --border: rgb(48, 54, 61);
          --accent-bright: rgb(121, 192, 255);

          display: flex;
          flex-direction: row;
          width: 100%;
          height: 100%;
          overflow-x: scroll;
          scroll-speed-x: 2;
          background-color: rgb(13, 17, 23);
          color: rgb(230, 237, 243);
          padding: 1;
        }
        .header {
          display: block;
          width: 35;
          height: 8;
          flex-shrink: 0;
          margin-right: 2;
          border: tall;
          border-color: var(--surface);
          padding: 1;
        }
        h1 {
          color: var(--accent-bright);
          font-weight: bold;
          margin-bottom: 1;
        }
        .desc {
          color: rgb(139, 148, 158);
          margin-bottom: 1;
        }
        .item {
          display: block;
          width: 25;
          height: 8;
          margin-right: 2;
          background-color: var(--surface);
          color: rgb(219, 234, 254);
          border: tall;
          border-color: var(--border);
          padding: 1;
        }
        .nested-container {
          display: block;
          width: 40;
          height: 8;
          margin-right: 2;
        }
        .inner-title {
          color: rgb(191, 219, 254);
          font-weight: bold;
          margin-bottom: 1;
        }
        .inner-scroll {
          display: flex;
          flex-direction: row;
          width: 38;
          height: 5;
          overflow-x: scroll;
          scroll-speed-x: 1;
        }
        .box-a {
          border: tall;
          border-color: var(--accent-bright);
          background-color: var(--surface);
        }
        .box-b {
          border: tall;
          border-color: rgb(88, 166, 255);
          background-color: var(--surface);
        }
        .inner-item {
          display: block;
          width: 10;
          margin-right: 1;
          background-color: rgb(15, 23, 42);
          color: var(--accent-bright);
          border: tall;
          border-color: var(--border);
          padding: 0 1;
        }
        .first {
          color: rgb(74, 222, 128);
        }
        .last {
          color: rgb(248, 81, 73);
        }
      </style>
    )html";
};

int main() {
  auto app = Ref<HorizontalScrollDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

Scroll Behavior ​

Fullscreen Demo
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// scroll-behavior: smooth versus auto.
//
// Try it: click the jump buttons and compare how each column travels.
#include <rtxui/rtxui.hpp>

using namespace rtxui;

class ScrollBehaviorDemo : public Component<ScrollBehaviorDemo> {
 public:
  std::string_view view = R"html(
    <div class="container">
      <div class="header">
        <h1>RTXUI Scroll Behavior Demo</h1>
        <p class="desc">
          Compare the two scroll behaviors below using the mouse wheel, Arrow keys, or PageUp/PageDown.
        </p>
      </div>

      <div class="row">
        <div class="column">
          <h2>scroll-behavior: auto (Instant Content)</h2>
          <p class="subtitle">Content snaps instantly, scrollbar thumb glides smoothly.</p>
          <div id="scroll-auto" class="scrollbox">
            <div class="item">Item 1</div>
            <div class="item">Item 2</div>
            <div class="item">Item 3</div>
            <div class="item">Item 4</div>
            <div class="item">Item 5</div>
            <div class="item">Item 6</div>
            <div class="item">Item 7</div>
            <div class="item">Item 8</div>
            <div class="item">Item 9</div>
            <div class="item">Item 10</div>
            <div class="item">Item 11</div>
            <div class="item">Item 12</div>
          </div>
        </div>

        <div class="column">
          <h2>scroll-behavior: smooth (Smooth Content)</h2>
          <p class="subtitle">Both viewport content and scrollbar thumb glide smoothly.</p>
          <div id="scroll-smooth" class="scrollbox">
            <div class="item">Item 1</div>
            <div class="item">Item 2</div>
            <div class="item">Item 3</div>
            <div class="item">Item 4</div>
            <div class="item">Item 5</div>
            <div class="item">Item 6</div>
            <div class="item">Item 7</div>
            <div class="item">Item 8</div>
            <div class="item">Item 9</div>
            <div class="item">Item 10</div>
            <div class="item">Item 11</div>
            <div class="item">Item 12</div>
          </div>
        </div>
      </div>
    </div>

    <style>
      self {
        --border: rgb(48, 54, 61);
        --muted: rgb(139, 148, 158);

        display: block;
        width: 80;
        margin: 0 auto;
        background-color: rgb(13, 17, 23);
        color: rgb(230, 237, 243);
      }
      .container {
        display: block;
        padding: 2;
      }
      .header {
        display: block;
        margin-bottom: 2;
      }
      h1 {
        color: rgb(121, 192, 255);
        font-weight: bold;
        margin-bottom: 1;
      }
      .desc {
        color: var(--muted);
      }
      .row {
        display: flex;
        flex-direction: row;
        width: 100%;
      }
      .column {
        display: block;
        flex-grow: 1;
        width: 48%;
        margin-right: 2%;
      }
      h2 {
        color: rgb(224, 242, 254);
        font-weight: bold;
        margin-bottom: 1;
      }
      .subtitle {
        color: var(--muted);
        margin-bottom: 1;
        height: 2;
      }
      .scrollbox {
        display: block;
        height: 10;
        overflow-y: scroll;
        border: tall;
        border-color: var(--border);
        background-color: rgb(22, 27, 34);
        padding: 1;
      }
      #scroll-auto {
        scroll-behavior: auto;
      }
      #scroll-smooth {
        scroll-behavior: smooth;
      }
      .item {
        display: block;
        height: 2;
        margin-bottom: 1;
        background-color: var(--border);
        color: rgb(255, 255, 255);
        padding-left: 1;
      }
    </style>
  )html";
};

int main() {
  auto component = Ref<ScrollBehaviorDemo>::New();
  Screen screen(component);
  screen.Loop();
  return 0;
}

Anchor Navigation ​

RTXUI supports anchor navigation using <a> tags. When an <a> tag with a target hash (e.g. href="#sec-intro") is clicked, RTXUI automatically scrolls the element with the corresponding id (e.g. id="sec-intro") into view. Combined with position: sticky and scroll-behavior: smooth, this allows building robust sidebar navigation layouts.

Fullscreen Demo
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// Anchor navigation.
//
// An <a href="#id"> scrolls its target into view inside the nearest scroll
// container.
//
// Try it: click the links in the index to jump between sections.
#include <rtxui/rtxui.hpp>
#include <string>

using namespace rtxui;

class AnchorDemo : public Component<AnchorDemo> {
 public:
  std::string_view view = R"html(
    <div class="container">
      <div class="header">
        <h2>Anchor Navigation Demo</h2>
        <p class="description">
          Click the links in the sidebar to scroll the corresponding section into view.
        </p>
      </div>

      <div class="workspace">
        <!-- Sticky Sidebar Navigation -->
        <div class="sidebar">
          <div class="nav-title">SECTIONS</div>
          <a class="nav-link" href="#sec-intro">Introduction</a>
          <a class="nav-link" href="#sec-features">Features</a>
          <a class="nav-link" href="#sec-install">Installation</a>
          <a class="nav-link" href="#sec-usage">Usage</a>
          <a class="nav-link" href="#sec-components">Components</a>
          <a class="nav-link" href="#sec-docs">Documentation</a>
          <a class="nav-link" href="#sec-faq">FAQ</a>
          <a class="nav-link" href="#sec-contact">Contact</a>
        </div>

        <!-- Scrollable Content Pane -->
        <div class="scroll-window">
          <div id="sec-intro" class="section sec-odd">
            <div class="section-title">Introduction</div>
            <p>Welcome to RTXUI. This framework lets you build terminal user interfaces using familiar XML templates and CSS styles.</p>
            <p>Layout features include block, inline, flexbox, grid, fixed, absolute, and sticky positioning.</p>
          </div>

          <div id="sec-features" class="section sec-even">
            <div class="section-title">Features</div>
            <p>• Declarative XML markup parsing</p>
            <p>• Complete CSS layout and flexbox model</p>
            <p>• Rich borders, margins, padding, and z-index</p>
            <p>• Mouse support: hover, active, focus, and clicks</p>
          </div>

          <div id="sec-install" class="section sec-odd">
            <div class="section-title">Installation</div>
            <p>To use RTXUI in your CMake project, add the library using FetchContent:</p>
            <p>FetchContent_Declare(rtxui GIT_REPOSITORY ...)</p>
            <p>Then link it with target_link_libraries(your_target rtxui::rtxui).</p>
          </div>

          <div id="sec-usage" class="section sec-even">
            <div class="section-title">Usage</div>
            <p>Initialize a component class, define its view property with HTML/XML markup,</p>
            <p>implement InitReflection(), and start the main Screen loop.</p>
            <p>Bind C++ states to reactive template properties for dynamic UI updates.</p>
          </div>

          <div id="sec-components" class="section sec-odd">
            <div class="section-title">Components</div>
            <p>RTXUI supports custom reusable components. Standard built-in components</p>
            <p>include divs, spans, inputs, buttons, sliders, textareas, and checkboxes.</p>
            <p>Create nested hierarchies using standard XML slot definitions.</p>
          </div>

          <div id="sec-docs" class="section sec-even">
            <div class="section-title">Documentation</div>
            <p>Styles are resolved dynamically based on CSS selectors and active classes.</p>
            <p>Use the C++ API to bind state variables and handle interactive events.</p>
          </div>

          <div id="sec-faq" class="section sec-odd">
            <div class="section-title">FAQ</div>
            <p>Q: Does it support mouse inputs? Yes, hovering and clicking are fully supported.</p>
            <p>Q: Can I use CSS grid? Yes, grid-template-columns and grid-gap are available.</p>
            <p>Q: Does it have animations? Yes, CSS transitions are supported.</p>
          </div>

          <div id="sec-contact" class="section sec-even">
            <div class="section-title">Contact</div>
            <p>Created by Arthur Sonzogni.</p>
            <p>Licensed under the MIT License.</p>
          </div>
        </div>
      </div>
    </div>

    <style>
      self {
        --border: rgb(48, 54, 61);
        --muted: rgb(139, 148, 158);

        display: block;
        padding: 1 2;
        background-color: rgb(13, 17, 23); /* Deep dark slate background */
        color: rgb(230, 237, 243);
      }
      .container {
        display: block;
        max-width: 80;
        width: 100%;
        margin-left: auto;
        margin-right: auto;
      }
      .header {
        display: block;
        margin-bottom: 1;
      }
      h2 {
        color: rgb(88, 166, 255); /* Bright blue */
        margin-bottom: 0;
      }
      .description {
        color: var(--muted); /* Muted gray text */
      }
      .workspace {
        display: flex;
        flex-direction: row;
        align-items: flex-start;
        gap: 2;
        width: 100%;
      }
      .sidebar {
        position: sticky;
        top: 0;
        display: flex;
        flex-direction: column;
        width: 18;
        border: tall;
        border-color: var(--border);
        background-color: rgb(30, 41, 59, 0.4);
        padding: 1;
        flex-shrink: 0;
        z-index: 10;
      }
      .nav-title {
        color: var(--muted);
        font-weight: bold;
        margin-bottom: 1;
      }
      .nav-link {
        display: block;
        color: rgb(56, 189, 248); /* Cyan link */
        margin-bottom: 1;
        padding-left: 1;
        cursor: pointer;
      }
      .nav-link:hover {
        background-color: rgb(30, 41, 59, 0.8);
        color: white;
      }
      .scroll-window {
        display: block;
        height: 18;
        border: tall;
        border-color: var(--border);
        overflow-y: scroll;
        scroll-speed: 1;
        scroll-behavior: smooth;
        flex-grow: 1;
      }
      .section {
        display: block;
        margin: 2;
        padding: 2;
      }
      .section-title {
        font-weight: bold;
        margin-bottom: 1;
      }
      .sec-odd {
        background-color: rgba(30, 58, 138, 0.15); /* Deep slate royal blue */
      }
      .sec-even {
        background-color: rgba(14, 165, 233, 0.15); /* Modern cyan sky blue */
      }
      .sec-odd .section-title {
        color: rgb(121, 192, 255); /* Bright royal blue text */
      }
      .sec-even .section-title {
        color: rgb(56, 189, 248); /* Bright sky blue text */
      }
    </style>
  )html";
};

int main() {
  auto app = Ref<AnchorDemo>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}

An application using all of it ​

A file browser: the listing scrolls, its rows are focusable, and moving the focus walks them while keeping the focused row on screen.

Fullscreen Demo
cpp
// Copyright 2026 Arthur Sonzogni. All rights reserved.
// Use of this source code is governed by the MIT license that can be found in
// the LICENSE file.
//
// A complete application driven by the keyboard.
//
// Where app_dashboard.cpp is about composing layout and state, this one is
// about the two things a terminal UI lives or dies by: scrolling a list that
// is longer than the viewport, and moving a selection through it without a
// mouse. Rows carry `tabindex`, so RTXUI's built-in focus navigation walks
// them and scrolls the focused one into view; the preview pane re-renders
// from computed values as the selection moves.
//
// Try it: move with the arrow keys (or j/k via the buttons), Enter to open a
// directory, Backspace to go up. The list scrolls to follow the selection.
#include <rtxui/rtxui.hpp>
#include <string>
#include <vector>

using namespace rtxui;

namespace {

struct FileEntry {
  std::string name;
  std::string kind;  // "dir" | "file"
  std::string size;
  std::string modified;
  bool selected = false;

  bool operator==(const FileEntry& other) const = default;
};

// A small fixed tree, so the example stays self-contained and deterministic
// rather than depending on whatever happens to be on disk.
struct Directory {
  std::string path;
  std::vector<FileEntry> entries;
};

const std::vector<Directory>& Tree() {
  static const std::vector<Directory> tree = {
      {"/",
       {
           {"src", "dir", "-", "2026-08-01"},
           {"docs", "dir", "-", "2026-07-28"},
           {"example", "dir", "-", "2026-08-09"},
           {"CMakeLists.txt", "file", "14 kB", "2026-08-09"},
           {"README.md", "file", "6.2 kB", "2026-08-09"},
           {"LICENSE", "file", "1.1 kB", "2026-01-12"},
       }},
      {"/src",
       {
           {"rtxui", "dir", "-", "2026-08-09"},
           {"dummy.cpp", "file", "42 B", "2026-03-04"},
       }},
      {"/docs",
       {
           {"guide", "dir", "-", "2026-08-09"},
           {"index.md", "file", "2.4 kB", "2026-08-09"},
           {"css_reference.md", "file", "31 kB", "2026-08-09"},
           {"html_reference.md", "file", "18 kB", "2026-08-09"},
           {"reactivity.md", "file", "7.8 kB", "2026-08-09"},
       }},
      {"/example",
       {
           {"app_dashboard.cpp", "file", "9.1 kB", "2026-08-09"},
           {"app_filebrowser.cpp", "file", "8.4 kB", "2026-08-09"},
           {"counter.cpp", "file", "2.8 kB", "2026-08-09"},
           {"helloworld.cpp", "file", "1.4 kB", "2026-08-09"},
           {"playground.cpp", "file", "7.0 kB", "2026-08-09"},
           {"transitions.cpp", "file", "2.6 kB", "2026-08-09"},
           {"grid.cpp", "file", "2.2 kB", "2026-08-09"},
           {"lists.cpp", "file", "3.9 kB", "2026-08-09"},
           {"table.cpp", "file", "6.1 kB", "2026-08-09"},
           {"tooltip.cpp", "file", "4.3 kB", "2026-08-09"},
           {"sticky.cpp", "file", "4.6 kB", "2026-08-09"},
           {"markdown.cpp", "file", "6.8 kB", "2026-08-09"},
       }},
  };
  return tree;
}

}  // namespace

class FileBrowser : public Component<FileBrowser> {
 public:
  std::string path = "/";
  int cursor = 0;

  std::vector<FileEntry> entries = Tree().front().entries;

  // --- Computed values -----------------------------------------------------

  std::string current_path() const { return path; }
  std::string entry_count() const {
    return std::to_string(entries.size()) + " items";
  }

  std::string selected_name() const {
    const FileEntry* entry = Selected();
    return entry ? entry->name : "-";
  }
  std::string selected_kind() const {
    const FileEntry* entry = Selected();
    return entry ? (entry->kind == "dir" ? "Directory" : "File") : "-";
  }
  std::string selected_size() const {
    const FileEntry* entry = Selected();
    return entry ? entry->size : "-";
  }
  std::string selected_modified() const {
    const FileEntry* entry = Selected();
    return entry ? entry->modified : "-";
  }
  bool selected_is_dir() const {
    const FileEntry* entry = Selected();
    return entry && entry->kind == "dir";
  }
  bool can_go_up() const { return path != "/"; }

  // --- Handlers ------------------------------------------------------------

  void MoveDown() { SetCursor(cursor + 1); }
  void MoveUp() { SetCursor(cursor - 1); }

  void Open() {
    const FileEntry* entry = Selected();
    if (!entry || entry->kind != "dir") {
      return;
    }
    std::string next =
        path == "/" ? "/" + entry->name : path + "/" + entry->name;
    Navigate(next);
  }

  void GoUp() {
    if (path == "/") {
      return;
    }
    size_t slash = path.find_last_of('/');
    Navigate(slash == 0 ? "/" : path.substr(0, slash));
  }

  std::string_view view = R"html(
      <div class="app">
        <div class="header">
          <span class="brand">FILES</span>
          <span class="path">{current_path}</span>
          <span class="spacer"></span>
          <span class="count">{entry_count}</span>
        </div>

        <div class="body">
          <div class="listing" tabindex="0">
            <for each="{entries}" as="entry">
              <div class="row {entry.row_class}" tabindex="0" onclick="Select({$index})">
                <span class="glyph {entry.kind}">{entry.glyph}</span>
                <span class="name">{entry.name}</span>
                <span class="size">{entry.size}</span>
                <span class="modified">{entry.modified}</span>
              </div>
            </for>
          </div>

          <div class="preview">
            <span class="preview-name">{selected_name}</span>
            <span class="preview-kind">{selected_kind}</span>

            <div class="kv"><span class="k">Size</span><span class="v">{selected_size}</span></div>
            <div class="kv"><span class="k">Modified</span><span class="v">{selected_modified}</span></div>

            <if condition="{selected_is_dir}">
              <button class="primary" onclick="Open">Open directory</button>
            </if>
            <if condition="{can_go_up}">
              <button onclick="GoUp">Go up</button>
            </if>
          </div>
        </div>

        <div class="footer">
          <button class="key" onclick="MoveUp">k / up</button>
          <button class="key" onclick="MoveDown">j / down</button>
          <span class="spacer"></span>
          <span class="hint">the list scrolls to keep the selection visible</span>
        </div>
      </div>

      <style>
        self {
          --bg: rgb(13, 17, 23);
          --surface: rgb(22, 27, 34);
          --raised: rgb(31, 38, 47);
          --border: rgb(48, 54, 61);
          --text: rgb(230, 237, 243);
          --muted: rgb(139, 148, 158);
          --accent: rgb(88, 166, 255);

          display: block;
          width: 100%;
          height: 100%;
          background-color: var(--bg);
          color: var(--text);
        }
        .app {
          display: flex;
          flex-direction: column;
          height: 100%;
        }

        .header {
          display: flex;
          align-items: center;
          gap: 2;
          background-color: var(--surface);
          border-bottom: solid;
          border-color: var(--border);
          padding: 0 2;
        }
        .brand {
          color: var(--accent);
          font-weight: bold;
        }
        .path {
          color: var(--text);
        }
        .count {
          color: var(--muted);
        }
        .spacer {
          flex-grow: 1;
        }

        .body {
          display: flex;
          flex-grow: 1;
          gap: 1;
          padding: 1 2;
        }

        /* The list is deliberately shorter than its content: scroll-behavior
           smooth plus the browser-style scroll-into-view on focus is what
           keeps the selected row visible as the cursor moves. */
        .listing {
          display: flex;
          flex-direction: column;
          flex-grow: 1;
          border: tall;
          border-color: var(--border);
          background-color: var(--surface);
          padding: 0 1;
          overflow-y: scroll;
          scroll-behavior: smooth;
        }
        .row {
          display: flex;
          gap: 1;
          align-items: center;
          padding: 0 1;
          transition: background-color 0.15s ease;
        }
        .row:hover {
          background-color: var(--raised);
        }
        .row.selected, .row:focus {
          background-color: var(--raised);
          border-left: tall;
          border-color: var(--accent);
        }
        .glyph.dir {
          color: rgb(210, 153, 34);
        }
        .glyph.file {
          color: var(--muted);
        }
        .name {
          flex-grow: 1;
        }
        .size {
          width: 10;
          color: var(--muted);
          text-align: right;
        }
        .modified {
          width: 12;
          color: var(--muted);
          text-align: right;
        }

        .preview {
          display: flex;
          flex-direction: column;
          border: tall;
          border-color: var(--border);
          background-color: var(--surface);
          padding: 1 2;
          width: 30;
        }
        .preview-name {
          color: var(--accent);
          font-weight: bold;
        }
        .preview-kind {
          color: var(--muted);
          margin-bottom: 1;
        }
        .kv {
          display: flex;
          justify-content: space-between;
          width: 100%;
        }
        .k { color: var(--muted); }
        .v { font-weight: bold; }

        button {
          border: tall;
          border-color: var(--border);
          background-color: var(--bg);
          color: var(--text);
          padding: 0 1;
          margin-top: 1;
          text-align: center;
          transition: background-color 0.15s ease, border-color 0.15s ease,
                      color 0.15s ease;
        }
        button:hover, button:focus {
          border-color: var(--accent);
          color: var(--accent);
        }
        button:active {
          background-color: var(--accent);
          color: var(--bg);
        }
        button.primary {
          border-color: var(--accent);
          color: var(--accent);
        }

        .footer {
          display: flex;
          align-items: center;
          gap: 2;
          border-top: solid;
          border-color: var(--border);
          background-color: var(--surface);
          color: var(--muted);
          padding: 0 2;
        }
        .footer button.key {
          margin-top: 0;
        }
        .hint {
          color: var(--border);
        }
      </style>
    )html";

  FileBrowser() {
    BindCollection("entries", &entries, [](const FileEntry& entry) {
      return std::make_shared<ManualStructVisitor>(
          std::map<std::string, std::string, std::less<>>{
              {"name", entry.name},
              {"kind", entry.kind},
              {"size", entry.size},
              {"modified", entry.modified},
              {"glyph", entry.kind == "dir" ? "/" : "."},
              {"row_class", entry.selected ? "selected" : ""},
          });
    });

    Bind(path);
    Bind(cursor);
    Bind(current_path);
    Bind(entry_count);
    Bind(selected_name);
    Bind(selected_kind);
    Bind(selected_size);
    Bind(selected_modified);
    Bind(selected_is_dir);
    Bind(can_go_up);
    Bind(MoveDown);
    Bind(MoveUp);
    Bind(Open);
    Bind(GoUp);

    Import("Select",
           [this](std::string index_str) { SetCursor(std::stoi(index_str)); });

    SetCursor(0);
  }

  // Arrow keys drive the cursor; Enter opens; Backspace goes up. Returning
  // true marks the event handled so it doesn't also reach the focus system.
  bool OnEvent(Event event) override {
    if (!event.is<Event::Keyboard>()) {
      return Component<FileBrowser>::OnEvent(event);
    }
    auto key = event.get<Event::Keyboard>();
    if (key.motion != Event::Keyboard::Motion::Pressed &&
        key.motion != Event::Keyboard::Motion::Repeat) {
      return Component<FileBrowser>::OnEvent(event);
    }

    switch (key.special) {
      case Event::Keyboard::Special::ArrowDown:
        MoveDown();
        return false;  // let focus move too, so the list scrolls to follow
      case Event::Keyboard::Special::ArrowUp:
        MoveUp();
        return false;
      case Event::Keyboard::Special::Return:
        Open();
        return true;
      case Event::Keyboard::Special::Backspace:
        GoUp();
        return true;
      default:
        break;
    }
    if (key.codepoint == 'j') {
      MoveDown();
      return true;
    }
    if (key.codepoint == 'k') {
      MoveUp();
      return true;
    }
    return Component<FileBrowser>::OnEvent(event);
  }

 private:
  const FileEntry* Selected() const {
    if (cursor < 0 || cursor >= static_cast<int>(entries.size())) {
      return nullptr;
    }
    return &entries[cursor];
  }

  void SetCursor(int index) {
    if (index < 0 || index >= static_cast<int>(entries.size())) {
      return;
    }
    for (FileEntry& entry : entries) {
      entry.selected = false;
    }
    entries[index].selected = true;
    cursor = index;
  }

  void Navigate(const std::string& next) {
    for (const Directory& directory : Tree()) {
      if (directory.path == next) {
        path = next;
        entries = directory.entries;
        SetCursor(0);
        return;
      }
    }
  }
};

int main() {
  auto app = Ref<FileBrowser>::New();
  Screen screen(app);
  screen.Loop();
  return 0;
}