Compare commits

...

1 commit

Author SHA1 Message Date
Emil Ernerfeldt
9b0de108c8 wip: node-graph 2021-02-08 19:09:59 +01:00
6 changed files with 226 additions and 0 deletions

11
Cargo.lock generated
View file

@ -661,6 +661,7 @@ dependencies = [
"epi", "epi",
"image", "image",
"serde", "serde",
"slotmap",
"syntect", "syntect",
] ]
@ -1812,6 +1813,16 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 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]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.6.1" version = "1.6.1"

View file

@ -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) { pub fn stroke_ui(ui: &mut crate::Ui, stroke: &mut epaint::Stroke, text: &str) {

View file

@ -22,6 +22,10 @@ epi = { version = "0.9.0", path = "../epi" }
image = { version = "0.23", default_features = false, features = ["jpeg", "png"], optional = true } 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 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": # feature "persistence":
serde = { version = "1", features = ["derive"], optional = true } serde = { version = "1", features = ["derive"], optional = true }

View file

@ -4,6 +4,7 @@ mod easy_mark_editor;
mod fractal_clock; mod fractal_clock;
#[cfg(feature = "http")] #[cfg(feature = "http")]
mod http_app; mod http_app;
pub mod node_graph;
pub use color_test::ColorTest; pub use color_test::ColorTest;
pub use demo::DemoApp; pub use demo::DemoApp;

View 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);
}
}
}

View file

@ -9,6 +9,7 @@ pub struct Apps {
http: crate::apps::HttpApp, http: crate::apps::HttpApp,
clock: crate::apps::FractalClock, clock: crate::apps::FractalClock,
color_test: crate::apps::ColorTest, color_test: crate::apps::ColorTest,
node_graph: crate::apps::node_graph::NodeGraph,
} }
impl Apps { impl Apps {
@ -20,6 +21,7 @@ impl Apps {
("http", &mut self.http as &mut dyn epi::App), ("http", &mut self.http as &mut dyn epi::App),
("clock", &mut self.clock as &mut dyn epi::App), ("clock", &mut self.clock as &mut dyn epi::App),
("colors", &mut self.color_test 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() .into_iter()
} }
@ -43,11 +45,17 @@ impl epi::App for WrapApp {
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
fn load(&mut self, storage: &dyn epi::Storage) { fn load(&mut self, storage: &dyn epi::Storage) {
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() *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")] #[cfg(feature = "persistence")]
fn save(&mut self, storage: &mut dyn epi::Storage) { fn save(&mut self, storage: &mut dyn epi::Storage) {
epi::set_value(storage, epi::APP_KEY, self); epi::set_value(storage, epi::APP_KEY, self);
// for (_, app) in self.iter_mut() {
// app.save(storage);
// }
} }
fn warm_up_enabled(&self) -> bool { fn warm_up_enabled(&self) -> bool {