wip: node-graph
This commit is contained in:
parent
f5431f308a
commit
9b0de108c8
6 changed files with 226 additions and 0 deletions
11
Cargo.lock
generated
11
Cargo.lock
generated
|
@ -661,6 +661,7 @@ dependencies = [
|
|||
"epi",
|
||||
"image",
|
||||
"serde",
|
||||
"slotmap",
|
||||
"syntect",
|
||||
]
|
||||
|
||||
|
@ -1812,6 +1813,16 @@ version = "0.4.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
||||
|
||||
[[package]]
|
||||
name = "slotmap"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab3003725ae562cf995f3dc82bb99e70926e09000396816765bb6d7adbe740b1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smallvec"
|
||||
version = "1.6.1"
|
||||
|
|
|
@ -48,6 +48,17 @@ pub fn reset_button<T: Default + PartialEq>(ui: &mut Ui, value: &mut T) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Show a button to reset a value to its default.
|
||||
/// The button is only enabled if the value does not already have its original value.
|
||||
pub fn reset_button_with<T: Default + PartialEq>(ui: &mut Ui, value: &mut T, def: T) {
|
||||
if ui
|
||||
.add(Button::new("Reset").enabled(*value != def))
|
||||
.clicked()
|
||||
{
|
||||
*value = def;
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) {
|
||||
|
|
|
@ -22,6 +22,10 @@ epi = { version = "0.9.0", path = "../epi" }
|
|||
image = { version = "0.23", default_features = false, features = ["jpeg", "png"], optional = true }
|
||||
syntect = { version = "4", default_features = false, features = ["default-fancy"], optional = true } # optional syntax highlighting
|
||||
|
||||
# feature "node_graph":
|
||||
slotmap = { version = "1", default_features = false, features = ["serde"] } # TODO: optional
|
||||
# petgraph = { version = "0.5", default_features = false, features = [] } # TODO: optional
|
||||
|
||||
# feature "persistence":
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ mod easy_mark_editor;
|
|||
mod fractal_clock;
|
||||
#[cfg(feature = "http")]
|
||||
mod http_app;
|
||||
pub mod node_graph;
|
||||
|
||||
pub use color_test::ColorTest;
|
||||
pub use demo::DemoApp;
|
||||
|
|
191
egui_demo_lib/src/apps/node_graph.rs
Normal file
191
egui_demo_lib/src/apps/node_graph.rs
Normal file
|
@ -0,0 +1,191 @@
|
|||
use egui::{containers::*, *};
|
||||
|
||||
// pub use petgraph::graph::{EdgeIndex as EdgeId, NodeIndex as NodeId};
|
||||
slotmap::new_key_type! { pub struct EdgeId; }
|
||||
slotmap::new_key_type! { pub struct NodeId; }
|
||||
|
||||
pub type Nodes = slotmap::SlotMap<NodeId, Node>;
|
||||
pub type Edges = slotmap::SlotMap<EdgeId, Edge>;
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
#[cfg_attr(feature = "persistence", serde(default))]
|
||||
pub struct NodeGraph {
|
||||
// graph: petgraph::Graph<Node, Edge>,
|
||||
nodes: Nodes,
|
||||
edges: Edges,
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Edge {
|
||||
nodes: [NodeId; 2],
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
pub fn new(from: NodeId, to: NodeId) -> Self {
|
||||
Self { nodes: [from, to] }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq)]
|
||||
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
|
||||
pub struct Node {
|
||||
/// PERSISTED MODE: position is relative to parent!
|
||||
rect: Rect,
|
||||
title: String,
|
||||
description: String,
|
||||
}
|
||||
|
||||
impl Default for Node {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
rect: Rect::from_center_size(Pos2::ZERO, Vec2::splat(200.0)),
|
||||
title: "unnamed".to_owned(),
|
||||
description: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Node {
|
||||
pub fn new(title: impl Into<String>, pos: impl Into<Pos2>) -> Self {
|
||||
Self {
|
||||
title: title.into(),
|
||||
rect: Rect::from_center_size(pos.into(), Vec2::splat(100.0)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn window(&mut self, id: NodeId, ui: &mut Ui) {
|
||||
let translation = ui.min_rect().min - Pos2::ZERO;
|
||||
|
||||
// TODO: set to use same layer as parent `ui`
|
||||
let response = Window::new(self.title.clone())
|
||||
.id(egui::Id::new(id))
|
||||
.collapsible(false)
|
||||
.scroll(false)
|
||||
.resizable(true)
|
||||
.default_size(self.rect.size())
|
||||
.title_bar(false)
|
||||
.current_pos(self.rect.min + translation)
|
||||
//.show_inside(ui, |ui|{ // TODO
|
||||
.show(ui.ctx(), |ui| {
|
||||
self.ui(ui);
|
||||
});
|
||||
let response = response.expect("Window can't be closed");
|
||||
self.rect = response.rect.translate(-translation);
|
||||
}
|
||||
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
let Self {
|
||||
rect: _,
|
||||
title,
|
||||
description,
|
||||
} = self;
|
||||
|
||||
// Manual titlebar with editable title:
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.add(
|
||||
TextEdit::singleline(title)
|
||||
.desired_width(32.0)
|
||||
.text_style(TextStyle::Heading)
|
||||
.frame(false),
|
||||
);
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
// Body:
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Description:");
|
||||
ui.text_edit_singleline(description);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Edge {
|
||||
fn ui(&self, ui: &Ui, nodes: &Nodes) {
|
||||
let translation = ui.min_rect().min - Pos2::ZERO;
|
||||
|
||||
if let (Some(node0), Some(node1)) = (nodes.get(self.nodes[0]), nodes.get(self.nodes[1])) {
|
||||
let rects = [
|
||||
node0.rect.translate(translation),
|
||||
node1.rect.translate(translation),
|
||||
];
|
||||
|
||||
let x = lerp(rects[0].center().x..=rects[1].center().x, 0.5);
|
||||
let y = lerp(rects[0].center().y..=rects[1].center().y, 0.5);
|
||||
|
||||
let p0 = rects[0].clamp(pos2(x, y));
|
||||
let p1 = rects[1].clamp(pos2(x, y));
|
||||
|
||||
let stroke = Stroke::new(2.0, Color32::from_gray(200));
|
||||
ui.painter().arrow(p0, p1 - p0, stroke);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeGraph {
|
||||
fn egiu_deps() -> Self {
|
||||
let mut graph = Self::default();
|
||||
let emath = graph.add_node(Node::new("emath", [200., 500.]));
|
||||
let epaint = graph.add_node(Node::new("epaint", [200., 400.]));
|
||||
let egui = graph.add_node(Node::new("egui", [200., 300.]));
|
||||
|
||||
graph.add_edge(Edge::new(egui, emath));
|
||||
graph.add_edge(Edge::new(egui, epaint));
|
||||
|
||||
graph
|
||||
}
|
||||
|
||||
fn add_node(&mut self, node: Node) -> NodeId {
|
||||
self.nodes.insert(node)
|
||||
}
|
||||
|
||||
fn add_edge(&mut self, edge: Edge) -> EdgeId {
|
||||
self.edges.insert(edge)
|
||||
}
|
||||
}
|
||||
|
||||
impl epi::App for NodeGraph {
|
||||
fn name(&self) -> &str {
|
||||
"Node Graph"
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
fn load(&mut self, storage: &dyn epi::Storage) {
|
||||
*self = epi::get_value(storage, "egui_demo_lib/apps/node_graph").unwrap_or_default()
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
||||
epi::set_value(storage, "egui_demo_lib/apps/node_graph", self);
|
||||
}
|
||||
|
||||
fn update(&mut self, ctx: &egui::CtxRef, _frame: &mut epi::Frame<'_>) {
|
||||
// TODO: side panel with "add windows" and whatnot
|
||||
|
||||
egui::SidePanel::left("control_ui", 100.0).show(ctx, |ui| self.control_ui(ui));
|
||||
|
||||
egui::CentralPanel::default()
|
||||
.frame(Frame::dark_canvas(&ctx.style()))
|
||||
.show(ctx, |ui| self.contents_ui(ui));
|
||||
}
|
||||
}
|
||||
|
||||
impl NodeGraph {
|
||||
pub fn control_ui(&mut self, ui: &mut Ui) {
|
||||
// egui::reset_button_with(ui, self, Self::egiu_deps());
|
||||
if ui.button("Reset").clicked() {
|
||||
*self = Self::egiu_deps();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contents_ui(&mut self, ui: &mut Ui) {
|
||||
for (node_id, node) in &mut self.nodes {
|
||||
node.window(node_id, ui);
|
||||
}
|
||||
for (_edge_id, edge) in &mut self.edges {
|
||||
edge.ui(ui, &mut self.nodes);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ pub struct Apps {
|
|||
http: crate::apps::HttpApp,
|
||||
clock: crate::apps::FractalClock,
|
||||
color_test: crate::apps::ColorTest,
|
||||
node_graph: crate::apps::node_graph::NodeGraph,
|
||||
}
|
||||
|
||||
impl Apps {
|
||||
|
@ -20,6 +21,7 @@ impl Apps {
|
|||
("http", &mut self.http as &mut dyn epi::App),
|
||||
("clock", &mut self.clock as &mut dyn epi::App),
|
||||
("colors", &mut self.color_test as &mut dyn epi::App),
|
||||
("node_graph", &mut self.node_graph as &mut dyn epi::App),
|
||||
]
|
||||
.into_iter()
|
||||
}
|
||||
|
@ -43,11 +45,17 @@ impl epi::App for WrapApp {
|
|||
#[cfg(feature = "persistence")]
|
||||
fn load(&mut self, storage: &dyn epi::Storage) {
|
||||
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default()
|
||||
// for (_, app) in self.iter_mut() { // less brittle!
|
||||
// app.load(storage); // less brittle!
|
||||
// } // less brittle!
|
||||
}
|
||||
|
||||
#[cfg(feature = "persistence")]
|
||||
fn save(&mut self, storage: &mut dyn epi::Storage) {
|
||||
epi::set_value(storage, epi::APP_KEY, self);
|
||||
// for (_, app) in self.iter_mut() {
|
||||
// app.save(storage);
|
||||
// }
|
||||
}
|
||||
|
||||
fn warm_up_enabled(&self) -> bool {
|
||||
|
|
Loading…
Reference in a new issue