1use bytemuck::Zeroable;
5use feather_macro::*;
6use feather_ui::color::{sRGB, sRGB32};
7use feather_ui::component::button::Button;
8use feather_ui::component::region::Region;
9use feather_ui::component::text::Text;
10use feather_ui::component::window::Window;
11use feather_ui::component::{mouse_area, shape};
12use feather_ui::layout::{fixed, leaf};
13use feather_ui::persist::{FnPersist2, FnPersistStore};
14use feather_ui::{
15 AbsRect, App, DAbsPoint, DAbsRect, DPoint, DRect, PxRect, RelRect, ScopeID, Slot, SourceID,
16 UNSIZED_AXIS, gen_id, im, winit,
17};
18use std::rc::Rc;
19use std::sync::Arc;
20
21#[derive(PartialEq, Clone, Debug)]
22struct CounterState {
23 count: i32,
24}
25
26#[derive(Default, Empty, Area, Anchor, ZIndex, Limits, RLimits, Padding)]
27struct FixedData {
28 area: DRect,
29 anchor: DPoint,
30 limits: feather_ui::DLimits,
31 rlimits: feather_ui::RelLimits,
32 padding: DAbsRect,
33 zindex: i32,
34}
35
36impl fixed::Prop for FixedData {}
37impl fixed::Child for FixedData {}
38impl leaf::Prop for FixedData {}
39impl leaf::Padded for FixedData {}
40
41struct BasicApp {}
42
43impl FnPersistStore for BasicApp {
44 type Store = (CounterState, im::HashMap<Arc<SourceID>, Option<Window>>);
45}
46
47impl FnPersist2<CounterState, ScopeID<'_>, im::HashMap<Arc<SourceID>, Option<Window>>>
48 for BasicApp
49{
50 fn init(&self) -> Self::Store {
51 (CounterState { count: -1 }, im::HashMap::new())
52 }
53 fn call(
54 &mut self,
55 mut store: Self::Store,
56 app: CounterState,
57 mut id: ScopeID<'_>,
58 ) -> (Self::Store, im::HashMap<Arc<SourceID>, Option<Window>>) {
59 if store.0 != app {
60 let button = {
61 let text = Text::<FixedData> {
62 id: gen_id!(id),
63 props: Rc::new(FixedData {
64 area: AbsRect::new(8.0, 0.0, 8.0, 0.0)
65 + RelRect::new(0.0, 0.5, UNSIZED_AXIS, UNSIZED_AXIS),
66 anchor: feather_ui::RelPoint::new(0.0, 0.5).into(),
67 ..Default::default()
68 }),
69 color: sRGB::new(1.0, 1.0, 0.0, 1.0),
70 text: format!("Clicks: {}", app.count),
71 font_size: 40.0,
72 line_height: 56.0,
73 align: Some(cosmic_text::Align::Center),
74 ..Default::default()
75 };
76
77 let rect = shape::round_rect::<DRect>(
78 gen_id!(id),
79 feather_ui::FILL_DRECT,
80 0.0,
81 0.0,
82 wide::f32x4::splat(10.0),
83 sRGB::new(0.2, 0.7, 0.4, 1.0),
84 sRGB::transparent(),
85 DAbsPoint::zero(),
86 );
87
88 Button::<FixedData>::new(
89 gen_id!(id),
90 FixedData {
91 area: AbsRect::new(45.0, 45.0, 0.0, 0.0)
92 + RelRect::new(0.0, 0.0, UNSIZED_AXIS, 1.0),
93
94 ..Default::default()
95 },
96 Slot(feather_ui::APP_SOURCE_ID.into(), 0),
97 feather_ui::children![fixed::Prop, rect, text],
98 )
99 };
100
101 let block = {
102 let text = Text::<FixedData> {
103 id: gen_id!(id),
104 props: Rc::new(FixedData {
105 area: RelRect::new(0.5, 0.0, UNSIZED_AXIS, UNSIZED_AXIS).into(),
106 limits: feather_ui::AbsLimits::new(.., 10.0..200.0).into(),
107 rlimits: feather_ui::RelLimits::new(..1.0, ..),
108 anchor: feather_ui::RelPoint::new(0.5, 0.0).into(),
109 padding: AbsRect::new(8.0, 8.0, 8.0, 8.0).into(),
110 ..Default::default()
111 }),
112 text: (0..app.count).map(|_| "█").collect::<String>(),
113 font_size: 40.0,
114 line_height: 56.0,
115 wrap: feather_ui::cosmic_text::Wrap::WordOrGlyph,
116 align: Some(cosmic_text::Align::Center),
117 ..Default::default()
118 };
119
120 let rect = shape::round_rect::<DRect>(
121 gen_id!(id),
122 feather_ui::FILL_DRECT,
123 0.0,
124 0.0,
125 wide::f32x4::splat(10.0),
126 sRGB::new(0.7, 0.2, 0.4, 1.0),
127 sRGB::transparent(),
128 DAbsPoint::zero(),
129 );
130
131 Region::<FixedData>::new_layer(
132 gen_id!(id),
133 FixedData {
134 area: AbsRect::new(45.0, 245.0, 0.0, 0.0)
135 + RelRect::new(0.0, 0.0, UNSIZED_AXIS, UNSIZED_AXIS),
136 limits: feather_ui::AbsLimits::new(100.0..300.0, ..).into(),
137 ..Default::default()
138 },
139 sRGB32::from_alpha(128),
140 0.0,
141 feather_ui::children![fixed::Prop, rect, text],
142 )
143 };
144
145 let pixel = shape::round_rect::<DRect>(
146 gen_id!(id),
147 PxRect::new(1.0, 1.0, 2.0, 2.0).into(),
148 0.0,
149 0.0,
150 wide::f32x4::zeroed(),
151 sRGB::new(1.0, 1.0, 1.0, 1.0),
152 sRGB::transparent(),
153 DAbsPoint::zero(),
154 );
155
156 let region = Region::new(
157 gen_id!(id),
158 FixedData {
159 area: AbsRect::new(90.0, 90.0, 0.0, 200.0)
160 + RelRect::new(0.0, 0.0, UNSIZED_AXIS, 0.0),
161 zindex: 0,
162 ..Default::default()
163 },
164 feather_ui::children![fixed::Prop, button, block, pixel],
165 );
166 let window = Window::new(
167 gen_id!(id),
168 winit::window::Window::default_attributes()
169 .with_title(env!("CARGO_CRATE_NAME"))
170 .with_resizable(true),
171 Box::new(region),
172 );
173
174 store.1 = im::HashMap::new();
175 store.1.insert(window.id.clone(), Some(window));
176 store.0 = app.clone();
177 }
178 let windows = store.1.clone();
179 (store, windows)
180 }
181}
182
183use feather_ui::WrapEventEx;
184
185fn main() {
186 let onclick = Box::new(
187 |_: mouse_area::MouseAreaEvent,
188 mut appdata: feather_ui::AccessCell<CounterState>|
189 -> feather_ui::InputResult<()> {
190 {
191 appdata.count += 1;
192 feather_ui::InputResult::Consume(())
193 }
194 }
195 .wrap(),
196 );
197
198 let (mut app, event_loop, _, _) = App::<CounterState, BasicApp>::new::<()>(
199 CounterState { count: 0 },
200 vec![onclick],
201 BasicApp {},
202 |_| (),
203 )
204 .unwrap();
205
206 event_loop.run_app(&mut app).unwrap();
207}