1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
use crate::{
    messenger::MessageData,
    widget::{
        component::interactive::navigation::{use_nav_scroll_view, NavJump, NavScroll, NavSignal},
        context::WidgetMountOrChangeContext,
        utils::Vec2,
        WidgetId, WidgetIdOrRef,
    },
    widget_hook,
};
use serde::{Deserialize, Serialize};

fn is_zero(v: &Vec2) -> bool {
    v.x.abs() < 1.0e-6 && v.y.abs() < 1.0e-6
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScrollViewState {
    #[serde(default)]
    pub value: Vec2,
    #[serde(default)]
    pub size_factor: Vec2,
}
implement_props_data!(ScrollViewState);

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ScrollViewRange {
    #[serde(default)]
    #[serde(skip_serializing_if = "is_zero")]
    pub from: Vec2,
    #[serde(default)]
    #[serde(skip_serializing_if = "is_zero")]
    pub to: Vec2,
}
implement_props_data!(ScrollViewRange);

impl Default for ScrollViewRange {
    fn default() -> Self {
        Self {
            from: Vec2 { x: 0.0, y: 0.0 },
            to: Vec2 { x: 1.0, y: 1.0 },
        }
    }
}

#[derive(Debug, Default, Clone, Serialize, Deserialize)]
pub struct ScrollViewNotifyProps(
    #[serde(default)]
    #[serde(skip_serializing_if = "WidgetIdOrRef::is_none")]
    pub WidgetIdOrRef,
);
implement_props_data!(ScrollViewNotifyProps);

#[derive(Debug, Clone)]
pub struct ScrollViewNotifyMessage {
    pub sender: WidgetId,
    pub state: ScrollViewState,
}
implement_message_data!(ScrollViewNotifyMessage);

widget_hook! {
    pub use_scroll_view_notified_state(life_cycle) {
        life_cycle.change(|context| {
            for msg in context.messenger.messages {
                if let Some(msg) = msg.as_any().downcast_ref::<ScrollViewNotifyMessage>() {
                    drop(context.state.write_with(msg.state.clone()));
                }
            }
        });
    }
}

widget_hook! {
    pub use_scroll_view(life_cycle) [use_nav_scroll_view] {
        fn notify<T>(context: &WidgetMountOrChangeContext, data: T)
        where
            T: 'static + MessageData,
        {
            if let Ok(notify) = context.props.read::<ScrollViewNotifyProps>() {
                if let Some(to) = notify.0.read() {
                    context.messenger.write(to, data);
                }
            }
        }

        life_cycle.mount(|context| {
            notify(&context, ScrollViewNotifyMessage {
                sender: context.id.to_owned(),
                state: ScrollViewState::default(),
            });
            drop(context.state.write_with(ScrollViewState::default()));
        });

        life_cycle.change(|context| {
            let mut data = context.state.read_cloned_or_default::<ScrollViewState>();
            let range = context.props.read::<ScrollViewRange>();
            let mut dirty = false;
            for msg in context.messenger.messages {
                if let Some(
                    NavSignal::Jump(NavJump::Scroll(NavScroll::Change(value, factor, relative)))
                ) = msg.as_any().downcast_ref() {
                    if *relative {
                        data.value.x += value.x;
                        data.value.y += value.y;
                    } else {
                        data.value = *value;
                    }
                    if let Ok(range) = &range {
                        data.value.x = data.value.x.max(range.from.x).min(range.to.x);
                        data.value.y = data.value.y.max(range.from.y).min(range.to.y);
                    }
                    data.size_factor = *factor;
                    dirty = true;
                }
            }
            if dirty {
                notify(&context, ScrollViewNotifyMessage {
                    sender: context.id.to_owned(),
                    state: data.clone(),
                });
                drop(context.state.write_with(data));
            }
        });
    }
}