touchportal_sdk/states.rs
1use derive_builder::Builder;
2use indexmap::IndexSet;
3use serde::{Deserialize, Serialize};
4
5/// In Touch Portal the user can use States which can be used by IF statement and with Events for
6/// example but can also be used in button texts or most actions.
7///
8/// With your plugin you can add states to Touch Portal that represent states from the software you
9/// are integrating as a plug-in. You can define a state as part of a category. Events can link to
10/// the id of the states to be able to act on changes of those states for example.
11///
12/// Please note: when a user makes a reference to any of the states from a plug-in in their actions
13/// they are stored in that text locally. When you change the state id for example in your plugin,
14/// all existing references to the old state are not updated and will result in null errors and no
15/// conversion will be done. Only change ID's when you are absolutely sure it will not break
16/// anything for your users.
17#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
18#[serde(rename_all = "camelCase")]
19pub struct State {
20 /// This is the id of the state.
21 ///
22 /// It is used to identify the states within Touch Portal. This id needs to be unique across
23 /// plugins. This means that if you give it the id "1" there is a big chance that it will be a
24 /// duplicate. Touch Portal may reject it or when the other state is updated, yours will be as
25 /// well with wrong data. Best practice is to create a unique prefix for all your states like
26 /// in our case; `tp_sid_fruit`.
27 #[builder(setter(into))]
28 pub(crate) id: String,
29
30 /// This text describes the state and is used in the IF statement to let the user see what
31 /// state it is changing.
32 ///
33 /// We recommend to make this text work in the flow of the inline nature of the IF statement
34 /// within Touch Portal. This is also the title that is used in list where you can use this
35 /// state value for logic execution.
36 #[builder(setter(into))]
37 #[serde(rename = "desc")]
38 pub(crate) description: String,
39
40 /// This is the value the state will have if it is not set already but is looked up.
41 #[builder(setter(into))]
42 #[serde(rename = "default")]
43 initial: String,
44
45 #[serde(flatten)]
46 pub(crate) kind: StateType,
47
48 /// The name of the parent group of this state.
49 ///
50 /// The parent group of this state will be used to group the state in the menus used throughout
51 /// Touch Portal. Every state belonging to the same parent group name will be in the same
52 /// selection menu.
53 #[builder(setter(into, strip_option), default)]
54 #[serde(skip_serializing_if = "Option::is_none")]
55 parent_group: Option<String>,
56}
57
58impl State {
59 pub fn builder() -> StateBuilder {
60 StateBuilder::default()
61 }
62}
63
64#[derive(Debug, Clone, Deserialize, Serialize)]
65#[non_exhaustive]
66#[serde(rename_all = "lowercase")]
67#[serde(tag = "type")]
68pub enum StateType {
69 /// A state where you specify a limited amount of state values the state can be.
70 Choice(ChoiceState),
71
72 /// A state that contains a free text field.
73 ///
74 /// This type can be used for smart conversion as well.
75 ///
76 /// `#FF115599` (`#AARRGGBB`) can be interpreted by the plug-in visuals action as a color. The
77 /// format needs to be this or it will not be seen as a color and will not be converted.
78 ///
79 /// A base64 representation of an image will also be allowed for specific actions such as the
80 /// plug-in visuals action. This will read the base64 string representation and convert it to
81 /// an image and show it on the button. We suggest to keep these as small as possible. Images
82 /// used like this on a button are not stored and only exist temporary. This allows for a
83 /// performant updating process. Allow for multiple updates per second depending on the
84 /// computer used, the device used and the quality of the network.
85 ///
86 /// The base64 string should only hold the base64 data. The meta data should be stripped. The
87 /// format has to be a PNG. It has to be a squared image.
88 Text(TextState),
89}
90
91#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
92#[serde(rename_all = "camelCase")]
93pub struct ChoiceState {
94 /// Specify the collection of values that can be used to choose from.
95 ///
96 /// These can also be dynamically changed if you use the dynamic actions.
97 #[builder(setter(each(name = "choice", into)))]
98 #[serde(rename = "valueChoices")]
99 pub(crate) choices: IndexSet<String>,
100}
101
102impl ChoiceState {
103 pub fn builder() -> ChoiceStateBuilder {
104 ChoiceStateBuilder::default()
105 }
106}
107
108#[derive(Debug, Clone, Builder, Deserialize, Serialize)]
109#[serde(rename_all = "camelCase")]
110pub struct TextState {}
111
112impl TextState {
113 pub fn builder() -> TextStateBuilder {
114 TextStateBuilder::default()
115 }
116}
117
118#[test]
119fn serialize_example_state() {
120 assert_eq!(
121 serde_json::to_value(
122 State::builder()
123 .id("tp_sid_fruit")
124 .description("Fruit Kind description")
125 .initial("Apple")
126 .parent_group("Fruits")
127 .kind(StateType::Choice(
128 ChoiceState::builder()
129 .choice("Apple")
130 .choice("Pears")
131 .choice("Grapes")
132 .choice("Bananas")
133 .build()
134 .unwrap()
135 ))
136 .build()
137 .unwrap()
138 )
139 .unwrap(),
140 serde_json::json! {{
141 "id":"tp_sid_fruit",
142 "type":"choice",
143 "desc":"Fruit Kind description",
144 "default":"Apple",
145 "parentGroup":"Fruits",
146 "valueChoices": [
147 "Apple",
148 "Pears",
149 "Grapes",
150 "Bananas"
151 ]
152 }}
153 );
154}