1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
use rust_on_rails::prelude::*;
use crate::elements::shapes::{Rectangle};
use crate::events::{KeyboardActiveEvent, NavigateEvent};
use crate::layout::{Column, Stack, Bin, Row, Padding, Offset, Size, Opt};
use crate::components::avatar::AvatarContent;
use crate::PelicanUI;
use crate::AppPage;
use std::fmt::Debug;
use super::mobile_keyboard::MobileKeyboard;
use super::navigation::{MobileNavigator, DesktopNavigator, Header, Bumper};
#[derive(Debug, Component)]
pub struct Interface (Stack, Option<MobileInterface>, Option<DesktopInterface>);
impl OnEvent for Interface {}
impl Interface {
/// Creates a new `Interface` component, which is used to initialize the main interface for the application.
/// The `Interface` is tailored based on the device type (mobile or desktop) and contains information like
/// the start page, navigation, and user profile. Depending on the platform (mobile or desktop), it will create
/// either a `MobileInterface` or a `DesktopInterface` to manage the layout and navigation of the application.
///
/// # Parameters:
/// - **`ctx`**: A mutable reference to the context, used to retrieve resources like themes, configurations, and other application state.
/// - **`start_page`**: The starting page of the application, which should implement the `AppPage` trait. This will be the page shown when the app starts.
/// - **`navigation`**: A tuple containing:
/// - An index `usize` representing the selected page for navigation.
/// - A vector of tuples, where each tuple contains:
/// - A static string for the label (e.g., the name of the page).
/// - A static string for the description (e.g., additional info about the page).
/// - A closure that takes a mutable reference to the context and performs actions when the navigation item is clicked.
/// - **`profile`**: A tuple containing:
/// - A static string representing the username or profile identifier.
/// - The content for the avatar, which is of type `AvatarContent` (this will be used for displaying the user's avatar).
/// - A closure that takes a mutable reference to the context and allows modification of the profile information.
///
/// # Returns:
/// - **`Interface`**: The constructed `Interface` component, containing the appropriate platform-specific interface (mobile or desktop).
///
/// # Example:
/// ```rust
/// let navigation = (0, vec![
/// ("Home", "Main page", Box::new(|ctx| { /* Navigate to home page */ })),
/// ("Profile", "User profile", Box::new(|ctx| { /* Navigate to profile page */ }))
/// ]);
/// let profile = ("Ella Mae", AvatarContent::Icon("profile", AvatarIconStyle::Primary), Box::new(|ctx| { /* View profile */ }));
/// let interface = Interface::new(ctx, HomePage, navigation, profile);
/// ```
pub fn new(
ctx: &mut Context,
start_page: impl AppPage,
navigation: (usize, Vec<(&'static str, &'static str, Box<dyn FnMut(&mut Context)>)>),
profile: (&'static str, AvatarContent, Box<dyn FnMut(&mut Context)>),
) -> Self {
let (mobile, desktop) = match crate::config::IS_MOBILE {
true => (Some(MobileInterface::new(ctx, start_page, navigation, profile)), None),
false => (None, Some(DesktopInterface::new(ctx, start_page, navigation, profile)))
};
Interface(Stack::default(), mobile, desktop)
}
}
#[derive(Debug, Component)]
struct MobileInterface (Column, Box<dyn AppPage>, Opt<MobileNavigator>, Option<MobileKeyboard>);
impl MobileInterface {
fn new(
ctx: &mut Context,
start_page: impl AppPage,
navigation: (usize, Vec<(&'static str, &'static str, Box<dyn FnMut(&mut Context)>)>),
profile: (&'static str, AvatarContent, Box<dyn FnMut(&mut Context)>),
) -> Self {
let navigator = MobileNavigator::new(ctx, navigation, profile);
#[cfg(target_os = "ios")] // move to rust_on_rails layer
let insets = safe_area_insets();
#[cfg(not(target_os = "ios"))]
let insets = (0., 0., 0., 0.);
MobileInterface(
Column::new(0.0, Offset::Center, Size::Fit, Padding(0.0, insets.0, 0.0, insets.1)),
Box::new(start_page), Opt::new(navigator, false), None,
)
}
}
impl OnEvent for MobileInterface {
fn on_event(&mut self, ctx: &mut Context, event: &mut dyn Event) -> bool {
if let Some(_event) = event.downcast_ref::<TickEvent>() {
// self.2.display(self.1.navigator_status());
} else if let Some(KeyboardActiveEvent(enabled)) = event.downcast_ref::<KeyboardActiveEvent>() {
self.3 = match enabled {
true => Some(MobileKeyboard::new(ctx)),
false => None
};
} else if let Some(NavigateEvent(page)) = event.downcast_ref::<NavigateEvent>() {
self.1 = page.get_page(ctx);
}
true
}
}
#[derive(Debug, Component)]
struct DesktopInterface (Row, DesktopNavigator, Bin<Stack, Rectangle>, Box<dyn AppPage>);
impl DesktopInterface {
fn new(
ctx: &mut Context,
start_page: impl AppPage,
navigation: (usize, Vec<(&'static str, &'static str, Box<dyn FnMut(&mut Context)>)>),
profile: (&'static str, AvatarContent, Box<dyn FnMut(&mut Context)>),
) -> Self {
let color = ctx.get::<PelicanUI>().theme.colors.outline.secondary;
DesktopInterface(
Row(0.0, Offset::Start, Size::Fit, Padding::default()),
DesktopNavigator::new(ctx, navigation, profile),
Bin (
Stack(Offset::default(), Offset::default(), Size::Static(1.0), Size::Fit, Padding::default()),
Rectangle::new(color)
),
Box::new(start_page)
)
}
}
impl OnEvent for DesktopInterface {
fn on_event(&mut self, ctx: &mut Context, event: &mut dyn Event) -> bool {
if let Some(NavigateEvent(page)) = event.downcast_ref::<NavigateEvent>() {
self.3 = page.get_page(ctx);
}
true
}
}
#[derive(Debug, Component)]
pub struct Page (Column, Header, Content, Option<Bumper>, #[skip] bool);
impl OnEvent for Page {}
impl Page {
/// Creates a new `Page` component that initializes the page layout with a header, content, optional bumper, and navigation status.
/// The page is structured with a flexible width and a vertical layout that contains the header, content, and optional bumper.
/// It also supports a flag to manage whether navigation is enabled or not.
///
/// # Parameters:
/// - **`header`**: The header of the page, which is of type `Header`. It typically contains the title or navigation elements for the page.
/// - **`content`**: The main content of the page, which is of type `Content`. This part holds the primary information displayed on the page.
/// - **`bumper`**: An optional `Bumper` component, which could be used for additional UI elements or effects at the bottom of the page.
/// - **`has_nav`**: A boolean flag that determines whether the page includes a navigation interface (e.g., a bottom navigation bar).
///
/// # Returns:
/// - **`Page`**: The constructed `Page` component, which consists of a `Column` layout, header, content, optional bumper, and navigation status.
///
/// # Example:
/// ```rust
/// let page = Page::new(header, content, Some(bumper), true);
/// ```
/// This creates a new page with a header, content, a bumper at the bottom, and navigation enabled.
///
pub fn new(header: Header, content: Content, bumper: Option<Bumper>, has_nav: bool) -> Self {
let width = Size::custom(move |widths: Vec<(f32, f32)>|(widths[1].0, f32::MAX));
Page(
Column::new(12.0, Offset::Center, width, Padding::default()),
header,
content,
bumper,
has_nav
)
}
/// Returns a mutable reference to the `Header` component of the page.
pub fn header(&mut self) -> &mut Header {&mut self.1}
/// Returns a mutable reference to the `Content` component of the page.
pub fn content(&mut self) -> &mut Content {&mut self.2}
/// Returns a mutable reference to the `Bumper` component of the page.
pub fn bumper(&mut self) -> &mut Option<Bumper> {&mut self.3}
/// Returns the navigation status of the page as a boolean.
pub fn navigator_status(&self) -> bool {self.4}
}
/// A component that holds and arranges its child elements in a column.
/// The `Content` component is used to display and manage the content area of a page,
/// with the ability to dynamically modify the list of drawable items.
#[derive(Debug, Component)]
pub struct Content (Stack, ContentChildren);
impl Content {
/// Creates a new `Content` component with the specified layout and child items.
///
/// # Parameters:
/// - `offset`: The offset for positioning the content relative to its parent.
/// - `content`: A vector of `Box<dyn Drawable>` elements representing the items to display in the content area.
///
/// # Returns:
/// - **`Content`**: A new `Content` component that contains the stack layout and the provided items.
///
pub fn new(offset: Offset, content: Vec<Box<dyn Drawable>>) -> Self {
let width = Size::custom(move |widths: Vec<(f32, f32)>|(widths[0].0.min(375.0), 375.0));
let height = Size::custom(move |_: Vec<(f32, f32)>|(0.0, f32::MAX));
Content(
Stack(Offset::Center, offset, width, height, Padding(24.0, 0.0, 24.0, 0.0)),
ContentChildren::new(content),
)
}
/// Returns a mutable reference to the list of items contained within the `Content` component.
///
/// This allows the user to dynamically add, remove, or modify the content items after the
/// `Content` component has been created.
///
/// # Returns:
/// - **`&mut Vec<Box<dyn Drawable>>`**: A mutable reference to the vector of drawable items.
pub fn items(&mut self) -> &mut Vec<Box<dyn Drawable>> {&mut self.1.1}
}
impl OnEvent for Content {
fn on_event(&mut self, _ctx: &mut Context, event: &mut dyn Event) -> bool {
if let Some(event) = event.downcast_ref::<MouseEvent>() {
if let MouseEvent{state: MouseState::Scroll(_, y), ..} = event {
*self.1.0.scroll() += y;
*self.1.0.scroll() = self.1.0.scroll().clamp(0.0, 100.); // 100 = content height
}
}
true
}
}
#[derive(Debug, Component)]
struct ContentChildren (Column, Vec<Box<dyn Drawable>>);
impl OnEvent for ContentChildren {}
impl ContentChildren {
pub fn new(content: Vec<Box<dyn Drawable>>) -> Self {
ContentChildren(Column::center(24.0), content)
}
}
#[cfg(target_os = "ios")]
extern "C" {
fn get_safe_area_insets() -> *const f64;
}
#[cfg(target_os = "ios")]
pub fn safe_area_insets() -> (f32, f32, f32, f32) {
unsafe {
let ptr = get_safe_area_insets();
(
*ptr.add(0) as f32, // top
*ptr.add(1) as f32, // bottom
*ptr.add(2) as f32, // left
*ptr.add(3) as f32, // right
)
}
}