basic_rs/
basic-rs.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2025 Fundament Research Institute <https://fundament.institute>
3
4use 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}