summaryrefslogtreecommitdiffhomepage
path: root/mrbgems/raylib/mrblib/raylib.rb
blob: 7e6542cff8ffc89a03816eb3790065771d0f0d07 (plain)
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
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Friendly Ruby sugar layered over the generated raylib bindings (see
# tools/gen_raylib.rb). The generated API is the complete, positional surface
# (e.g. Rl.draw_text(text, x, y, size, color), Rl.init_window(w, h, title), all
# the Rl::Color / Rl::Vector2 / ... structs, Rl::KEY_* constants, etc.).
#
# This file adds the niceties from docs/API_SPEC.md: block-scoped Begin/End
# pairs, the web-safe main loop, keyword-arg helpers for a few common calls,
# symbol keys, and convenience aliases.

module Rl
  # Symbol -> keycode (letters/digits map to ASCII; named keys to KEY_* consts).
  SYMBOL_KEYS = {}
  ('a'..'z').each { |c| SYMBOL_KEYS[c.to_sym] = c.upcase.ord }
  ('0'..'9').each { |c| SYMBOL_KEYS[c.to_sym] = c.ord }
  {
    space: :KEY_SPACE, enter: :KEY_ENTER, escape: :KEY_ESCAPE, tab: :KEY_TAB,
    backspace: :KEY_BACKSPACE, up: :KEY_UP, down: :KEY_DOWN, left: :KEY_LEFT,
    right: :KEY_RIGHT, left_shift: :KEY_LEFT_SHIFT, left_control: :KEY_LEFT_CONTROL
  }.each { |sym, const| SYMBOL_KEYS[sym] = const_get(const) if const_defined?(const) }
  # Function keys F1-F12 (raylib keycodes 290-301). Added so :f2 etc. resolve —
  # the KEY_F* constants aren't all exposed to mruby, so use the int values.
  { f1: 290, f2: 291, f3: 292, f4: 293, f5: 294, f6: 295, f7: 296, f8: 297,
    f9: 298, f10: 299, f11: 300, f12: 301 }.each { |sym, kc| SYMBOL_KEYS[sym] = kc }

  class << self
    def resolve_key(key)
      case key
      when Integer then key
      when Symbol  then SYMBOL_KEYS.fetch(key) { raise ArgumentError, "unknown key #{key.inspect}" }
      else raise ArgumentError, "key must be Integer or Symbol"
      end
    end

    # --- convenience aliases (spec-style names) ---
    alias_method :target_fps=,  :set_target_fps
    alias_method :master_volume=, :set_master_volume
    alias_method :frame_time,   :get_frame_time
    alias_method :time,         :get_time
    alias_method :fps,          :get_fps
    alias_method :screen_width,  :get_screen_width
    alias_method :screen_height, :get_screen_height
    alias_method :mouse_x,       :get_mouse_x
    alias_method :mouse_y,       :get_mouse_y
    alias_method :mouse_position, :get_mouse_position
    alias_method :mouse_wheel,    :get_mouse_wheel_move
    # The generator only suffixes `?` on Is* predicates; WindowShouldClose binds
    # as `window_should_close`. Provide the spec's `?` form (API_SPEC.md) — the
    # desktop seam below relies on it.
    alias_method :window_should_close?, :window_should_close

    def platform = _is_web ? :web : :desktop
    def web?     = _is_web
    def desktop? = !_is_web

    # --- input with symbol-key support (override the generated int-only ones) ---
    alias_method :_c_key_down?, :key_down?
    alias_method :_c_key_pressed?, :key_pressed?
    alias_method :_c_key_released?, :key_released?
    alias_method :_c_key_up?, :key_up?
    def key_down?(k)     = _c_key_down?(resolve_key(k))
    def key_pressed?(k)  = _c_key_pressed?(resolve_key(k))
    def key_released?(k) = _c_key_released?(resolve_key(k))
    def key_up?(k)       = _c_key_up?(resolve_key(k))

    # --- the single sanctioned loop (web-safe seam) ---
    # Also the bridge drain point: agent/console commands are queued off-frame
    # and run here, on the main thread, before the game block (P6). Desktop only
    # for R1; the web drain is wired in R4.
    #
    # Screenshot mode (desktop only): gate on JAMSTACK_SCREENSHOT=<path>. After
    # JAMSTACK_SCREENSHOT_FRAMES frames (default 30) the game block has rendered
    # and swapped, so the framebuffer holds a settled frame; take_screenshot writes
    # a pixel-exact PNG to <path>, then the loop breaks + close_window exits clean.
    # Generic — works on ANY game script with zero per-script changes (see
    # bin/screenshot). JAMSTACK_SCREENSHOT_DELAY=<seconds> adds a wall-clock wait
    # before the capture (for async/asset settle). Set JAMSTACK_SCREENSHOT_ONCE=0
    # to keep running after capture (default exits).
    def while_window_open(&block)
      ::Jamstack::Log.setup
      if ::Jamstack::Bridge.enabled?
        ::Jamstack::Bridge.start
        ::Jamstack::Live.start
      end
      if _is_web
        # Web: no TCP/.live (browser sandbox); eval arrives via the jamstack_eval
        # C export. Still advance the frame counter and log loop exceptions.
        _run_web_loop do
          ::Jamstack::Log.tick!
          begin
            block.call
          rescue Exception => e
            ::Jamstack::Log.exception(e, tag: "loop")
            raise
          end
        end
      else
        ss_path  = ::Jamstack.getenv('JAMSTACK_SCREENSHOT')
        ss_frames = 30
        ss_delay  = 0.0
        ss_once   = 1
        if (v = ::Jamstack.getenv('JAMSTACK_SCREENSHOT_FRAMES')); ss_frames = v.to_i; end
        if (v = ::Jamstack.getenv('JAMSTACK_SCREENSHOT_DELAY'));  ss_delay  = v.to_f; end
        if (v = ::Jamstack.getenv('JAMSTACK_SCREENSHOT_ONCE'));   ss_once   = v.to_i; end
        ss_done  = false
        frame_no = 0
        until window_should_close?
          ::Jamstack::Log.tick!
          ::Jamstack::Bridge.drain
          ::Jamstack::Live.poll
          begin
            block.call
          rescue Exception => e
            ::Jamstack::Log.exception(e, tag: "loop")
            raise
          end
          frame_no += 1
          next unless ss_path && !ss_done && frame_no >= ss_frames
          sleep(ss_delay) if ss_delay > 0.0
          take_screenshot(ss_path)
          ss_done = true
          ::Jamstack::Log.info("screenshot -> #{ss_path} (frame #{frame_no})") rescue nil
          break if ss_once != 0
        end
        close_window
      end
    end

    # --- block-scoped Begin/End pairs (API_SPEC 1.3), exception-safe ---
    def draw(clear_color: RAYWHITE)
      begin_drawing
      clear_background(clear_color)
      begin; yield; ensure end_drawing; end
    end

    def scissor_mode(x:, y:, width:, height:)
      begin_scissor_mode(x, y, width, height)
      begin; yield; ensure end_scissor_mode; end
    end

    def mode_2d(camera)
      begin_mode2d(camera)
      begin; yield; ensure end_mode2d; end
    end

    def mode_3d(camera)
      begin_mode3d(camera)
      begin; yield; ensure end_mode3d; end
    end

    def texture_mode(render_texture)
      begin_texture_mode(render_texture)
      begin; yield; ensure end_texture_mode; end
    end

    def blend_mode(mode)
      begin_blend_mode(mode)
      begin; yield; ensure end_blend_mode; end
    end

    def shader_mode(shader)
      begin_shader_mode(shader)
      begin; yield; ensure end_shader_mode; end
    end

    # --- keyword-arg helpers for common many-arg calls (API_SPEC 1.4/1.5) ---
    alias_method :_c_draw_text, :draw_text
    def draw_text(text:, x:, y:, font_size:, color:)
      _c_draw_text(text.to_s, x, y, font_size, color)
    end

    alias_method :_c_draw_texture_pro, :draw_texture_pro
    def draw_texture_pro(texture:, source:, dest:, origin: Vector2.new(0, 0),
                         rotation: 0, tint: WHITE)
      _c_draw_texture_pro(texture, source, dest, origin, rotation, tint)
    end
  end
end