From 6151f28ef3e3d0fa9c0a6c1894dba8db009cf6cc Mon Sep 17 00:00:00 2001 From: Sander Schobers Date: Thu, 19 Dec 2019 07:08:52 +0100 Subject: [PATCH] Created new repository. --- audio.go | 37 +++++++ bitmap.go | 260 +++++++++++++++++++++++++++++++++++++++++++ c.go | 6 + c_windows.go | 6 + c_windows_static.go | 6 + color.go | 36 ++++++ display.go | 129 +++++++++++++++++++++ event.go | 265 ++++++++++++++++++++++++++++++++++++++++++++ flagmut.go | 23 ++++ font.go | 107 ++++++++++++++++++ graphics.go | 26 +++++ keyboard.go | 188 +++++++++++++++++++++++++++++++ monitor.go | 34 ++++++ mouse.go | 54 +++++++++ newbitmapflag.go | 31 ++++++ platform_windows.go | 12 ++ primitives.go | 30 +++++ system.go | 66 +++++++++++ 18 files changed, 1316 insertions(+) create mode 100644 audio.go create mode 100644 bitmap.go create mode 100644 c.go create mode 100644 c_windows.go create mode 100644 c_windows_static.go create mode 100644 color.go create mode 100644 display.go create mode 100644 event.go create mode 100644 flagmut.go create mode 100644 font.go create mode 100644 graphics.go create mode 100644 keyboard.go create mode 100644 monitor.go create mode 100644 mouse.go create mode 100644 newbitmapflag.go create mode 100644 platform_windows.go create mode 100644 primitives.go create mode 100644 system.go 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 +}