Created
June 3, 2026 09:57
-
-
Save skinnyjames/4f0628a5a87fd5f48960a98be648bb82 to your computer and use it in GitHub Desktop.
friction.rb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| module HackOS | |
| class Panel < Hokusai::Block | |
| template <<~EOF | |
| [template] | |
| clipped { | |
| @tap="on_tapdown" | |
| @drag="on_taphold" | |
| @taphold="on_taphold" | |
| @swipe="on_swipe" | |
| @taprelease="on_taprelease" | |
| :autoclip="autoclip" | |
| :offset="offset" | |
| } | |
| dynamic { @size_updated="set_size" } | |
| slot | |
| EOF | |
| uses( | |
| clipped: Hokusai::Blocks::Clipped, | |
| dynamic: Hokusai::Blocks::Dynamic, | |
| ) | |
| computed :autoclip, default: true | |
| computed :friction, default: 0.96, convert: proc(&:to_f) | |
| attr_accessor :top, :offset, :clipped_content_height, :panel_height | |
| def on_tapdown(event) | |
| @tap = event.pos.y | |
| @timer ||= Timer.new # for swipes | |
| @velocity = nil | |
| end | |
| def on_taprelease(event) | |
| @tap = nil | |
| @delta = nil | |
| @timer = nil | |
| end | |
| # move the screen according to hold pos.y | |
| def on_taphold(event) | |
| @delta = (@tap - event.pos.y.to_f) | |
| @tap = event.pos.y | |
| @timer.restart | |
| clamp_offset(@delta) | |
| end | |
| def on_swipe(event) | |
| if event.swipe_direction == :up || event.swipe_direction == :down | |
| @velocity = (@delta / (@timer.elapsed * 1000)) | |
| end | |
| end | |
| def clamp_offset(delta) | |
| if offset + delta < panel_top | |
| self.offset = panel_top | |
| elsif offset + delta > content_height - panel_height | |
| self.offset = content_height - panel_height | |
| else | |
| self.offset += delta | |
| end | |
| end | |
| def momentum | |
| @velocity *= friction; | |
| if @velocity.abs < 0.01 | |
| @velocity = nil | |
| return | |
| end | |
| offsetdelta = @velocity * 16 | |
| clamp_offset(offsetdelta) | |
| end | |
| def set_size(_, height) | |
| if panel_height != clipped_content_height || clipped_content_height.zero? | |
| self.clipped_content_height = height | |
| end | |
| end | |
| def content_height | |
| clipped_content_height | |
| end | |
| def panel_top | |
| top || 0.0 | |
| end | |
| def set_size(_, height) | |
| if panel_height != clipped_content_height || clipped_content_height.zero? | |
| self.clipped_content_height = height | |
| end | |
| end | |
| def initialize(**args) | |
| @top = nil | |
| @offset = 0 | |
| @clipped_content_height = 0 | |
| @panel_height = 0 | |
| @velocity = 0.0 # between 0 and 1 | |
| super | |
| end | |
| def render(canvas) | |
| self.top ||= canvas.y | |
| self.panel_height = canvas.height | |
| if @velocity | |
| momentum | |
| end | |
| @timer&.next | |
| yield canvas | |
| end | |
| end | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment