GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
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 {