git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commit1a4a27a4aa917e316df40a5824dfc4ae66cd6a24
parent92e9d5597e
authorLucas Galante <[email protected]>
date2026-07-12 00:19
refactor(widget)!: DELETE Layer + Page; Paginator's empty page stack folds away (Phase 6au)

Per the 5r survey (recorded in the paginator module docs), no app ever
put content in Paginator's pages: every consumer manages its own page
content keyed on selected_page(), so the Vec<Page> was a stack of empty
containers being arranged, registered, visibility-toggled, and
event-proxied for nothing. Its one observable output — the page-area
background quad (page_color x page_opacity over the content rect) — now
paints directly in Paint::paint.

With the stack gone:
- Page had no constructor left; Layer's only constructor was Page's
  base. Both files deleted (their 2 in-file tests with them).
- Element::is_page deleted (only override was Page; only caller was
  Layer's page-parent quad suppression).
- ScrollBar's downcast write-back into a parent Page deleted (empty
  pages never overflowed, so it was unreachable through the paginator;
  no other parent was ever a Page).
- PageSelector shrinks 11 -> 3 methods (selected_page /
  set_selected_page / sidebar_w): the pages surface (set_pages,
  set_pages_with_items, add_widget_to_page, clear_page_widgets) and the
  never-called is_page_hidden / set_page_hidden / set_sidebar_mode /
  set_sidebar_label all had zero callers workspace-wide. MenuBar's
  impls of the deleted methods (and its dead page_hidden field) go too.

169 tests pass. A/B on the live compositor: cce-email, cce-layout-
interface, cce-files all AE=0; live click check on cce-layout-
interface's tab strip switches Element -> Canvas correctly.

Co-Authored-By: Claude Fable 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018u7qTwzX95dd5ysAkaSCLk

 src/widget/container/layer.rs      | 147 ------------
 src/widget/container/menu.rs       |  41 +---
 src/widget/container/mod.rs        |   4 -
 src/widget/container/page.rs       | 445 -------------------------------------
 src/widget/container/paginator.rs  | 217 ++++--------------
 src/widget/container/scroll_bar.rs |  10 -
 src/widget/mod.rs                  |  11 +-
 7 files changed, 42 insertions(+), 833 deletions(-)

