package allg5ui import ( "image" "image/color" "math" "unicode" "opslag.de/schobers/zntg" "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 clean zntg.Actions defer func() { clean.Do() }() 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 } clean = clean.Add(disp.Destroy) eq, err := allg5.NewEventQueue() if err != nil { return nil, err } clean = clean.Add(eq.Destroy) user := allg5.NewUserEventSource() eq.RegisterKeyboard() eq.RegisterMouse() eq.RegisterDisplay(disp) eq.RegisterUserEvents(user) allg5.CaptureNewBitmapFlags().Mutate(func(m allg5.FlagMutation) { m.Set(allg5.NewBitmapFlagMinLinear) }) clean = nil return &Renderer{disp, eq, nil, map[string]*font{}, user, ui.KeyState{}, ui.KeyModifierNone, 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 keys ui.KeyState modifiers ui.KeyModifier cursor ui.MouseCursor } // Renderer implementation (events) func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) { r.disp.Flip() var ev = eventWait(r.eq, wait) if ev == nil { return } cursor := r.cursor r.cursor = ui.MouseCursorDefault var unhandled bool 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: if r.modifiers&ui.KeyModifierControl == ui.KeyModifierNone && !unicode.IsControl(e.UnicodeCharacter) { t.Handle(&ui.TextInputEvent{EventBase: eventBase(e), Character: e.UnicodeCharacter}) } else { unhandled = true } case *allg5.KeyDownEvent: key := key(e.KeyCode) r.keys[key] = true r.modifiers = r.keys.Modifiers() t.Handle(&ui.KeyDownEvent{EventBase: eventBase(e), Key: key, Modifiers: r.modifiers}) case *allg5.KeyUpEvent: key := key(e.KeyCode) r.keys[key] = false r.modifiers = r.keys.Modifiers() t.Handle(&ui.KeyUpEvent{EventBase: eventBase(e), Key: key, Modifiers: r.modifiers}) 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) } unhandled = true } ev = r.eq.Get() } if !unhandled && cursor != r.cursor { 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) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) { im, err := source.CreateImage() if err != nil { return nil, err } bmp, err := allg5.NewBitmapFromImage(im, true) if err != nil { return nil, err } if keepSource { return &texture{bmp, source}, nil } return &texture{bmp, nil}, nil } func (r *Renderer) CreateTexture(source ui.ImageSource) (ui.Texture, error) { return r.createTexture(source, true) } func (r *Renderer) CreateTextureGo(im image.Image, source bool) (ui.Texture, error) { return r.createTexture(ui.ImageGoSource{im}, true) } func (r *Renderer) CreateTexturePath(path string, source bool) (ui.Texture, error) { bmp, err := allg5.LoadBitmap(path) if err != nil { return nil, err } return &texture{bmp, nil}, nil } func (r *Renderer) CreateTextureTarget(w, h float32) (ui.Texture, error) { bmp, err := allg5.NewVideoBitmap(int(w), int(h)) if err != nil { return nil, err } return &texture{bmp, nil}, nil } func (r *Renderer) DefaultTarget() ui.Texture { return &texture{r.disp.Target(), nil} } func (r *Renderer) Display() *allg5.Display { return r.disp } func (r *Renderer) DrawTexture(texture ui.Texture, p geom.PointF32) { bmp := r.mustGetBitmap(texture) x, y := snap(p) bmp.Draw(x, y) } func (r *Renderer) DrawTextureOptions(texture ui.Texture, p geom.PointF32, opts ui.DrawOptions) { bmp := r.mustGetBitmap(texture) 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(t ui.Texture) *allg5.Bitmap { texture, ok := t.(*texture) if !ok { panic("texture must be created on same renderer") } return texture.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(texture ui.Texture) { bmp := r.mustGetBitmap(texture) 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(texture ui.Texture) { bmp := r.mustGetBitmap(texture) r.disp.SetIcon(bmp) } func (r *Renderer) SetMouseCursor(c ui.MouseCursor) { r.cursor = 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.Texture { return &texture{allg5.CurrentTarget(), nil} } 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.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 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))) }