Skip to content

Instantly share code, notes, and snippets.

@sabine
Last active July 26, 2020 22:23
Show Gist options
  • Save sabine/a21423c96eeb594ed789be629ffd6e19 to your computer and use it in GitHub Desktop.
Save sabine/a21423c96eeb594ed789be629ffd6e19 to your computer and use it in GitHub Desktop.
Form API
pub mod form2 {
use seed::{prelude::*, *};
use seed_hooks::*;
#[derive(Clone, Debug)]
pub struct FormData {
pub name: String,
pub color: Option<crate::form_fields::color_picker::Color>, //Option<(i32, f64, f64)>,
}
pub fn initialize(
id: Local,
name: Option<String>,
color: Option<crate::form_fields::color_picker::Color>,
) {
form_data(id).set(FormData {
name: name.unwrap_or("".to_string()),
color: color.clone(),
});
crate::form_fields::color_picker::initialize(id, color);
}
#[atom]
pub fn form_data(id: Local) -> Atom<FormData> {
FormData {
name: "".to_string(),
color: None,
}
}
// Individual field's data and respective UI
// ====================================
fn name_ui<Ms: 'static, F: FnOnce(String) -> () + Clone + 'static>(
value: &String,
setter: F,
) -> Node<Ms> {
div![
label!["Name:"],
input![
attrs! {At::Value => value},
input_ev(Ev::Input, move |inp| setter(inp))
]
]
}
#[reaction]
fn color_ui<Ms: 'static, F: FnOnce(String) -> () + Clone + 'static>(
id: Local,
setter: crate::form_fields::color_picker::Impure<F>,
) -> Reaction<Node<Ms>> {
div![
label!["Color:"],
crate::form_fields::color_picker::observable_color_picker(
id,
input_ev(Ev::Input, move |inp| setter.0(inp))
)
.observe()
]
}
#[reaction]
pub fn form_ui<Ms: 'static>(id: Local) -> Reaction<Node<Ms>> {
let data = form_data(id).observe();
div![
name_ui(&data.name, move |inp| form_data(id)
.update(|f| f.name = inp)),
color_ui(
id,
crate::form_fields::color_picker::Impure(move |inp: String| form_data(id)
.update(|f| f.color =
serde_json::from_str(&inp).expect("JSON deserialization failure")))
)
.observe(),
]
}
// Actual View
#[topo::nested]
pub fn view<Ms: 'static + Clone>() -> Node<Ms> {
let form_id = Local::new();
let form_data = form_data(form_id).get();
div![
h1!["Form Data"],
p![format!("name: {}", form_data.name)],
p![format!("color: {:?}", form_data.color)],
h1!["Form"],
form_ui(form_id).get(),
]
}
}
pub mod color_picker {
use seed::{prelude::*, *};
use seed_hooks::*;
fn hsv_to_hsl_string(color: &Color) -> String {
let hsv = color.0;
let l = hsv.2 * (1.0 - hsv.1 / 2.0);
let s = if l == 1.0 || l == 0.0 {
0.0
} else {
(hsv.2 - l) / f64::min(l, 1.0 - l)
};
format!(
"hsla({}, {}%, {}%, 1)",
hsv.0,
f64::trunc(s * 100.0),
f64::trunc(l * 100.0)
)
}
#[atom]
fn widget_status(_local: Local) -> Atom<ColorPickerState> {
ColorPickerState {
widget_status: HsvWidgetStatus::Unpicked,
hsv: Color((100, 0.01, 0.01)),
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Color(pub (i32, f64, f64));
impl Color {
fn set_hue(self: &mut Self, h: i32) {
(self.0).0 = h;
}
fn set_saturation(self: &mut Self, s: f64) {
(self.0).1 = s;
}
fn set_value(self: &mut Self, v: f64) {
(self.0).2 = v;
}
}
#[derive(Clone, Debug)]
pub enum HsvWidgetStatus {
Unpicked,
Open,
Picked(Color), //(i32, f64, f64)),
}
#[derive(Clone, Debug)]
struct ColorPickerState {
widget_status: HsvWidgetStatus,
hsv: Color, // (i32, f64, f64),
}
#[derive(Clone)]
pub struct Impure<T>(pub T)
where
T: Clone;
impl<T> PartialEq for Impure<T>
where
T: Clone,
{
fn eq(&self, _other: &Self) -> bool {
true
}
}
impl<T> std::hash::Hash for Impure<T>
where
T: Clone,
{
fn hash<H: std::hash::Hasher>(&self, _state: &mut H) {}
}
impl<T> std::cmp::Eq for Impure<T> where T: Clone {}
pub fn initialize(id: Local, initial_value: Option<Color>) {
//Option<(i32, f64, f64)>) {
widget_status(id).set(match initial_value {
None => ColorPickerState {
widget_status: HsvWidgetStatus::Unpicked,
hsv: Color((100, 0.01, 0.01)),
},
Some(hsv) => ColorPickerState {
widget_status: HsvWidgetStatus::Picked(hsv.clone()),
hsv: hsv,
},
});
}
pub fn observable_color_picker<Ms: 'static>(
id: Local,
ev_handler: EventHandler<Ms>,
) -> Reaction<Node<Ms>> {
picker_ui(id, Impure(ev_handler))
}
#[reaction]
fn picker_ui<Ms: 'static>(
id: Local,
ev_handler: Impure<EventHandler<Ms>>,
) -> Reaction<Node<Ms>> {
let status = widget_status(id).observe();
seed::log!(status);
match status.widget_status {
HsvWidgetStatus::Unpicked => picker_unpicked_ui(id),
HsvWidgetStatus::Open => picker_open_ui(id, ev_handler),
HsvWidgetStatus::Picked(hsv) => picker_picked_ui(id, hsv),
}
}
fn picker_unpicked_ui<Ms: 'static>(id: Local) -> Node<Ms> {
div![
C!(styles::STYLES.buttons.button),
"Choose Color",
widget_status(id).on_click(|s| (*s).widget_status = HsvWidgetStatus::Open)
]
}
fn picker_picked_ui<Ms: 'static>(id: Local, hsv_value: Color) -> Node<Ms> {
// hsv_value: (i32, f64, f64)) -> Node<Ms> {
div![
div![attrs! {
At::Style => format!("background-color: {}; width: 30px;height:30px;margin:20px",
hsv_to_hsl_string(&hsv_value)),
}],
div![
C!(styles::STYLES.buttons.button),
"Choose another Color",
widget_status(id).on_click(|s| (*s).widget_status = HsvWidgetStatus::Open)
]
]
}
//#[reaction]
fn picker_open_ui<Ms: 'static>(id: Local, ev_handler: Impure<EventHandler<Ms>>) -> Node<Ms> {
let el_ref = use_state(|| ElRef::<web_sys::HtmlElement>::new());
let widget_data = widget_status(id).observe();
let hsv_value = widget_data.hsv.clone();
seed::log("picker_open_ui");
div![
div![attrs! {
At::Style => format!("background-color: {}; width: 100px;height:30px;margin:100px",
hsv_to_hsl_string(&hsv_value)),
}],
div![
label!("Hue:"),
input![
attrs! {At::Value => (hsv_value.0).0,
At::Type => "range",At::Min => 0, At::Max => 360},
widget_status(id)
.on_input(|widget, hue| widget.hsv.set_hue(hue.parse::<i32>().unwrap()))
]
],
div![
label!("Saturation:"),
input![
attrs! {At::Value => (hsv_value.0).1,
At::Type => "range", At::Min => 0.0, At::Max => 1.0,
At::Step => "0.01" },
widget_status(id).on_input(|widget, saturation| {
widget
.hsv
.set_saturation(saturation.parse::<f64>().unwrap())
})
]
],
div![
label!["Value:"],
input![
attrs! {At::Value => (hsv_value.0).2, At::Type => "range", At::Min => 0.0, At::Max => 1.0,
At::Step => "0.01" },
widget_status(id).on_input(|widget, lightness| {
widget.hsv.set_value(lightness.parse::<f64>().unwrap())
})
]
],
div![
div![
C!(styles::STYLES.buttons.button),
"Ok",
widget_status(id).on_click(move |s| {
let el = el_ref.get().get().unwrap();
let target = el.dyn_into::<web_sys::EventTarget>().unwrap();
target.dispatch_event(&web_sys::Event::new("input").unwrap());
(*s).widget_status = HsvWidgetStatus::Picked(hsv_value);
})
],
input![
attrs! {
At::Style => "display:hidden",
At::Value=> serde_json::to_string(&widget_data.hsv).expect("JSON serialization failure"),
},
el_ref.get(),
ev_handler.0
]
]
]
}
#[reaction]
//#[topo::nested]
pub fn view<Ms: 'static>() -> Reaction<Node<Ms>> {
div![
observable_color_picker(Local::new(), input_ev(Ev::Input, |color| seed::log!(color)))
.observe(),
]
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment