GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
refactor(widget)!: context-menu actions become one enum method (6bd phase 1)
The 13 per-action methods (cut_selection..collapse_all_nodes) on
Element, the Input capability hooks, and the Adapted forwards all
collapse into `context_action(ContextAction) -> bool`. The Input
default keeps whole-value Cut/Copy/Paste through the value-string
pair (any widget remains a valid right-click target); TextBox,
TreeList, and Breadcrumb narrow to one hook each (TreeList's seven
action bodies move verbatim to inherent methods via an impl split —
no code movement). core.rs dispatch maps the menu strings (incl. the
load-bearing "Cear") to enum values. Element trait: 91 -> 79 methods.
Verified: 168 tests; workspace builds; live probes — tree Copy Key
("input.accel_speed") / Copy Value ("0.5") / Collapse (group folds),
TextBox Paste ("hello" lands in the search box with just_changed).
src/widget/container/breadcrumb.rs | 6 ++-
src/widget/container/treelist.rs | 21 +++++++-
src/widget/core.rs | 60 +++++++--------------
src/widget/input/text_box.rs | 57 +++++++++++---------
src/widget/mod.rs | 53 +++++++++----------
src/widget/model.rs | 105 ++++++++++---------------------------
6 files changed, 126 insertions(+), 176 deletions(-)
diff --git a/src/widget/container/breadcrumb.rs b/src/widget/container/breadcrumb.rs
index 372c133..9c293e4 100644
--- a/src/widget/container/breadcrumb.rs
+++ b/src/widget/container/breadcrumb.rs
@@ -185,10 +185,14 @@ impl Input for Breadcrumb {
}
- fn copy_path(&self) {
+ fn context_action(&mut self, action: crate::widget::ContextAction) -> bool {
+ if action != crate::widget::ContextAction::CopyPath {
+ return false;
+ }
let idx = self.right_clicked_seg.unwrap_or(self.path.len());
let path_str = self.path_to_seg(idx);
crate::widget::clipboard::copy_to_clipboard(&path_str);
+ true
}
}
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index 06d33bb..e12add1 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -1117,8 +1117,25 @@ impl Input for TreeList {
}
}
- // The tree context-menu actions, dispatched by the global context menu through the
- // adapter's Element forwards.
+ fn context_action(&mut self, action: crate::widget::ContextAction) -> bool {
+ use crate::widget::ContextAction as CA;
+ match action {
+ CA::CopyKey => self.copy_key(),
+ CA::CopyValue => self.copy_value(),
+ CA::DeleteKey => self.delete_key(),
+ CA::ExpandNode => self.expand_node(),
+ CA::CollapseNode => self.collapse_node(),
+ CA::ExpandAll => self.expand_all_nodes(),
+ CA::CollapseAll => self.collapse_all_nodes(),
+ _ => return false,
+ }
+ true
+ }
+}
+
+impl TreeList {
+ // The tree context-menu actions, dispatched by the global context menu through
+ // `Input::context_action`.
fn copy_key(&self) {
if let Some(idx) = self.selected_key_idx {
if idx < self.flat_keys.len() {
diff --git a/src/widget/core.rs b/src/widget/core.rs
index e438b97..45208cb 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -492,47 +492,25 @@ pub mod context_menu {
if let Some(target_ptr) = ctx.tree.get_ptr(target_id) {
unsafe {
let target = &mut *target_ptr;
- match opt.as_str() {
- "Cut" => {
- let _ = target.cut_selection();
- }
- "Copy" => {
- target.copy_selection();
- }
- "Paste" => {
- let _ = target.paste_from_clipboard();
- }
- "Select All" => {
- target.select_all();
- }
- "Cear" => {
- target.clear_text();
- }
- "Copy Key" => {
- target.copy_key();
- }
- "Copy Value" => {
- target.copy_value();
- }
- "Delete" => {
- target.delete_key();
- }
- "Expand" => {
- target.expand_node();
- }
- "Collapse" => {
- target.collapse_node();
- }
- "Expand All" => {
- target.expand_all_nodes();
- }
- "Collapse All" => {
- target.collapse_all_nodes();
- }
- "Copy Path" => {
- target.copy_path();
- }
- _ => {}
+ use crate::widget::ContextAction as CA;
+ let action = match opt.as_str() {
+ "Cut" => Some(CA::Cut),
+ "Copy" => Some(CA::Copy),
+ "Paste" => Some(CA::Paste),
+ "Select All" => Some(CA::SelectAll),
+ "Cear" => Some(CA::ClearText),
+ "Copy Key" => Some(CA::CopyKey),
+ "Copy Value" => Some(CA::CopyValue),
+ "Delete" => Some(CA::DeleteKey),
+ "Expand" => Some(CA::ExpandNode),
+ "Collapse" => Some(CA::CollapseNode),
+ "Expand All" => Some(CA::ExpandAll),
+ "Collapse All" => Some(CA::CollapseAll),
+ "Copy Path" => Some(CA::CopyPath),
+ _ => None,
+ };
+ if let Some(action) = action {
+ let _ = target.context_action(action);
}
}
}
diff --git a/src/widget/input/text_box.rs b/src/widget/input/text_box.rs
index e3a4ca4..145ed06 100644
--- a/src/widget/input/text_box.rs
+++ b/src/widget/input/text_box.rs
@@ -1406,32 +1406,37 @@ impl Input for TextBox {
self.set_value(val)
}
- fn cut_selection(&mut self) -> bool {
- let res = self.cut_selection();
- if res {
- self.just_changed = true;
- }
- res
- }
-
- fn copy_selection(&self) {
- self.copy_selection();
- }
-
- fn paste_from_clipboard(&mut self) -> bool {
- let res = self.paste_from_clipboard();
- if res {
- self.just_changed = true;
+ fn context_action(&mut self, action: crate::widget::ContextAction) -> bool {
+ use crate::widget::ContextAction as CA;
+ match action {
+ CA::Cut => {
+ let res = self.cut_selection();
+ if res {
+ self.just_changed = true;
+ }
+ res
+ }
+ CA::Copy => {
+ self.copy_selection();
+ true
+ }
+ CA::Paste => {
+ let res = self.paste_from_clipboard();
+ if res {
+ self.just_changed = true;
+ }
+ res
+ }
+ CA::SelectAll => {
+ self.select_all();
+ true
+ }
+ CA::ClearText => {
+ self.set_value("");
+ true
+ }
+ _ => false,
}
- res
- }
-
- fn select_all(&mut self) {
- self.select_all();
- }
-
- fn clear_text(&mut self) {
- self.set_value("");
}
fn draggable(&self, _rect: Rect) -> bool {
@@ -1691,7 +1696,7 @@ mod tests {
assert!(opts.contains(&"Cear".to_string()));
// Simulate choosing the "Cear" option
- Element::clear_text(&mut tb);
+ Element::context_action(&mut tb, crate::widget::ContextAction::ClearText);
assert_eq!(tb.text, "");
assert_eq!(tb.edit_buffer, "");
}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index c54e63a..38ab595 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -75,6 +75,25 @@ pub enum Justification {
Right,
}
+/// A context-menu action dispatched on the menu's target widget (6bd phase 1: one enum
+/// replaces the 13 per-action `Element` methods). `ClearText` is the search-box "Cear" item.
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
+pub enum ContextAction {
+ Cut,
+ Copy,
+ Paste,
+ SelectAll,
+ ClearText,
+ CopyKey,
+ CopyValue,
+ CopyPath,
+ DeleteKey,
+ ExpandNode,
+ CollapseNode,
+ ExpandAll,
+ CollapseAll,
+}
+
use crate::colors;
use std::sync::atomic::AtomicUsize;
use std::collections::HashMap;
@@ -285,36 +304,12 @@ pub trait Element {
fn set_value_string(&mut self, _val: &str) -> bool { false }
fn take_change(&mut self) -> bool { false }
- fn cut_selection(&mut self) -> bool {
- if let Some(val) = self.get_value_string() {
- clipboard::copy_to_clipboard(&val);
- self.set_value_string("")
- } else {
- false
- }
- }
- fn copy_selection(&self) {
- if let Some(val) = self.get_value_string() {
- clipboard::copy_to_clipboard(&val);
- }
- }
- fn paste_from_clipboard(&mut self) -> bool {
- if let Some(text) = clipboard::read_from_clipboard() {
- self.set_value_string(&text)
- } else {
- false
- }
+ /// Dispatch a context-menu action on this widget. Returns whether it was applied.
+ /// Default inert; the adapter forwards to `Input::context_action` (whose default gives
+ /// every widget whole-value Cut/Copy/Paste through the value-string pair).
+ fn context_action(&mut self, _action: ContextAction) -> bool {
+ false
}
- fn select_all(&mut self) {}
- fn clear_text(&mut self) {}
- fn copy_key(&self) {}
- fn copy_value(&self) {}
- fn copy_path(&self) {}
- fn delete_key(&mut self) {}
- fn expand_node(&mut self) {}
- fn collapse_node(&mut self) {}
- fn expand_all_nodes(&mut self) {}
- fn collapse_all_nodes(&mut self) {}
fn set_rect(&mut self, x: f32, y: f32, w: f32, h: f32) {
if let Some(b) = self.base_mut() {
diff --git a/src/widget/model.rs b/src/widget/model.rs
index 0b87c04..37885f2 100644
--- a/src/widget/model.rs
+++ b/src/widget/model.rs
@@ -491,28 +491,34 @@ pub trait Input {
// before these hooks existed keep their exact behavior; TextBox overrides with real
// selection-aware implementations.
- fn cut_selection(&mut self) -> bool {
- if let Some(val) = self.value_string() {
- crate::widget::clipboard::copy_to_clipboard(&val);
- self.set_value_string("")
- } else {
- false
- }
- }
- fn copy_selection(&self) {
- if let Some(val) = self.value_string() {
- crate::widget::clipboard::copy_to_clipboard(&val);
- }
- }
- fn paste_from_clipboard(&mut self) -> bool {
- if let Some(text) = crate::widget::clipboard::read_from_clipboard() {
- self.set_value_string(&text)
- } else {
- false
+ fn context_action(&mut self, action: crate::widget::ContextAction) -> bool {
+ match action {
+ crate::widget::ContextAction::Cut => {
+ if let Some(val) = self.value_string() {
+ crate::widget::clipboard::copy_to_clipboard(&val);
+ self.set_value_string("")
+ } else {
+ false
+ }
+ }
+ crate::widget::ContextAction::Copy => {
+ if let Some(val) = self.value_string() {
+ crate::widget::clipboard::copy_to_clipboard(&val);
+ true
+ } else {
+ false
+ }
+ }
+ crate::widget::ContextAction::Paste => {
+ if let Some(text) = crate::widget::clipboard::read_from_clipboard() {
+ self.set_value_string(&text)
+ } else {
+ false
+ }
+ }
+ _ => false,
}
}
- fn select_all(&mut self) {}
- fn clear_text(&mut self) {}
/// Whether direct `focus()`/`unfocus()` calls flip the base `focused` flag. Legacy widgets
/// differ: most set it in their `focus` overrides, but TextBox never did — its detached
@@ -588,22 +594,6 @@ pub trait Input {
// concrete `Adapted<W>` (or a `&dyn XController` held directly), per RFC §3.5.
- /// Copy this widget's path/content to the clipboard — the context menu's "Copy Path" action
- /// calls `Element::copy_path` on its target (Breadcrumb is the only implementor).
- fn copy_path(&self) {}
-
- /// The tree context-menu actions ("Copy Key" / "Copy Value" / "Delete" / "Expand" /
- /// "Collapse" / "Expand All" / "Collapse All") — the global context menu dispatches them
- /// on its `dyn Element` target; `Adapted` forwards here. TreeList is the only implementor
- /// (transitional, dies with the `Element` deletion like the controller hooks above).
- fn copy_key(&self) {}
- fn copy_value(&self) {}
- fn delete_key(&mut self) {}
- fn expand_node(&mut self) {}
- fn collapse_node(&mut self) {}
- fn expand_all_nodes(&mut self) {}
- fn collapse_all_nodes(&mut self) {}
-
/// Keyboard modifier state pushed in by hosts before dispatch (legacy
/// `Element::set_modifiers`).
fn set_modifiers(&mut self, _ctrl: bool, _shift: bool, _alt: bool) {}
@@ -1348,20 +1338,8 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
fn set_selected(&mut self, selected: bool) {
Input::set_selected(&mut self.inner, selected)
}
- fn cut_selection(&mut self) -> bool {
- Input::cut_selection(&mut self.inner)
- }
- fn copy_selection(&self) {
- Input::copy_selection(&self.inner)
- }
- fn paste_from_clipboard(&mut self) -> bool {
- Input::paste_from_clipboard(&mut self.inner)
- }
- fn select_all(&mut self) {
- Input::select_all(&mut self.inner)
- }
- fn clear_text(&mut self) {
- Input::clear_text(&mut self.inner)
+ fn context_action(&mut self, action: crate::widget::ContextAction) -> bool {
+ Input::context_action(&mut self.inner, action)
}
/// Row-rect assignment (row-layout hosts): apply the widget's clamp
/// ([`Layout::adjust_row_rect`] — TextBox's `width`/`max_width`), then the base write the
@@ -1422,33 +1400,6 @@ impl<W: Layout + Paint + Input + 'static> Element for Adapted<W> {
Input::set_drag_bounds(&mut self.inner, bx, by, bw, bh)
}
- // --- Controller downcasts -> the `Input` capability hooks ---
- fn copy_path(&self) {
- Input::copy_path(&self.inner)
- }
-
- fn copy_key(&self) {
- Input::copy_key(&self.inner)
- }
- fn copy_value(&self) {
- Input::copy_value(&self.inner)
- }
- fn delete_key(&mut self) {
- Input::delete_key(&mut self.inner)
- }
- fn expand_node(&mut self) {
- Input::expand_node(&mut self.inner)
- }
- fn collapse_node(&mut self) {
- Input::collapse_node(&mut self.inner)
- }
- fn expand_all_nodes(&mut self) {
- Input::expand_all_nodes(&mut self.inner)
- }
- fn collapse_all_nodes(&mut self) {
- Input::collapse_all_nodes(&mut self.inner)
- }
-
// --- Legacy direct-dispatch entry points. Hosts (treelist's add-key button, parameters_bg's
// checkboxes, app pages) call these ON the widget instead of routing an Event through
// `propagate_event`; without these overrides they'd hit the inert Element defaults and the