tea 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. data/COPYING +674 -0
  2. data/COPYING.LESSER +165 -0
  3. data/README.rdoc +93 -0
  4. data/doc/example/bitmap_draw.rb +21 -0
  5. data/doc/example/bitmap_load.rb +14 -0
  6. data/doc/example/bitmap_new.rb +19 -0
  7. data/doc/example/circle.rb +24 -0
  8. data/doc/example/circle_alpha.rb +45 -0
  9. data/doc/example/circle_alpha_bitmap.rb +51 -0
  10. data/doc/example/circle_bitmap.rb +18 -0
  11. data/doc/example/clip.rb +46 -0
  12. data/doc/example/event_app.rb +45 -0
  13. data/doc/example/event_keyboard.rb +43 -0
  14. data/doc/example/event_mouse.rb +85 -0
  15. data/doc/example/font_hello.rb +22 -0
  16. data/doc/example/font_word_wrap.rb +44 -0
  17. data/doc/example/grab.rb +28 -0
  18. data/doc/example/init.rb +10 -0
  19. data/doc/example/lines.rb +49 -0
  20. data/doc/example/lines_aa.rb +44 -0
  21. data/doc/example/lines_alpha.rb +33 -0
  22. data/doc/example/point.rb +26 -0
  23. data/doc/example/rect.rb +15 -0
  24. data/doc/example/rect_alpha.rb +75 -0
  25. data/doc/example/screen_set_mode.rb +18 -0
  26. data/doc/example/screen_update.rb +14 -0
  27. data/doc/example/sfont_hello.rb +22 -0
  28. data/doc/example/smile.png +0 -0
  29. data/doc/example/smile_bounce.rb +44 -0
  30. data/doc/example/smile_move.rb +58 -0
  31. data/doc/example/smile_move_2.rb +78 -0
  32. data/doc/example/sound.rb +101 -0
  33. data/doc/example/state_app.rb +33 -0
  34. data/doc/example/state_keyboard.rb +23 -0
  35. data/doc/example/state_mouse.rb +60 -0
  36. data/doc/key_constants.textile +129 -0
  37. data/doc/key_modifiers.textile +19 -0
  38. data/doc/reference.textile +421 -0
  39. data/lib/tea.rb +34 -0
  40. data/lib/tea/c_bitmap.rb +122 -0
  41. data/lib/tea/c_error.rb +11 -0
  42. data/lib/tea/c_font.rb +302 -0
  43. data/lib/tea/c_sound.rb +144 -0
  44. data/lib/tea/m_color.rb +50 -0
  45. data/lib/tea/m_event.rb +65 -0
  46. data/lib/tea/m_event_app.rb +96 -0
  47. data/lib/tea/m_event_dispatch.rb +54 -0
  48. data/lib/tea/m_event_keyboard.rb +311 -0
  49. data/lib/tea/m_event_mouse.rb +189 -0
  50. data/lib/tea/mix_blitting.rb +31 -0
  51. data/lib/tea/mix_clipping.rb +71 -0
  52. data/lib/tea/mix_grabbing.rb +86 -0
  53. data/lib/tea/mix_image_saving.rb +70 -0
  54. data/lib/tea/mix_primitive.rb +613 -0
  55. data/lib/tea/o_screen.rb +98 -0
  56. metadata +137 -0
