199 lines
4.2 KiB
Crystal
199 lines
4.2 KiB
Crystal
|
|
|||
|
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.now
|
|||
|
@last_update = @start
|
|||
|
end
|
|||
|
|
|||
|
def step
|
|||
|
now = Time.now
|
|||
|
|
|||
|
dt = now.epoch_ms - @last_update.epoch_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
|
|||
|
|