use std::cell::RefCell;
use std::rc::Rc;
use saudade::{
App, Button, Color, Column, Container, Dialog, Dropdown, Event, EventCtx, Label, Painter,
PopupRequest, Rect, TextInput, Theme, Widget, WindowConfig,
};
const W: i32 = 300;
const H: i32 = 170;
const RETURN: usize = 1;
const INITIAL_DATE: &str = "27.03.2014";
fn main() {
let flight_type = Rc::new(RefCell::new(
Dropdown::new(Rect::new(16, 16, 268, 24)).with_items(["one-way flight", "return flight"]),
));
let start = Rc::new(RefCell::new(
TextInput::new(Rect::new(16, 52, 170, 24)).with_text(INITIAL_DATE),
));
let back = Rc::new(RefCell::new(
TextInput::new(Rect::new(16, 84, 170, 24))
.with_text(INITIAL_DATE)
.with_enabled(false),
));
let start_err = Rc::new(RefCell::new(
Label::new(Rect::new(192, 57, 92, 16), "").with_color(Color::RED),
));
let back_err = Rc::new(RefCell::new(
Label::new(Rect::new(192, 89, 92, 16), "").with_color(Color::RED),
));
let dialog = Rc::new(RefCell::new(Dialog::new()));
let book = Rc::new(RefCell::new(
Button::new(Rect::new(16, 124, 268, 26), "Book")
.default(true)
.on_click({
let flight_type = flight_type.clone();
let start = start.clone();
let back = back.clone();
let dialog = dialog.clone();
move |cx| {
let is_return = flight_type.borrow().selected_index() == Some(RETURN);
let start_date = start.borrow().text();
let message = if is_return {
format!(
"You have booked a return flight\nleaving {}\nand returning {}.",
start_date,
back.borrow().text(),
)
} else {
format!("You have booked a one-way flight\non {}.", start_date)
};
dialog.borrow_mut().show_info("Flight booked", message);
cx.request_paint();
}
}),
));
flight_type.borrow_mut().set_on_change({
let start = start.clone();
let back = back.clone();
let book = book.clone();
let start_err = start_err.clone();
let back_err = back_err.clone();
move |cx, idx| {
let is_return = idx == RETURN;
back.borrow_mut().set_enabled(is_return);
let start_text = start.borrow().text();
let back_text = back.borrow().text();
revalidate(
&start_text,
&back_text,
is_return,
&book,
&start_err,
&back_err,
);
cx.request_paint();
}
});
start.borrow_mut().set_on_change({
let flight_type = flight_type.clone();
let back = back.clone();
let book = book.clone();
let start_err = start_err.clone();
let back_err = back_err.clone();
move |cx, text| {
let is_return = flight_type.borrow().selected_index() == Some(RETURN);
let back_text = back.borrow().text();
revalidate(text, &back_text, is_return, &book, &start_err, &back_err);
cx.request_paint();
}
});
back.borrow_mut().set_on_change({
let flight_type = flight_type.clone();
let start = start.clone();
let book = book.clone();
let start_err = start_err.clone();
let back_err = back_err.clone();
move |cx, text| {
let is_return = flight_type.borrow().selected_index() == Some(RETURN);
let start_text = start.borrow().text();
revalidate(&start_text, text, is_return, &book, &start_err, &back_err);
cx.request_paint();
}
});
let content = Container::new(W, H)
.add(SharedTextInput(start.clone()))
.add(SharedTextInput(back.clone()))
.add(SharedLabel(start_err.clone()))
.add(SharedLabel(back_err.clone()))
.add(SharedButton(book.clone()))
.add(SharedDropdown(flight_type.clone()));
let root = Column::new()
.add_fill(content)
.add_overlay(SharedDialog(dialog.clone()));
App::new(WindowConfig::new("Flight Booker", W, H), root)
.with_theme(Theme::windows_31())
.run();
}
fn parse_date(text: &str) -> Option<(u32, u32, u32)> {
let parts: Vec<&str> = text.trim().split('.').collect();
if parts.len() != 3 {
return None;
}
let day: u32 = parts[0].parse().ok()?;
let month: u32 = parts[1].parse().ok()?;
let year: u32 = parts[2].parse().ok()?;
if !(1..=31).contains(&day) || !(1..=12).contains(&month) {
return None;
}
Some((year, month, day))
}
fn revalidate(
start_text: &str,
back_text: &str,
is_return: bool,
book: &Rc<RefCell<Button>>,
start_err: &Rc<RefCell<Label>>,
back_err: &Rc<RefCell<Label>>,
) {
let start_date = parse_date(start_text);
let back_date = parse_date(back_text);
let start_bad = start_date.is_none();
let back_bad = is_return && back_date.is_none();
let out_of_order =
matches!((is_return, start_date, back_date), (true, Some(s), Some(b)) if b < s);
book.borrow_mut()
.set_enabled(!start_bad && !back_bad && !out_of_order);
start_err.borrow_mut().text = invalid_text(start_bad);
back_err.borrow_mut().text = invalid_text(back_bad);
}
fn invalid_text(bad: bool) -> String {
if bad {
"invalid date".to_string()
} else {
String::new()
}
}
struct SharedDropdown(Rc<RefCell<Dropdown>>);
impl Widget for SharedDropdown {
fn bounds(&self) -> Rect {
self.0.borrow().bounds()
}
fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint(painter, theme);
}
fn paint_overlay(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint_overlay(painter, theme);
}
fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
self.0.borrow_mut().event(event, ctx);
}
fn captures_pointer(&self) -> bool {
self.0.borrow().captures_pointer()
}
fn focusable(&self) -> bool {
self.0.borrow().focusable()
}
fn set_focused(&mut self, focused: bool) {
self.0.borrow_mut().set_focused(focused);
}
fn layout(&mut self, bounds: Rect) {
self.0.borrow_mut().layout(bounds);
}
fn popup_request(&self) -> Option<PopupRequest> {
self.0.borrow().popup_request()
}
}
struct SharedTextInput(Rc<RefCell<TextInput>>);
impl Widget for SharedTextInput {
fn bounds(&self) -> Rect {
self.0.borrow().bounds()
}
fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint(painter, theme);
}
fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
self.0.borrow_mut().event(event, ctx);
}
fn captures_pointer(&self) -> bool {
self.0.borrow().captures_pointer()
}
fn focusable(&self) -> bool {
self.0.borrow().focusable()
}
fn set_focused(&mut self, focused: bool) {
self.0.borrow_mut().set_focused(focused);
}
fn layout(&mut self, bounds: Rect) {
self.0.borrow_mut().layout(bounds);
}
fn wants_ticks(&self) -> bool {
self.0.borrow().wants_ticks()
}
}
struct SharedButton(Rc<RefCell<Button>>);
impl Widget for SharedButton {
fn bounds(&self) -> Rect {
self.0.borrow().bounds()
}
fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint(painter, theme);
}
fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
self.0.borrow_mut().event(event, ctx);
}
fn captures_pointer(&self) -> bool {
self.0.borrow().captures_pointer()
}
fn focusable(&self) -> bool {
self.0.borrow().focusable()
}
fn set_focused(&mut self, focused: bool) {
self.0.borrow_mut().set_focused(focused);
}
fn accepts_accelerators(&self) -> bool {
self.0.borrow().accepts_accelerators()
}
fn layout(&mut self, bounds: Rect) {
self.0.borrow_mut().layout(bounds);
}
}
struct SharedLabel(Rc<RefCell<Label>>);
impl Widget for SharedLabel {
fn bounds(&self) -> Rect {
self.0.borrow().bounds()
}
fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint(painter, theme);
}
fn layout(&mut self, bounds: Rect) {
self.0.borrow_mut().layout(bounds);
}
}
struct SharedDialog(Rc<RefCell<Dialog>>);
impl Widget for SharedDialog {
fn bounds(&self) -> Rect {
self.0.borrow().bounds()
}
fn paint(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint(painter, theme);
}
fn paint_overlay(&mut self, painter: &mut Painter, theme: &Theme) {
self.0.borrow_mut().paint_overlay(painter, theme);
}
fn event(&mut self, event: &Event, ctx: &mut EventCtx) {
self.0.borrow_mut().event(event, ctx);
}
fn captures_pointer(&self) -> bool {
self.0.borrow().captures_pointer()
}
fn accepts_accelerators(&self) -> bool {
self.0.borrow().accepts_accelerators()
}
fn layout(&mut self, bounds: Rect) {
self.0.borrow_mut().layout(bounds);
}
fn popup_request(&self) -> Option<PopupRequest> {
self.0.borrow().popup_request()
}
}