require "sdl" require "sdl/image" require "sdl/ttf" ## Helper code comes here class Naka def self.init SDL.init SDL::Init::VIDEO SDL::IMG.init SDL::IMG::Init::PNG SDL::TTF.init at_exit { SDL.quit SDL::TTF.quit } end end class Naka::Renderer < SDL::Renderer end class Naka::Window alias Flags = LibSDL::WindowFlags alias Position = LibSDL::WindowPosition getter renderer @current_font : SDL::TTF::Font? = nil def initialize(title, width, height, x : Position = Position::UNDEFINED, y : Position = Position::UNDEFINED, flags : Flags = Flags::SHOWN) @window = SDL::Window.new(title, width, height, x, y, flags) @renderer = Naka::Renderer.new @window @renderer.draw_blend_mode = SDL::BlendMode::BLEND end def newImage(file_path) : SDL::Texture SDL::IMG.load(file_path, @renderer).tap do |texture| texture.blend_mode = SDL::BlendMode::BLEND end end def newFont(file_path, font_size = 12) : SDL::TTF::Font SDL::TTF::Font.new file_path, font_size end # FIXME: We’ll probably want options for scaling, rotations, and so on. def draw(texture : SDL::Texture, x : Int32, y : Int32, scale_x = 1, scale_y = 1) @renderer.copy texture, dstrect: SDL::Rect[x, y, (texture.width * scale_x).to_i, (texture.height * scale_y).to_i] end def draw(surface : SDL::Surface, x : Int32, y : Int32) @renderer.copy surface, dstrect: SDL::Rect[x, y, surface.width, surface.height] end def set_font(@current_font) end # FIXME: # - This only prints properly “solid” text. Shaded text (depixelated) # would need some additional OpenGL manipulations. # - This is very inefficient resources-wise. If we are willing to drop # ligatures support (is it even supported in SDL::TTF?), we should # build a textures atlas of all glyphs and use that to render text. # - Alternatively, it may be worth it to let users generate a texture of # some text and draw it, manually. def print(text, x, y) font = @current_font return if font.nil? surface = font.render_solid(text, SDL::Color[0, 0, 0, 255], @renderer.draw_color) draw surface, x, y end end class Naka::Timer getter start getter last_update def initialize @start = Time.local @last_update = @start end def step now = Time.local dt = now.to_unix_ms - @last_update.to_unix_ms @last_update = now dt end end class Naka::Event class Quit end class Draw getter window : Naka::Window def initialize(@window) end end class Update getter dt : Int64 def initialize(@dt) end end # FIXME: Split in KeyUp and KeyDown. class KeyUp getter key : LibSDL::Keycode getter scan_code : LibSDL::Scancode def initialize(@key, @scan_code) end end class KeyDown getter key : LibSDL::Keycode getter scan_code : LibSDL::Scancode getter repeat : Bool def initialize(@key, @scan_code, @repeat) end end # FIXME: THIS MAIN LOOP IS **NOT** READY FOR PRODUCTION # Update events are still missing, and both Draw and Update should # be time-based. # Why having a `window` parameter instead of making a Window#loop method? # Because we may want to have a `windows` parameter in the future, although # unlikely. But still possible. def self.loop(window, &block) max_fps = 60 max_dt = 1000. / 60 timer = Naka::Timer.new renderer = window.renderer ::loop do exit_requested = false while event = SDL::Event.poll r_value = yield case event when SDL::Event::Quit Naka::Event::Quit.new when SDL::Event::Keyboard keysym = event.keysym if event.type == LibSDL::EventType::KEYUP Naka::Event::KeyUp.new keysym.sym, keysym.scancode elsif event.type == LibSDL::EventType::KEYDOWN Naka::Event::KeyDown.new keysym.sym, keysym.scancode, event.repeat != 0 end else # FIXME: Most event types may need proper Naka:: classes. event end if r_value == Naka::Event::Quit exit_requested = true break end end break if exit_requested dt = timer.step r_value = yield Naka::Event::Update.new dt break if r_value == Naka::Event::Quit renderer.draw_color = SDL::Color[255, 255, 255, 255] renderer.clear yield Naka::Event::Draw.new window renderer.present sleep 0.001 end end end