diff --git a/src/widget/container/layer.rs b/src/widget/container/layer.rs
deleted file mode 100644
index 3e8bc42..0000000
--- a/src/widget/container/layer.rs
+++ /dev/null
@@ -1,147 +0,0 @@
-use crate::widget::*;
-use crate::context::UiContext;
-
-
-#[derive(Debug, Clone)]
-pub struct Layer {
-    pub base: Widget,
-    pub children: Vec<*mut (dyn Element + 'static)>,
-    pub parent: Option<*mut (dyn Element + 'static)>,
-    pub visible: bool,
-    pub padding: Option<f32>,
-}
-
-impl Layer {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: Widget::new_rect(x, y, w, h),
-            children: Vec::new(),
-            parent: None,
-            visible: true,
-            padding: None,
-        }
-    }
-
-    pub fn with_padding(mut self, padding: f32) -> Self {
-        self.padding = Some(padding);
-        self
-    }
-}
-
-impl Element for Layer {
-    fn base(&self) -> Option<&Widget> { Some(&self.base) }
-    fn blocks_backplate_drag(&self) -> bool { false }
-
-    fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base) }
-    fn as_any(&self) -> &dyn std::any::Any { self }
-    fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
-    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
-        self as *const Self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
-        self as *mut Self as *mut (dyn Element + 'static)
-    }
-
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        (self.base.x, self.base.y, self.base.w, self.base.h)
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.base.x = x;
-        self.base.y = y;
-        self.base.w = w;
-        self.base.h = h;
-    }
-
-    fn color(&self) -> [f32; 4] {
-        let mut c = crate::colors::layer_color();
-        c[3] *= crate::layout::layer_opacity();
-        c
-    }
-
-
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
-    }
-
-    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) {
-        let id = self.base.id();
-        let self_ptr = self as *mut Layer as *mut (dyn Element + 'static);
-        self.children.push(child);
-        unsafe {
-            let c_id = (*child).base().map(|b| b.id()).unwrap();
-            ctx.register_widget(c_id, child);
-            ctx.link_ids(id, c_id);
-            (*child).set_parent(Some(self_ptr), ctx);
-        }
-    }
-
-    fn clear_children(&mut self, ctx: &mut UiContext) {
-        self.children.clear();
-        let id = self.base.id();
-        ctx.clear_children_ids(id);
-    }
-
-    fn children(&self, _ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        self.children.clone()
-    }
-
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, _ctx: &mut UiContext) {
-        self.parent = parent;
-    }
-
-    fn parent(&self, _ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
-        self.parent
-    }
-
-    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        let mut has_page_parent = false;
-        if let Some(parent_ptr) = self.parent {
-            unsafe {
-                if (*parent_ptr).is_page() {
-                    has_page_parent = true;
-                }
-            }
-        }
-        if !has_page_parent {
-            let c = self.color();
-            if c[3] > 0.0 {
-                let (x, y, w, h) = self.rect();
-                quads.push((x, y, w, h, c));
-            }
-        }
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            let (wx, wy, ww, wh) = widget.rect();
-            let has_rounded = widget.rounded_corners() != (false, false, false, false);
-            for (qx, qy, qw, qh, qc) in widget.all_quads(ctx) {
-                if has_rounded && (qx - wx).abs() < 0.1 && (qy - wy).abs() < 0.1 && (qw - ww).abs() < 0.1 && (qh - wh).abs() < 0.1 {
-                    continue;
-                }
-                quads.push((qx, qy, qw, qh, qc));
-            }
-        }
-        quads
-    }
-
-    fn hit_test(&self, px: f32, py: f32, ctx: &UiContext) -> bool {
-        if ctx.is_coordinate_covered(self as *const Self as *const () as usize, px, py) {
-            return false;
-        }
-        for &child_ptr in &self.children {
-            let widget = unsafe { &*child_ptr };
-            if widget.hit_test(px, py, ctx) {
-                return true;
-            }
-        }
-        false
-    }
-}
diff --git a/src/widget/container/menu.rs b/src/widget/container/menu.rs
index c7c8a4c..d0d9043 100644
--- a/src/widget/container/menu.rs
+++ b/src/widget/container/menu.rs
@@ -24,7 +24,7 @@ use crate::scene::paint::PaintCtx;
 use crate::widget::display::TextLabel;
 use crate::widget::{
     Adapted, ButtonStrip, Element, ElementState, Event, EventCtx, Input, Key, Layout,
-    MenuController, MouseButton, NamedKey, PageSelector, Paint, UiContext, DROPDOWN_ITEM_H,
+    MenuController, MouseButton, NamedKey, PageSelector, Paint, DROPDOWN_ITEM_H,
 };
 
 pub struct MenuBar {
@@ -53,7 +53,6 @@ pub struct MenuBar {
     pub context_title_hovered: bool,
     pub right_align_title: bool,
     pub parent: Option<*mut (dyn Element + 'static)>,
-    pub page_hidden: bool,
     pub layout_dirty: bool,
     pub on_context_change_cb: Option<Box<dyn Fn(usize) + Send + Sync>>,
     pub on_menu_click_cb: Option<Box<dyn Fn(usize, usize) + Send + Sync>>,
@@ -90,7 +89,6 @@ impl MenuBar {
             context_title_hovered: false,
             right_align_title: false,
             parent: None,
-            page_hidden: false,
             layout_dirty: true,
             on_context_change_cb: None,
             on_menu_click_cb: None,
@@ -1078,30 +1076,6 @@ impl PageSelector for MenuBar {
         self.menus.set_selected(Some(page));
     }
 
-    fn is_page_hidden(&self) -> bool {
-        self.page_hidden
-    }
-
-    fn set_page_hidden(&mut self, hidden: bool) {
-        self.page_hidden = hidden;
-    }
-
-    fn set_pages(&mut self, pages: Vec<String>) {
-        self.menu_items = pages.clone();
-        self.vertical_items = pages.clone();
-        self.menus.buttons = pages;
-        self.menus.generate_rotated_labels();
-    }
-
-    fn set_pages_with_items(&mut self, pages: Vec<String>, items: Vec<Vec<String>>) {
-        self.menu_items = pages.clone();
-        self.vertical_items = pages.clone();
-        self.menu_dropdowns = items;
-        self.menu_dropdown_checked = vec![vec![None; 0]; self.menu_dropdowns.len()];
-        self.menus.buttons = pages;
-        self.menus.generate_rotated_labels();
-    }
-
     fn sidebar_w(&self) -> f32 {
         let padding_x = crate::layout::paginator_tab_padding_x();
         let margin_x = 5.0;
@@ -1116,19 +1090,6 @@ impl PageSelector for MenuBar {
             max_req_w.max(24.0) + 2.0 * margin_x
         }
     }
-
-    fn set_sidebar_mode(&mut self, _enabled: bool) {}
-
-    fn set_sidebar_label(&mut self, label: Option<String>) {
-        self.label = label.clone();
-        if self.vertical {
-            self.title = label.unwrap_or_default();
-            self.label = None;
-        }
-    }
-
-    fn add_widget_to_page(&mut self, _page_idx: usize, _widget: *mut (dyn Element + 'static), _ctx: &mut UiContext) {}
-    fn clear_page_widgets(&mut self, _page_idx: usize, _ctx: &mut UiContext) {}
 }
 
 unsafe impl Send for MenuBar {}
diff --git a/src/widget/container/mod.rs b/src/widget/container/mod.rs
index 0ea25cc..e1014c4 100644
--- a/src/widget/container/mod.rs
+++ b/src/widget/container/mod.rs
@@ -8,8 +8,6 @@ pub mod breadcrumb;
 pub mod spreadsheet;
 pub mod scroll_box;
 pub mod switcher;
-pub mod layer;
-pub mod page;
 pub mod paginator;
 pub mod scroll_bar;
 pub mod treelist;
@@ -26,8 +24,6 @@ pub use breadcrumb::Breadcrumb;
 pub use spreadsheet::Spreadsheet;
 pub use scroll_box::ScrollBox;
 pub use switcher::Switcher;
-pub use layer::Layer;
-pub use page::Page;
 pub use paginator::Paginator;
 pub use scroll_bar::ScrollBar;
 pub use treelist::{TreeList, TreeElement};
diff --git a/src/widget/container/page.rs b/src/widget/container/page.rs
deleted file mode 100644
index 02e01c5..0000000
--- a/src/widget/container/page.rs
+++ /dev/null
@@ -1,445 +0,0 @@
-use crate::widget::*;
-use crate::context::UiContext;
-
-use super::layer::Layer;
-
-use super::container_layout::ContainerLayout;
-
-pub struct Page {
-    pub base: Layer,
-    pub visible: bool,
-    pub owned_children: Vec<Box<dyn Element>>,
-    pub layout: Box<dyn ContainerLayout>,
-    pub scroll_y: f32,
-    pub content_h: f32,
-    pub scroll_bar: ScrollBar,
-}
-
-impl std::fmt::Debug for Page {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Page")
-            .field("base", &self.base)
-            .field("visible", &self.visible)
-            .field("scroll_y", &self.scroll_y)
-            .field("content_h", &self.content_h)
-            .finish()
-    }
-}
-
-impl Page {
-    pub fn new(x: f32, y: f32, w: f32, h: f32) -> Self {
-        Self {
-            base: Layer::new(x, y, w, h),
-            visible: true,
-            owned_children: Vec::new(),
-            layout: Box::new(super::container_layout::VerticalLayout::default()),
-            scroll_y: 0.0,
-            content_h: 0.0,
-            scroll_bar: ScrollBar::new(),
-        }
-    }
-
-    pub fn with_layout<L: ContainerLayout + 'static>(mut self, layout: L) -> Self {
-        self.layout = Box::new(layout);
-        self
-    }
-
-    pub fn with_label(mut self, label: &str) -> Self {
-        self.base.base.label = Some(label.to_string());
-        self
-    }
-
-    pub fn add_child_owned(&mut self, child: Box<dyn Element>, ctx: &mut UiContext) {
-        let ptr = &*child as *const (dyn Element + 'static) as *mut (dyn Element + 'static);
-        self.owned_children.push(child);
-        self.add_child(ptr, ctx);
-    }
-}
-
-fn clip_quad(
-    quad: (f32, f32, f32, f32, [f32; 4]),
-    bounds: (f32, f32, f32, f32),
-) -> Option<(f32, f32, f32, f32, [f32; 4])> {
-    let (qx, qy, qw, qh, qc) = quad;
-    let (bx, by, bw, bh) = bounds;
-
-    let x1 = qx.max(bx);
-    let y1 = qy.max(by);
-    let x2 = (qx + qw).min(bx + bw);
-    let y2 = (qy + qh).min(by + bh);
-
-    let w = x2 - x1;
-    let h = y2 - y1;
-
-    if w > 0.0 && h > 0.0 {
-        Some((x1, y1, w, h, qc))
-    } else {
-        None
-    }
-}
-
-impl Element for Page {
-    fn base(&self) -> Option<&Widget> { Some(&self.base.base) }
-    fn is_page(&self) -> bool { true }
-    fn is_scrollable(&self) -> bool { true }
-    fn blocks_backplate_drag(&self) -> bool { false }
-
-    fn check_out_of_bounds(&self, event: &Event, _ctx: &UiContext) -> bool {
-        if self.scroll_bar.dragging {
-            return false;
-        }
-        if let Event::PointerMove { x, y, .. }
-        | Event::MouseButton { x, y, .. }
-        | Event::MouseWheel { x, y, .. } = event
-        {
-            let (rx, ry, rw, rh) = self.rect();
-            let screen_y = *y - self.scroll_y;
-            if *x < rx || *x > rx + rw || screen_y < ry || screen_y > ry + rh {
-                return true;
-            }
-        }
-        false
-    }
-
-    fn transform_event_for_child(&self, child: *mut (dyn Element + 'static), mut event: Event, _ctx: &UiContext) -> Event {
-        let sb_ptr = &self.scroll_bar as *const ScrollBar as *mut ScrollBar as *mut (dyn Element + 'static);
-        if std::ptr::addr_eq(child, sb_ptr) {
-            match &mut event {
-                Event::PointerMove { y, local_y, .. }
-                | Event::MouseButton { y, local_y, .. }
-                | Event::MouseWheel { y, local_y, .. } => {
-                    *y -= self.scroll_y;
-                    *local_y -= self.scroll_y;
-                }
-                _ => {}
-            }
-        }
-        event
-    }
-
-    fn base_mut(&mut self) -> Option<&mut Widget> { Some(&mut self.base.base) }
-    fn as_any(&self) -> &dyn std::any::Any { self }
-    fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self }
-    fn as_ptr(&self) -> *mut (dyn Element + 'static) {
-        self as *const Self as *mut Self as *mut (dyn Element + 'static)
-    }
-    fn as_ptr_mut(&mut self) -> *mut (dyn Element + 'static) {
-        self as *mut Self as *mut (dyn Element + 'static)
-    }
-
-    fn rect(&self) -> (f32, f32, f32, f32) {
-        self.base.rect()
-    }
-
-    fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
-        self.base.set_rect(x, y, w, h);
-    }
-
-    fn layout(&mut self, origin: Point, constraints: LayoutConstraints, ctx: &mut UiContext) {
-        let size = self.measure(constraints, ctx);
-        self.set_rect(origin.x, origin.y, size.width, size.height);
-        
-        if self.visible {
-            let layout_content_h = self.layout.layout(origin.x, origin.y, size.width, size.height, &self.base.children, ctx);
-            self.content_h = self.content_h.max(layout_content_h);
-
-            let max_scroll = (self.content_h - size.height).max(0.0);
-            self.scroll_y = self.scroll_y.clamp(0.0, max_scroll);
-
-            let sb_w = 6.0;
-            let sb_padding = 2.0;
-            let sb_x = origin.x + size.width - sb_w - sb_padding;
-            self.scroll_bar.set_rect(sb_x, origin.y + 4.0, sb_w, size.height - 8.0);
-            self.scroll_bar.update(self.scroll_y, self.content_h, size.height);
-            
-            let self_ptr = self as *mut Page as *mut (dyn Element + 'static);
-            self.scroll_bar.parent = Some(self_ptr);
-        }
-    }
-
-    fn color(&self) -> [f32; 4] {
-        let mut c = crate::colors::page_color();
-        c[3] *= crate::layout::page_opacity();
-        c
-    }
-
-    fn visible(&self) -> bool {
-        self.visible
-    }
-
-    fn set_visible(&mut self, visible: bool) {
-        self.visible = visible;
-    }
-
-    fn add_child(&mut self, child: *mut (dyn Element + 'static), ctx: &mut UiContext) {
-        let id = self.base.base.id();
-        let self_ptr = self as *mut Page as *mut (dyn Element + 'static);
-        self.base.children.push(child);
-        unsafe {
-            let c_id = (*child).base().map(|b| b.id()).unwrap();
-            ctx.register_widget(c_id, child);
-            ctx.link_ids(id, c_id);
-            (*child).set_parent(Some(self_ptr), ctx);
-        }
-    }
-
-    fn clear_children(&mut self, ctx: &mut UiContext) {
-        self.base.clear_children(ctx);
-        self.owned_children.clear();
-    }
-
-    fn children(&self, ctx: &UiContext) -> Vec<*mut (dyn Element + 'static)> {
-        let mut list = self.base.children(ctx);
-        let (_, _, _, h) = self.rect();
-        if self.content_h > h {
-            let sb_ptr = &self.scroll_bar as *const ScrollBar as *mut ScrollBar as *mut (dyn Element + 'static);
-            list.push(sb_ptr);
-        }
-        list
-    }
-
-    fn set_parent(&mut self, parent: Option<*mut (dyn Element + 'static)>, ctx: &mut UiContext) {
-        self.base.set_parent(parent, ctx);
-    }
-
-    fn parent(&self, ctx: &UiContext) -> Option<*mut (dyn Element + 'static)> {
-        self.base.parent(ctx)
-    }
-
-    fn all_quads(&self, ctx: &UiContext) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
-        if !self.visible {
-            return Vec::new();
-        }
-        let mut quads = Vec::new();
-        let c = self.color();
-        let bounds = self.rect();
-        if c[3] > 0.0 {
-            quads.push((bounds.0, bounds.1, bounds.2, bounds.3, c));
-        }
-        
-        for q in self.base.all_quads(ctx) {
-            if let Some(clipped) = clip_quad(q, bounds) {
-                quads.push(clipped);
-            }
-        }
-
-        if self.content_h > bounds.3 {
-            quads.extend(self.scroll_bar.all_quads(ctx));
-        }
-
-        quads
-    }
-
-    fn hit_test(&self, px: f32, py: f32, _ctx: &UiContext) -> bool {
-        if !self.visible {
-            return false;
-        }
-        let (rx, ry, rw, rh) = self.rect();
-        let screen_y = py - self.scroll_y;
-        screen_y >= ry && screen_y <= ry + rh && px >= rx && px <= rx + rw
-    }
-
-    fn mouse_wheel(&mut self, delta: &MouseScrollDelta, px: f32, py: f32, ctx: &mut UiContext) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if self.hit_test(px, py, ctx) {
-            let scroll_speed = 24.0;
-            let dy = match delta {
-                MouseScrollDelta::LineDelta(_, y) => -y * scroll_speed,
-                MouseScrollDelta::PixelDelta(pos) => -pos.y as f32,
-            };
-            let old_scroll = self.scroll_y;
-            let (x, y, w, h) = self.rect();
-            let max_scroll = (self.content_h - h).max(0.0);
-            self.scroll_y = (self.scroll_y + dy).clamp(0.0, max_scroll);
-            self.scroll_bar.scroll_y = self.scroll_y;
-            if (self.scroll_y - old_scroll).abs() > 0.01 {
-                self.set_rect(x, y, w, h);
-                self.mark_dirty(ctx);
-                return true;
-            }
-        }
-        false
-    }
-
-    fn keyboard_input(&mut self, event: &KeyEvent, ctx: &mut UiContext) -> bool {
-        if !self.visible {
-            return false;
-        }
-        if event.state != ElementState::Pressed {
-            return false;
-        }
-        let (x, y, w, h) = self.rect();
-        let max_scroll = (self.content_h - h).max(0.0);
-        if max_scroll <= 0.0 {
-            return false;
-        }
-
-        let old_scroll = self.scroll_y;
-        match &event.logical_key {
-            Key::Named(NamedKey::ArrowDown) => {
-                self.scroll_y = (self.scroll_y + 24.0).clamp(0.0, max_scroll);
-            }
-            Key::Named(NamedKey::ArrowUp) => {
-                self.scroll_y = (self.scroll_y - 24.0).clamp(0.0, max_scroll);
-            }
-            Key::Named(NamedKey::PageDown) => {
-                self.scroll_y = (self.scroll_y + h).clamp(0.0, max_scroll);
-            }
-            Key::Named(NamedKey::PageUp) => {
-                self.scroll_y = (self.scroll_y - h).clamp(0.0, max_scroll);
-            }
-            Key::Named(NamedKey::Home) => {
-                self.scroll_y = 0.0;
-            }
-            Key::Named(NamedKey::End) => {
-                self.scroll_y = max_scroll;
-            }
-            _ => return false,
-        }
-
-        self.scroll_bar.scroll_y = self.scroll_y;
-        if (self.scroll_y - old_scroll).abs() > 0.01 {
-            self.set_rect(x, y, w, h);
-            self.mark_dirty(ctx);
-            true
-        } else {
-            false
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    #[test]
-    fn test_page_bounds_scrolled() {
-        let ctx = UiContext::new();
-        let mut page = Page::new(0.0, 0.0, 800.0, 600.0);
-        
-        // 1. Unscrolled check
-        assert_eq!(page.scroll_y, 0.0);
-        
-        // Inside page boundaries (unscrolled)
-        let event_in = Event::MouseButton {
-            button: MouseButton::Left,
-            state: ElementState::Pressed,
-            x: 100.0,
-            y: 200.0,
-            local_x: 100.0,
-            local_y: 200.0,
-        };
-        assert!(!page.check_out_of_bounds(&event_in, &ctx));
-        assert!(page.hit_test(100.0, 200.0, &ctx));
-
-        // Outside page boundaries (unscrolled)
-        let event_out = Event::MouseButton {
-            button: MouseButton::Left,
-            state: ElementState::Pressed,
-            x: 100.0,
-            y: 700.0,
-            local_x: 100.0,
-            local_y: 700.0,
-        };
-        assert!(page.check_out_of_bounds(&event_out, &ctx));
-        assert!(!page.hit_test(100.0, 700.0, &ctx));
-
-        // 2. Scrolled check (scrolled down by 300px)
-        page.scroll_y = 300.0;
-
-        // Pointer virtual y = 500.0 (which translates to screen y = 200.0, inside page height of 600)
-        let event_scrolled_in = Event::MouseButton {
-            button: MouseButton::Left,
-            state: ElementState::Pressed,
-            x: 100.0,
-            y: 500.0,
-            local_x: 100.0,
-            local_y: 500.0,
-        };
-        assert!(!page.check_out_of_bounds(&event_scrolled_in, &ctx));
-        assert!(page.hit_test(100.0, 500.0, &ctx));
-
-        // Pointer virtual y = 1000.0 (which translates to screen y = 700.0, outside page height of 600)
-        let event_scrolled_out = Event::MouseButton {
-            button: MouseButton::Left,
-            state: ElementState::Pressed,
-            x: 100.0,
-            y: 1000.0,
-            local_x: 100.0,
-            local_y: 1000.0,
-        };
-        assert!(page.check_out_of_bounds(&event_scrolled_out, &ctx));
-        assert!(!page.hit_test(100.0, 1000.0, &ctx));
-    }
-
-    #[test]
-    fn test_page_keyboard_input() {
-        let mut page = Page::new(0.0, 0.0, 800.0, 600.0);
-        page.content_h = 1000.0; // max_scroll = 400.0
-        
-        let mut ctx = UiContext::new();
-        
-        // 1. ArrowDown
-        let event_down = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::ArrowDown),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        assert!(page.keyboard_input(&event_down, &mut ctx));
-        assert_eq!(page.scroll_y, 24.0);
-
-        // 2. PageDown
-        let event_pgdown = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::PageDown),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        assert!(page.keyboard_input(&event_pgdown, &mut ctx));
-        assert_eq!(page.scroll_y, 400.0);
-
-        // 3. PageUp
-        let event_pgup = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::PageUp),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        assert!(page.keyboard_input(&event_pgup, &mut ctx));
-        assert_eq!(page.scroll_y, 0.0);
-
-        // 4. End
-        let event_end = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::End),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        assert!(page.keyboard_input(&event_end, &mut ctx));
-        assert_eq!(page.scroll_y, 400.0);
-
-        // 5. Home
-        let event_home = KeyEvent {
-            state: ElementState::Pressed,
-            logical_key: Key::Named(NamedKey::Home),
-            text: None,
-            repeat: false,
-            ctrl: false,
-            shift: false,
-        };
-        assert!(page.keyboard_input(&event_home, &mut ctx));
-        assert_eq!(page.scroll_y, 0.0);
-    }
-}
-
diff --git a/src/widget/container/paginator.rs b/src/widget/container/paginator.rs
index 4c81856..be09364 100644
--- a/src/widget/container/paginator.rs
+++ b/src/widget/container/paginator.rs
@@ -1,26 +1,18 @@
-//! Narrow-trait `Paginator` (Phase 5r) — a vertical sidebar tab strip plus a stack of pages, of
-//! which only the selected one shows. Both halves are EMBEDDED legacy widgets owned by value:
-//! the tabs live in a [`ButtonStrip`], the content in a `Vec<Page>` (Page is an embedded-base
-//! container that dissolves in Phase 6 — it does not go through `Adapted` itself). The adapter's
-//! container concern does the subtree plumbing, filtered to the strip + selected page by
-//! [`Layout::child_visible`]; the model keeps the legacy specifics: strip/pages arrangement in
-//! [`Layout::arrange_children`], event proxying in [`Input::on_event`] (via `EventCtx::ui`), and
-//! the [`PageSelector`] / [`MenuController`] capabilities.
+//! Narrow-trait `Paginator` (Phase 5r; pages folded away in Phase 6au) — a vertical sidebar tab
+//! strip. The tabs live in an EMBEDDED [`ButtonStrip`] owned by value; every app manages its own
+//! page content keyed on `selected_page()`, so the former `Vec<Page>` stack (empty `Page`
+//! containers toggled visible/hidden) is gone — its only observable output, the page-area
+//! background quad, is painted directly here.
 //!
-//! Two legacy behaviors ride hooks new with this migration:
-//! - [`Layout::register_embedded_children`]: legacy `tick`/`layout` re-registered the strip and
-//!   pages into the ctx registry every frame — load-bearing for the spatial grid (the registered
-//!   strip is what makes the sidebar block backplate drags; see
-//!   `backplate::tests::test_paginator_blocks_backplate_drag`).
+//! Two legacy behaviors ride hooks from the 5r migration:
+//! - [`Layout::register_embedded_children`]: legacy `tick`/`layout` re-registered the strip into
+//!   the ctx registry every frame — load-bearing for the spatial grid (the registered strip is
+//!   what makes the sidebar block backplate drags).
 //! - [`Paint::aggregates_child_extra_quads`] + [`Paint::forwarded_highlight`]: legacy
-//!   `extra_quads` served the children's chrome only (cce-email and cce-layout-interface render
-//!   the tab column through that getter — the paginator's own background quad lives in
+//!   `extra_quads` served the strip's chrome only (cce-email and cce-layout-interface render
+//!   the tab column through that getter — the paginator's own background quads live in
 //!   `all_quads` alone), and legacy `highlight_quad` forwarded to the strip's (the hovered-tab
 //!   tint cce-layout-interface draws directly).
-//!
-//! In practice every app uses only the tab-strip half (`selected_page`/`set_selected_page`/
-//! `sidebar_w`/`menu_click`) and manages page content itself; the `pages` container surface
-//! (`add_widget_to_page` & co.) is ported bug-for-bug but has no callers workspace-wide.
 
 use crate::colors;
 use crate::scene::layout::Rect;
@@ -30,13 +22,10 @@ use crate::widget::{
     Adapted, Element, Event, EventCtx, Input, Layout, MenuController, PageSelector, Paint,
     UiContext, WidgetId,
 };
-use super::page::Page;
 
 pub struct Paginator {
     pub sidebar_menu: ButtonStrip,
-    pub pages: Vec<Page>,
     pub selected_page: usize,
-    pub page_hidden: bool,
     pub sidebar_w: f32,
     pub page_labels: Vec<String>,
     pub on_page_changed_cb: Option<Box<dyn Fn(usize) + Send + Sync>>,
@@ -49,9 +38,7 @@ impl Paginator {
 
         let temp_paginator = Paginator {
             sidebar_menu: ButtonStrip::new(0.0, 0.0, 0.0, 0.0),
-            pages: Vec::new(),
             selected_page: 0,
-            page_hidden: false,
             sidebar_w: 0.0,
             page_labels: pages.clone(),
             on_page_changed_cb: None,
@@ -66,21 +53,9 @@ impl Paginator {
             sidebar_menu.set_selected(Some(0));
         }
 
-        let mut pages_containers = Vec::new();
-        for _ in 0..num_pages {
-            let mut page = Page::new(0.0, 0.0, 0.0, 0.0);
-            page.visible = false;
-            pages_containers.push(page);
-        }
-        if num_pages > 0 {
-            pages_containers[0].visible = true;
-        }
-
         Adapted::new(Paginator {
             sidebar_menu,
-            pages: pages_containers,
             selected_page: 0,
-            page_hidden: false,
             sidebar_w,
             page_labels: pages,
             on_page_changed_cb: None,
@@ -126,47 +101,15 @@ impl PageSelector for Paginator {
     }
 
     fn set_selected_page(&mut self, page: usize) {
-        if page < self.pages.len() {
+        if page < self.page_labels.len() {
             self.selected_page = page;
             self.sidebar_menu.set_selected(Some(page));
-            for (i, page_item) in self.pages.iter_mut().enumerate() {
-                page_item.visible = i == page;
-            }
             if let Some(ref cb) = self.on_page_changed_cb {
                 cb(page);
             }
         }
     }
 
-    fn is_page_hidden(&self) -> bool {
-        self.page_hidden
-    }
-
-    fn set_page_hidden(&mut self, hidden: bool) {
-        self.page_hidden = hidden;
-    }
-
-    fn set_pages(&mut self, pages: Vec<String>) {
-        self.page_labels = pages.clone();
-        self.sidebar_menu.buttons = pages.clone();
-        self.sidebar_menu.generate_rotated_labels();
-
-        self.pages.clear();
-        for _ in 0..pages.len() {
-            let mut page = Page::new(0.0, 0.0, 0.0, 0.0);
-            page.visible = false;
-            self.pages.push(page);
-        }
-        if !self.pages.is_empty() {
-            let idx = self.selected_page.min(self.pages.len() - 1);
-            self.pages[idx].visible = true;
-        }
-    }
-
-    fn set_pages_with_items(&mut self, pages: Vec<String>, _items: Vec<Vec<String>>) {
-        self.set_pages(pages);
-    }
-
     fn sidebar_w(&self) -> f32 {
         if self.page_labels.is_empty() {
             return self.sidebar_w;
@@ -198,22 +141,6 @@ impl PageSelector for Paginator {
         }
         max_w.max(1.0)
     }
-
-    fn set_sidebar_mode(&mut self, _enabled: bool) {}
-
-    fn set_sidebar_label(&mut self, _label: Option<String>) {}
-
-    fn add_widget_to_page(&mut self, page_idx: usize, widget: *mut (dyn Element + 'static), ctx: &mut UiContext) {
-        if page_idx < self.pages.len() {
-            self.pages[page_idx].add_child(widget, ctx);
-        }
-    }
-
-    fn clear_page_widgets(&mut self, page_idx: usize, ctx: &mut UiContext) {
-        if page_idx < self.pages.len() {
-            self.pages[page_idx].clear_children(ctx);
-        }
-    }
 }
 
 impl Layout for Paginator {
@@ -222,59 +149,20 @@ impl Layout for Paginator {
     }
 
     fn container_children(&self) -> Vec<*mut (dyn Element + 'static)> {
-        let mut childs: Vec<*mut (dyn Element + 'static)> = Vec::new();
-        childs.push(&self.sidebar_menu as &dyn Element as *const (dyn Element + 'static) as *mut (dyn Element + 'static));
-        for plate in &self.pages {
-            childs.push(plate as &dyn Element as *const (dyn Element + 'static) as *mut (dyn Element + 'static));
-        }
-        childs
+        vec![&self.sidebar_menu as &dyn Element as *const (dyn Element + 'static) as *mut (dyn Element + 'static)]
     }
 
-    /// The strip always shows; a page only while selected and not hidden (legacy
-    /// `is_child_visible`).
-    fn child_visible(&self, child: *mut (dyn Element + 'static)) -> bool {
-        let strip_ptr = &self.sidebar_menu as &dyn Element as *const (dyn Element + 'static);
-        if std::ptr::addr_eq(child, strip_ptr) {
-            return true;
-        }
-        if let Some(plate) = self.pages.get(self.selected_page) {
-            if !self.page_hidden {
-                let plate_ptr = plate as &dyn Element as *const (dyn Element + 'static);
-                if std::ptr::addr_eq(child, plate_ptr) {
-                    return true;
-                }
-            }
-        }
-        false
-    }
-
-    /// The legacy `set_rect` body: strip on the left at its measured width, every page filling
-    /// the remainder, page visibility synced to the selection.
+    /// The legacy `set_rect` body: strip on the left at its measured width.
     fn arrange_children(&mut self, rect: Rect, _host: *mut (dyn Element + 'static)) {
-        let (x, y, w, h) = (rect.x, rect.y, rect.width, rect.height);
+        let (x, y, h) = (rect.x, rect.y, rect.height);
         let sidebar_w = self.sidebar_w();
         self.sidebar_menu.set_rect(x, y, sidebar_w, h);
-
-        let page_x = x + sidebar_w;
-        let page_w = (w - sidebar_w).max(0.0);
-        let selected = self.selected_page;
-        let hidden = self.page_hidden;
-        for (i, plate) in self.pages.iter_mut().enumerate() {
-            plate.set_rect(page_x, y, page_w, h);
-            plate.visible = (i == selected) && !hidden;
-        }
     }
 
     fn register_embedded_children(&mut self, host_id: WidgetId, ctx: &mut UiContext) {
         let menu_ptr = &mut self.sidebar_menu as *mut ButtonStrip;
         ctx.register_widget(self.sidebar_menu.base.id(), menu_ptr);
         ctx.link_ids(host_id, self.sidebar_menu.base.id());
-
-        for plate in &mut self.pages {
-            let plate_ptr = plate as *mut Page;
-            ctx.register_widget(plate.base.base.id(), plate_ptr);
-            ctx.link_ids(host_id, plate.base.base.id());
-        }
     }
 }
 
@@ -283,14 +171,28 @@ impl Paint for Paginator {
         colors::sidebar_bg_color()
     }
 
-    /// Own geometry is just the sidebar background (the legacy `all_quads` head; the strip's
-    /// and selected page's pixels arrive through the adapter's child aggregation / the paint
-    /// walk's recursion).
+    /// Own geometry: the sidebar background plus the page-area background — the latter is the
+    /// one visual the former empty `Page` stack contributed (its bg quad over the content
+    /// area), painted directly since the pages folded away (Phase 6au).
     fn paint(&self, rect: Rect, ctx: &mut PaintCtx) {
         let c = self.color();
         if c[3] > 0.0 {
             ctx.quad(rect, c);
         }
+        if !self.page_labels.is_empty() {
+            let mut pc = colors::page_color();
+            pc[3] *= crate::layout::page_opacity();
+            if pc[3] > 0.0 {
+                let sidebar_w = self.sidebar_w();
+                let page_rect = Rect {
+                    x: rect.x + sidebar_w,
+                    y: rect.y,
+                    width: (rect.width - sidebar_w).max(0.0),
+                    height: rect.height,
+                };
+                ctx.quad(page_rect, pc);
+            }
+        }
     }
 
     /// Legacy `extra_quads` served the strip's + selected page's chrome only — cce-email and
@@ -320,26 +222,15 @@ impl Input for Paginator {
         true
     }
 
-    /// Event proxying, the legacy forwarding bodies: strip first (draining its click into the
-    /// page selection), then the selected page while not hidden.
+    /// Event proxying, the legacy forwarding body: the strip, draining its click into the page
+    /// selection. (The former empty pages also received every event, but had nothing to do with
+    /// them — no children, never scrollable.)
     fn on_event(&mut self, event: &Event, ectx: &mut EventCtx) -> bool {
         let Some(ui) = ectx.ui.as_deref_mut() else {
             return false;
         };
-        let selected = self.selected_page;
-        let hidden = self.page_hidden;
         match event {
-            Event::PointerMove { x: px, y: py, .. } => {
-                let mut changed = self.sidebar_menu.cursor_moved(*px, *py, ui);
-                for (i, plate) in self.pages.iter_mut().enumerate() {
-                    if i == selected && !hidden {
-                        if plate.cursor_moved(*px, *py, ui) {
-                            changed = true;
-                        }
-                    }
-                }
-                changed
-            }
+            Event::PointerMove { x: px, y: py, .. } => self.sidebar_menu.cursor_moved(*px, *py, ui),
             Event::MouseButton { button, state, x: px, y: py, .. } => {
                 let mut changed = false;
                 if self.sidebar_menu.mouse_input(*button, *state, *px, *py, ui) {
@@ -349,37 +240,10 @@ impl Input for Paginator {
                         self.just_clicked = Some(idx);
                     }
                 }
-                for (i, plate) in self.pages.iter_mut().enumerate() {
-                    if i == selected && !hidden {
-                        if plate.mouse_input(*button, *state, *px, *py, ui) {
-                            changed = true;
-                        }
-                    }
-                }
-                changed
-            }
-            Event::MouseWheel { delta, x: px, y: py, .. } => {
-                let mut changed = self.sidebar_menu.mouse_wheel(delta, *px, *py, ui);
-                for (i, plate) in self.pages.iter_mut().enumerate() {
-                    if i == selected && !hidden {
-                        if plate.mouse_wheel(delta, *px, *py, ui) {
-                            changed = true;
-                        }
-                    }
-                }
-                changed
-            }
-            Event::KeyInput(key_event) => {
-                let mut changed = self.sidebar_menu.keyboard_input(key_event, ui);
-                for (i, plate) in self.pages.iter_mut().enumerate() {
-                    if i == selected && !hidden {
-                        if plate.keyboard_input(key_event, ui) {
-                            changed = true;
-                        }
-                    }
-                }
                 changed
             }
+            Event::MouseWheel { delta, x: px, y: py, .. } => self.sidebar_menu.mouse_wheel(delta, *px, *py, ui),
+            Event::KeyInput(key_event) => self.sidebar_menu.keyboard_input(key_event, ui),
             _ => false,
         }
     }
@@ -439,14 +303,13 @@ mod tests {
         let (id, ptr) = (p.id(), p.as_ptr_mut());
         ctx.register_widget(id, ptr);
 
-        // Click the second tab (the strip commits selection on release): the selection moves,
-        // the pages' visibility flips, and menu_click reports (1, 0) once.
+        // Click the second tab (the strip commits selection on release): the selection moves
+        // and menu_click reports (1, 0) once.
         let (bx, by, bw, bh) = p.sidebar_menu.item_rect(1);
         assert!(bw > 0.0, "strip laid out by arrange_children");
         p.mouse_input(MouseButton::Left, ElementState::Pressed, bx + bw / 2.0, by + bh / 2.0, &mut ctx);
         p.mouse_input(MouseButton::Left, ElementState::Released, bx + bw / 2.0, by + bh / 2.0, &mut ctx);
         assert_eq!(p.selected_page, 1);
-        assert!(p.pages[1].visible && !p.pages[0].visible);
         {
             let elem: &mut dyn Element = &mut p;
             assert_eq!(elem.as_menu_controller_mut().unwrap().menu_click(), Some((1, 0)));
diff --git a/src/widget/container/scroll_bar.rs b/src/widget/container/scroll_bar.rs
index 2295a55..3df499d 100644
--- a/src/widget/container/scroll_bar.rs
+++ b/src/widget/container/scroll_bar.rs
@@ -117,16 +117,6 @@ impl Element for ScrollBar {
                     if (self.scroll_y - new_scroll_y).abs() > 0.01 {
                         self.scroll_y = new_scroll_y;
                         changed = true;
-
-                        if let Some(parent_ptr) = self.parent {
-                            unsafe {
-                                if let Some(page) = (*parent_ptr).as_any_mut().downcast_mut::<Page>() {
-                                    page.scroll_y = new_scroll_y;
-                                    let (px, py, pw, ph) = page.rect();
-                                    page.set_rect(px, py, pw, ph);
-                                }
-                            }
-                        }
                     }
                 }
             }
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 070de74..b688240 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -664,7 +664,6 @@ pub trait Element {
     }
 
     fn z_index(&self) -> i32 { 0 }
-    fn is_page(&self) -> bool { false }
     fn is_scrollable(&self) -> bool { false }
     fn blocks_backplate_drag(&self) -> bool { true }
     fn rounded_corners(&self) -> (bool, bool, bool, bool) { (false, false, false, false) }
@@ -748,7 +747,7 @@ pub use self::container::{
     ColumnsLayout, MosaicLayout, ReverseMosaicLayout,
     Header, ContentBg, ParametersBg,
     ScrollBox, MenuBar, Spreadsheet, Breadcrumb,
-    Switcher, Layer, Page, Paginator, ScrollBar, TreeList, TreeElement
+    Switcher, Paginator, ScrollBar, TreeList, TreeElement
 };
 pub use self::display::{
     TextLabel, Label, StyledLabel, LabelPrim, TextItem, Svg, UsageBar,
@@ -761,15 +760,7 @@ pub use self::display::{
 pub trait PageSelector {
     fn selected_page(&self) -> usize;
     fn set_selected_page(&mut self, page: usize);
-    fn is_page_hidden(&self) -> bool;
-    fn set_page_hidden(&mut self, hidden: bool);
-    fn set_pages(&mut self, pages: Vec<String>);
-    fn set_pages_with_items(&mut self, pages: Vec<String>, items: Vec<Vec<String>>);
     fn sidebar_w(&self) -> f32;
-    fn set_sidebar_mode(&mut self, enabled: bool);
-    fn set_sidebar_label(&mut self, label: Option<String>);
-    fn add_widget_to_page(&mut self, page_idx: usize, widget: *mut (dyn Element + 'static), ctx: &mut UiContext);
-    fn clear_page_widgets(&mut self, page_idx: usize, ctx: &mut UiContext);
 }
 
 pub trait MenuController {