diff --git a/src/value/primitive.rs b/src/value/primitive.rs index 417df50..945f47f 100644 --- a/src/value/primitive.rs +++ b/src/value/primitive.rs @@ -40,6 +40,7 @@ impl_try_from!(PrimitiveValue { for String => |x| x.as_string(); for f64 => |x| x.as_f64(); for bool => |x| x.as_bool(); + for Vec => |x| x.as_vec(); }); impl From for PrimitiveValue { @@ -91,4 +92,68 @@ impl PrimitiveValue { .parse() .map_err(|e| anyhow!("couldn't convert {:?} to bool: {}", &self, e)) } + + pub fn as_vec(&self) -> Result> { + parse_vec(self.0.to_owned()).map_err(|e| anyhow!("Couldn't convert {:#?} to a vec: {}", &self, e)) + } +} +fn parse_vec(a: String) -> Result> { + match a.strip_prefix('[').and_then(|x| x.strip_suffix(']')) { + Some(content) => { + let mut items: Vec = content.split(',').map(|x: &str| x.to_string()).collect(); + let mut removed = 0; + for times_ran in 0..items.len() { + // escapes `,` if there's a `\` before em + if items[times_ran - removed].ends_with("\\") { + items[times_ran - removed].pop(); + let it = items.remove((times_ran + 1) - removed); + items[times_ran - removed] += ","; + items[times_ran - removed] += ⁢ + removed += 1; + } + } + Ok(items) + } + None => Err(anyhow!("Is your array built like this: '[these,are,items]'?")), + } +} + +#[cfg(test)] +mod test { + use super::*; + use pretty_assertions::assert_eq; + #[test] + fn test_parse_vec() { + assert_eq!( + vec![""], + parse_vec("[]".to_string()).unwrap(), + "should be able to parse empty lists" + ); + assert_eq!( + vec!["hi"], + parse_vec("[hi]".to_string()).unwrap(), + "should be able to parse single element list" + ); + assert_eq!( + vec!["hi", "ho", "hu"], + parse_vec("[hi,ho,hu]".to_string()).unwrap(), + "should be able to parse three element list" + ); + assert_eq!( + vec!["hi,ho"], + parse_vec("[hi\\,ho]".to_string()).unwrap(), + "should be able to parse list with escaped comma" + ); + assert_eq!( + vec!["hi,ho", "hu"], + parse_vec("[hi\\,ho,hu]".to_string()).unwrap(), + "should be able to parse two element list with escaped comma" + ); + assert!(parse_vec("".to_string()).is_err(), "Should fail when parsing empty string"); + assert!( + parse_vec("[a,b".to_string()).is_err(), + "Should fail when parsing unclosed list" + ); + assert!(parse_vec("a]".to_string()).is_err(), "Should fail when parsing unopened list"); + } } diff --git a/src/widgets/widget_definitions.rs b/src/widgets/widget_definitions.rs index cef7776..71f79ff 100644 --- a/src/widgets/widget_definitions.rs +++ b/src/widgets/widget_definitions.rs @@ -23,6 +23,7 @@ pub(super) fn widget_to_gtk_widget(bargs: &mut BuilderArgs) -> Result build_gtk_color_button(bargs)?.upcast(), "expander" => build_gtk_expander(bargs)?.upcast(), "color-chooser" => build_gtk_color_chooser(bargs)?.upcast(), + "combo-box" => build_gtk_combo_box(bargs)?.upcast(), _ => return Ok(None), }; Ok(Some(gtk_widget)) @@ -146,6 +147,30 @@ pub(super) fn resolve_orientable_attrs(bargs: &mut BuilderArgs, gtk_widget: >k // concrete widgets +/// @widget combo-box +fn build_gtk_combo_box(bargs: &mut BuilderArgs) -> Result { + let gtk_widget = gtk::ComboBoxText::new(); + let on_change_handler_id: Rc>> = Rc::new(RefCell::new(None)); + resolve_block!(bargs, gtk_widget, { + // @prop items + prop(items: as_vec) { + gtk_widget.remove_all(); + for i in items { + gtk_widget.append_text(&i); + } + }, + // @prop onchange - runs the code when a item was selected, replacing {} with the item as a string + prop(onchange: as_string) { + let old_id = on_change_handler_id.replace(Some( + gtk_widget.connect_changed(move |gtk_widget| { + run_command(&onchange, gtk_widget.get_active_text().unwrap_or("".into())); + }) + )); + old_id.map(|id| gtk_widget.disconnect(id)); + }, + }); + Ok(gtk_widget) +} /// @widget expander widget fn build_gtk_expander(bargs: &mut BuilderArgs) -> Result { let gtk_widget = gtk::Expander::new(None);