mittens_engine/engine/ecs/component/
scrolling.rs1use crate::engine::ecs::ComponentId;
2use crate::engine::ecs::component::Component;
3use crate::engine::ecs::{IntentValue, SignalEmitter};
4
5#[derive(Debug, Clone)]
25pub struct ScrollingComponent {
26 pub viewport_height: f32,
28 pub content_height: f32,
30 pub scroll_offset: f32,
32 pub track: Option<ComponentId>,
36 pub track_base_pos: [f32; 3],
38 pub drag_scope: Option<ComponentId>,
40
41 component: Option<ComponentId>,
42}
43
44impl ScrollingComponent {
45 pub fn new(viewport_height: f32, content_height: f32) -> Self {
46 Self {
47 viewport_height,
48 content_height,
49 scroll_offset: 0.0,
50 track: None,
51 track_base_pos: [0.0, 0.0, 0.0],
52 drag_scope: None,
53 component: None,
54 }
55 }
56
57 pub fn set_track(&mut self, track: ComponentId, base_pos: [f32; 3]) {
58 self.track = Some(track);
59 self.track_base_pos = base_pos;
60 }
61
62 pub fn set_drag_scope(&mut self, drag_scope: ComponentId) {
63 self.drag_scope = Some(drag_scope);
64 }
65
66 pub fn set_content_height(&mut self, content_height: f32) -> bool {
67 self.content_height = content_height.max(0.0);
68 self.clamp_to_content()
69 }
70
71 pub fn max_scroll(&self) -> f32 {
73 (self.content_height - self.viewport_height).max(0.0)
74 }
75
76 pub fn apply_drag(&mut self, delta_y: f32) -> bool {
80 let prev_offset = self.scroll_offset;
81 self.scroll_offset -= delta_y;
82 self.scroll_offset = self.scroll_offset.clamp(0.0, self.max_scroll());
83 (self.scroll_offset - prev_offset).abs() > f32::EPSILON
84 }
85
86 pub fn track_translation(&self) -> [f32; 3] {
88 [
89 self.track_base_pos[0],
90 self.track_base_pos[1] + self.scroll_offset,
91 self.track_base_pos[2],
92 ]
93 }
94
95 pub fn clamp_to_content(&mut self) -> bool {
97 let clamped = self.scroll_offset.clamp(0.0, self.max_scroll());
98 if (clamped - self.scroll_offset).abs() > f32::EPSILON {
99 self.scroll_offset = clamped;
100 true
101 } else {
102 false
103 }
104 }
105}
106
107impl Component for ScrollingComponent {
108 fn name(&self) -> &'static str {
109 "scrolling"
110 }
111
112 fn set_id(&mut self, id: ComponentId) {
113 self.component = Some(id);
114 }
115
116 fn as_any(&self) -> &dyn std::any::Any {
117 self
118 }
119
120 fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
121 self
122 }
123
124 fn init(&mut self, emit: &mut dyn SignalEmitter, component: ComponentId) {
125 emit.push_intent_now(
126 component,
127 IntentValue::RegisterScrolling {
128 component_ids: vec![component],
129 },
130 );
131 }
132
133 fn to_mms_ast(
134 &self,
135 _world: &crate::engine::ecs::World,
136 ) -> crate::scripting::ast::ComponentExpression {
137 use crate::engine::ecs::component::ce_helpers::*;
138 ce_call(
139 "Scrolling",
140 "new",
141 vec![
142 num(self.viewport_height as f64),
143 num(self.content_height as f64),
144 ],
145 )
146 }
147}