use rust_on_rails::prelude::*;
use crate::elements::shapes::{Rectangle};
use crate::elements::text::TextStyle;
use crate::layout::{Column, Stack, Row, Padding, Offset, Size};
use crate::components::avatar::{AvatarContent, AvatarRow};
use crate::components::button::{IconButton, Button};
use crate::components::text_input::TextInput;
use crate::elements::text::Text;
use crate::{PelicanUI, Callback};
use crate::AppPage;
use crate::ElementID;
use std::fmt::Debug;
use super::{DesktopInterface, MobileInterface};
#[derive(Debug, Component)]
pub struct Interface (Stack, Rectangle, Option<MobileInterface>, Option<DesktopInterface>);
impl OnEvent for Interface {}
impl Interface {
pub fn new(
ctx: &mut Context,
start_page: impl AppPage,
start_index: Option<usize>,
navigation: Option<Vec<(&'static str, &'static str, Box<Callback>)>>,
profile: Option<(&'static str, AvatarContent, Box<Callback>)>,
) -> Self {
let color = ctx.get::<PelicanUI>().theme.colors.background.primary;
let (mobile, desktop) = match crate::config::IS_MOBILE {
true => (Some(MobileInterface::new(ctx, start_page, start_index, navigation, profile)), None),
false => (None, Some(DesktopInterface::new(ctx, start_page, start_index, navigation, profile)))
};
Interface(Stack::default(), Rectangle::new(color), mobile, desktop)
}
}
#[derive(Debug, Component)]
pub struct Page(Column, Header, Content, Option<Bumper>, #[skip] bool);
impl OnEvent for Page {}
impl Page {
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
)
}
pub fn header(&mut self) -> &mut Header {&mut self.1}
pub fn content(&mut self) -> &mut Content {&mut self.2}
pub fn bumper(&mut self) -> &mut Option<Bumper> {&mut self.3}
pub fn navigator_status(&self) -> bool {self.4}
}
#[derive(Debug, Component)]
pub struct Content (Stack, ContentChildren, #[skip] bool);
impl Content {
pub fn new(offset: Offset, does_scroll: bool, 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), does_scroll )
}
pub fn items(&mut self) -> &mut Vec<Box<dyn Drawable>> {&mut self.1.1}
pub fn can_scroll(&mut self) -> &mut bool {&mut self.2}
}
impl OnEvent for Content {
fn on_event(&mut self, _ctx: &mut Context, event: &mut dyn Event) -> bool {
if let Some(MouseEvent{state: MouseState::Scroll(_, y), ..}) = event.downcast_ref::<MouseEvent>() {
*self.1.0.scroll() += y;
*self.1.0.scroll() = self.1.0.scroll().clamp(0.0, 100.); }
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, *ptr.add(1) as f32, *ptr.add(2) as f32, *ptr.add(3) as f32, )
}
}
#[derive(Debug, Component)]
pub struct Header(Row, HeaderIcon, HeaderContent, HeaderIcon);
impl OnEvent for Header {}
impl Header {
pub fn home(ctx: &mut Context, title: &'static str) -> Self {
Header(
Row(16.0, Offset::Center, Size::Fit, Padding(24.0, 16.0, 24.0, 16.0)),
HeaderIcon::new(None),
HeaderContent::home(ctx, title),
HeaderIcon::new(None)
)
}
pub fn stack(
ctx: &mut Context,
left: Option<IconButton>,
title: &'static str,
right: Option<IconButton>
) -> Self {
Header(
Row(16.0, Offset::Center, Size::Fit, Padding(24.0, 16.0, 24.0, 16.0)),
HeaderIcon::new(left),
HeaderContent::stack(ctx, title),
HeaderIcon::new(right)
)
}
pub fn chat(
ctx: &mut Context,
left: Option<IconButton>,
right: Option<IconButton>,
avatars: Vec<AvatarContent>,
) -> Self {
Header(
Row(16.0, Offset::Center, Size::Fit, Padding(24.0, 16.0, 24.0, 16.0)),
HeaderIcon::new(left),
HeaderContent::chat(ctx, avatars),
HeaderIcon::new(right)
)
}
}
#[derive(Debug, Component)]
struct HeaderContent(Column, Text, Option<AvatarRow>);
impl OnEvent for HeaderContent {}
impl HeaderContent {
pub fn home(ctx: &mut Context, title: &'static str) -> Self {
let text_size = ctx.get::<PelicanUI>().theme.fonts.size.h3;
let width = Size::custom(move |widths: Vec<(f32, f32)>|(widths[0].0, f32::MAX));
HeaderContent(
Column::new(10.0, Offset::Center, width, Padding::default()),
Text::new(ctx, title, TextStyle::Heading, text_size, Align::Left),
None,
)
}
pub fn stack(ctx: &mut Context, title: &'static str) -> Self {
let text_size = ctx.get::<PelicanUI>().theme.fonts.size.h4;
let width = Size::custom(move |widths: Vec<(f32, f32)>|(widths[0].0, f32::MAX));
HeaderContent(
Column::new(10.0, Offset::Center, width, Padding::default()),
Text::new(ctx, title, TextStyle::Heading, text_size, Align::Left),
None,
)
}
pub fn chat(ctx: &mut Context, avatars: Vec<AvatarContent>) -> Self {
let text_size = ctx.get::<PelicanUI>().theme.fonts.size.h5;
let title = if avatars.len() > 1 {"Ella Couch"} else {"Group Message"};
let width = Size::custom(move |widths: Vec<(f32, f32)>|(widths[0].0, f32::MAX));
HeaderContent(
Column::new(10.0, Offset::Center, width, Padding::default()),
Text::new(ctx, title, TextStyle::Heading, text_size, Align::Left),
Some(AvatarRow::new(ctx, avatars)),
)
}
}
#[derive(Debug, Component)]
struct HeaderIcon(Stack, Option<IconButton>);
impl OnEvent for HeaderIcon {}
impl HeaderIcon {
pub fn new(icon: Option<IconButton>) -> Self {
HeaderIcon(
Stack(Offset::Center, Offset::Center, Size::Static(48.0), Size::Static(48.0), Padding::default()),
icon
)
}
}
#[derive(Debug, Component)]
pub struct Bumper (Stack, Rectangle, BumperContent);
impl OnEvent for Bumper {}
impl Bumper {
pub fn new(ctx: &mut Context, content: Vec<Box<dyn Drawable>>) -> Self {
let background = ctx.get::<PelicanUI>().theme.colors.background.primary;
let width = Size::custom(move |widths: Vec<(f32, f32)>|(widths[0].0, 375.0));
let height = Size::custom(move |heights: Vec<(f32, f32)>|(heights[1].0, heights[1].1));
Bumper(
Stack(Offset::Center, Offset::Start, width, height, Padding::default()),
Rectangle::new(background), BumperContent::new(content)
)
}
pub fn double_button(ctx: &mut Context, a: Button, b: Button) -> Self {
Self::new(ctx, vec![Box::new(a), Box::new(b)])
}
pub fn single_button(ctx: &mut Context, a: Button) -> Self {
Self::new(ctx, vec![Box::new(a)])
}
pub fn input(ctx: &mut Context, input: TextInput) -> Self {
Self::new(ctx, vec![Box::new(input)])
}
pub fn items(&mut self) -> &mut Vec<Box<dyn Drawable>> {
&mut self.2.1
}
}
#[derive(Debug, Component)]
struct BumperContent (Row, Vec<Box<dyn Drawable>>);
impl OnEvent for BumperContent {}
impl BumperContent {
fn new(content: Vec<Box<dyn Drawable>>) -> Self {
BumperContent(Row(16.0, Offset::Center, Size::Fit, Padding(24.0, 16.0, 24.0, 16.0)), content)
}
}
#[derive(Debug, Component)]
pub struct NavigationButton(Stack, Option<Button>, Option<IconButton>, #[skip] ElementID);
impl OnEvent for NavigationButton {}
impl NavigationButton {
pub fn new(id: ElementID, button: Option<Button>, icon_button: Option<IconButton>) -> Self {
NavigationButton(Stack::default(), button, icon_button, id)
}
pub fn id(&self) -> ElementID {
self.3
}
pub fn button(&mut self) -> &mut Option<Button> {
&mut self.1
}
pub fn icon_button(&mut self) -> &mut Option<IconButton> {
&mut self.2
}
}