improved tree widget and separatated "FileTree" implementation out into separate src file
This commit is contained in:
@@ -22,7 +22,7 @@ use tiles::PanelFactory;
|
|||||||
use tiles::documentation::Documentation;
|
use tiles::documentation::Documentation;
|
||||||
use tiles::editor::Editor;
|
use tiles::editor::Editor;
|
||||||
use tiles::io_devices::framebuffer::FrameBufferUi;
|
use tiles::io_devices::framebuffer::FrameBufferUi;
|
||||||
use zxq5_egui::context_tree::{ContextTree, NewFileTree};
|
use zxq5_egui::tree::{MenuTree, TreeEvent, file::FileTree};
|
||||||
|
|
||||||
pub mod appearance;
|
pub mod appearance;
|
||||||
mod controller;
|
mod controller;
|
||||||
@@ -44,7 +44,7 @@ pub struct DamnSimpleUI {
|
|||||||
|
|
||||||
show_process_manager: bool,
|
show_process_manager: bool,
|
||||||
process_manager: ProcessManager,
|
process_manager: ProcessManager,
|
||||||
file_tree: ContextTree<Action, NewFileTree>,
|
file_tree: MenuTree<Action, FileTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl eframe::App for DamnSimpleUI {
|
impl eframe::App for DamnSimpleUI {
|
||||||
@@ -74,13 +74,17 @@ impl eframe::App for DamnSimpleUI {
|
|||||||
});
|
});
|
||||||
|
|
||||||
Panel::left("left_panel").resizable(true).show(ui, |ui| {
|
Panel::left("left_panel").resizable(true).show(ui, |ui| {
|
||||||
let inner_res = self.file_tree.ui(ui);
|
let response = self.file_tree.ui(ui);
|
||||||
|
let Some(event) = response.inner else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(event) = inner_res.inner {
|
match event {
|
||||||
// match event {
|
TreeEvent::ContextAction(action, _tree) => self.perform_action(action),
|
||||||
// ContextTreeEvent::Clicked(node) => self.perform_action(Action::OpenFile(node)),
|
TreeEvent::Clicked(tree) => {
|
||||||
// ContextTreeEvent::ContextAction(action, node) => self.perform_action(action),
|
let filename = tree.filename().to_owned();
|
||||||
// }
|
self.perform_action(Action::OpenFile(filename));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -147,16 +151,15 @@ impl DamnSimpleUI {
|
|||||||
show_process_manager: true,
|
show_process_manager: true,
|
||||||
process_manager: ProcessManager::new(),
|
process_manager: ProcessManager::new(),
|
||||||
|
|
||||||
file_tree: ContextTree::new(
|
file_tree: MenuTree::new()
|
||||||
NewFileTree::build(&std::env::current_dir().unwrap()),
|
.root(Some(FileTree::build(&std::env::current_dir().unwrap())))
|
||||||
|ui, node| match node {
|
.ctx(|ui, node| match node {
|
||||||
NewFileTree::Leaf { filename } => ui
|
FileTree::File { path: filename } => ui
|
||||||
.button("Open Binary")
|
.button("Open Binary")
|
||||||
.clicked()
|
.clicked()
|
||||||
.then_some(Action::LoadBinary(DataSource::File(filename.clone()))),
|
.then_some(Action::LoadBinary(DataSource::File(filename.clone()))),
|
||||||
NewFileTree::Branch { .. } => None,
|
FileTree::Directory { .. } => None,
|
||||||
},
|
}),
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
use egui::{InnerResponse, TextBuffer, Widget};
|
|
||||||
use std::borrow::Cow;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
#[derive(Clone, PartialOrd, Ord, Eq, PartialEq)]
|
|
||||||
pub enum NewFileTree {
|
|
||||||
Branch {
|
|
||||||
path: PathBuf,
|
|
||||||
children: Vec<NewFileTree>,
|
|
||||||
},
|
|
||||||
Leaf {
|
|
||||||
filename: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NewFileTree {
|
|
||||||
pub fn build(path: &Path) -> Self {
|
|
||||||
match path.is_dir() {
|
|
||||||
true => Self::Branch {
|
|
||||||
path: path.to_path_buf(),
|
|
||||||
children: {
|
|
||||||
let mut children: Vec<Self> = std::fs::read_dir(path)
|
|
||||||
.map(|ok| {
|
|
||||||
ok.filter_map(|file| file.ok())
|
|
||||||
.map(|file| Self::build(&path.join(file.file_name())))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
children.sort_unstable();
|
|
||||||
children
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false => Self::Leaf {
|
|
||||||
filename: path.to_path_buf(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TreeView for NewFileTree {
|
|
||||||
fn node(&self) -> Node<'_, Self> {
|
|
||||||
match self {
|
|
||||||
Self::Branch { path, children } => Node::Branch {
|
|
||||||
name: path.file_name().unwrap().to_string_lossy(),
|
|
||||||
children,
|
|
||||||
},
|
|
||||||
Self::Leaf { filename } => Node::Leaf {
|
|
||||||
name: filename.file_name().unwrap().to_string_lossy(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ContextTreeEvent<'t, A, T: TreeView> {
|
|
||||||
Clicked(Node<'t, T>),
|
|
||||||
ContextAction(A, Node<'t, T>),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
|
||||||
pub enum Node<'t, T: TreeView> {
|
|
||||||
Leaf {
|
|
||||||
name: Cow<'t, str>,
|
|
||||||
},
|
|
||||||
Branch {
|
|
||||||
name: Cow<'t, str>,
|
|
||||||
children: &'t Vec<T>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait TreeView: Sized + Clone {
|
|
||||||
fn node(&self) -> Node<'_, Self>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ContextTree<A, T: TreeView> {
|
|
||||||
root: T,
|
|
||||||
context: Box<dyn FnMut(&mut egui::Ui, &T) -> Option<A>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Action, Tree> ContextTree<Action, Tree>
|
|
||||||
where
|
|
||||||
Tree: TreeView,
|
|
||||||
{
|
|
||||||
type Ctx = Box<dyn FnMut(&mut egui::Ui, &Tree) -> Option<Action>>;
|
|
||||||
|
|
||||||
pub fn new<T: FnMut(&mut egui::Ui, &Tree) -> Option<Action> + 'static>(
|
|
||||||
root: Tree,
|
|
||||||
context: T,
|
|
||||||
) -> Self {
|
|
||||||
Self {
|
|
||||||
root,
|
|
||||||
context: Box::new(context),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ctx_menu<'t>(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
ctx: &mut Self::Ctx,
|
|
||||||
tree: &'t Tree,
|
|
||||||
) -> Option<ContextTreeEvent<'t, Action, Tree>> {
|
|
||||||
ctx(ui, tree).and_then(|a| Some(ContextTreeEvent::ContextAction(a, tree.node())))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ui(
|
|
||||||
&mut self,
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
) -> InnerResponse<Option<ContextTreeEvent<'_, Action, Tree>>> {
|
|
||||||
let mut action = None;
|
|
||||||
let frame_response = egui::Frame::new().show(ui, |ui| {
|
|
||||||
egui::ScrollArea::both()
|
|
||||||
.auto_shrink([false; 2])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
Self::ui_inner(ui, &mut action, &mut self.context, &self.root);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
InnerResponse::new(action, frame_response.response)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui_inner<'ctx, 't>(
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
action: &mut Option<ContextTreeEvent<'t, Action, Tree>>,
|
|
||||||
ctx: &mut Self::Ctx,
|
|
||||||
tree: &'t Tree,
|
|
||||||
) {
|
|
||||||
match tree.node() {
|
|
||||||
Node::Leaf { ref name } => {
|
|
||||||
let response = egui::Button::new(name.as_str()).frame(false).ui(ui);
|
|
||||||
|
|
||||||
response.context_menu(|ui| *action = Self::ctx_menu(ui, ctx, tree));
|
|
||||||
|
|
||||||
if response.clicked() {
|
|
||||||
*action = Some(ContextTreeEvent::Clicked(tree.node()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Node::Branch {
|
|
||||||
ref name,
|
|
||||||
ref children,
|
|
||||||
} => {
|
|
||||||
let show_response = egui::CollapsingHeader::new(name.as_str()).show(ui, |ui| {
|
|
||||||
for child in children.iter() {
|
|
||||||
Self::ui_inner(ui, action, ctx, child);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
show_response
|
|
||||||
.header_response
|
|
||||||
.context_menu(|ui| *action = Self::ctx_menu(ui, ctx, tree));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
use egui::{CollapsingHeader, InnerResponse, Widget};
|
|
||||||
use std::cmp::Ordering;
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
|
|
||||||
pub enum FileTree {
|
|
||||||
Branch {
|
|
||||||
path: PathBuf,
|
|
||||||
children: Vec<FileTree>,
|
|
||||||
},
|
|
||||||
Leaf {
|
|
||||||
filename: PathBuf,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for FileTree {}
|
|
||||||
|
|
||||||
impl PartialEq<Self> for FileTree {
|
|
||||||
fn eq(&self, other: &Self) -> bool {
|
|
||||||
matches!(
|
|
||||||
(self, other),
|
|
||||||
(Self::Branch { .. }, Self::Branch { .. }) | (Self::Leaf { .. }, Self::Leaf { .. })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd<Self> for FileTree {
|
|
||||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
|
||||||
Some(self.cmp(other))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for FileTree {
|
|
||||||
fn cmp(&self, other: &Self) -> Ordering {
|
|
||||||
match (self, other) {
|
|
||||||
(Self::Leaf { filename: a }, Self::Leaf { filename: b }) => a.cmp(b),
|
|
||||||
(Self::Branch { path: a, .. }, Self::Branch { path: b, .. }) => a.cmp(b),
|
|
||||||
(Self::Branch { .. }, _) => Ordering::Less,
|
|
||||||
(_, Self::Branch { .. }) => Ordering::Greater,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum FileTreeEvent<A> {
|
|
||||||
Clicked(PathBuf),
|
|
||||||
ContextAction(A),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FileTree {
|
|
||||||
pub fn ui<A>(
|
|
||||||
&self,
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
ctx: &mut impl FnMut(&mut egui::Ui, &Path) -> Option<A>,
|
|
||||||
) -> InnerResponse<Option<FileTreeEvent<A>>> {
|
|
||||||
let mut action = None;
|
|
||||||
let frame_response = egui::Frame::new().show(ui, |ui| {
|
|
||||||
egui::ScrollArea::both()
|
|
||||||
.auto_shrink([false; 2])
|
|
||||||
.show(ui, |ui| {
|
|
||||||
self.ui_inner(ui, &mut action, ctx);
|
|
||||||
})
|
|
||||||
});
|
|
||||||
InnerResponse::new(action, frame_response.response)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ui_inner<A>(
|
|
||||||
&self,
|
|
||||||
ui: &mut egui::Ui,
|
|
||||||
action: &mut Option<FileTreeEvent<A>>,
|
|
||||||
ctx: &mut impl FnMut(&mut egui::Ui, &Path) -> Option<A>,
|
|
||||||
) {
|
|
||||||
match self {
|
|
||||||
Self::Leaf { filename } => {
|
|
||||||
let response = egui::Button::new(filename.file_name().unwrap().to_string_lossy())
|
|
||||||
.frame(false)
|
|
||||||
.ui(ui);
|
|
||||||
|
|
||||||
response.context_menu(|ui| {
|
|
||||||
if let Some(a) = ctx(ui, filename) {
|
|
||||||
*action = Some(FileTreeEvent::ContextAction(a));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if response.clicked() {
|
|
||||||
*action = Some(FileTreeEvent::Clicked(PathBuf::from(filename)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Branch { path, children } => {
|
|
||||||
CollapsingHeader::new(path.file_name().unwrap().to_string_lossy())
|
|
||||||
.show(ui, |ui| {
|
|
||||||
for child in children {
|
|
||||||
child.ui_inner(ui, action, ctx);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.header_response
|
|
||||||
.context_menu(|ui| {
|
|
||||||
if let Some(a) = ctx(ui, path) {
|
|
||||||
*action = Some(FileTreeEvent::ContextAction(a));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build(path: &Path) -> Self {
|
|
||||||
match path.is_dir() {
|
|
||||||
true => Self::Branch {
|
|
||||||
path: path.to_path_buf(),
|
|
||||||
children: {
|
|
||||||
let mut children: Vec<Self> = std::fs::read_dir(path)
|
|
||||||
.map(|ok| {
|
|
||||||
ok.filter_map(|file| file.ok())
|
|
||||||
.map(|file| Self::build(&path.join(file.file_name())))
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
children.sort_unstable();
|
|
||||||
children
|
|
||||||
},
|
|
||||||
},
|
|
||||||
false => Self::Leaf {
|
|
||||||
filename: path.to_path_buf(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,6 +3,5 @@
|
|||||||
#![feature(inherent_associated_types)]
|
#![feature(inherent_associated_types)]
|
||||||
|
|
||||||
pub mod buttons;
|
pub mod buttons;
|
||||||
pub mod context_tree;
|
|
||||||
pub mod file_tree;
|
|
||||||
pub mod tabs;
|
pub mod tabs;
|
||||||
|
pub mod tree;
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
use super::{Node, TreeView};
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialOrd, Ord, Eq, PartialEq)]
|
||||||
|
pub enum FileTree {
|
||||||
|
Directory {
|
||||||
|
path: PathBuf,
|
||||||
|
children: Vec<FileTree>,
|
||||||
|
},
|
||||||
|
File {
|
||||||
|
path: PathBuf,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileTree {
|
||||||
|
pub fn build(path: &Path) -> Self {
|
||||||
|
match path.is_dir() {
|
||||||
|
true => Self::Directory {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
children: {
|
||||||
|
let mut children: Vec<Self> = std::fs::read_dir(path)
|
||||||
|
.map(|ok| {
|
||||||
|
ok.filter_map(|file| file.ok())
|
||||||
|
.map(|file| Self::build(&path.join(file.file_name())))
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.unwrap_or_default();
|
||||||
|
children.sort_unstable();
|
||||||
|
children
|
||||||
|
},
|
||||||
|
},
|
||||||
|
false => Self::File {
|
||||||
|
path: path.to_path_buf(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn filename(&self) -> &Path {
|
||||||
|
match self {
|
||||||
|
Self::Directory { path, .. } | Self::File { path } => path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TreeView for FileTree {
|
||||||
|
fn node(&self) -> Node<'_, Self> {
|
||||||
|
match self {
|
||||||
|
Self::Directory { path, children } => {
|
||||||
|
Node::Branch(path.file_name().unwrap().to_string_lossy(), children)
|
||||||
|
}
|
||||||
|
Self::File { path: filename } => {
|
||||||
|
Node::Leaf(filename.file_name().unwrap().to_string_lossy())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
use egui::response::InnerResponse;
|
||||||
|
use egui::{Ui, Widget};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub mod file;
|
||||||
|
|
||||||
|
pub trait TreeView: Sized + Clone {
|
||||||
|
fn node(&self) -> Node<'_, Self>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum TreeEvent<'t, A, T: TreeView> {
|
||||||
|
Clicked(&'t T),
|
||||||
|
ContextAction(A, &'t T),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
|
||||||
|
pub enum Node<'t, T: TreeView> {
|
||||||
|
Leaf(Cow<'t, str>),
|
||||||
|
Branch(Cow<'t, str>, &'t Vec<T>),
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CtxMenu<A, T: TreeView>(pub Box<dyn FnMut(&mut Ui, &T) -> Option<A>>);
|
||||||
|
|
||||||
|
impl<Action, Tree: TreeView> CtxMenu<Action, Tree> {
|
||||||
|
pub fn new(f: impl FnMut(&mut Ui, &Tree) -> Option<Action> + 'static) -> Self {
|
||||||
|
Self(Box::new(f))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui<'t>(&mut self, ui: &mut Ui, tree: &'t Tree) -> Option<TreeEvent<'t, Action, Tree>> {
|
||||||
|
self.0(ui, tree).and_then(|a| Some(TreeEvent::ContextAction(a, tree)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct MenuTree<A, T: TreeView> {
|
||||||
|
root: Option<T>,
|
||||||
|
context: Option<CtxMenu<A, T>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<Action, Tree> MenuTree<Action, Tree>
|
||||||
|
where
|
||||||
|
Tree: TreeView,
|
||||||
|
{
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
root: None,
|
||||||
|
context: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn root(mut self, tree: Option<Tree>) -> Self {
|
||||||
|
self.root = tree.clone();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ctx(mut self, ctx: impl FnMut(&mut Ui, &Tree) -> Option<Action> + 'static) -> Self {
|
||||||
|
self.context = Some(CtxMenu::new(ctx));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ui(&mut self, ui: &mut Ui) -> InnerResponse<Option<TreeEvent<'_, Action, Tree>>> {
|
||||||
|
let mut action = None;
|
||||||
|
|
||||||
|
let frame_response = egui::Frame::new().show(ui, |ui| {
|
||||||
|
egui::ScrollArea::both()
|
||||||
|
.auto_shrink([false; 2])
|
||||||
|
.show(ui, |ui| {
|
||||||
|
let Some(root) = &self.root else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Some(ctx) = &mut self.context else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
Self::ui_inner(ui, &mut action, ctx, root);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
InnerResponse::new(action, frame_response.response)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ui_inner<'ctx, 't>(
|
||||||
|
ui: &mut Ui,
|
||||||
|
action: &mut Option<TreeEvent<'t, Action, Tree>>,
|
||||||
|
ctx: &mut CtxMenu<Action, Tree>,
|
||||||
|
tree: &'t Tree,
|
||||||
|
) {
|
||||||
|
match tree.node() {
|
||||||
|
Node::Leaf(name) => {
|
||||||
|
let response = egui::Button::new(name.as_str()).frame(false).ui(ui);
|
||||||
|
|
||||||
|
response.context_menu(|ui| *action = ctx.ui(ui, tree));
|
||||||
|
|
||||||
|
if response.clicked() {
|
||||||
|
*action = Some(TreeEvent::Clicked(tree));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Node::Branch(name, children) => {
|
||||||
|
let show_response = egui::CollapsingHeader::new(name.as_str()).show(ui, |ui| {
|
||||||
|
for child in children.iter() {
|
||||||
|
Self::ui_inner(ui, action, ctx, child);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
show_response
|
||||||
|
.header_response
|
||||||
|
.context_menu(|ui| *action = ctx.ui(ui, tree));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user