GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git
Add right-click context menu with expand/collapse options to domain headers in TreeList
src/widget/container/treelist.rs | 154 ++++++++++++++++++++++++++++++++++-----
src/widget/core.rs | 12 +++
src/widget/mod.rs | 4 +
3 files changed, 152 insertions(+), 18 deletions(-)
diff --git a/src/widget/container/treelist.rs b/src/widget/container/treelist.rs
index 785a2f8..0539e89 100644
--- a/src/widget/container/treelist.rs
+++ b/src/widget/container/treelist.rs
@@ -126,6 +126,7 @@ pub struct TreeList {
pub hovered_row_idx: Option<usize>,
pub item_height: f32,
pub clicked_item: Option<TreeElement>,
+ pub right_clicked_section: Option<String>,
pub parent: Option<*mut (dyn Element + 'static)>,
pub children: Vec<*mut (dyn Element + 'static)>,
}
@@ -142,6 +143,7 @@ impl TreeList {
hovered_row_idx: None,
item_height: 28.0,
clicked_item: None,
+ right_clicked_section: None,
parent: None,
children: Vec::new(),
}
@@ -265,24 +267,42 @@ impl Element for TreeList {
let row_idx = (relative_y / self.item_height) as usize;
if row_idx < self.items.len() {
let item = self.items[row_idx].clone();
- if let TreeElement::Leaf { original_idx, ref path, ref name, indent, ref val } = item {
- self.selected_key_idx = Some(original_idx);
- self.clicked_item = Some(TreeElement::Leaf {
- path: path.clone(),
- name: name.clone(),
- indent,
- val: val.clone(),
- original_idx,
- });
-
- let options = vec![
- path.clone(),
- "Copy Key".to_string(),
- "Copy Value".to_string(),
- ];
- let scroll_offset = crate::widget::hover_animation::get_scroll_offset();
- ctx.show_context_menu(px, py - scroll_offset, options, 1, self.as_ptr_mut());
- changed = true;
+ match item {
+ TreeElement::Section { ref path, collapsed, .. } => {
+ self.right_clicked_section = Some(path.clone());
+
+ let mut options = vec![path.clone()];
+ if collapsed {
+ options.push("Expand".to_string());
+ } else {
+ options.push("Collapse".to_string());
+ }
+ options.push("Expand All".to_string());
+ options.push("Collapse All".to_string());
+
+ let scroll_offset = crate::widget::hover_animation::get_scroll_offset();
+ ctx.show_context_menu(px, py - scroll_offset, options, 1, self.as_ptr_mut());
+ changed = true;
+ }
+ TreeElement::Leaf { original_idx, ref path, ref name, indent, ref val } => {
+ self.selected_key_idx = Some(original_idx);
+ self.clicked_item = Some(TreeElement::Leaf {
+ path: path.clone(),
+ name: name.clone(),
+ indent,
+ val: val.clone(),
+ original_idx,
+ });
+
+ let options = vec![
+ path.clone(),
+ "Copy Key".to_string(),
+ "Copy Value".to_string(),
+ ];
+ let scroll_offset = crate::widget::hover_animation::get_scroll_offset();
+ ctx.show_context_menu(px, py - scroll_offset, options, 1, self.as_ptr_mut());
+ changed = true;
+ }
}
}
}
@@ -565,6 +585,104 @@ impl Element for TreeList {
}
}
}
+
+ fn expand_node(&mut self) {
+ if let Some(path) = self.right_clicked_section.clone() {
+ self.collapsed_sections.remove(&path);
+ self.rebuild_tree();
+ self.clicked_item = Some(TreeElement::Section {
+ path,
+ name: String::new(),
+ indent: 0,
+ collapsed: false,
+ });
+ }
+ self.right_clicked_section = None;
+ }
+
+ fn collapse_node(&mut self) {
+ if let Some(path) = self.right_clicked_section.clone() {
+ self.collapsed_sections.insert(path.clone());
+ self.rebuild_tree();
+ self.clicked_item = Some(TreeElement::Section {
+ path,
+ name: String::new(),
+ indent: 0,
+ collapsed: true,
+ });
+ }
+ self.right_clicked_section = None;
+ }
+
+ fn expand_all_nodes(&mut self) {
+ self.collapsed_sections.clear();
+ self.rebuild_tree();
+ if let Some(path) = self.right_clicked_section.clone() {
+ self.clicked_item = Some(TreeElement::Section {
+ path,
+ name: String::new(),
+ indent: 0,
+ collapsed: false,
+ });
+ } else {
+ self.clicked_item = Some(TreeElement::Section {
+ path: String::new(),
+ name: String::new(),
+ indent: 0,
+ collapsed: false,
+ });
+ }
+ self.right_clicked_section = None;
+ }
+
+ fn collapse_all_nodes(&mut self) {
+ self.collapsed_sections = self.get_all_section_paths();
+ self.rebuild_tree();
+ if let Some(path) = self.right_clicked_section.clone() {
+ self.clicked_item = Some(TreeElement::Section {
+ path,
+ name: String::new(),
+ indent: 0,
+ collapsed: true,
+ });
+ } else {
+ self.clicked_item = Some(TreeElement::Section {
+ path: String::new(),
+ name: String::new(),
+ indent: 0,
+ collapsed: true,
+ });
+ }
+ self.right_clicked_section = None;
+ }
+}
+
+impl TreeList {
+ fn get_all_section_paths(&self) -> HashSet<String> {
+ let mut sections = HashSet::new();
+ for (key_path, _) in &self.flat_keys {
+ let tokens = parse_path(key_path);
+ let mut current_prefix = String::new();
+ for i in 0..(tokens.len().saturating_sub(1)) {
+ let token = &tokens[i];
+ match token {
+ PathToken::Key(k) => {
+ if current_prefix.is_empty() {
+ current_prefix = k.clone();
+ } else {
+ current_prefix = format!("{}.{}", current_prefix, k);
+ }
+ }
+ PathToken::Index(idx) => {
+ let s = format!("[{}]", idx);
+ current_prefix = format!("{}{}", current_prefix, s);
+ }
+ };
+ sections.insert(current_prefix.clone());
+ }
+ }
+ sections
+ }
}
fn parse_hex_f32(s: &str) -> Option<[f32; 4]> {
diff --git a/src/widget/core.rs b/src/widget/core.rs
index a402534..5e29f3a 100644
--- a/src/widget/core.rs
+++ b/src/widget/core.rs
@@ -552,6 +552,18 @@ pub mod context_menu {
"Copy Value" => {
target.copy_value();
}
+ "Expand" => {
+ target.expand_node();
+ }
+ "Collapse" => {
+ target.collapse_node();
+ }
+ "Expand All" => {
+ target.expand_all_nodes();
+ }
+ "Collapse All" => {
+ target.collapse_all_nodes();
+ }
_ => {}
}
}
diff --git a/src/widget/mod.rs b/src/widget/mod.rs
index 110e824..9c4877d 100644
--- a/src/widget/mod.rs
+++ b/src/widget/mod.rs
@@ -295,6 +295,10 @@ pub trait Element {
fn select_all(&mut self) {}
fn copy_key(&self) {}
fn copy_value(&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() {