use crate::FONT;
use crate::definitions::{GoodPixelColor, IpType, Octet, Palette, RenderData, State, TemporaryIp};
use buoyant::focus::BoundaryBehavior;
use buoyant::view::prelude::*;
use buoyant::view::rotary::{Rotary, RotaryEvent, RotaryState};
use core::{fmt::Write, net::Ipv4Addr};
use heapless::String;
fn funnel<F: Fn(&mut State)>(f: F) -> F {
f
}
pub fn settings<'a, 'b, C: GoodPixelColor, F: Fn(&State) + 'a>(
data: RenderData<'a, C>,
state: &'b State,
save: F,
) -> impl View<C, State> + use<'a, C, F> {
let p = data.palette;
let save = funnel(move |s| save(s));
let toggle_dhcp = funnel(|s| s.dhcp = !s.dhcp);
let enter_ip = |t: IpType| {
funnel(move |s| {
s.opened_input = Some(t);
s.temporary_ip = match t {
IpType::StaticIp => s.static_ip.into(),
IpType::Gateway => s.gateway.into(),
IpType::Dns => s.dns.into(),
}
})
};
let set_ip = |t: IpType| {
funnel(move |s| {
if let Ok(ip) = s.temporary_ip.try_into() {
s.opened_input = None;
match t {
IpType::StaticIp => s.static_ip = ip,
IpType::Gateway => s.gateway = ip,
IpType::Dns => s.dns = ip,
}
}
})
};
let cancel_ip = funnel(move |s: &mut State| s.opened_input = None);
let static_ip = state.static_ip;
let net_mask = state.net_mask;
let gateway = state.gateway;
let dns = state.dns;
let dhcp = state.dhcp;
let ip = state.temporary_ip;
VStack::new((
HStack::new((
Text::new("Static IP", &FONT),
Spacer::default(),
Button::new(enter_ip(IpType::StaticIp), move |s| {
button_gut(s.is_focused(), p, ip_view(static_ip))
}),
)),
Spacer::default(),
HStack::new((
Text::new("Net Mask", &FONT),
Spacer::default(),
netmask(net_mask, p),
)),
Spacer::default(),
HStack::new((
Text::new("Gateway", &FONT),
Spacer::default(),
Button::new(enter_ip(IpType::Gateway), move |s| {
button_gut(s.is_focused(), p, ip_view(gateway))
}),
)),
Spacer::default(),
HStack::new((
Text::new("DNS", &FONT),
Spacer::default(),
Button::new(enter_ip(IpType::Dns), move |s| {
button_gut(s.is_focused(), p, ip_view(dns))
}),
)),
Spacer::default(),
HStack::new((
Text::new("DHCP", &FONT),
Spacer::default(),
Button::new(toggle_dhcp, move |s| {
let text = if dhcp { "ON" } else { "OFF" };
button_gut(s.is_focused(), p, Text::new(text, &FONT))
}),
)),
Button::new(save, move |s| {
button_gut(s.is_focused(), p, Text::new("Save", &FONT))
}),
))
.popover(state.opened_input.as_ref(), move |opened_input| {
buoyant::match_view!(opened_input, {
IpType::StaticIp => ip_setter(ip, set_ip(IpType::StaticIp), cancel_ip, p),
IpType::Gateway => ip_setter(ip, set_ip(IpType::Gateway), cancel_ip, p),
IpType::Dns => ip_setter(ip, set_ip(IpType::Dns), cancel_ip, p),
})
.background(
Alignment::Center,
RoundedRectangle::new(5)
.stroked(1)
.foreground_color(p.light_gray()),
)
.background_color(p.dark_blue(), RoundedRectangle::new(5))
.padding(Edges::All, 10)
.bound_focus(BoundaryBehavior::Wrap)
})
}
fn ip_view<C: GoodPixelColor>(ip: Ipv4Addr) -> impl View<C, State> {
let [a, b, c, d] = ip.octets();
let mut buf = String::<15, u8>::new();
write!(&mut buf, "{a}.{b}.{c}.{d}").ok();
Text::new(buf, &FONT)
}
fn ip_setter<C: GoodPixelColor>(
ip: TemporaryIp,
submit: impl Fn(&mut State),
_cancel: impl Fn(&mut State),
palette: &'static Palette<C>,
) -> impl View<C, State> {
fn set<const OCTET: usize, const INDEX: usize>(s: &mut State, v: u8) {
const { assert!(INDEX < 3) };
const { assert!(OCTET < 4) };
s.temporary_ip.0[OCTET].0[INDEX] = v;
}
let TemporaryIp(
[
Octet([a0, a1, a2]),
Octet([b0, b1, b2]),
Octet([c0, c1, c2]),
Octet([d0, d1, d2]),
],
) = ip;
let or5_9 = |is_max: bool| if is_max { 5 } else { 9 };
let [a1max, a2max] = [or5_9(a0 == 2), or5_9((a0, a1) == (2, 5))];
let [b1max, b2max] = [or5_9(b0 == 2), or5_9((b0, b1) == (2, 5))];
let [c1max, c2max] = [or5_9(c0 == 2), or5_9((c0, c1) == (2, 5))];
let [d1max, d2max] = [or5_9(d0 == 2), or5_9((d0, d1) == (2, 5))];
let a = HStack::new((
ip_digit(a0, 2, set::<0, 0>, palette),
ip_digit(a1, a1max, set::<0, 1>, palette),
ip_digit(a2, a2max, set::<0, 2>, palette),
));
let b = HStack::new((
ip_digit(b0, 2, set::<1, 0>, palette),
ip_digit(b1, b1max, set::<1, 1>, palette),
ip_digit(b2, b2max, set::<1, 2>, palette),
));
let c = HStack::new((
ip_digit(c0, 2, set::<2, 0>, palette),
ip_digit(c1, c1max, set::<2, 1>, palette),
ip_digit(c2, c2max, set::<2, 2>, palette),
));
let d = HStack::new((
ip_digit(d0, 2, set::<3, 0>, palette),
ip_digit(d1, d1max, set::<3, 1>, palette),
ip_digit(d2, d2max, set::<3, 2>, palette),
));
let ip = HStack::new((
a,
Text::new(".", &FONT),
b,
Text::new(".", &FONT),
c,
Text::new(".", &FONT),
d,
));
VStack::new((
ip,
Spacer::default().frame().with_height(10),
Button::new(submit, move |s| {
button_gut(s.is_focused(), palette, Text::new("Submit", &FONT))
}),
))
.padding(Edges::Vertical, 5)
.padding(Edges::Horizontal, 10)
}
fn ip_digit<C: GoodPixelColor>(
num: u8,
max: u8,
set_ditit: impl Fn(&mut State, u8) + 'static,
palette: &'static Palette<C>,
) -> impl View<C, State> {
#[derive(Copy, Clone)]
struct Num(u8);
#[rustfmt::skip]
impl AsRef<str> for Num {
fn as_ref(&self) -> &str {
match self.0 {
0 => "0", 1 => "1", 2 => "2", 3 => "3", 4 => "4",
5 => "5", 6 => "6", 7 => "7", 8 => "8", 9 => "9", _ => "?",
}
}
}
let on_action = move |s: &mut State, event: RotaryEvent| match (event, num) {
(RotaryEvent::Previous, _) if num == max => set_ditit(s, 0),
(RotaryEvent::Previous, _) => set_ditit(s, num + 1),
(RotaryEvent::Next, 0) => set_ditit(s, max),
(RotaryEvent::Next, _) => set_ditit(s, num - 1),
_ => (),
};
Rotary::new(on_action, move |page_state: RotaryState| {
let is_focused = page_state == RotaryState::Focused || page_state == RotaryState::Captive;
button_gut(is_focused, palette, Text::new(Num(num), &FONT))
})
}
fn netmask<C: GoodPixelColor>(mask: u8, palette: &'static Palette<C>) -> impl View<C, State> {
let on_action = |s: &mut State, event: RotaryEvent| match (event, s.net_mask) {
(RotaryEvent::Previous, 32) => s.net_mask = 8,
(RotaryEvent::Previous, _) => s.net_mask += 1,
(RotaryEvent::Next, 8) => s.net_mask = 32,
(RotaryEvent::Next, _) => s.net_mask -= 1,
_ => (),
};
Rotary::new(on_action, move |page_state: RotaryState| {
let is_focused = page_state == RotaryState::Focused || page_state == RotaryState::Captive;
let mut buf = String::<3, u8>::new();
write!(&mut buf, "/{mask}").ok();
button_gut(is_focused, palette, Text::new(buf, &FONT))
})
}
fn button_gut<C: GoodPixelColor>(
is_focused: bool,
palette: &'static Palette<C>,
inner: impl View<C, State>,
) -> impl View<C, State> {
let inner = inner.padding(Edges::All, 3);
buoyant::if_view!((is_focused) {
inner.background(
Alignment::Center,
RoundedRectangle::new(3)
.stroked(1)
.foreground_color(palette.light_gray())
)
} else {
inner
})
}