commit 6151f28ef3e3d0fa9c0a6c1894dba8db009cf6cc Author: Sander Schobers Date: Thu Dec 19 07:08:52 2019 +0100 Created new repository. diff --git a/audio.go b/audio.go new file mode 100644 index 0000000..d5cc8fa --- /dev/null +++ b/audio.go @@ -0,0 +1,37 @@ +package allg5 + +/* +#define ALLEGRO_KCM_AUDIO_SRC + +#include +*/ +import "C" + +type Recorder struct { + recorder *C.ALLEGRO_AUDIO_RECORDER + Frequency int + Depth int +} + +func NewRecorder(fragments, samples, frequency, depth int) *Recorder { + var depthC C.ALLEGRO_AUDIO_DEPTH + switch depth { + case 16: + depthC = C.ALLEGRO_AUDIO_DEPTH_INT16 + default: + depthC = C.ALLEGRO_AUDIO_DEPTH_UINT8 + } + var rec = C.al_create_audio_recorder(C.size_t(fragments), C.uint(samples), C.uint(frequency), depthC, C.ALLEGRO_CHANNEL_CONF_1) + if rec == nil { + return nil + } + return &Recorder{rec, frequency, depth} +} + +func (r *Recorder) Start() { + C.al_start_audio_recorder(r.recorder) +} + +func (r *Recorder) Destroy() { + C.al_destroy_audio_recorder(r.recorder) +} diff --git a/bitmap.go b/bitmap.go new file mode 100644 index 0000000..9a3e024 --- /dev/null +++ b/bitmap.go @@ -0,0 +1,260 @@ +package allg5 + +// #include +// #include +import "C" + +import ( + "errors" + "image" + "unsafe" +) + +// Bitmap represents an in memory bitmap +type Bitmap struct { + bitmap *C.ALLEGRO_BITMAP + width int + height int + subs []*Bitmap +} + +type DrawOptions struct { + Center bool + Scale *Scale + Tint *Color + Rotation *Rotation +} + +type Scale struct { + Horizontal float32 + Vertical float32 +} + +func NewScale(hor, ver float32) *Scale { + return &Scale{hor, ver} +} + +func NewUniformScale(s float32) *Scale { + return &Scale{s, s} +} + +type Rotation struct { + Angle float32 + Center bool +} + +func newBitmap(width, height int, mut func(m FlagMutation), flags []NewBitmapFlag) (*Bitmap, error) { + var newBmpFlags = CaptureNewBitmapFlags() + defer newBmpFlags.Revert() + newBmpFlags.Mutate(func(m FlagMutation) { + if mut != nil { + mut(m) + } + for _, f := range flags { + m.Set(f) + } + }) + b := C.al_create_bitmap(C.int(width), C.int(height)) + if b == nil { + return nil, errors.New("error creating bitmap") + } + return &Bitmap{b, width, height, nil}, nil +} + +// NewBitmap creates a new bitmap of given width and height and optional flags +func NewBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) { + return newBitmap(width, height, nil, flags) +} + +// NewVideoBitmap creates a new video bitmap of given width and height and optional flags +func NewVideoBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) { + return newBitmap(width, height, func(m FlagMutation) { + m.Unset(NewBitmapFlagMemoryBitmap) + m.Set(NewBitmapFlagVideoBitmap) + }, flags) +} + +// NewMemoryBitmap creates a new video bitmap of given width and height and optional flags +func NewMemoryBitmap(width, height int, flags ...NewBitmapFlag) (*Bitmap, error) { + return newBitmap(width, height, func(m FlagMutation) { + m.Unset(NewBitmapFlagVideoBitmap) + m.Set(NewBitmapFlagMemoryBitmap) + }, flags) +} + +// NewBitmapFromImage creates a new bitmap starting from a Go native image (image.Image) +func NewBitmapFromImage(src image.Image, video bool) (*Bitmap, error) { + var newBmpFlags = CaptureNewBitmapFlags() + defer newBmpFlags.Revert() + newBmpFlags.Mutate(func(m FlagMutation) { + m.Unset(NewBitmapFlagVideoBitmap) + m.Set(NewBitmapFlagMemoryBitmap) + m.Set(NewBitmapFlagMinLinear) + }) + var bnd = src.Bounds() + width, height := bnd.Dx(), bnd.Dy() + var b = C.al_create_bitmap(C.int(width), C.int(height)) + if b == nil { + return nil, errors.New("error creating memory bitmap") + } + region := C.al_lock_bitmap(b, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_WRITEONLY) + if region == nil { + C.al_destroy_bitmap(b) + return nil, errors.New("unable to lock bitmap") + } + dst := (*[1 << 30]uint8)(region.data) + left, top := bnd.Min.X, bnd.Min.Y + for y := 0; y < height; y++ { + row := dst[y*int(region.pitch):] + for x := 0; x < width; x++ { + r, g, b, a := src.At(left+x, top+y).RGBA() + row[x*4] = uint8(r >> 8) + row[x*4+1] = uint8(g >> 8) + row[x*4+2] = uint8(b >> 8) + row[x*4+3] = uint8(a >> 8) + } + } + C.al_unlock_bitmap(b) + if video { + newBmpFlags.Mutate(func(m FlagMutation) { + m.Unset(NewBitmapFlagMemoryBitmap) + m.Set(NewBitmapFlagVideoBitmap) + m.Set(NewBitmapFlagMinLinear) + }) + C.al_convert_bitmap(b) + } + return &Bitmap{b, width, height, nil}, nil +} + +// LoadBitmap tries to load the image at the specified path as a bitmap +func LoadBitmap(path string) (*Bitmap, error) { + p := C.CString(path) + defer C.free(unsafe.Pointer(p)) + b := C.al_load_bitmap(p) + if b == nil { + return nil, errors.New("error loading bitmap") + } + width := int(C.al_get_bitmap_width(b)) + height := int(C.al_get_bitmap_height(b)) + return &Bitmap{b, width, height, nil}, nil +} + +// Draw draws the bitmap at the given location +func (b *Bitmap) Draw(left, top float32) { + C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0) +} + +func (b *Bitmap) DrawOptions(left, top float32, options DrawOptions) { + width := float32(b.width) + height := float32(b.height) + + scale := options.Scale != nil + if scale { + width *= options.Scale.Horizontal + height *= options.Scale.Vertical + } + if options.Center { + left -= width * 0.5 + top -= height * 0.5 + } + rotated := options.Rotation != nil + var centerX C.float + var centerY C.float + if rotated && options.Rotation.Center { + centerX = C.float(b.width) * 0.5 + centerY = C.float(b.height) * 0.5 + } + + if scale { + if options.Tint == nil { // scaled + if rotated { // scaled & rotated + C.al_draw_scaled_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Scale.Horizontal), C.float(options.Scale.Vertical), C.float(options.Rotation.Angle), 0) + } else { // scaled + C.al_draw_scaled_bitmap(b.bitmap, 0, 0, C.float(b.width), C.float(b.height), C.float(left), C.float(top), C.float(width), C.float(height), 0) + } + } else { // tinted & scaled + if rotated { // scaled, tinted & rotated + C.al_draw_tinted_scaled_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Scale.Horizontal), C.float(options.Scale.Vertical), C.float(options.Rotation.Angle), 0) + } else { // tinted, scaled + C.al_draw_tinted_scaled_bitmap(b.bitmap, options.Tint.color, 0, 0, C.float(b.width), C.float(b.height), C.float(left), C.float(top), C.float(width), C.float(height), 0) + } + } + } else { + if options.Tint == nil { + if rotated { // rotated + C.al_draw_rotated_bitmap(b.bitmap, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0) + } else { + C.al_draw_bitmap(b.bitmap, C.float(left), C.float(top), 0) + } + } else { // tinted + if rotated { // tinted & rotated + C.al_draw_tinted_rotated_bitmap(b.bitmap, options.Tint.color, centerX, centerY, C.float(left), C.float(top), C.float(options.Rotation.Angle), 0) + } else { + C.al_draw_tinted_bitmap(b.bitmap, options.Tint.color, C.float(left), C.float(top), 0) + } + } + } +} + +// Sub creates a sub-bitmap of the original bitmap +func (b *Bitmap) Sub(x, y, w, h int) *Bitmap { + var sub = C.al_create_sub_bitmap(b.bitmap, C.int(x), C.int(y), C.int(w), C.int(h)) + if sub == nil { + return nil + } + var bmp = &Bitmap{sub, w, h, nil} + b.subs = append(b.subs, bmp) + return bmp +} + +// Subs returns the slice of sub-bitmaps +func (b *Bitmap) Subs() []*Bitmap { + return b.subs +} + +func (b *Bitmap) Width() int { + return b.width +} + +func (b *Bitmap) Height() int { + return b.height +} + +func (b *Bitmap) IsVideo() bool { + return C.al_get_bitmap_flags(b.bitmap)&C.ALLEGRO_VIDEO_BITMAP == C.ALLEGRO_VIDEO_BITMAP +} + +func (b *Bitmap) Image() image.Image { + im := image.NewRGBA(image.Rect(0, 0, b.width, b.height)) + region := C.al_lock_bitmap(b.bitmap, C.ALLEGRO_PIXEL_FORMAT_ABGR_8888, C.ALLEGRO_LOCK_READONLY) + if region == nil { + return nil + } + defer C.al_unlock_bitmap(b.bitmap) + src := (*[1 << 30]uint8)(region.data) + dst := im.Pix + var srcOff, dstOff int + for y := 0; y < b.height; y++ { + copy(dst[dstOff:], src[srcOff:srcOff+b.width*4]) + srcOff += int(region.pitch) + dstOff += im.Stride + } + return im +} + +func (b *Bitmap) SetAsTarget() { + C.al_set_target_bitmap(b.bitmap) +} + +// Destroy destroys the bitmap +func (b *Bitmap) Destroy() { + var bmp = b.bitmap + if bmp == nil { + return + } + b.bitmap = nil + for _, sub := range b.subs { + sub.Destroy() + } + C.al_destroy_bitmap(bmp) +} diff --git a/c.go b/c.go new file mode 100644 index 0000000..b482ed5 --- /dev/null +++ b/c.go @@ -0,0 +1,6 @@ +// +build !windows + +package allg5 + +// #cgo pkg-config: allegro-5 allegro_audio-5 allegro_font-5 allegro_image-5 allegro_primitives-5 allegro_ttf-5 +import "C" diff --git a/c_windows.go b/c_windows.go new file mode 100644 index 0000000..b4d91db --- /dev/null +++ b/c_windows.go @@ -0,0 +1,6 @@ +// +build windows,!static + +package allg5 + +// #cgo LDFLAGS: -lallegro -lallegro_audio -lallegro_font -lallegro_image -lallegro_primitives -lallegro_ttf +import "C" diff --git a/c_windows_static.go b/c_windows_static.go new file mode 100644 index 0000000..e76510c --- /dev/null +++ b/c_windows_static.go @@ -0,0 +1,6 @@ +// +build windows,static + +package allg5 + +// #cgo LDFLAGS: -lallegro_monolith-static -static -ljpeg -ldumb -lFLAC -lfreetype -lvorbisfile -lvorbis -logg -lphysfs -lpng16 -lzlib -luuid -lkernel32 -lwinmm -lpsapi -lopengl32 -lglu32 -luser32 -lcomdlg32 -lgdi32 -lshell32 -lole32 -ladvapi32 -lws2_32 -lshlwapi -lstdc++ -lwebp +import "C" diff --git a/color.go b/color.go new file mode 100644 index 0000000..8e43305 --- /dev/null +++ b/color.go @@ -0,0 +1,36 @@ +package allg5 + +// #include +import "C" +import "image/color" + +var _ color.Color = &Color{} + +type Color struct { + color C.ALLEGRO_COLOR +} + +func NewColor(r, g, b byte) Color { + return Color{C.al_map_rgb(C.uchar(r), C.uchar(g), C.uchar(b))} +} + +func NewColorAlpha(r, g, b, a byte) Color { + return Color{C.al_map_rgba(C.uchar(r), C.uchar(g), C.uchar(b), C.uchar(a))} +} + +func NewColorGo(c color.Color) Color { + r, g, b, a := c.RGBA() + return Color{C.al_premul_rgba(C.uchar(r>>8), C.uchar(g>>8), C.uchar(b>>8), C.uchar(a>>8))} +} + +// RGBA implements the color.Color interface. +func (c Color) RGBA() (r, g, b, a uint32) { + var cr, cg, cb, ca C.uchar + C.al_unmap_rgba(c.color, &cr, &cg, &cb, &ca) + a = uint32(ca) + r = uint32(cr) * a + g = uint32(cg) * a + b = uint32(cb) * a + a *= a + return +} diff --git a/display.go b/display.go new file mode 100644 index 0000000..f693199 --- /dev/null +++ b/display.go @@ -0,0 +1,129 @@ +package allg5 + +// #include +import "C" + +import ( + "errors" + "unsafe" +) + +// Display represents a display +type Display struct { + display *C.ALLEGRO_DISPLAY +} + +type NewDisplayOptions struct { + Fullscreen bool + Resizable bool + Windowed bool + Maximized bool + Frameless bool + Vsync bool + Shaders bool + OpenGL bool +} + +// NewDisplay creates a display +func NewDisplay(width, height int, options NewDisplayOptions) (*Display, error) { + var flags C.int = C.ALLEGRO_WINDOWED + if options.Fullscreen { + if options.Windowed { + flags |= C.ALLEGRO_FULLSCREEN_WINDOW + } else { + flags = C.ALLEGRO_FULLSCREEN + } + } else if options.Frameless { + flags |= C.ALLEGRO_FRAMELESS + } + if options.Resizable { + flags |= C.ALLEGRO_RESIZABLE + if options.Maximized { + flags |= C.ALLEGRO_MAXIMIZED + } + } + if options.Vsync { + C.al_set_new_display_option(C.ALLEGRO_VSYNC, 1, C.ALLEGRO_SUGGEST) + } + if options.OpenGL { + flags |= C.ALLEGRO_OPENGL + } + if options.Shaders { + flags |= C.ALLEGRO_PROGRAMMABLE_PIPELINE + } + C.al_set_new_display_flags(flags) + d := C.al_create_display(C.int(width), C.int(height)) + if d == nil { + return nil, errors.New("error creating display") + } + return &Display{d}, nil +} + +// Flip flips the buffer to the display +func (d *Display) Flip() { + C.al_flip_display() +} + +func (d *Display) Width() int { + return int(C.al_get_display_width(d.display)) +} + +func (d *Display) Height() int { + return int(C.al_get_display_height(d.display)) +} + +func (d *Display) Position() (int, int) { + var x, y C.int + C.al_get_window_position(d.display, &x, &y) + return int(x), int(y) +} + +func (d *Display) Resize(w, h int) { + C.al_resize_display(d.display, C.int(w), C.int(h)) +} + +func (d *Display) SetAsTarget() { + C.al_set_target_backbuffer(d.display) +} + +func (d *Display) SetIcon(i *Bitmap) { + C.al_set_display_icon(d.display, i.bitmap) +} + +func (d *Display) SetMouseCursor(c MouseCursor) { + C.al_set_system_mouse_cursor(d.display, C.ALLEGRO_SYSTEM_MOUSE_CURSOR(c)) +} + +func (d *Display) SetMousePosition(x, y int) { + C.al_set_mouse_xy(d.display, C.int(x), C.int(y)) +} + +func (d *Display) SetPosition(x, y int) { + C.al_set_window_position(d.display, C.int(x), C.int(y)) +} + +func (d *Display) SetWindowTitle(title string) { + t := C.CString(title) + defer C.free(unsafe.Pointer(t)) + C.al_set_window_title(d.display, t) +} + +func (d *Display) Target() *Bitmap { + return &Bitmap{C.al_get_backbuffer(d.display), d.Width(), d.Height(), nil} +} + +// Destroy destroys the display +func (d *Display) Destroy() { + C.al_destroy_display(d.display) +} + +func CurrentTarget() *Bitmap { + var bmp = C.al_get_target_bitmap() + return &Bitmap{bmp, int(C.al_get_bitmap_width(bmp)), int(C.al_get_bitmap_height(bmp)), nil} +} + +func SetNewWindowTitle(title string) { + t := C.CString(title) + defer C.free(unsafe.Pointer(t)) + C.al_set_new_window_title(t) +} diff --git a/event.go b/event.go new file mode 100644 index 0000000..5261c44 --- /dev/null +++ b/event.go @@ -0,0 +1,265 @@ +package allg5 + +/* +#define ALLEGRO_KCM_AUDIO_SRC + +#include +#include + +#define USER_EVENT_TYPE ALLEGRO_GET_EVENT_TYPE('u', 's', 'e', 'r') + +void init_user_event(ALLEGRO_EVENT* e) +{ + e->user.type = USER_EVENT_TYPE; +} +*/ +import "C" + +import ( + "errors" + "unsafe" +) + +type EventQueue struct { + queue *C.ALLEGRO_EVENT_QUEUE +} + +type Event interface { + Stamp() float64 +} + +type EventBase struct { + stamp float64 +} + +func (eb EventBase) Stamp() float64 { + return eb.stamp +} + +type DisplayCloseEvent struct { + EventBase +} + +type DisplayResizeEvent struct { + EventBase + X, Y int + Width int + Height int +} + +type DisplayOrientation int + +const ( + DisplayOrientation0Degrees DisplayOrientation = iota + DisplayOrientation90Degrees + DisplayOrientation180Degrees + DisplayOrientation270Degrees + DisplayOrientationFaceUp + DisplayOrientationFaceDown +) + +func toDisplayOrientation(o C.int) DisplayOrientation { + switch o { + case C.ALLEGRO_DISPLAY_ORIENTATION_0_DEGREES: + return DisplayOrientation0Degrees + case C.ALLEGRO_DISPLAY_ORIENTATION_90_DEGREES: + return DisplayOrientation90Degrees + case C.ALLEGRO_DISPLAY_ORIENTATION_180_DEGREES: + return DisplayOrientation180Degrees + case C.ALLEGRO_DISPLAY_ORIENTATION_270_DEGREES: + return DisplayOrientation270Degrees + case C.ALLEGRO_DISPLAY_ORIENTATION_FACE_UP: + return DisplayOrientationFaceUp + case C.ALLEGRO_DISPLAY_ORIENTATION_FACE_DOWN: + return DisplayOrientationFaceDown + default: + panic("not supported") + } +} + +type DisplayOrientationEvent struct { + EventBase + Orientation DisplayOrientation +} + +type KeyEvent struct { + EventBase + KeyCode Key + Display *Display +} + +type KeyCharEvent struct { + KeyEvent + UnicodeCharacter rune + Modifiers KeyMod + Repeat bool +} + +type KeyDownEvent struct { + KeyEvent +} + +type KeyUpEvent struct { + KeyEvent +} + +type MouseButtonDownEvent struct { + MouseEvent + Button MouseButton + Pressure float32 +} + +type MouseButtonUpEvent struct { + MouseEvent + Button MouseButton + Pressure float32 +} + +type MouseEnterEvent struct { + MouseEvent +} + +type MouseEvent struct { + EventBase + X, Y int + Z, W int + Display *Display +} +type MouseLeaveEvent struct { + MouseEvent +} + +type MouseMoveEvent struct { + MouseEvent + DeltaX, DeltaY int + DeltaZ, DeltaW int + Pressure float32 +} + +type RecorderFragmentEvent struct { + EventBase + Buffer unsafe.Pointer + Samples int +} + +type UserEvent struct { + EventBase +} + +type UserEventSource struct { + source *C.ALLEGRO_EVENT_SOURCE +} + +func NewEventQueue() (*EventQueue, error) { + q := C.al_create_event_queue() + if q == nil { + return nil, errors.New("unable to create event queue") + } + return &EventQueue{q}, nil +} + +func (eq *EventQueue) register(source *C.ALLEGRO_EVENT_SOURCE) { + C.al_register_event_source(eq.queue, source) +} + +func (eq *EventQueue) RegisterDisplay(d *Display) { + eq.register(C.al_get_display_event_source(d.display)) +} + +func (eq *EventQueue) RegisterMouse() { + eq.register(C.al_get_mouse_event_source()) +} + +func (eq *EventQueue) RegisterKeyboard() { + eq.register(C.al_get_keyboard_event_source()) +} + +func (eq *EventQueue) RegisterRecorder(rec *Recorder) { + eq.register(C.al_get_audio_recorder_event_source(rec.recorder)) +} + +func (eq *EventQueue) RegisterUserEvents(source *UserEventSource) { + eq.register(source.source) +} + +func (eq *EventQueue) mapEvent(e *C.ALLEGRO_EVENT) Event { + any := (*C.ALLEGRO_ANY_EVENT)(unsafe.Pointer(e)) + eb := EventBase{float64(any.timestamp)} + switch any._type { + case C.ALLEGRO_EVENT_AUDIO_RECORDER_FRAGMENT: + recorder := (*C.ALLEGRO_AUDIO_RECORDER_EVENT)(unsafe.Pointer(e)) + return &RecorderFragmentEvent{eb, unsafe.Pointer(recorder.buffer), int(recorder.samples)} + case C.ALLEGRO_EVENT_DISPLAY_CLOSE: + return &DisplayCloseEvent{eb} + case C.ALLEGRO_EVENT_DISPLAY_ORIENTATION: + display := (*C.ALLEGRO_DISPLAY_EVENT)(unsafe.Pointer(e)) + return &DisplayOrientationEvent{eb, toDisplayOrientation(display.orientation)} + case C.ALLEGRO_EVENT_DISPLAY_RESIZE: + display := (*C.ALLEGRO_DISPLAY_EVENT)(unsafe.Pointer(e)) + C.al_acknowledge_resize(display.source) + return &DisplayResizeEvent{eb, int(display.x), int(display.y), int(display.width), int(display.height)} + case C.ALLEGRO_EVENT_MOUSE_AXES: + mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e)) + return &MouseMoveEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, int(mouse.dx), int(mouse.dy), int(mouse.dz), int(mouse.dw), float32(mouse.pressure)} + case C.ALLEGRO_EVENT_MOUSE_BUTTON_DOWN: + mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e)) + return &MouseButtonDownEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, MouseButton(mouse.button), float32(mouse.pressure)} + case C.ALLEGRO_EVENT_MOUSE_ENTER_DISPLAY: + mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e)) + return &MouseEnterEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}} + case C.ALLEGRO_EVENT_MOUSE_BUTTON_UP: + mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e)) + return &MouseButtonUpEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}, MouseButton(mouse.button), float32(mouse.pressure)} + case C.ALLEGRO_EVENT_MOUSE_LEAVE_DISPLAY: + mouse := (*C.ALLEGRO_MOUSE_EVENT)(unsafe.Pointer(e)) + return &MouseLeaveEvent{MouseEvent{eb, int(mouse.x), int(mouse.y), int(mouse.z), int(mouse.w), nil}} + case C.ALLEGRO_EVENT_KEY_DOWN: + key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e)) + return &KeyDownEvent{KeyEvent{eb, Key(key.keycode), nil}} + case C.ALLEGRO_EVENT_KEY_UP: + key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e)) + return &KeyUpEvent{KeyEvent{eb, Key(key.keycode), nil}} + case C.ALLEGRO_EVENT_KEY_CHAR: + key := (*C.ALLEGRO_KEYBOARD_EVENT)(unsafe.Pointer(e)) + return &KeyCharEvent{KeyEvent{eb, Key(key.keycode), nil}, rune(key.unichar), KeyMod(key.modifiers), bool(key.repeat)} + case C.USER_EVENT_TYPE: + return &UserEvent{eb} + } + return nil +} + +func (eq *EventQueue) Get() Event { + var event C.ALLEGRO_EVENT + if !bool(C.al_get_next_event(eq.queue, &event)) { + return nil + } + return eq.mapEvent(&event) +} + +func (eq *EventQueue) GetWait() Event { + var event C.ALLEGRO_EVENT + C.al_wait_for_event(eq.queue, &event) + return eq.mapEvent(&event) +} + +func (eq *EventQueue) Destroy() { + C.al_destroy_event_queue(eq.queue) +} + +func NewUserEventSource() *UserEventSource { + s := (*C.ALLEGRO_EVENT_SOURCE)(C.malloc(C.sizeof_ALLEGRO_EVENT_SOURCE)) + source := &UserEventSource{s} + C.al_init_user_event_source(s) + return source +} + +func (s *UserEventSource) Destroy() { + C.al_destroy_user_event_source(s.source) + C.free(unsafe.Pointer(s.source)) +} + +func (s *UserEventSource) EmitEvent() bool { + e := (*C.ALLEGRO_EVENT)(C.malloc(C.sizeof_ALLEGRO_EVENT)) + C.init_user_event(e) + return bool(C.al_emit_user_event(s.source, e, nil)) +} diff --git a/flagmut.go b/flagmut.go new file mode 100644 index 0000000..43684b4 --- /dev/null +++ b/flagmut.go @@ -0,0 +1,23 @@ +package allg5 + +// #include +import "C" + +type FlagMutation interface { + Set(f NewBitmapFlag) + Unset(f NewBitmapFlag) +} + +type flagMut struct { + flg C.int +} + +func (m *flagMut) Set(f NewBitmapFlag) { + m.flg |= C.int(f) +} + +func (m *flagMut) Unset(f NewBitmapFlag) { + if m.flg&C.int(f) == C.int(f) { + m.flg ^= C.int(f) + } +} diff --git a/font.go b/font.go new file mode 100644 index 0000000..3418eea --- /dev/null +++ b/font.go @@ -0,0 +1,107 @@ +package allg5 + +// #include +// #include +// #include +import "C" + +import ( + "fmt" + "unsafe" +) + +type Font struct { + font *C.ALLEGRO_FONT + hght float32 + asc float32 + desc float32 +} + +type HorizontalAlignment int + +const ( + AlignLeft HorizontalAlignment = iota + AlignCenter + AlignRight +) + +func LoadTTFFont(path string, size int) (*Font, error) { + p := C.CString(path) + defer C.free(unsafe.Pointer(p)) + + f := C.al_load_ttf_font(p, C.int(size), 0) + if f == nil { + return nil, fmt.Errorf("unable to load ttf font '%s'", path) + } + return &Font{f, 0, 0, 0}, nil +} + +func (f *Font) drawFlags(a HorizontalAlignment) C.int { + switch a { + case AlignLeft: + return C.ALLEGRO_ALIGN_LEFT + case AlignCenter: + return C.ALLEGRO_ALIGN_CENTRE + case AlignRight: + return C.ALLEGRO_ALIGN_RIGHT + } + return C.ALLEGRO_ALIGN_LEFT +} + +func (f *Font) Draw(left, top float32, color Color, align HorizontalAlignment, text string) { + t := C.CString(text) + defer C.free(unsafe.Pointer(t)) + + flags := f.drawFlags(align) + C.al_draw_text(f.font, color.color, C.float(left), C.float(top), flags, t) +} + +// Ascent returns the ascent of the font +func (f *Font) Ascent() float32 { + if f.asc == 0 { + f.asc = float32(C.al_get_font_ascent(f.font)) + } + return f.asc +} + +// Descent returns the descent of the font. +func (f *Font) Descent() float32 { + if f.desc == 0 { + f.desc = float32(C.al_get_font_descent(f.font)) + } + return f.desc +} + +// Height returns the height of the font +func (f *Font) Height() float32 { + if f.hght == 0 { + f.hght = f.Ascent() + f.Descent() + } + return f.hght +} + +// TextDimensions returns the bounding box of the rendered text. +func (f *Font) TextDimensions(text string) (x, y, w, h float32) { + t := C.CString(text) + defer C.free(unsafe.Pointer(t)) + + var bbx, bby, bbw, bbh C.int + C.al_get_text_dimensions(f.font, t, &bbx, &bby, &bbw, &bbh) + x = float32(bbx) + y = float32(bby) + w = float32(bbw) + h = float32(bbh) + return +} + +// TextWidth returns the width of the rendered text. +func (f *Font) TextWidth(text string) float32 { + t := C.CString(text) + defer C.free(unsafe.Pointer(t)) + + return float32(C.al_get_text_width(f.font, t)) +} + +func (f *Font) Destroy() { + C.al_destroy_font(f.font) +} diff --git a/graphics.go b/graphics.go new file mode 100644 index 0000000..ef018cb --- /dev/null +++ b/graphics.go @@ -0,0 +1,26 @@ +package allg5 + +// #include +import "C" + +// BitmapFlag is extra information provided for creating a bitmap +type BitmapFlag int + +const ( + // BitmapFlagLinearScaleDown enables linear scaling when scaling down. Gives better results when combined with BitmapFlagMipMap + BitmapFlagLinearScaleDown BitmapFlag = C.ALLEGRO_MIN_LINEAR + // BitmapFlagLinearScaleUp enables linear scaling when scaling up. + BitmapFlagLinearScaleUp = C.ALLEGRO_MAG_LINEAR + // BitmapFlagMipMap enables mipmaps for drawing a scaled down version. Bitmap must square and its sides must be a power of two. + BitmapFlagMipMap = C.ALLEGRO_MIPMAP +) + +// ClearToColor clears the target bitmap to the color +func ClearToColor(c Color) { + C.al_clear_to_color(c.color) +} + +// SetNewBitmapFlags sets the default bitmap flags for a newly created bitmap +func SetNewBitmapFlags(flags BitmapFlag) { + C.al_set_new_bitmap_flags(C.int(flags)) +} \ No newline at end of file diff --git a/keyboard.go b/keyboard.go new file mode 100644 index 0000000..2961a8c --- /dev/null +++ b/keyboard.go @@ -0,0 +1,188 @@ +package allg5 + +// #include +import "C" + +type Key int + +const ( + KeyA Key = 1 + KeyB = 2 + KeyC = 3 + KeyD = 4 + KeyE = 5 + KeyF = 6 + KeyG = 7 + KeyH = 8 + KeyI = 9 + KeyJ = 10 + KeyK = 11 + KeyL = 12 + KeyM = 13 + KeyN = 14 + KeyO = 15 + KeyP = 16 + KeyQ = 17 + KeyR = 18 + KeyS = 19 + KeyT = 20 + KeyU = 21 + KeyV = 22 + KeyW = 23 + KeyX = 24 + KeyY = 25 + KeyZ = 26 + Key0 = 27 + Key1 = 28 + Key2 = 29 + Key3 = 30 + Key4 = 31 + Key5 = 32 + Key6 = 33 + Key7 = 34 + Key8 = 35 + Key9 = 36 + KeyPad0 = 37 + KeyPad1 = 38 + KeyPad2 = 39 + KeyPad3 = 40 + KeyPad4 = 41 + KeyPad5 = 42 + KeyPad6 = 43 + KeyPad7 = 44 + KeyPad8 = 45 + KeyPad9 = 46 + KeyF1 = 47 + KeyF2 = 48 + KeyF3 = 49 + KeyF4 = 50 + KeyF5 = 51 + KeyF6 = 52 + KeyF7 = 53 + KeyF8 = 54 + KeyF9 = 55 + KeyF10 = 56 + KeyF11 = 57 + KeyF12 = 58 + KeyEscape = 59 + KeyTilde = 60 + KeyMinus = 61 + KeyEquals = 62 + KeyBackspace = 63 + KeyTab = 64 + KeyOpenBrace = 65 + KeyCloseBrace = 66 + KeyEnter = 67 + KeySemicolon = 68 + KeyQuote = 69 + KeyBackslash = 70 + KeyBackslash2 = 71 /* DirectInput calls this DIK_OEM_102: "< > | on UK/Germany keyboards" */ + KeyComma = 72 + KeyFullstop = 73 + KeySlash = 74 + KeySpace = 75 + KeyInsert = 76 + KeyDelete = 77 + KeyHome = 78 + KeyEnd = 79 + KeyPageUp = 80 + KeyPageDown = 81 + KeyLeft = 82 + KeyRight = 83 + KeyUp = 84 + KeyDown = 85 + KeyPadSlash = 86 + KeyPadAsterisk = 87 + KeyPadMinus = 88 + KeyPadPlus = 89 + KeyPadDelete = 90 + KeyPadEnter = 91 + KeyPrintScreen = 92 + KeyPause = 93 + KeyAbntC1 = 94 + KeyYen = 95 + KeyKana = 96 + KeyConvert = 97 + KeyNoConvert = 98 + KeyAt = 99 + KeyCircumflex = 100 + KeyColon2 = 101 + KeyKanji = 102 + KeyPadEquals = 103 /* MacOS X */ + KeyBackQuote = 104 /* MacOS X */ + KeySemicolon2 = 105 /* MacOS X -- TODO: ask lillo what this should be */ + KeyCommand = 106 /* MacOS X */ + KeyBack = 107 /* Android back key */ + KeyVolumeUp = 108 + KeyVolumeDown = 109 + KeySearch = 110 + KeyDPadCenter = 111 + KeyButtonX = 112 + KeyButtonY = 113 + KeyDPadUp = 114 + KeyDPadDown = 115 + KeyDPadLeft = 116 + KeyDPadRight = 117 + KeySelect = 118 + KeyStart = 119 + KeyButtonL1 = 120 + KeyButtonR1 = 121 + KeyButtonL2 = 122 + KeyButtonR2 = 123 + KeyButtonA = 124 + KeyButtonB = 125 + KeyThumbL = 126 + KeyThumbR = 127 + KeyUnknown = 128 + KeyModifiers = 215 + KeyLShift = 215 + KeyRShift = 216 + KeyLCtrl = 217 + KeyRCtrl = 218 + KeyAlt = 219 + KeyAltGr = 220 + KeyLWin = 221 + KeyRWin = 222 + KeyMenu = 223 + KeyScrollLock = 224 + KeyNumLock = 225 + KeyCapsLock = 226 +) + +type KeyMod uint + +const ( + KeyModShift KeyMod = 0x00001 + KeyModCtrl = 0x00002 + KeyModAlt = 0x00004 + KeyModLWin = 0x00008 + KeyModRWin = 0x00010 + KeyModMenu = 0x00020 + KeyModAltGr = 0x00040 + KeyModCommand = 0x00080 + KeyModScrollLock = 0x00100 + KeyModNumlock = 0x00200 + KeyModCapsLock = 0x00400 + KeyModInaltseq = 0x00800 + KeyModAccent1 = 0x01000 + KeyModAccent2 = 0x02000 + KeyModAccent3 = 0x04000 + KeyModAccent4 = 0x08000 +) + +func IsKeyDown(k Key) bool { + var state C.ALLEGRO_KEYBOARD_STATE + C.al_get_keyboard_state(&state) + return bool(C.al_key_down(&state, C.int(k))) +} + +func IsAnyKeyDown(keys ...Key) bool { + var state C.ALLEGRO_KEYBOARD_STATE + C.al_get_keyboard_state(&state) + for _, k := range keys { + if bool(C.al_key_down(&state, C.int(k))) { + return true + } + } + return false +} diff --git a/monitor.go b/monitor.go new file mode 100644 index 0000000..f6b1852 --- /dev/null +++ b/monitor.go @@ -0,0 +1,34 @@ +package allg5 + +// #include +import "C" + +type Monitor struct { + X1, Y1 int + X2, Y2 int +} + +func monitor(m *C.ALLEGRO_MONITOR_INFO) Monitor { + return Monitor{int(m.x1), int(m.y1), int(m.x2), int(m.y2)} +} + +func DefaultMonitor() Monitor { + var m C.ALLEGRO_MONITOR_INFO + C.al_get_monitor_info(C.ALLEGRO_DEFAULT_DISPLAY_ADAPTER, &m) + return monitor(&m) +} + +func Monitors() []Monitor { + var n = NumberOfVideoAdapters() + var mons []Monitor + var m C.ALLEGRO_MONITOR_INFO + for i := 0; i < n; i++ { + C.al_get_monitor_info(C.int(i), &m) + mons = append(mons, monitor(&m)) + } + return mons +} + +func NumberOfVideoAdapters() int { + return int(C.al_get_num_video_adapters()) +} diff --git a/mouse.go b/mouse.go new file mode 100644 index 0000000..2e917f8 --- /dev/null +++ b/mouse.go @@ -0,0 +1,54 @@ +package allg5 + +// #include +import "C" + +type MouseButton uint + +const ( + MouseButtonLeft MouseButton = 1 + MouseButtonRight = 2 + MouseButtonMiddle = 3 +) + +type MouseCursor uint + +const ( + MouseCursorNone MouseCursor = 0 + MouseCursorDefault = 1 + MouseCursorArrow = 2 + MouseCursorBusy = 3 + MouseCursorQuestion = 4 + MouseCursorEdit = 5 + MouseCursorMove = 6 + MouseCursorResizeN = 7 + MouseCursorResizeW = 8 + MouseCursorResizeS = 9 + MouseCursorResizeE = 10 + MouseCursorResizeNW = 11 + MouseCursorResizeSW = 12 + MouseCursorResizeSE = 13 + MouseCursorResizeNE = 14 + MouseCursorProgress = 15 + MouseCursorPrecision = 16 + MouseCursorLink = 17 + MouseCursorAltSelect = 18 + MouseCursorUnavailable = 19 +) + +func IsMouseButtonDown(b MouseButton) bool { + var state C.ALLEGRO_MOUSE_STATE + C.al_get_mouse_state(&state) + return bool(C.al_mouse_button_down(&state, C.int(b))) +} + +func IsAnyMouseButtonDown(buttons ...MouseButton) bool { + var state C.ALLEGRO_MOUSE_STATE + C.al_get_mouse_state(&state) + for _, b := range buttons { + if bool(C.al_mouse_button_down(&state, C.int(b))) { + return true + } + } + return false +} diff --git a/newbitmapflag.go b/newbitmapflag.go new file mode 100644 index 0000000..d978066 --- /dev/null +++ b/newbitmapflag.go @@ -0,0 +1,31 @@ +package allg5 + +// #include +import "C" + +type NewBitmapFlag int + +const ( + NewBitmapFlagMemoryBitmap = NewBitmapFlag(C.ALLEGRO_MEMORY_BITMAP) + NewBitmapFlagVideoBitmap = NewBitmapFlag(C.ALLEGRO_VIDEO_BITMAP) + NewBitmapFlagMinLinear = NewBitmapFlag(C.ALLEGRO_MIN_LINEAR) +) + +type NewBitmapFlagsCapture struct { + cap C.int +} + +func CaptureNewBitmapFlags() *NewBitmapFlagsCapture { + var cap = C.al_get_new_bitmap_flags() + return &NewBitmapFlagsCapture{cap} +} + +func (c *NewBitmapFlagsCapture) Mutate(mut func(FlagMutation)) { + var m = &flagMut{c.cap} + mut(m) + C.al_set_new_bitmap_flags(m.flg) +} + +func (c *NewBitmapFlagsCapture) Revert() { + C.al_set_new_bitmap_flags(c.cap) +} diff --git a/platform_windows.go b/platform_windows.go new file mode 100644 index 0000000..3908d18 --- /dev/null +++ b/platform_windows.go @@ -0,0 +1,12 @@ +// +build windows + +package allg5 + +// #include +// #include +import "C" +import "unsafe" + +func (d *Display) WindowHandle() unsafe.Pointer { + return unsafe.Pointer(C.al_get_win_window_handle(d.display)) +} diff --git a/primitives.go b/primitives.go new file mode 100644 index 0000000..114e3f5 --- /dev/null +++ b/primitives.go @@ -0,0 +1,30 @@ +package allg5 + +// #include +// #include +import "C" +import "unsafe" + +func DrawFilledRectangle(x1, y1, x2, y2 float32, c Color) { + C.al_draw_filled_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color) +} + +func DrawFilledTriangle(x1, y1, x2, y2, x3, y3 float32, c Color) { + C.al_draw_filled_triangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), C.float(x3), C.float(y3), c.color) +} + +func DrawLine(x1, y1, x2, y2 float32, c Color, thickness float32) { + C.al_draw_line(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness)) +} + +func DrawPolyline(vertices []float32, c Color, thickness float32) { + C.al_draw_polyline((*C.float)(unsafe.Pointer(&vertices[0])), 8, C.int(len(vertices)>>1), C.ALLEGRO_LINE_JOIN_ROUND, C.ALLEGRO_LINE_CAP_ROUND, c.color, C.float(thickness), 1) +} + +func DrawRectangle(x1, y1, x2, y2 float32, c Color, thickness float32) { + C.al_draw_rectangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), c.color, C.float(thickness)) +} + +func DrawTriangle(x1, y1, x2, y2, x3, y3 float32, c Color, thickness float32) { + C.al_draw_triangle(C.float(x1), C.float(y1), C.float(x2), C.float(y2), C.float(x3), C.float(y3), c.color, C.float(thickness)) +} diff --git a/system.go b/system.go new file mode 100644 index 0000000..478f247 --- /dev/null +++ b/system.go @@ -0,0 +1,66 @@ +package allg5 + +/* +#define ALLEGRO_KCM_AUDIO_SRC + +#include +#include +#include +#include +#include +#include + +bool init() { + return al_init(); +} +*/ +import "C" + +import ( + "errors" + "runtime" +) + +func init() { + runtime.LockOSThread() +} + +type InitConfig struct { + Audio bool + Font bool + Image bool + Primitives bool + Keyboard bool + Mouse bool +} + +var InitAll = InitConfig{true, true, true, true, true, true} + +// Init initializes the Allegro system +func Init(config InitConfig) error { + if !bool(C.init()) { + return errors.New("failed to initialize Allegro") + } + if config.Audio && !bool(C.al_install_audio()) { + return errors.New("failed to initialize audio addon") + } + if config.Font && !bool(C.al_init_font_addon()) { + return errors.New("failed to initialize font addon") + } + if config.Font && !bool(C.al_init_ttf_addon()) { + return errors.New("failed to initialize ttf addon") + } + if config.Image && !bool(C.al_init_image_addon()) { + return errors.New("failed to initialize image addon") + } + if config.Primitives && !bool(C.al_init_primitives_addon()) { + return errors.New("failed to initialize primitives addon") + } + if config.Keyboard && !bool(C.al_install_keyboard()) { + return errors.New("failed to install keyboard") + } + if config.Mouse && !bool(C.al_install_mouse()) { + return errors.New("failed to install mouse") + } + return nil +}