use plotly_derive::FieldSetter;
use serde::Serialize;
use serde_json::{Map, Value};
use crate::{
color::Color,
common::{Anchor, Font, Pad},
Relayout, Restyle,
};
#[derive(Serialize, Debug, Copy, Clone)]
#[serde(rename_all = "snake_case")]
pub enum ButtonMethod {
Restyle,
Relayout,
Animate,
Update,
Skip,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, Debug, FieldSetter)]
pub struct Button {
args: Option<Value>,
args2: Option<Value>,
execute: Option<bool>,
label: Option<String>,
method: Option<ButtonMethod>,
name: Option<String>,
#[serde(rename = "templateitemname")]
template_item_name: Option<String>,
visible: Option<bool>,
}
impl Button {
pub fn new() -> Self {
Default::default()
}
}
#[derive(FieldSetter)]
pub struct ButtonBuilder {
label: Option<String>,
name: Option<String>,
template_item_name: Option<String>,
visible: Option<bool>,
#[field_setter(default = "Map::new()")]
restyles: Map<String, Value>,
#[field_setter(default = "Map::new()")]
relayouts: Map<String, Value>,
}
impl ButtonBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn push_restyle(mut self, restyle: impl Restyle + Serialize) -> Self {
let restyle = serde_json::to_value(&restyle).unwrap();
for (k, v) in restyle.as_object().unwrap() {
self.restyles.insert(k.clone(), v.clone());
}
self
}
pub fn push_relayout(mut self, relayout: impl Relayout + Serialize) -> Self {
let relayout = serde_json::to_value(&relayout).unwrap();
for (k, v) in relayout.as_object().unwrap() {
self.relayouts.insert(k.clone(), v.clone());
}
self
}
fn method_and_args(
restyles: Map<String, Value>,
relayouts: Map<String, Value>,
) -> (ButtonMethod, Value) {
match (restyles.is_empty(), relayouts.is_empty()) {
(true, true) => (ButtonMethod::Skip, Value::Null),
(false, true) => (ButtonMethod::Restyle, vec![restyles].into()),
(true, false) => (ButtonMethod::Relayout, vec![relayouts].into()),
(false, false) => (ButtonMethod::Update, vec![restyles, relayouts].into()),
}
}
pub fn build(self) -> Button {
let (method, args) = Self::method_and_args(self.restyles, self.relayouts);
Button {
label: self.label,
args: Some(args),
method: Some(method),
name: self.name,
template_item_name: self.template_item_name,
visible: self.visible,
..Default::default()
}
}
}
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum UpdateMenuType {
Dropdown,
Buttons,
}
#[derive(Serialize, Debug, Clone)]
#[serde(rename_all = "snake_case")]
pub enum UpdateMenuDirection {
Left,
Right,
Up,
Down,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Debug, FieldSetter, Clone)]
pub struct UpdateMenu {
active: Option<i32>,
#[serde(rename = "bgcolor")]
background_color: Option<Box<dyn Color>>,
#[serde(rename = "bordercolor")]
border_color: Option<Box<dyn Color>>,
#[serde(rename = "borderwidth")]
border_width: Option<usize>,
buttons: Option<Vec<Button>>,
direction: Option<UpdateMenuDirection>,
font: Option<Font>,
name: Option<String>,
pad: Option<Pad>,
#[serde(rename = "showactive")]
show_active: Option<bool>,
template_item_name: Option<String>,
#[serde(rename = "type")]
ty: Option<UpdateMenuType>,
visible: Option<bool>,
x: Option<f64>,
#[serde(rename = "xanchor")]
x_anchor: Option<Anchor>,
y: Option<f64>,
#[serde(rename = "yanchor")]
y_anchor: Option<Anchor>,
}
impl UpdateMenu {
pub fn new() -> Self {
Default::default()
}
}
#[cfg(test)]
mod tests {
use serde_json::{json, to_value};
use super::*;
use crate::{
common::{Title, Visible},
Layout,
};
#[test]
fn test_serialize_button_method() {
assert_eq!(to_value(ButtonMethod::Restyle).unwrap(), json!("restyle"));
assert_eq!(to_value(ButtonMethod::Relayout).unwrap(), json!("relayout"));
assert_eq!(to_value(ButtonMethod::Animate).unwrap(), json!("animate"));
assert_eq!(to_value(ButtonMethod::Update).unwrap(), json!("update"));
assert_eq!(to_value(ButtonMethod::Skip).unwrap(), json!("skip"));
}
#[test]
fn test_serialize_button() {
let button = Button::new()
.args(json!([
{ "visible": [true, false] },
{ "width": 20},
]))
.args2(json!([]))
.execute(true)
.label("Label")
.method(ButtonMethod::Update)
.name("Name")
.template_item_name("Template")
.visible(true);
let expected = json!({
"args": [
{ "visible": [true, false] },
{ "width": 20},
],
"args2": [],
"execute": true,
"label": "Label",
"method": "update",
"name": "Name",
"templateitemname": "Template",
"visible": true,
});
assert_eq!(to_value(button).unwrap(), expected);
}
#[test]
fn test_button_builder() {
let expected = json!({
"args": [
{ "visible": [true, false] },
{ "title": {"text": "Hello"}, "width": 20},
],
"label": "Label",
"method": "update",
"name": "Name",
"templateitemname": "Template",
"visible": true,
});
let button = ButtonBuilder::new()
.label("Label")
.name("Name")
.template_item_name("Template")
.visible(true)
.push_restyle(crate::Bar::<i32, i32>::modify_visible(vec![
Visible::True,
Visible::False,
]))
.push_relayout(Layout::modify_title(Title::new("Hello")))
.push_relayout(Layout::modify_width(20))
.build();
assert_eq!(to_value(button).unwrap(), expected);
}
}