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