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(name, path string, size int) error { var f, err = allg5.LoadTTFFont(path, 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))) }