zntg/ui/allg5ui/renderer.go

336 lines
8.0 KiB
Go
Raw Normal View History

package allg5ui
import (
"image"
"image/color"
"math"
"opslag.de/schobers/geom"
"opslag.de/schobers/zntg/allg5"
"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)
2019-03-11 16:30:41 +00:00
return &Renderer{disp, eq, map[string]*font{}, user, ui.MouseCursorDefault, ui.MouseCursorDefault}, nil
}
// Renderer implements ui.Renderer using Allegro 5.
type Renderer struct {
2019-03-11 16:30:41 +00:00
disp *allg5.Display
eq *allg5.EventQueue
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()
2019-03-11 16:30:41 +00:00
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)})
2019-03-05 21:51:57 +00:00
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)})
2019-06-24 19:56:52 +00:00
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)})
}
ev = r.eq.Get()
}
2019-03-11 16:30:41 +00:00
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) 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)
}
2019-03-11 16:30:41 +00:00
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
r.newCursor = c
}
func (r *Renderer) SetWindowTitle(t string) {
r.disp.SetWindowTitle(t)
}
func (r *Renderer) Target() ui.Image {
return &uiImage{allg5.CurrentTarget()}
}
2019-06-24 18:58:14 +00:00
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)
2019-06-24 18:58:14 +00:00
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)))
}