use super::VisualStateGroup;
use ferroui_base::{
ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, FerroObject, FerroObjectImpl,
FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, Size, StyledProperty, WeakRef,
};
use std::cell::{Cell, RefCell};
#[repr(C)]
pub struct StateTriggerBase {
base: FerroObject,
active: Cell<bool>,
owner: RefCell<Option<WeakRef<VisualStateGroup>>>,
}
ferro_class!(StateTriggerBase: FerroObject);
ferro_class_info!(StateTriggerBase { markup: { namespace: "FerroUI.Controls" } });
ferro_impl_classes!(StateTriggerBase: FerroObjectImpl);
impl StateTriggerBase {
pub fn construct() -> Self {
Self { base: FerroObject::construct(), active: Cell::new(false), owner: RefCell::new(None) }
}
pub fn set_active(&self, value: bool) {
if self.active.replace(value) != value {
self.owner_changed();
}
}
pub(crate) fn is_active(&self) -> bool {
self.active.get()
}
pub(crate) fn set_owner(&self, group: Option<&Ref<VisualStateGroup>>) {
*self.owner.borrow_mut() = group.map(|group| group.downgrade());
}
pub(crate) fn owner_changed(&self) {
let owner = self.owner.borrow().as_ref().and_then(WeakRef::upgrade);
if let Some(group) = owner {
super::state_triggers::evaluate_group(&group);
}
}
pub(crate) fn qualification(&self, window: Option<Size>) -> Option<TriggerRank> {
if let Some(adaptive) = self.downcast_ref::<AdaptiveTrigger>() {
return adaptive.adaptive_qualification(window);
}
self.is_active().then_some(TriggerRank { extensible: true, min_width: 0.0, min_height: 0.0 })
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct TriggerRank {
extensible: bool,
min_width: f64,
min_height: f64,
}
impl TriggerRank {
pub(crate) fn beats(&self, other: &TriggerRank) -> bool {
(self.extensible, self.min_width, self.min_height) > (other.extensible, other.min_width, other.min_height)
}
}
#[repr(C)]
pub struct StateTrigger {
base: StateTriggerBase,
}
ferro_class!(StateTrigger: StateTriggerBase);
ferro_class_info!(StateTrigger { new: StateTrigger::new, markup: { namespace: "FerroUI.Controls" } });
impl FerroObjectImpl for StateTrigger {
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed(this, change);
if change.property() == Self::is_active_property().as_property() {
this.set_active(this.is_active());
}
}
}
ferro_properties! { impl StateTrigger {
pub fn is_active_property() -> StyledProperty<bool> {
FerroProperty::register::<StateTrigger, _>("IsActive", false)
}
} }
impl StateTrigger {
pub fn construct() -> Self {
Self { base: StateTriggerBase::construct() }
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn is_active(&self) -> bool {
self.get_value(Self::is_active_property())
}
pub fn set_is_active(&self, value: bool) {
self.set_value(Self::is_active_property(), value);
}
}
#[repr(C)]
pub struct AdaptiveTrigger {
base: StateTriggerBase,
}
ferro_class!(AdaptiveTrigger: StateTriggerBase);
ferro_class_info!(AdaptiveTrigger { new: AdaptiveTrigger::new, markup: { namespace: "FerroUI.Controls" } });
impl FerroObjectImpl for AdaptiveTrigger {
fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
Self::parent_on_property_changed(this, change);
if change.property() == Self::min_window_width_property().as_property()
|| change.property() == Self::min_window_height_property().as_property()
{
this.owner_changed();
}
}
}
ferro_properties! { impl AdaptiveTrigger {
pub fn min_window_width_property() -> StyledProperty<f64> {
FerroProperty::register::<AdaptiveTrigger, _>("MinWindowWidth", -1.0)
}
pub fn min_window_height_property() -> StyledProperty<f64> {
FerroProperty::register::<AdaptiveTrigger, _>("MinWindowHeight", -1.0)
}
} }
impl AdaptiveTrigger {
pub fn construct() -> Self {
Self { base: StateTriggerBase::construct() }
}
pub fn new() -> Ref<Self> {
instantiate(Self::construct())
}
pub fn min_window_width(&self) -> f64 {
self.get_value(Self::min_window_width_property())
}
pub fn set_min_window_width(&self, value: f64) {
self.set_value(Self::min_window_width_property(), value);
}
pub fn min_window_height(&self) -> f64 {
self.get_value(Self::min_window_height_property())
}
pub fn set_min_window_height(&self, value: f64) {
self.set_value(Self::min_window_height_property(), value);
}
fn adaptive_qualification(&self, window: Option<Size>) -> Option<TriggerRank> {
let (min_width, min_height) = (self.min_window_width(), self.min_window_height());
if min_width < 0.0 && min_height < 0.0 {
return None;
}
let window = window?;
let fits = (min_width < 0.0 || min_width <= window.width) && (min_height < 0.0 || min_height <= window.height);
fits.then_some(TriggerRank { extensible: false, min_width, min_height })
}
}