Scroller.cpp
Go to the documentation of this file.
1 #include "Scroller.hpp"
2 
4 
5 namespace iv
6 {
7 
8 static constexpr float ScrollStep = 40.0f;
9 static constexpr float SmoothingFactor = 0.5f;
10 static constexpr float DragActivationThresholdDistancePx = 5.0f;
11 static constexpr float SmoothingDeactivationThreshold_Px = 5.0f;
12 
13 static constexpr float BorderOverreachPx = 10.0f;
14 static constexpr float DelayUntilFullClamp_frames = 8;
15 
16 static constexpr float ResidualSpeedInitialFactor = 0.575f;
17 static constexpr float ResidualSpeedDropOffMultiplier = 0.945f;
18 static constexpr float ResidualSpeedMinimumThreshold = 0.1f;
19 
21  OneChildElem< SlotChild >( inst ),
22  SlotChild( this ),
23  FixedUpdateClient( inst, Defs::Time::Default ),
24  FrameUpdateClient( inst ),
25  cm( inst, this, "Scroller", ClientMarker::Status() ),
26  attr_position( &this->cm, this, 0 ),
27  attr_outsize( &this->cm, this, 0 ),
28  attr_insize( &this->cm, this, 0 ),
29  attr_overlapTop( &this->cm, this, 0 ),
30  attr_overlapBottom( &this->cm, this, 0 ),
31  position( 0 ),
32  smoothedPosition( 0 ),
33  ibq( inst ),
34  iq( inst ),
35  _candidate( std::nullopt ),
36  _active( std::nullopt ),
37  _candidate_pos( 0 ),
38  _residual_speed( 0 ),
39  _full_clamp_countdown( 0 )
40 {
42  this->cm.owns( this->ibq.cm );
43  this->fixed_update_pause();
44 }
45 
47 {
48  static iv::TableId DebugTable = TableId::create( "Scroller" );
49 }
50 
52 {
53  if( this->_candidate.has_value() )
54  {
55  auto key = this->_candidate.value();
56  float2 pos = this->input_position( key );
57 
58  float dist = std::abs( pos.y - this->_candidate_pos );
59  if( dist > DragActivationThresholdDistancePx )
60  {
61  this->accept_candidate();
62  }
63  }
64 
65  if( this->_active.has_value() )
66  {
67  this->refresh_clamp_countdown();
68  auto key = this->_active.value();
69  float2 pos = this->input_position( key );
70 
71  auto pos_prev = this->position.Get();
72  this->position.Set( pos_prev - ( pos.y - this->_candidate_pos ) );
73  this->clamp_position();
74  //this->_candidate_pos = pos_prev + this->_candidate_pos - this->position.Get();
75  this->_candidate_pos = pos.y;
76  }
77 }
78 
79 void Scroller::fixed_update( TimeId time, int time_step, int steps )
80 {
81  for( size_t i = 0; i < steps; i++ )
82  {
83  // full clamp countdown update
84  if( this->_residual_speed != 0.0f )
85  this->refresh_clamp_countdown();
86  else if( this->_full_clamp_countdown > 0 )
87  this->_full_clamp_countdown--;
88 
89  // residual speed update
90  this->position.Set( this->position.Get() + this->_residual_speed );
91  this->clamp_position();
92  this->_residual_speed *= ResidualSpeedDropOffMultiplier;
93 
94  // smooth position update
95  float direction = this->position.Get() - this->smoothedPosition.Get();
96  this->smoothedPosition.Set( this->smoothedPosition.Get() + direction * SmoothingFactor );
97 
98  // residual speed termination
99  if( std::abs( this->_residual_speed ) < ResidualSpeedMinimumThreshold || std::abs( direction ) < SmoothingDeactivationThreshold_Px )
100  this->_residual_speed = 0.0f;
101 
102  // smooth position termination
103  if( this->_residual_speed == 0.0f && this->_full_clamp_countdown <= 0 && std::abs( direction ) < SmoothingDeactivationThreshold_Px )
104  {
105  this->smoothedPosition.Set( this->position.Get() );
106  this->fixed_update_pause();
107  break;
108  }
109  }
110 }
111 
112 void Scroller::accept_candidate()
113 {
114  auto key = this->_candidate.value();
115  this->_candidate = std::nullopt;
116  this->_active = key;
117 
118  if( auto root = this->input_getRoot() )
119  {
120  root->reserve_key( key, true );
121  this->reserve_hover_keys( root, true );
122  root->treeRefresh();
123  }
124 }
125 
126 float2 Scroller::input_position( Input::DeviceKey key )
127 {
128  auto input_pos = this->iq.input_position( key.first, key.second );
129  return this->FromScreenPlaneToLocalPlane( input_pos );
130 }
131 
132 
134 {
135  this->_input_id = id;
136  this->input_treeRefresh();
137  return this;
138 }
139 
141 {
142  return this->_input_id;
143 }
144 
145 void Scroller::input_process( InputRoot * root, Input::DeviceKey key, bool & press, bool & real, bool & offspace )
146 {
147  this->Elem::input_process( root, key, press, real, offspace );
148 
149  // check binding
150  if( !this->ibq.IsBound( this->_input_id, key ) )
151  {
152  this->cm.log( SRC_INFO, Defs::Log::Input, "Refuse input (not bound)." );
153  return;
154  }
155 
156  // hittest
157  int2 pos = this->iq.input_position( key.first, key.second );
158  bool hit = this->picking_test( pos );
159  float2 input_pos = this->input_position( key );
160 
161  //
162  if( !this->_active.has_value() && !this->_candidate.has_value() && press && real && hit && !offspace )
163  {
164  this->_candidate = key;
165  this->_candidate_pos = input_pos.y;
166  this->frame_update_resume();
167  this->_residual_speed = 0.0f;
168  }
169  else if( this->_candidate == key && !press )
170  {
171  this->_candidate = std::nullopt;
172  }
173  else if( this->_active == key && !press )
174  {
175  this->frame_update_pause();
176  this->_active = std::nullopt;
177  this->reserve_hover_keys( root, false );
178  this->input_treeRefresh();
179  this->_residual_speed = ( this->position.Get() - this->smoothedPosition.Get() ) * ResidualSpeedInitialFactor;
180  if( this->_residual_speed != 0.0f )
181  this->fixed_update_resume();
182  }
183 }
184 
185 void Scroller::refresh_clamp_countdown()
186 {
187  this->_full_clamp_countdown = DelayUntilFullClamp_frames;
188  //this->fixed_update_resume();
189 }
190 
191 void Scroller::clamp_position()
192 {
193  // determine overreach
194  float overreach;
195  if( this->_full_clamp_countdown > 0 )
196  overreach = BorderOverreachPx;
197  else
198  overreach = 0.0f;
199 
200  if( this->child.Get() )
201  {
202  // fetch dimensions
203  auto insize = this->child.Get()->preferredSize.Get().y;
204  auto outsize = this->size.Get().y;
205 
206  // do clamping
207  if( insize > outsize )
208  this->position.Set( std::clamp( this->position.Get(), 0.0f - overreach, insize - outsize + overreach ) );
209  else
210  this->position.Set( 0 );
211  }
212  else
213  {
214  this->position.Set( 0 );
215  }
216 }
217 
219 {
220  if( !this->Elem::input_trigger_process( root, key ) )
221  return false;
222 
223  if( key.first == Input::Key::MouseScrollUp )
224  {
225  this->refresh_clamp_countdown();
226  this->position.Set( this->position.Get() - ScrollStep );
227  this->clamp_position();
228  return false;
229  }
230  else if( key.first == Input::Key::MouseScrollDown )
231  {
232  this->refresh_clamp_countdown();
233  this->position.Set( this->position.Get() + ScrollStep );
234  this->clamp_position();
235  return false;
236  }
237 
238  return true;
239 }
240 
242 {
243  if( !this->child.Get() )
244  {
245  this->preferredSize.Set( float3( 0, 0, 0 ) );
246  return;
247  }
248 
249  if( this->child.dirty() || this->expectedSize.dirty() )
250  {
251  this->expectedSize.clear_dirty();
252  this->child.Get()->expectedSize.Set( this->expectedSize.Get() );
253  }
254 
255  this->child.Get()->elem()->first_pass( er );
256 
257  if( this->child.dirty() || this->child.Get()->preferredSize.dirty() )
258  {
259  this->preferredSize.Set( this->child.Get()->preferredSize.Get() );
260  }
261 
262  if( this->position.dirty() )
263  {
264  this->position.clear_dirty();
265  if( this->position.Get() != this->smoothedPosition.Get() )
266  this->fixed_update_resume();
267  }
268 
269  if( this->child.dirty() || this->child.Get()->preferredSize.dirty() || this->smoothedPosition.dirty() || this->scissor.dirty() )
270  er->QueueSecondPass( this );
271 }
272 
274 {
275  if( !this->child.Get() )
276  return;
277 
278  if( this->child.dirty() || this->size.dirty() || this->modelTransform.dirty() || this->child.Get()->preferredSize.dirty() || this->smoothedPosition.dirty() || this->scissor.dirty() )
279  {
280  // clear dirty
281  this->child.Get()->preferredSize.clear_dirty();
282  this->child.clear_dirty();
283  this->size.clear_dirty();
284  this->modelTransform.clear_dirty();
285  this->smoothedPosition.clear_dirty();
286  this->scissor.clear_dirty();
287 
288  // update visualization attributes
289  auto insize = this->child.Get()->preferredSize.Get().y;
290  auto outsize = this->size.Get().y;
291  auto position_max = ( insize > outsize ) ? ( insize - outsize ) : 0.0f;
292 
293  this->Attribute_Set( &this->attr_position, this->smoothedPosition.Get() );
294  this->Attribute_Set( &this->attr_insize, insize );
295  this->Attribute_Set( &this->attr_outsize, outsize );
296  this->Attribute_Set( &this->attr_overlapTop, ( this->smoothedPosition.Get() < 0.0f ) ? ( 0.0f - this->smoothedPosition.Get() ) : 0.0f );
297  this->Attribute_Set( &this->attr_overlapBottom, ( this->smoothedPosition.Get() > position_max ) ? ( this->smoothedPosition.Get() - position_max ) : 0.0f );
298 
299  // aggregate size
300  float3 new_size = this->size.Get();
301  new_size.y = this->child.Get()->preferredSize.Get().y;
302 
303  // aggregate transform
304  this->clamp_position();
305  //auto smoothedPosition = this->smoothedPosition.Get(); // this allows physical overreach
306  auto smoothedPosition = std::clamp( this->smoothedPosition.Get(), 0.0f, std::max( 0.0f, insize - outsize ) ); // this just shows overreach
307  auto model = this->modelTransform.Get() * glm::translate( float4x4( 1 ), float3( 0, 0 - smoothedPosition, 0 ) );
308 
309  // my scissor
310  ShaderScissor my_scissor;
311  my_scissor.enabled = true;
312  my_scissor.model = this->modelTransform.Get();
313  my_scissor.size = this->size.Get();
314 
315  // set values
316  this->child.Get()->size.Set( new_size );
317  this->child.Get()->elem()->modelTransform.Set( model );
318  this->child.Get()->elem()->scissor.Set( this->scissor.Get() * my_scissor );
319  this->child.Get()->elem()->second_pass( er );
320  }
321 }
322 
323 void Scroller::reserve_hover_keys( InputRoot * root, bool reserve )
324 {
325  // reserve hover keys
326  this->ibq.ForeachHoverBinding(
327  this->_input_id,
328  [ root, reserve ]( Input::Key key, int device_id )
329  {
330  root->reserve_key( Input::DeviceKey( key, device_id ), reserve );
331  }
332  );
333 }
334 
336 {
337  this->Elem::enabled( val );
338  return this;
339 }
340 
341 }