@@ -0,0 +1,144 @@
1
+ # This file contains the Sound class.
2
+
3
+ require 'sdl'
4
+
5
+ require 'tea/c_error'
6
+
7
+ #
8
+ module Tea
9
+
10
+ # The Sound class allows loading and playing of audio files.
11
+ #
12
+ # Currently supported formats: OGG, WAV, AIFF, RIFF, VOC.
13
+ class Sound
14
+
15
+ # Sound states returned by Tea::Sound#state.
16
+ STOPPED = :STOPPED
17
+ PLAYING = :PLAYING
18
+ PAUSED = :PAUSED
19
+
20
+ # As per Ruby/SDL's mixer API.
21
+ VOLUME_MAX = 128
22
+
23
+ # Tracked because Ruby/SDL's mixer API can't get the channel volumes.
24
+ @@volume = VOLUME_MAX
25
+
26
+ # Tracks which Sound objects are playing in which channels, to report
27
+ # accurate playing state info.
28
+ @@channel_sound_ids = []
29
+
30
+ # Get the master volume. Volume ranges from 0..128 inclusive.
31
+ def Sound.volume
32
+ @@volume
33
+ end
34
+
35
+ # Set the master volume. new_volume should be between 0..128 inclusive.
36
+ def Sound.volume=(new_volume)
37
+ v = (new_volume >= 0) ? (new_volume <= VOLUME_MAX ? new_volume : VOLUME_MAX) : 0
38
+ SDL::Mixer.set_volume -1, new_volume
39
+ @@volume = v
40
+ end
41
+
42
+ # Pause all sound.
43
+ def Sound.pause_all
44
+ SDL::Mixer.pause -1
45
+ end
46
+
47
+ # Resume playing all paused sound.
48
+ def Sound.resume_all
49
+ SDL::Mixer.resume -1
50
+ end
51
+
52
+ # Stop all sounds, playing or paused.
53
+ def Sound.stop_all
54
+ SDL::Mixer.halt -1
55
+ end
56
+
57
+
58
+ # Load a sound file.
59
+ #
60
+ # May raise Tea::Error on failure.
61
+ def initialize(path)
62
+ @wave = SDL::Mixer::Wave.load(path)
63
+ @channel = -1
64
+
65
+ # Tracked because Ruby/SDL's mixer API can't get sound volumes.
66
+ @volume = VOLUME_MAX
67
+ rescue SDL::Error => e
68
+ raise Tea::Error, e.message, e.backtrace
69
+ end
70
+
71
+ # Get the sound's volume. Volume ranges from 0..128 inclusive.
72
+ def volume
73
+ @volume
74
+ end
75
+
76
+ # Set the sound's volume. new_volume should be between 0..128 inclusive.
77
+ def volume=(new_volume)
78
+ v = (new_volume >= 0) ? (new_volume <= VOLUME_MAX ? new_volume : VOLUME_MAX) : 0
79
+ @wave.set_volume v
80
+ @volume = v
81
+ end
82
+
83
+ # Play the sound. If this sound is still playing, cut it off and start
84
+ # playing it from the start.
85
+ #
86
+ # loops should be the number of times to repeat playing the sound. Use -1
87
+ # to loop the sound forever.
88
+ def play(loops=0)
89
+ SDL::Mixer.halt(@channel) if channel_valid?
90
+
91
+ @channel = SDL::Mixer.play_channel(-1, @wave, loops)
92
+ @@channel_sound_ids[@channel] = object_id
93
+ end
94
+
95
+ # Pause the sound if it's playing, otherwise do nothing.
96
+ def pause
97
+ SDL::Mixer.pause(@channel) if channel_valid?
98
+ end
99
+
100
+ # Resume the sound if it's paused, otherwise do nothing.
101
+ def resume
102
+ SDL::Mixer.resume(@channel) if channel_valid?
103
+ end
104
+
105
+ # Stop the sound if it's playing or paused, otherwise do nothing.
106
+ def stop
107
+ SDL::Mixer.halt(@channel) if channel_valid?
108
+ end
109
+
110
+ # Check if the sound is stopped, playing or paused. Returns one of
111
+ # Tea::Sound::STOPPED, Tea::Sound::PLAYING or Tea::Sound::PAUSED. A sound
112
+ # that isn't playing or paused is considered stopped.
113
+ def state
114
+ if channel_valid?
115
+ if SDL::Mixer.play?(@channel)
116
+ # For some reason, pause? returns 0 and 1 instead of true and false.
117
+ if SDL::Mixer.pause?(@channel) != 0
118
+ PAUSED
119
+ else
120
+ PLAYING
121
+ end
122
+ else
123
+ STOPPED
124
+ end
125
+ else
126
+ STOPPED
127
+ end
128
+ end
129
+
130
+ private
131
+
132
+ # Check if @channel really maps to this sound. Returns the channel number
133
+ # if so, otherwise nil.
134
+ def channel_valid?
135
+ if @channel >= 0 && @@channel_sound_ids[@channel] == object_id
136
+ @channel
137
+ else
138
+ nil
139
+ end
140
+ end
141
+
142
+ end
143
+
144
+ end
@@ -0,0 +1,50 @@
1
+ # This file holds the Color module.
2
+
3
+ #
4
+ module Tea
5
+
6
+ # The Color module holds utility methods that can mix and split colors.
7
+ module Color
8
+
9
+ # Create a color from red, green, blue and optionally alpha parts.
10
+ # All values passed in should be within 0..255 inclusive.
11
+ def Color.mix(red, green, blue, alpha=255)
12
+ ((red & 0xff) << 24) |
13
+ ((green & 0xff) << 16) |
14
+ ((blue & 0xff) << 8) |
15
+ (alpha & 0xff)
16
+ end
17
+
18
+ # Break a colour up into its red, green, blue and alpha parts.
19
+ # Returns a 4-element array of the form [red, green, blue, alpha], where
20
+ # each element is within 0..255 inclusive.
21
+ def Color.split(color)
22
+ [(color & 0xff000000) >> 24,
23
+ (color & 0x00ff0000) >> 16,
24
+ (color & 0x0000ff00) >> 8,
25
+ (color & 0x000000ff)]
26
+ end
27
+
28
+ CLEAR = Color.mix( 0, 0, 0, 0)
29
+
30
+ BLACK = Color.mix( 0, 0, 0)
31
+ DARK_RED = Color.mix(128, 0, 0)
32
+ DARK_GREEN = Color.mix( 0, 128, 0)
33
+ DARK_YELLOW = Color.mix(128, 128, 0)
34
+ DARK_BLUE = Color.mix( 0, 0, 128)
35
+ DARK_MAGENTA = Color.mix(128, 0, 128)
36
+ DARK_CYAN = Color.mix( 0, 128, 128)
37
+ DARK_GRAY = Color.mix(128, 128, 128)
38
+
39
+ GRAY = Color.mix(192, 192, 192)
40
+ RED = Color.mix(255, 0, 0)
41
+ GREEN = Color.mix( 0, 255, 0)
42
+ YELLOW = Color.mix(255, 255, 0)
43
+ BLUE = Color.mix( 0, 0, 255)
44
+ MAGENTA = Color.mix(255, 0, 255)
45
+ CYAN = Color.mix( 0, 255, 255)
46
+ WHITE = Color.mix(255, 255, 255)
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,65 @@
1
+ # This file holds the core Event module.
2
+
3
+ require 'sdl'
4
+
5
+ require 'tea/c_error'
6
+ require 'tea/m_event_app'
7
+ require 'tea/m_event_dispatch'
8
+ require 'tea/m_event_keyboard'
9
+ require 'tea/m_event_mouse'
10
+
11
+ #
12
+ module Tea
13
+
14
+ # The Event module allows access to the event queue, and the classes of
15
+ # events that come out.
16
+ module Event
17
+
18
+ # Tea's generated event queue. Add to the back, get from the front.
19
+ @@event_queue = []
20
+
21
+ # Get the next event in the event queue. If wait is true and there are no
22
+ # events to return, this method will wait until there is one and return it.
23
+ # Otherwise, an empty event queue will return nil.
24
+ #
25
+ # May raise Tea::Error if getting an event fails.
26
+ def Event.get(wait=false)
27
+ if @@event_queue.length == 0
28
+ if wait
29
+ begin
30
+ sdl_event = SDL::Event.wait
31
+ if (out_events = translate_event(sdl_event))
32
+ @@event_queue.push *out_events
33
+ end
34
+ end until @@event_queue.length > 0
35
+ else
36
+ if (out_events = translate_event(SDL::Event.poll))
37
+ @@event_queue.push *out_events
38
+ end
39
+ end
40
+ end
41
+
42
+ tea_event = @@event_queue.shift
43
+ [App, Mouse, Kbd].each { |state_holder| state_holder.update_state tea_event }
44
+ tea_event
45
+ rescue SDL::Error => e
46
+ raise Tea::Error, e.message, e.backtrace
47
+ end
48
+
49
+ # Convert an SDL::Event into one or more Tea events. May return nil, a
50
+ # single event object or multiple events in an array.
51
+ def Event.translate_event(sdl_event)
52
+ case sdl_event
53
+ when SDL::Event::Active, SDL::Event::Quit
54
+ translate_app_event sdl_event
55
+ when SDL::Event::MouseMotion, SDL::Event::MouseButtonDown, SDL::Event::MouseButtonUp
56
+ translate_mouse_event sdl_event
57
+ when SDL::Event::KeyDown, SDL::Event::KeyUp
58
+ translate_keyboard_event sdl_event
59
+ end
60
+ end
61
+ private_class_method :translate_event
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,96 @@
1
+ # This file holds app-related event handling.
2
+
3
+ require 'sdl'
4
+
5
+ #
6
+ module Tea
7
+
8
+ module Mouse
9
+ # Event generated when mouse focus is gained.
10
+ class Gained
11
+ end
12
+
13
+ # Event generated when mouse focus is lost.
14
+ class Lost
15
+ end
16
+ end
17
+
18
+ module Kbd
19
+ # Event generated when keyboard input focus is gained.
20
+ class Gained
21
+ end
22
+
23
+ # Event generated when keyboard input focus is lost.
24
+ class Lost
25
+ end
26
+ end
27
+
28
+ module App
29
+ # Event generated when the player acts to close the screen window.
30
+ class Exit
31
+ end
32
+
33
+ # Event generated when the screen window is minimised.
34
+ class Minimized
35
+ end
36
+
37
+ # Event generated when the screen window is restored from being minimised.
38
+ class Restored
39
+ end
40
+
41
+ # Default for App.visible?.
42
+ @visible = true
43
+
44
+ # Returns true if the screen window has not been minimised, otherwise
45
+ # false.
46
+ def App.visible?
47
+ @visible
48
+ end
49
+
50
+ # Update the reported app state when a Tea event is retrieved, so
51
+ # App.visible? returns the right status. Called automatically by
52
+ # Event.get.
53
+ def App.update_state(tea_event)
54
+ case tea_event
55
+ when Minimized then @visible = false
56
+ when Restored then @visible = true
57
+ end
58
+ @visible
59
+ end
60
+ end
61
+
62
+ module Event
63
+
64
+ # APP constants rubysdl is missing. For internal use only.
65
+ APPMOUSEFOCUS_ = 0x01
66
+ APPINPUTFOCUS_ = 0x02
67
+ APPACTIVE_ = 0x04
68
+
69
+ # Translates an app-related SDL::Event into an array of Tea::Event
70
+ # objects. For internal use only.
71
+ def Event.translate_app_event(sdl_event)
72
+ out_events = []
73
+
74
+ case sdl_event
75
+ when SDL::Event::Quit
76
+ out_events.push App::Exit.new
77
+
78
+ when SDL::Event::Active
79
+ if (sdl_event.state & APPACTIVE_) != 0
80
+ out_events.push sdl_event.gain ? App::Restored.new : App::Minimized.new
81
+ end
82
+ if (sdl_event.state & APPINPUTFOCUS_) != 0
83
+ out_events.push sdl_event.gain ? Kbd::Gained.new : Kbd::Lost.new
84
+ end
85
+ if (sdl_event.state & APPMOUSEFOCUS_) != 0
86
+ out_events.push sdl_event.gain ? Mouse::Gained.new : Mouse::Lost.new
87
+ end
88
+ end
89
+
90
+ out_events
91
+ end
92
+ private_class_method :translate_app_event
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,54 @@
1
+ # This file contains the event dispatch mixin.
2
+
3
+ require 'tea/c_error'
4
+
5
+ #
6
+ module Tea
7
+
8
+ module Event
9
+
10
+ # The Dispatch mixin gives any object the ability to handle Tea events with
11
+ # method calls. To use it, include the mixin in your class
12
+ #
13
+ # include Tea::Event::Dispatch
14
+ #
15
+ # define the handling methods (all optional)
16
+ #
17
+ # def app_exit; end
18
+ # def app_restored; end
19
+ # def app_minimized; end
20
+ # def kbd_down; end
21
+ # def kbd_up; end
22
+ # def mouse_move; end
23
+ # def mouse_down; end
24
+ # def mouse_up; end
25
+ # def mouse_scroll; end
26
+ #
27
+ # and when you need events handled, call your instance's dispatch_event
28
+ # method
29
+ #
30
+ # handler = MyEventHandler.new
31
+ # ...
32
+ # loop do
33
+ # event = Tea::Event.get
34
+ # handler.dispatch_event event if event
35
+ # ...
36
+ # end
37
+ #
38
+ # Tea will then call the method matching the event received.
39
+ module Dispatch
40
+
41
+ def dispatch_event(tea_event)
42
+ class_string = tea_event.class.to_s
43
+ unless class_string =~ /^Tea::(?:App|Kbd|Mouse)::[A-Za-z]+$/
44
+ raise Tea::Error, "Can't dispatch on class #{class_string}", caller
45
+ end
46
+ msg = class_string.split('::', 2)[1].sub('::', '_').downcase.intern
47
+ send msg, tea_event if respond_to?(msg)
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,311 @@
1
+ # This file holds the key press and modifier events.
2
+
3
+ require 'sdl'
4
+
5
+ #
6
+ module Tea
7
+
8
+ module Kbd
9
+
10
+ # Superclass for all Kbd events.
11
+ class Event
12
+ @@sdl_key_table = {
13
+ SDL::Key::BACKSPACE => :BACKSPACE,
14
+ SDL::Key::TAB => :TAB,
15
+ #SDL::Key::CLEAR => clear, # ???
16
+ SDL::Key::RETURN => :ENTER,
17
+ SDL::Key::PAUSE => :PAUSE,
18
+ SDL::Key::ESCAPE => :ESCAPE,
19
+ SDL::Key::SPACE => :SPACE,
20
+ SDL::Key::EXCLAIM => :EXCLAMATION_MARK,
21
+ SDL::Key::QUOTEDBL => :DOUBLE_QUOTE,
22
+ SDL::Key::HASH => :HASH,
23
+ SDL::Key::DOLLAR => :DOLLAR,
24
+ SDL::Key::AMPERSAND => :AMPERSAND,
25
+ SDL::Key::QUOTE => :QUOTE,
26
+ SDL::Key::LEFTPAREN => :OPEN_PAREN,
27
+ SDL::Key::RIGHTPAREN => :CLOSE_PAREN,
28
+ SDL::Key::ASTERISK => :ASTERISK,
29
+ SDL::Key::PLUS => :PLUS,
30
+ SDL::Key::COMMA => :COMMA,
31
+ SDL::Key::MINUS => :MINUS,
32
+ SDL::Key::PERIOD => :PERIOD,
33
+ SDL::Key::SLASH => :SLASH,
34
+ SDL::Key::K0 => :K0,
35
+ SDL::Key::K1 => :K1,
36
+ SDL::Key::K2 => :K2,
37
+ SDL::Key::K3 => :K3,
38
+ SDL::Key::K4 => :K4,
39
+ SDL::Key::K5 => :K5,
40
+ SDL::Key::K6 => :K6,
41
+ SDL::Key::K7 => :K7,
42
+ SDL::Key::K8 => :K8,
43
+ SDL::Key::K9 => :K9,
44
+ SDL::Key::COLON => :COLON,
45
+ SDL::Key::SEMICOLON => :SEMICOLON,
46
+ SDL::Key::LESS => :LESS_THAN,
47
+ SDL::Key::EQUALS => :EQUALS,
48
+ SDL::Key::GREATER => :GREATER_THAN,
49
+ SDL::Key::QUESTION => :QUESTION_MARK,
50
+ SDL::Key::AT => :AT,
51
+ SDL::Key::LEFTBRACKET => :OPEN_SQUARE_BRACKET,
52
+ SDL::Key::BACKSLASH => :BACKSLASH,
53
+ SDL::Key::RIGHTBRACKET => :CLOSE_SQUARE_BRACKET,
54
+ SDL::Key::CARET => :CARET,
55
+ SDL::Key::UNDERSCORE => :UNDERSCORE,
56
+ SDL::Key::BACKQUOTE => :BACKTICK,
57
+ SDL::Key::A => :A,
58
+ SDL::Key::B => :B,
59
+ SDL::Key::C => :C,
60
+ SDL::Key::D => :D,
61
+ SDL::Key::E => :E,
62
+ SDL::Key::F => :F,
63
+ SDL::Key::G => :G,
64
+ SDL::Key::H => :H,
65
+ SDL::Key::I => :I,
66
+ SDL::Key::J => :J,
67
+ SDL::Key::K => :K,
68
+ SDL::Key::L => :L,
69
+ SDL::Key::M => :M,
70
+ SDL::Key::N => :N,
71
+ SDL::Key::O => :O,
72
+ SDL::Key::P => :P,
73
+ SDL::Key::Q => :Q,
74
+ SDL::Key::R => :R,
75
+ SDL::Key::S => :S,
76
+ SDL::Key::T => :T,
77
+ SDL::Key::U => :U,
78
+ SDL::Key::V => :V,
79
+ SDL::Key::W => :W,
80
+ SDL::Key::X => :X,
81
+ SDL::Key::Y => :Y,
82
+ SDL::Key::Z => :Z,
83
+ SDL::Key::DELETE => :DELETE,
84
+ SDL::Key::KP0 => :NP0,
85
+ SDL::Key::KP1 => :NP1,
86
+ SDL::Key::KP2 => :NP2,
87
+ SDL::Key::KP3 => :NP3,
88
+ SDL::Key::KP4 => :NP4,
89
+ SDL::Key::KP5 => :NP5,
90
+ SDL::Key::KP6 => :NP6,
91
+ SDL::Key::KP7 => :NP7,
92
+ SDL::Key::KP8 => :NP8,
93
+ SDL::Key::KP9 => :NP9,
94
+ SDL::Key::KP_PERIOD => :NP_PERIOD,
95
+ SDL::Key::KP_DIVIDE => :NP_DIVIDE,
96
+ SDL::Key::KP_MULTIPLY => :NP_MULTIPLY,
97
+ SDL::Key::KP_MINUS => :NP_MINUS,
98
+ SDL::Key::KP_PLUS => :NP_PLUS,
99
+ SDL::Key::KP_ENTER => :NP_ENTER,
100
+ SDL::Key::KP_EQUALS => :NP_EQUALS,
101
+ SDL::Key::UP => :UP,
102
+ SDL::Key::DOWN => :DOWN,
103
+ SDL::Key::RIGHT => :RIGHT,
104
+ SDL::Key::LEFT => :LEFT,
105
+ SDL::Key::INSERT => :INSERT,
106
+ SDL::Key::HOME => :HOME,
107
+ SDL::Key::END => :END,
108
+ SDL::Key::PAGEUP => :PAGE_UP,
109
+ SDL::Key::PAGEDOWN => :PAGE_DOWN,
110
+ SDL::Key::F1 => :F1,
111
+ SDL::Key::F2 => :F2,
112
+ SDL::Key::F3 => :F3,
113
+ SDL::Key::F4 => :F4,
114
+ SDL::Key::F5 => :F5,
115
+ SDL::Key::F6 => :F6,
116
+ SDL::Key::F7 => :F7,
117
+ SDL::Key::F8 => :F8,
118
+ SDL::Key::F9 => :F9,
119
+ SDL::Key::F10 => :F10,
120
+ SDL::Key::F11 => :F11,
121
+ SDL::Key::F12 => :F12,
122
+ #SDL::Key::F13 => F13, # Who
123
+ #SDL::Key::F14 => F14, # has
124
+ #SDL::Key::F15 => F15, # these?
125
+ SDL::Key::NUMLOCK => :NUM_LOCK,
126
+ SDL::Key::CAPSLOCK => :CAPS_LOCK,
127
+ SDL::Key::SCROLLOCK => :SCROLL_LOCK,
128
+ SDL::Key::RSHIFT => :R_SHIFT,
129
+ SDL::Key::LSHIFT => :L_SHIFT,
130
+ SDL::Key::RCTRL => :R_CTRL,
131
+ SDL::Key::LCTRL => :L_CTRL,
132
+ SDL::Key::RALT => :R_ALT,
133
+ SDL::Key::LALT => :L_ALT,
134
+ #SDL::Key::RMETA => right meta, # 'meta' should
135
+ #SDL::Key::LMETA => left meta, # be 'alt'?
136
+ SDL::Key::LSUPER => :L_SUPER,
137
+ SDL::Key::RSUPER => :R_SUPER,
138
+ SDL::Key::MODE => :ALT_GR,
139
+ #SDL::Key::HELP => help, # Rare enough to cause problems.
140
+ SDL::Key::PRINT => :PRINT_SCREEN,
141
+ SDL::Key::SYSREQ => :SYS_REQ,
142
+ SDL::Key::BREAK => :BREAK,
143
+ SDL::Key::MENU => :MENU,
144
+ #SDL::Key::POWER => power, # "Power Macintosh" power key
145
+ SDL::Key::EURO => :EURO, # Some European keyboards need this.
146
+ }
147
+
148
+ # Call a block for each defined Tea key constant.
149
+ def Event.each_key
150
+ @@sdl_key_table.each_value { |key_const| yield key_const }
151
+ end
152
+
153
+ protected
154
+
155
+ # Convert an SDL keysym to a Tea key constant
156
+ def sdl_keysym_to_key(sdl_keysym)
157
+ @@sdl_key_table[sdl_keysym]
158
+ end
159
+
160
+ # Convert SDL key modifier info into an array of active key modifiers.
161
+ def sdl_keymod_to_mods(sdl_keymod)
162
+ mods = {}
163
+
164
+ mods[:L_SHIFT] = (sdl_keymod & SDL::Key::MOD_LSHIFT) != 0
165
+ mods[:R_SHIFT] = (sdl_keymod & SDL::Key::MOD_RSHIFT) != 0
166
+ mods[:SHIFT] = mods[:L_SHIFT] || mods[:R_SHIFT]
167
+
168
+ mods[:L_CTRL] = (sdl_keymod & SDL::Key::MOD_LCTRL) != 0
169
+ mods[:R_CTRL] = (sdl_keymod & SDL::Key::MOD_RCTRL) != 0
170
+ mods[:CTRL] = mods[:L_CTRL] || mods[:R_CTRL]
171
+
172
+ mods[:L_ALT] = (sdl_keymod & SDL::Key::MOD_LALT) != 0
173
+ mods[:R_ALT] = (sdl_keymod & SDL::Key::MOD_RALT) != 0
174
+ mods[:ALT] = mods[:L_ALT] || mods[:R_ALT]
175
+
176
+ mods[:NUM_LOCK] = (sdl_keymod & SDL::Key::MOD_NUM) != 0
177
+ mods[:CAPS_LOCK] = (sdl_keymod & SDL::Key::MOD_CAPS) != 0
178
+ mods[:ALT_GR] = (sdl_keymod & SDL::Key::MOD_MODE) != 0
179
+
180
+ mods
181
+ end
182
+ end
183
+
184
+ # Event generated when a key is pressed down.
185
+ #
186
+ # +key+:: Physical key that was pressed, as a symbol (see key reference).
187
+ # +mods+:: Hash of the active key modifiers. Values are +true+ or
188
+ # +false+, and the keys can be: +:L_SHIFT+, +:R_SHIFT+,
189
+ # +:L_CTRL+, +:R_CTRL+, +:L_ALT+, +:R_ALT+, +:NUM_LOCK+,
190
+ # +:CAPS_LOCK+, +:ALT_GR+. Also, +:SHIFT+, +:CTRL+ and +:ALT+
191
+ # are provided for convenience, and Tea key constants with the
192
+ # same names can be used instead.
193
+ # +char+:: String character generated by that key and those modifiers.
194
+ # With Ruby >= 1.9, the encoding of this character is UTF-8,
195
+ # otherwise it varies with the running environment.
196
+ class Down < Event
197
+ attr_reader :key, :mods, :char
198
+ def initialize(sdl_event)
199
+ @key = sdl_keysym_to_key(sdl_event.sym)
200
+ @mods = sdl_keymod_to_mods(sdl_event.mod)
201
+
202
+ if sdl_event.unicode != 0
203
+ unicode_field = "%c"
204
+ # Ruby 1.9 uses UTF-8 Unicode encoding. Otherwise, who knows how
205
+ # Unicode code points are interpreted?
206
+ unicode_field = unicode_field.encode('utf-8') if RUBY_1_9
207
+ @char = unicode_field % sdl_event.unicode
208
+ else
209
+ @char = ''
210
+ end
211
+ end
212
+ end
213
+
214
+ # Event generated when a held-down key is released. +key+ and +mods+ are
215
+ # the same as in KeyDown.
216
+ #
217
+ # +key+:: Physical key that was pressed, as a symbol (see key reference).
218
+ # +mods+:: Hash of the active key modifiers. Values are +true+ or
219
+ # +false+, and the keys can be: +:L_SHIFT+, +:R_SHIFT+,
220
+ # +:L_CTRL+, +:R_CTRL+, +:L_ALT+, +:R_ALT+, +:NUM_LOCK+,
221
+ # +:CAPS_LOCK+, +:ALT_GR+. Also, +:SHIFT+, +:CTRL+ and +:ALT+
222
+ # are provided for convenience, and Tea key constants with the
223
+ # same names can be used instead.
224
+ class Up < Event
225
+ attr_reader :key, :mods
226
+ def initialize(sdl_event)
227
+ @key = sdl_keysym_to_key(sdl_event.sym)
228
+ @mods = sdl_keymod_to_mods(sdl_event.mod)
229
+ end
230
+ end
231
+
232
+ # Defaults for Kbd.key_down?, Kbd.mod_active? and Kbd.in_app?.
233
+ @key_states = {}
234
+ Event.each_key { |key| @key_states[key] = false }
235
+
236
+ @mod_states = { :L_SHIFT => false, :R_SHIFT => false, :SHIFT => false,
237
+ :L_CTRL => false, :R_CTRL => false, :CTRL => false,
238
+ :L_ALT => false, :R_ALT => false, :ALT => false,
239
+ :NUM_LOCK => false,
240
+ :CAPS_LOCK => false,
241
+ :ALT_GR => false }
242
+
243
+ @in_app = true
244
+
245
+ # Returns +true+ if +key+ is being pressed down.
246
+ def Kbd.key_down?(key)
247
+ if (down = @key_states[key]) == nil
248
+ raise Tea::Error, "Can't find key #{key} to check", caller
249
+ end
250
+ down
251
+ end
252
+
253
+ # Returns true if the modifier is 'active'. For +:L_SHIFT+, +:R_SHIFT+,
254
+ # +:L_CTRL+, +:R_CTRL+, +:L_ALT+, and +:ALT_GR+ (and convenience modifier
255
+ # constants +:SHIFT+, +:CTRL+ and +:ALT+), this means they're being held
256
+ # down. For +:NUM_LOCK+ and +:CAPS_LOCK+, this means their toggle is on.
257
+ def Kbd.mod_active?(mod)
258
+ if (active = @mod_states[mod]) == nil
259
+ raise Tea::Error, "Can't find modifier #{mod} to check", caller
260
+ end
261
+ active
262
+ end
263
+
264
+ # Returns true if the keyboard is focused in the screen window.
265
+ def Kbd.in_app?
266
+ @in_app
267
+ end
268
+
269
+ # Update the keyboard state, so that Kbd.key_down? and Kbd.mod_active?
270
+ # provide fresh data. Called automatically by Event.get.
271
+ def Kbd.update_state(tea_event)
272
+ case tea_event
273
+ when Down, Up
274
+ @key_states[tea_event.key] = (tea_event.class == Down)
275
+ @mod_states.merge! tea_event.mods
276
+ when Lost then @in_app = false
277
+ when Gained then @in_app = true
278
+ end
279
+ end
280
+
281
+ # Define Tea key symbols as constants to avoid typo errors.
282
+ Event.each_key { |key| const_set(key, key) }
283
+
284
+ # Extra modifier constants for making modifier detection more consistent.
285
+ SHIFT = :SHIFT
286
+ CTRL = :CTRL
287
+ ALT = :ALT
288
+
289
+ end
290
+
291
+ module Event
292
+
293
+ # Convert a keyboard-related SDL::Event into a Tea event. For internal use
294
+ # only.
295
+ def Event.translate_keyboard_event(sdl_event)
296
+ out_events = []
297
+
298
+ case sdl_event
299
+ when SDL::Event::KeyDown
300
+ out_events.push Kbd::Down.new(sdl_event)
301
+ when SDL::Event::KeyUp
302
+ out_events.push Kbd::Up.new(sdl_event)
303
+ end
304
+
305
+ out_events
306
+ end
307
+ private_class_method :translate_keyboard_event
308
+
309
+ end
310
+
311
+ end