349 lines
8.3 KiB
Go
349 lines
8.3 KiB
Go
package allg5ui
|
|
|
|
import (
|
|
"image"
|
|
"image/color"
|
|
"math"
|
|
|
|
"opslag.de/schobers/allg5"
|
|
"opslag.de/schobers/geom"
|
|
"opslag.de/schobers/zntg/ui"
|
|
)
|
|
|
|
var _ ui.Renderer = &Renderer{}
|
|
|
|
func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
|
var err = allg5.Init(allg5.InitAll)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
disp, err := allg5.NewDisplay(w, h, opts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
eq, err := allg5.NewEventQueue()
|
|
if err != nil {
|
|
disp.Destroy()
|
|
return nil, err
|
|
}
|
|
|
|
user := allg5.NewUserEventSource()
|
|
eq.RegisterKeyboard()
|
|
eq.RegisterMouse()
|
|
eq.RegisterDisplay(disp)
|
|
eq.RegisterUserEvents(user)
|
|
|
|
return &Renderer{disp, eq, nil, map[string]*font{}, user, ui.MouseCursorDefault, ui.MouseCursorDefault}, nil
|
|
}
|
|
|
|
// Renderer implements ui.Renderer using Allegro 5.
|
|
type Renderer struct {
|
|
disp *allg5.Display
|
|
eq *allg5.EventQueue
|
|
unh func(allg5.Event)
|
|
ft map[string]*font
|
|
user *allg5.UserEventSource
|
|
cursor ui.MouseCursor
|
|
newCursor ui.MouseCursor
|
|
}
|
|
|
|
// Renderer implementation (events)
|
|
|
|
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
|
r.disp.Flip()
|
|
|
|
r.newCursor = ui.MouseCursorDefault
|
|
var ev = eventWait(r.eq, wait)
|
|
if ev == nil {
|
|
return
|
|
}
|
|
for ev != nil {
|
|
switch e := ev.(type) {
|
|
case *allg5.DisplayCloseEvent:
|
|
t.Handle(&ui.DisplayCloseEvent{EventBase: eventBase(e)})
|
|
case *allg5.DisplayResizeEvent:
|
|
t.Handle(&ui.DisplayResizeEvent{EventBase: eventBase(e), Bounds: geom.RectF32(float32(e.X), float32(e.Y), float32(e.X+e.Width), float32(e.Y+e.Height))})
|
|
case *allg5.KeyCharEvent:
|
|
t.Handle(&ui.KeyPressEvent{EventBase: eventBase(e), Key: key(e.KeyCode), Modifiers: keyModifiers(e.Modifiers), Character: e.UnicodeCharacter})
|
|
case *allg5.MouseButtonDownEvent:
|
|
t.Handle(&ui.MouseButtonDownEvent{MouseEvent: mouseEvent(e.MouseEvent), Button: ui.MouseButton(e.Button)})
|
|
case *allg5.MouseButtonUpEvent:
|
|
t.Handle(&ui.MouseButtonUpEvent{MouseEvent: mouseEvent(e.MouseEvent), Button: ui.MouseButton(e.Button)})
|
|
case *allg5.MouseEnterEvent:
|
|
t.Handle(&ui.MouseLeaveEvent{MouseEvent: mouseEvent(e.MouseEvent)})
|
|
case *allg5.MouseLeaveEvent:
|
|
t.Handle(&ui.MouseLeaveEvent{MouseEvent: mouseEvent(e.MouseEvent)})
|
|
case *allg5.MouseMoveEvent:
|
|
t.Handle(&ui.MouseMoveEvent{MouseEvent: mouseEvent(e.MouseEvent), MouseWheel: float32(e.DeltaZ)})
|
|
case *allg5.UserEvent:
|
|
t.Handle(&ui.RefreshEvent{EventBase: eventBase(e)})
|
|
default:
|
|
if r.unh != nil {
|
|
r.unh(e)
|
|
}
|
|
}
|
|
ev = r.eq.Get()
|
|
}
|
|
|
|
if r.newCursor != r.cursor {
|
|
r.cursor = r.newCursor
|
|
switch r.cursor {
|
|
case ui.MouseCursorNone:
|
|
r.disp.SetMouseCursor(allg5.MouseCursorNone)
|
|
case ui.MouseCursorDefault:
|
|
r.disp.SetMouseCursor(allg5.MouseCursorDefault)
|
|
case ui.MouseCursorNotAllowed:
|
|
r.disp.SetMouseCursor(allg5.MouseCursorUnavailable)
|
|
case ui.MouseCursorPointer:
|
|
r.disp.SetMouseCursor(allg5.MouseCursorLink)
|
|
case ui.MouseCursorText:
|
|
r.disp.SetMouseCursor(allg5.MouseCursorEdit)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Renderer) RegisterRecorder(rec *allg5.Recorder) {
|
|
r.eq.RegisterRecorder(rec)
|
|
}
|
|
|
|
func (r *Renderer) Refresh() {
|
|
r.user.EmitEvent()
|
|
}
|
|
|
|
// Renderer implementation (lifetime)
|
|
|
|
func (r *Renderer) Destroy() error {
|
|
r.user.Destroy()
|
|
r.eq.Destroy()
|
|
for _, f := range r.ft {
|
|
f.Destroy()
|
|
}
|
|
r.ft = nil
|
|
r.disp.Destroy()
|
|
return nil
|
|
}
|
|
|
|
// Renderer implementation (drawing)
|
|
|
|
func (r *Renderer) Clear(c color.Color) {
|
|
allg5.ClearToColor(newColor(c))
|
|
}
|
|
|
|
func (r *Renderer) CreateImage(im image.Image) (ui.Image, error) {
|
|
bmp, err := allg5.NewBitmapFromImage(im, true)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &uiImage{bmp}, nil
|
|
}
|
|
|
|
func (r *Renderer) CreateImagePath(path string) (ui.Image, error) {
|
|
bmp, err := allg5.LoadBitmap(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &uiImage{bmp}, nil
|
|
}
|
|
|
|
func (r *Renderer) CreateImageSize(w, h float32) (ui.Image, error) {
|
|
bmp, err := allg5.NewVideoBitmap(int(w), int(h))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &uiImage{bmp}, nil
|
|
}
|
|
|
|
func (r *Renderer) DefaultTarget() ui.Image {
|
|
return &uiImage{r.disp.Target()}
|
|
}
|
|
|
|
func (r *Renderer) Display() *allg5.Display { return r.disp }
|
|
|
|
func (r *Renderer) DrawImage(im ui.Image, p geom.PointF32) {
|
|
bmp := r.mustGetBitmap(im)
|
|
x, y := snap(p)
|
|
bmp.Draw(x, y)
|
|
}
|
|
|
|
func (r *Renderer) DrawImageOptions(im ui.Image, p geom.PointF32, opts ui.DrawOptions) {
|
|
bmp := r.mustGetBitmap(im)
|
|
var o allg5.DrawOptions
|
|
if opts.Tint != nil {
|
|
tint := newColor(opts.Tint)
|
|
o.Tint = &tint
|
|
}
|
|
if opts.Scale != nil {
|
|
o.Scale = &allg5.Scale{Horizontal: opts.Scale.X, Vertical: opts.Scale.Y}
|
|
}
|
|
x, y := snap(p)
|
|
bmp.DrawOptions(x, y, o)
|
|
}
|
|
|
|
func (r *Renderer) FillRectangle(rect geom.RectangleF32, c color.Color) {
|
|
allg5.DrawFilledRectangle(rect.Min.X, rect.Min.Y, rect.Max.X, rect.Max.Y, newColor(c))
|
|
}
|
|
|
|
func (r *Renderer) Font(name string) ui.Font {
|
|
return r.ft[name]
|
|
}
|
|
|
|
func (r *Renderer) mustGetBitmap(im ui.Image) *allg5.Bitmap {
|
|
m, ok := im.(*uiImage)
|
|
if !ok {
|
|
panic("image must be created on same renderer")
|
|
}
|
|
return m.bmp
|
|
}
|
|
|
|
func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness float32) {
|
|
minX, minY := snap(rect.Min)
|
|
maxX, maxY := snap(rect.Max)
|
|
allg5.DrawRectangle(minX, minY, maxX, maxY, newColor(c), thickness)
|
|
}
|
|
|
|
func (r *Renderer) RegisterFont(path, name string, size int) error {
|
|
var f, err = allg5.LoadTTFFont(path, int(size))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var prev = r.ft[name]
|
|
if prev != nil {
|
|
prev.Destroy()
|
|
}
|
|
r.ft[name] = newFont(f)
|
|
return nil
|
|
}
|
|
|
|
func (r *Renderer) RegisterFonts(path string, fonts ...FontDefinition) error {
|
|
for _, f := range fonts {
|
|
err := r.RegisterFont(path, f.Name, f.Size)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *Renderer) RenderTo(im ui.Image) {
|
|
bmp := r.mustGetBitmap(im)
|
|
bmp.SetAsTarget()
|
|
}
|
|
|
|
func (r *Renderer) RenderToDisplay() {
|
|
r.disp.SetAsTarget()
|
|
}
|
|
|
|
func (r *Renderer) Size() geom.PointF32 {
|
|
return geom.PtF32(float32(r.disp.Width()), float32(r.disp.Height()))
|
|
}
|
|
|
|
func (r *Renderer) SetIcon(im ui.Image) {
|
|
bmp := r.mustGetBitmap(im)
|
|
r.disp.SetIcon(bmp)
|
|
}
|
|
|
|
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
|
|
r.newCursor = c
|
|
}
|
|
|
|
func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) {
|
|
r.unh = handler
|
|
}
|
|
|
|
func (r *Renderer) SetWindowTitle(t string) {
|
|
r.disp.SetWindowTitle(t)
|
|
}
|
|
|
|
func (r *Renderer) Target() ui.Image {
|
|
return &uiImage{allg5.CurrentTarget()}
|
|
}
|
|
|
|
func (r *Renderer) text(p geom.PointF32, font string, c color.Color, t string, align allg5.HorizontalAlignment) {
|
|
var f = r.ft[font]
|
|
if f == nil {
|
|
return
|
|
}
|
|
x, y := snap(p)
|
|
f.f.Draw(x, y, newColor(c), align, t)
|
|
}
|
|
|
|
func (r *Renderer) Text(p geom.PointF32, font string, c color.Color, t string) {
|
|
r.text(p, font, c, t, allg5.AlignLeft)
|
|
}
|
|
|
|
func (r *Renderer) TextAlign(p geom.PointF32, font string, c color.Color, t string, align ui.HorizontalAlignment) {
|
|
var alignment = allg5.AlignLeft
|
|
switch align {
|
|
case ui.AlignCenter:
|
|
alignment = allg5.AlignCenter
|
|
case ui.AlignRight:
|
|
alignment = allg5.AlignRight
|
|
}
|
|
r.text(p, font, c, t, alignment)
|
|
}
|
|
|
|
// Utility functions
|
|
|
|
func eventWait(eq *allg5.EventQueue, wait bool) allg5.Event {
|
|
if wait {
|
|
return eq.GetWait()
|
|
}
|
|
return eq.Get()
|
|
}
|
|
|
|
func eventBase(e allg5.Event) ui.EventBase {
|
|
return ui.EventBase{StampInSeconds: e.Stamp()}
|
|
}
|
|
|
|
func key(key allg5.Key) ui.Key {
|
|
switch key {
|
|
case allg5.KeyBackspace:
|
|
return ui.KeyBackspace
|
|
case allg5.KeyDelete:
|
|
return ui.KeyDelete
|
|
case allg5.KeyDown:
|
|
return ui.KeyDown
|
|
case allg5.KeyEnd:
|
|
return ui.KeyEnd
|
|
case allg5.KeyEscape:
|
|
return ui.KeyEscape
|
|
case allg5.KeyHome:
|
|
return ui.KeyHome
|
|
case allg5.KeyLeft:
|
|
return ui.KeyLeft
|
|
case allg5.KeyRight:
|
|
return ui.KeyRight
|
|
case allg5.KeyUp:
|
|
return ui.KeyUp
|
|
}
|
|
return ui.KeyNone
|
|
}
|
|
|
|
func keyModifiers(mods allg5.KeyMod) ui.KeyModifier {
|
|
var m ui.KeyModifier
|
|
if mods&allg5.KeyModShift == allg5.KeyModShift {
|
|
m |= ui.KeyModifierShift
|
|
} else if mods&allg5.KeyModCtrl == allg5.KeyModCtrl {
|
|
m |= ui.KeyModifierControl
|
|
} else if mods&allg5.KeyModAlt == allg5.KeyModAlt {
|
|
m |= ui.KeyModifierAlt
|
|
}
|
|
return m
|
|
}
|
|
|
|
func mouseEvent(e allg5.MouseEvent) ui.MouseEvent {
|
|
return ui.MouseEvent{EventBase: eventBase(e), X: float32(e.X), Y: float32(e.Y)}
|
|
}
|
|
|
|
func newColor(c color.Color) allg5.Color {
|
|
if c == nil {
|
|
return newColor(color.Black)
|
|
}
|
|
return allg5.NewColorGo(c)
|
|
}
|
|
|
|
func snap(p geom.PointF32) (float32, float32) {
|
|
return float32(math.Round(float64(p.X))), float32(math.Round(float64(p.Y)))
|
|
}
|