1use feather_ui::color::sRGB;
5use feather_ui::gen_id;
6
7use feather_ui::component::domain_line::DomainLine;
8use feather_ui::component::domain_point::DomainPoint;
9use feather_ui::component::mouse_area::MouseArea;
10use feather_ui::component::region::Region;
11use feather_ui::component::shape::{Shape, ShapeKind};
12use feather_ui::component::window::Window;
13use feather_ui::component::{ChildOf, mouse_area};
14use feather_ui::input::MouseButton;
15use feather_ui::layout::{base, fixed, leaf};
16use feather_ui::persist::FnPersist;
17use feather_ui::ultraviolet::Vec2;
18use feather_ui::{
19 AbsRect, App, CrossReferenceDomain, DRect, DataID, FILL_DRECT, Slot, SourceID, WrapEventEx, im,
20};
21use std::collections::HashSet;
22use std::f32;
23use std::sync::Arc;
24
25#[derive(PartialEq, Clone, Debug)]
26struct GraphState {
27 nodes: Vec<Vec2>,
28 edges: HashSet<(usize, usize)>,
29 offset: Vec2,
30 selected: Option<usize>,
31}
32
33struct BasicApp {}
34
35#[derive(Default, Clone, feather_macro::Area)]
36struct MinimalArea {
37 area: DRect,
38}
39
40impl base::Empty for MinimalArea {}
41impl base::ZIndex for MinimalArea {}
42impl base::Margin for MinimalArea {}
43impl base::Anchor for MinimalArea {}
44impl base::Limits for MinimalArea {}
45impl base::RLimits for MinimalArea {}
46impl fixed::Prop for MinimalArea {}
47impl fixed::Child for MinimalArea {}
48impl leaf::Prop for MinimalArea {}
49
50const NODE_RADIUS: f32 = 25.0;
51
52impl FnPersist<GraphState, im::HashMap<Arc<SourceID>, Option<Window>>> for BasicApp {
53 type Store = (GraphState, im::HashMap<Arc<SourceID>, Option<Window>>);
54
55 fn init(&self) -> Self::Store {
56 (
57 GraphState {
58 nodes: Vec::new(),
59 edges: HashSet::new(),
60 offset: Vec2::zero(),
61 selected: None,
62 },
63 im::HashMap::new(),
64 )
65 }
66 fn call(
67 &mut self,
68 mut store: Self::Store,
69 args: &GraphState,
70 ) -> (Self::Store, im::HashMap<Arc<SourceID>, Option<Window>>) {
71 if store.0 != *args {
72 let mut children: im::Vector<Option<Box<ChildOf<dyn fixed::Prop>>>> = im::Vector::new();
73 let domain: Arc<CrossReferenceDomain> = Default::default();
74
75 let mut node_ids: Vec<Arc<SourceID>> = Vec::new();
76
77 for i in 0..args.nodes.len() {
78 let node = args.nodes[i];
79 const BASE: sRGB = sRGB::new(0.2, 0.7, 0.4, 1.0);
80
81 let point = DomainPoint::new(gen_id!(), domain.clone());
82 node_ids.push(point.id.clone());
83
84 let circle = Shape::<DRect, { ShapeKind::Circle as u8 }>::new(
85 gen_id!(),
86 FILL_DRECT.into(),
87 0.0,
88 0.0,
89 Vec2::new(0.0, 20.0),
90 if args.selected == Some(i) {
91 sRGB::new(0.7, 1.0, 0.8, 1.0)
92 } else {
93 BASE
94 },
95 BASE,
96 );
97
98 let bag = Region::<MinimalArea>::new(
99 gen_id!(gen_id!(), i),
100 MinimalArea {
101 area: AbsRect::new(
102 node.x - NODE_RADIUS,
103 node.y - NODE_RADIUS,
104 node.x + NODE_RADIUS,
105 node.y + NODE_RADIUS,
106 )
107 .into(),
108 }
109 .into(),
110 feather_ui::children![fixed::Prop, point, circle],
111 );
112
113 children.push_back(Some(Box::new(bag)));
114 }
115
116 let edge_id = gen_id!();
117
118 for (a, b) in &args.edges {
119 let line = DomainLine::<()> {
120 id: edge_id
121 .child(DataID::Int(*a as i64))
122 .child(DataID::Int(*b as i64)),
123 fill: sRGB::white(),
124 domain: domain.clone(),
125 start: node_ids[*a].clone(),
126 end: node_ids[*b].clone(),
127 props: ().into(),
128 };
129
130 children.push_back(Some(Box::new(line)));
131 }
132
133 let subregion = Region::new(
134 gen_id!(),
135 MinimalArea {
136 area: AbsRect::new(
137 args.offset.x,
138 args.offset.y,
139 args.offset.x + 10000.0,
140 args.offset.y + 10000.0,
141 )
142 .into(),
143 }
144 .into(),
145 children,
146 );
147
148 let mousearea: MouseArea<MinimalArea> = MouseArea::new(
149 gen_id!(),
150 MinimalArea { area: FILL_DRECT },
151 Some(4.0),
152 [
153 Some(Slot(feather_ui::APP_SOURCE_ID.into(), 0)),
154 Some(Slot(feather_ui::APP_SOURCE_ID.into(), 0)),
155 Some(Slot(feather_ui::APP_SOURCE_ID.into(), 0)),
156 None,
157 None,
158 None,
159 ],
160 );
161
162 let region = Region::new(
163 gen_id!(),
164 MinimalArea { area: FILL_DRECT }.into(),
165 feather_ui::children![fixed::Prop, subregion, mousearea],
166 );
167
168 let window = Window::new(
169 gen_id!(),
170 feather_ui::winit::window::Window::default_attributes()
171 .with_title(env!("CARGO_CRATE_NAME"))
172 .with_resizable(true),
173 Box::new(region),
174 );
175
176 store.1 = im::HashMap::new();
177 store.1.insert(window.id.clone(), Some(window));
178 store.0 = args.clone();
179 }
180 let windows = store.1.clone();
181 (store, windows)
182 }
183}
184
185fn main() {
186 let handle_input = Box::new(
187 |e: mouse_area::MouseAreaEvent,
188 mut appdata: GraphState|
189 -> Result<GraphState, GraphState> {
190 match e {
191 mouse_area::MouseAreaEvent::OnClick(MouseButton::Left, pos) => {
192 if let Some(selected) = appdata.selected {
193 for i in 0..appdata.nodes.len() {
194 let diff = appdata.nodes[i] - pos + appdata.offset;
195 if diff.dot(diff) < NODE_RADIUS * NODE_RADIUS {
196 if appdata.edges.contains(&(selected, i)) {
197 appdata.edges.remove(&(i, selected));
198 appdata.edges.remove(&(selected, i));
199 } else {
200 appdata.edges.insert((selected, i));
201 appdata.edges.insert((i, selected));
202 }
203 break;
204 }
205 }
206
207 appdata.selected = None;
208 } else {
209 for i in 0..appdata.nodes.len() {
211 let diff = appdata.nodes[i] - pos + appdata.offset;
212 if diff.dot(diff) < NODE_RADIUS * NODE_RADIUS {
213 appdata.selected = Some(i);
214 return Ok(appdata);
215 }
216 }
217
218 appdata.nodes.push(pos - appdata.offset);
220 }
221
222 Ok(appdata)
223 }
224 mouse_area::MouseAreaEvent::OnDblClick(MouseButton::Left, pos) => {
225 appdata.nodes.push(pos - appdata.offset);
227 Ok(appdata)
228 }
229 mouse_area::MouseAreaEvent::OnDrag(MouseButton::Left, diff) => {
230 appdata.offset += diff;
231 Ok(appdata)
232 }
233 _ => Ok(appdata),
234 }
235 }
236 .wrap(),
237 );
238
239 let (mut app, event_loop): (
240 App<GraphState, BasicApp>,
241 feather_ui::winit::event_loop::EventLoop<()>,
242 ) = App::new(
243 GraphState {
244 nodes: vec![],
245 edges: HashSet::new(),
246 offset: Vec2::new(-5000.0, -5000.0),
247 selected: None,
248 },
249 vec![handle_input],
250 BasicApp {},
251 |_| (),
252 )
253 .unwrap();
254
255 event_loop.run_app(&mut app).unwrap();
256}