use crate::core::Rect;
use crate::widget::capability::types::{CapabilityAccessError, CapabilityValue};
use crate::widget::widget_trait::Widget;
pub const BASE_PROPERTY_NAMES: &[&str] = &["enabled", "visible", "tooltip", "geometry"];
pub fn base_property_get(
widget: &dyn Widget,
name: &str,
) -> Result<CapabilityValue, CapabilityAccessError> {
match name {
"enabled" => Ok(CapabilityValue::Bool(widget.is_enabled())),
"visible" => Ok(CapabilityValue::Bool(widget.is_visible())),
"tooltip" => Ok(CapabilityValue::String(widget.tooltip().to_string())),
"geometry" => Ok(geometry_to_value(widget.geometry())),
_ => Err(CapabilityAccessError::UnknownProperty),
}
}
pub fn base_property_set(
widget: &mut dyn Widget,
name: &str,
value: CapabilityValue,
) -> Result<(), CapabilityAccessError> {
match name {
"enabled" => match value {
CapabilityValue::Bool(enabled) => {
widget.set_enabled(enabled);
Ok(())
}
_ => Err(CapabilityAccessError::TypeMismatch),
},
"visible" => match value {
CapabilityValue::Bool(visible) => {
widget.set_visible(visible);
Ok(())
}
_ => Err(CapabilityAccessError::TypeMismatch),
},
"tooltip" => match value {
CapabilityValue::String(text) => {
widget.set_tooltip(text);
Ok(())
}
_ => Err(CapabilityAccessError::TypeMismatch),
},
"geometry" => Err(CapabilityAccessError::ReadOnlyProperty),
_ => Err(CapabilityAccessError::UnknownProperty),
}
}
pub fn geometry_to_value(geometry: Rect) -> CapabilityValue {
CapabilityValue::String(format!(
"{},{},{},{}",
geometry.x, geometry.y, geometry.width, geometry.height
))
}
pub trait WidgetProperties {
fn get(&self, name: &str) -> Result<CapabilityValue, CapabilityAccessError>;
fn set(&mut self, name: &str, value: CapabilityValue) -> Result<(), CapabilityAccessError>;
fn property_names(&self) -> &'static [&'static str];
}
#[macro_export]
macro_rules! property_names_of {
($($name:literal),* $(,)?) => {
&[$($name),*]
};
($($name:literal),* $(,)? BASE_PROPERTY_NAMES) => {
&[$($name,)* "enabled", "visible", "tooltip", "geometry"]
};
}
#[macro_export]
macro_rules! impl_widget_property_hooks {
() => {
fn properties_dyn(
&self,
) -> Option<&dyn $crate::widget::capability::properties_trait::WidgetProperties> {
Some(self)
}
fn properties_dyn_mut(
&mut self,
) -> Option<&mut dyn $crate::widget::capability::properties_trait::WidgetProperties> {
Some(self)
}
};
}
pub fn widget_property_get(
widget: &dyn Widget,
name: &str,
) -> Result<CapabilityValue, CapabilityAccessError> {
widget
.properties_dyn()
.map_or(Err(CapabilityAccessError::UnsupportedOnWidget), |props| props.get(name))
}
pub fn widget_property_set(
widget: &mut dyn Widget,
name: &str,
value: CapabilityValue,
) -> Result<(), CapabilityAccessError> {
match widget.properties_dyn_mut() {
Some(props) => props.set(name, value),
None => Err(CapabilityAccessError::UnsupportedOnWidget),
}
}
pub fn widget_property_names(widget: &dyn Widget) -> Option<&'static [&'static str]> {
widget.properties_dyn().map(WidgetProperties::property_names)
}
pub fn read_widget_property_by_name(
widget: &dyn Widget,
name: &str,
) -> Result<CapabilityValue, CapabilityAccessError> {
widget_property_get(widget, name)
}
pub fn write_widget_property_by_name(
widget: &mut dyn Widget,
name: &str,
value: CapabilityValue,
) -> Result<(), CapabilityAccessError> {
widget_property_set(widget, name, value)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Rect;
use crate::widget::base::BaseWidget;
use crate::widget::WidgetKind;
struct Probe {
base: BaseWidget,
}
impl crate::widget::widget_trait::Widget for Probe {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
crate::impl_widget_property_hooks!();
}
impl crate::event::EventHandler for Probe {
fn handle_event(&mut self, _event: &crate::event::Event) {}
}
impl WidgetProperties for Probe {
fn get(&self, name: &str) -> Result<CapabilityValue, CapabilityAccessError> {
base_property_get(self, name)
}
fn set(&mut self, name: &str, value: CapabilityValue) -> Result<(), CapabilityAccessError> {
base_property_set(self, name, value)
}
fn property_names(&self) -> &'static [&'static str] {
BASE_PROPERTY_NAMES
}
}
fn probe() -> Probe {
Probe { base: BaseWidget::new(WidgetKind::Panel, Rect::new(1, 2, 30, 40), "probe") }
}
#[test]
fn base_properties_round_trip() {
let mut widget = probe();
assert_eq!(widget.get("enabled"), Ok(CapabilityValue::Bool(true)));
widget.set("enabled", CapabilityValue::Bool(false)).expect("writable");
assert_eq!(widget.get("enabled"), Ok(CapabilityValue::Bool(false)));
widget.set("visible", CapabilityValue::Bool(false)).expect("writable");
assert_eq!(widget.get("visible"), Ok(CapabilityValue::Bool(false)));
widget.set("tooltip", CapabilityValue::String("tip".into())).expect("writable");
assert_eq!(widget.get("tooltip"), Ok(CapabilityValue::String("tip".into())));
}
#[test]
fn base_property_set_rejects_type_mismatch() {
let mut widget = probe();
assert_eq!(
widget.set("enabled", CapabilityValue::String("yes".into())),
Err(CapabilityAccessError::TypeMismatch)
);
}
#[test]
fn geometry_is_readable_but_read_only() {
let mut widget = probe();
assert_eq!(widget.get("geometry"), Ok(CapabilityValue::String("1,2,30,40".into())));
assert_eq!(
widget.set("geometry", CapabilityValue::String("0,0,1,1".into())),
Err(CapabilityAccessError::ReadOnlyProperty)
);
}
#[test]
fn unknown_names_are_distinguished() {
let widget = probe();
assert_eq!(widget.get("nope"), Err(CapabilityAccessError::UnknownProperty));
}
#[test]
fn definitive_contract_answers_are_not_delegated() {
let mut widget = probe();
assert_eq!(
write_widget_property_by_name(
&mut widget,
"geometry",
CapabilityValue::String("0,0,1,1".into()),
),
Err(CapabilityAccessError::ReadOnlyProperty),
);
}
#[test]
fn the_dispatcher_reaches_the_contract_through_dyn_widget() {
let mut widget = probe();
let dynamic: &mut dyn crate::widget::Widget = &mut widget;
assert_eq!(
write_widget_property_by_name(dynamic, "not_a_property", CapabilityValue::Bool(true)),
Err(CapabilityAccessError::UnknownProperty),
);
let dynamic: &dyn crate::widget::Widget = &widget;
assert_eq!(
read_widget_property_by_name(dynamic, "not_a_property"),
Err(CapabilityAccessError::UnknownProperty),
);
assert_eq!(
read_widget_property_by_name(dynamic, "enabled"),
Ok(CapabilityValue::Bool(true))
);
}
#[test]
fn declared_names_are_all_readable() {
let widget = probe();
for name in widget.property_names() {
assert!(
widget.get(name).is_ok(),
"property_names() declares {name:?} but get() rejects it"
);
}
}
}