-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add flight booker example from 7gui (#280)
* Add flight booker example from 7gui * Add images to 7gui flight booker
- Loading branch information
1 parent
6554367
commit 4dded85
Showing
3 changed files
with
139 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
[package] | ||
name = "flight_booker" | ||
edition = "2021" | ||
license.workspace = true | ||
version.workspace = true | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
floem = { path = "../.." } | ||
time = { version = "0.3.31", features = ["parsing", "macros"] } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
# Flight Booker | ||
|
||
This is an example that emulates an application that books flights, as | ||
described in [task 3][task3] of [7gui tasks][7gui]. | ||
|
||
> The focus of Flight Booker lies on modelling constraints between | ||
> widgets on the one hand and modelling constraints within a widget | ||
> on the other hand. Such constraints are very common in everyday | ||
> interactions with GUI applications. A good solution for Flight Booker | ||
> will make the constraints clear, succinct and explicit in the source | ||
> code and not hidden behind a lot of scaffolding. | ||
| Initial state | Invalid date format | Return date before start date | | ||
| ------- | ------- | ------- | | ||
| ![valid] | ![invalid] | ![return-disabled] | | ||
|
||
[task3]: https://eugenkiss.github.io/7guis/tasks/#flight | ||
[7gui]: https://eugenkiss.github.io/7guis/ | ||
|
||
[valid]: https://github.com/lapce/floem/assets/23398472/fe2758d3-7161-43a3-b059-8a4a1ce0c02e | ||
[invalid]: https://github.com/lapce/floem/assets/23398472/aeb843aa-520b-48f3-a39d-6acb414dba57 | ||
[return-disabled]: https://github.com/lapce/floem/assets/23398472/8f1268f9-efbd-4a4d-9a47-7a50425e3e39 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,106 @@ | ||
use floem::{ | ||
peniko::Color, | ||
reactive::{create_rw_signal, create_signal}, | ||
unit::UnitExt, | ||
view::View, | ||
views::{dyn_container, empty, h_stack, text, v_stack, Decorators}, | ||
widgets::{button, labeled_radio_button, text_input}, | ||
}; | ||
use time::Date; | ||
|
||
fn oneway_message(start_text: String) -> String { | ||
format!("You have booked a one-way flight on {start_text}") | ||
} | ||
|
||
fn return_message(start_text: String, return_text: String) -> String { | ||
format!("You have booked a flight on {start_text} and a return flight on {return_text}",) | ||
} | ||
|
||
#[derive(Eq, PartialEq, Clone)] | ||
enum FlightMode { | ||
OneWay, | ||
Return, | ||
} | ||
|
||
static DATE_FORMAT: &[time::format_description::FormatItem<'_>] = | ||
time::macros::format_description!("[day]-[month]-[year]"); | ||
|
||
pub fn app_view() -> impl View { | ||
let (flight_mode, flight_mode_set) = create_signal(FlightMode::OneWay); | ||
|
||
let start_text = create_rw_signal("24-02-2024".to_string()); | ||
let start_date = move || Date::parse(&start_text.get(), &DATE_FORMAT).ok(); | ||
let start_date_is_valid = move || start_date().is_some(); | ||
|
||
let return_text = create_rw_signal("24-02-2024".to_string()); | ||
let return_date = move || Date::parse(&return_text.get(), &DATE_FORMAT).ok(); | ||
let return_text_is_enabled = move || flight_mode.get() == FlightMode::Return; | ||
let return_date_is_valid = move || { | ||
if return_text_is_enabled() { | ||
return_date().is_some() | ||
} else { | ||
true | ||
} | ||
}; | ||
|
||
let dates_are_chronological = move || match flight_mode.get() { | ||
FlightMode::OneWay => true, | ||
FlightMode::Return => match (return_date(), start_date()) { | ||
(Some(ret), Some(start)) => ret >= start, | ||
_ => false, | ||
}, | ||
}; | ||
|
||
let did_booking = create_rw_signal(false); | ||
|
||
let mode_picker = h_stack(( | ||
labeled_radio_button(FlightMode::OneWay, flight_mode, || "One way flight") | ||
.on_click_stop(move |_| flight_mode_set.set(FlightMode::OneWay)), | ||
labeled_radio_button(FlightMode::Return, flight_mode, || "Return flight") | ||
.on_click_stop(move |_| flight_mode_set.set(FlightMode::Return)), | ||
)); | ||
|
||
let start_date_input = text_input(start_text) | ||
.placeholder("Start date") | ||
.style(move |s| s.apply_if(!start_date_is_valid(), |s| s.background(Color::RED))); | ||
let return_date_input = text_input(return_text) | ||
.placeholder("Return date") | ||
.style(move |s| s.apply_if(!return_date_is_valid(), |s| s.background(Color::RED))) | ||
.disabled(move || !return_text_is_enabled()); | ||
|
||
let book_button = button(|| "Book") | ||
.disabled(move || { | ||
!(dates_are_chronological() && start_date_is_valid() && return_date_is_valid()) | ||
}) | ||
.on_click_stop(move |_| did_booking.set(true)); | ||
|
||
let success_message = dyn_container( | ||
move || did_booking.get(), | ||
move |booked| match (booked, flight_mode.get()) { | ||
(true, FlightMode::OneWay) => Box::new(text(oneway_message(start_text.get()))), | ||
(true, FlightMode::Return) => { | ||
Box::new(text(return_message(start_text.get(), return_text.get()))) | ||
} | ||
(false, _) => Box::new(empty()), | ||
}, | ||
); | ||
|
||
v_stack(( | ||
mode_picker, | ||
start_date_input, | ||
return_date_input, | ||
book_button, | ||
success_message, | ||
)) | ||
.style(|s| s.gap(0, 5)) | ||
.style(|s| { | ||
s.size(100.pct(), 100.pct()) | ||
.flex_col() | ||
.items_center() | ||
.justify_center() | ||
}) | ||
} | ||
|
||
fn main() { | ||
floem::launch(app_view); | ||
} |