Added SDL backend.
Added Action{,s}. List of actions that can be used to defer cleanup code (see NewRenderer implementations). Added TextInputEvent (replaces the old KeyPressEvent) and added to new events KeyDown & KeyUp. Added VSync to NewRendererOptions. Removed IconScale from button. Added ImageSource interface that replaces the Image/Texture method on the Texture interface. This makes converting back a texture to an image optional (since this is atypical for a hardware texture for instance). Added new KeyModifier: OSCommand (Windows/Command key). Added KeyState that can keep the state of keys (pressed or not). Added KeyEnter, representing the Enter key. Changed signatures of CreateTexture methods in Renderer. Changed signatures of icon related method (removed factories). Basic example now depends on sdlgui.
This commit is contained in:
parent
f618c55b25
commit
cdfb863ab0
37
action.go
Normal file
37
action.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package zntg
|
||||||
|
|
||||||
|
type Action func()
|
||||||
|
|
||||||
|
func (a Action) Err() ActionErr {
|
||||||
|
return func() error {
|
||||||
|
a()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActionErr func() error
|
||||||
|
|
||||||
|
type Actions []ActionErr
|
||||||
|
|
||||||
|
func (a Actions) Add(fn Action) Actions {
|
||||||
|
return a.AddErr(fn.Err())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Actions) AddErr(fn ActionErr) Actions {
|
||||||
|
return append(a, fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Actions) Do() {
|
||||||
|
for _, a := range a {
|
||||||
|
a()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a Actions) DoErr() error {
|
||||||
|
for _, a := range a {
|
||||||
|
if err := a(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -97,6 +97,8 @@ func key(key allg5.Key) ui.Key {
|
|||||||
return ui.KeyE
|
return ui.KeyE
|
||||||
case allg5.KeyEnd:
|
case allg5.KeyEnd:
|
||||||
return ui.KeyEnd
|
return ui.KeyEnd
|
||||||
|
case allg5.KeyEnter:
|
||||||
|
return ui.KeyEnter
|
||||||
case allg5.KeyEquals:
|
case allg5.KeyEquals:
|
||||||
return ui.KeyEquals
|
return ui.KeyEquals
|
||||||
case allg5.KeyEscape:
|
case allg5.KeyEscape:
|
||||||
|
@ -4,6 +4,9 @@ import (
|
|||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
"math"
|
"math"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
|
||||||
"opslag.de/schobers/allg5"
|
"opslag.de/schobers/allg5"
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
@ -13,20 +16,25 @@ import (
|
|||||||
var _ ui.Renderer = &Renderer{}
|
var _ ui.Renderer = &Renderer{}
|
||||||
|
|
||||||
func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
||||||
|
var clean zntg.Actions
|
||||||
|
defer func() { clean.Do() }()
|
||||||
|
|
||||||
var err = allg5.Init(allg5.InitAll)
|
var err = allg5.Init(allg5.InitAll)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
disp, err := allg5.NewDisplay(w, h, opts)
|
disp, err := allg5.NewDisplay(w, h, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
clean = clean.Add(disp.Destroy)
|
||||||
|
|
||||||
eq, err := allg5.NewEventQueue()
|
eq, err := allg5.NewEventQueue()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
disp.Destroy()
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
clean = clean.Add(eq.Destroy)
|
||||||
|
|
||||||
user := allg5.NewUserEventSource()
|
user := allg5.NewUserEventSource()
|
||||||
eq.RegisterKeyboard()
|
eq.RegisterKeyboard()
|
||||||
@ -34,18 +42,25 @@ func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
|||||||
eq.RegisterDisplay(disp)
|
eq.RegisterDisplay(disp)
|
||||||
eq.RegisterUserEvents(user)
|
eq.RegisterUserEvents(user)
|
||||||
|
|
||||||
return &Renderer{disp, eq, nil, map[string]*font{}, user, ui.MouseCursorDefault, ui.MouseCursorDefault}, nil
|
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.
|
// Renderer implements ui.Renderer using Allegro 5.
|
||||||
type Renderer struct {
|
type Renderer struct {
|
||||||
disp *allg5.Display
|
disp *allg5.Display
|
||||||
eq *allg5.EventQueue
|
eq *allg5.EventQueue
|
||||||
unh func(allg5.Event)
|
unh func(allg5.Event)
|
||||||
ft map[string]*font
|
ft map[string]*font
|
||||||
user *allg5.UserEventSource
|
user *allg5.UserEventSource
|
||||||
|
|
||||||
|
keys ui.KeyState
|
||||||
|
modifiers ui.KeyModifier
|
||||||
cursor ui.MouseCursor
|
cursor ui.MouseCursor
|
||||||
newCursor ui.MouseCursor
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Renderer implementation (events)
|
// Renderer implementation (events)
|
||||||
@ -53,11 +68,14 @@ type Renderer struct {
|
|||||||
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
||||||
r.disp.Flip()
|
r.disp.Flip()
|
||||||
|
|
||||||
r.newCursor = ui.MouseCursorDefault
|
|
||||||
var ev = eventWait(r.eq, wait)
|
var ev = eventWait(r.eq, wait)
|
||||||
if ev == nil {
|
if ev == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cursor := r.cursor
|
||||||
|
r.cursor = ui.MouseCursorDefault
|
||||||
|
var unhandled bool
|
||||||
for ev != nil {
|
for ev != nil {
|
||||||
switch e := ev.(type) {
|
switch e := ev.(type) {
|
||||||
case *allg5.DisplayCloseEvent:
|
case *allg5.DisplayCloseEvent:
|
||||||
@ -65,7 +83,21 @@ func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
|||||||
case *allg5.DisplayResizeEvent:
|
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))})
|
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:
|
case *allg5.KeyCharEvent:
|
||||||
t.Handle(&ui.KeyPressEvent{EventBase: eventBase(e), Key: key(e.KeyCode), Modifiers: keyModifiers(e.Modifiers), Character: e.UnicodeCharacter})
|
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:
|
case *allg5.MouseButtonDownEvent:
|
||||||
t.Handle(&ui.MouseButtonDownEvent{MouseEvent: mouseEvent(e.MouseEvent), Button: ui.MouseButton(e.Button)})
|
t.Handle(&ui.MouseButtonDownEvent{MouseEvent: mouseEvent(e.MouseEvent), Button: ui.MouseButton(e.Button)})
|
||||||
case *allg5.MouseButtonUpEvent:
|
case *allg5.MouseButtonUpEvent:
|
||||||
@ -82,12 +114,12 @@ func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
|||||||
if r.unh != nil {
|
if r.unh != nil {
|
||||||
r.unh(e)
|
r.unh(e)
|
||||||
}
|
}
|
||||||
|
unhandled = true
|
||||||
}
|
}
|
||||||
ev = r.eq.Get()
|
ev = r.eq.Get()
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.newCursor != r.cursor {
|
if !unhandled && cursor != r.cursor {
|
||||||
r.cursor = r.newCursor
|
|
||||||
switch r.cursor {
|
switch r.cursor {
|
||||||
case ui.MouseCursorNone:
|
case ui.MouseCursorNone:
|
||||||
r.disp.SetMouseCursor(allg5.MouseCursorNone)
|
r.disp.SetMouseCursor(allg5.MouseCursorNone)
|
||||||
@ -130,32 +162,47 @@ func (r *Renderer) Clear(c color.Color) {
|
|||||||
allg5.ClearToColor(newColor(c))
|
allg5.ClearToColor(newColor(c))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) CreateTexture(im image.Image) (ui.Texture, error) {
|
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)
|
bmp, err := allg5.NewBitmapFromImage(im, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &texture{bmp}, nil
|
if keepSource {
|
||||||
|
return &texture{bmp, source}, nil
|
||||||
|
}
|
||||||
|
return &texture{bmp, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) CreateTexturePath(path string) (ui.Texture, error) {
|
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)
|
bmp, err := allg5.LoadBitmap(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &texture{bmp}, nil
|
return &texture{bmp, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) CreateTextureSize(w, h float32) (ui.Texture, error) {
|
func (r *Renderer) CreateTextureTarget(w, h float32) (ui.Texture, error) {
|
||||||
bmp, err := allg5.NewVideoBitmap(int(w), int(h))
|
bmp, err := allg5.NewVideoBitmap(int(w), int(h))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &texture{bmp}, nil
|
return &texture{bmp, nil}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) DefaultTarget() ui.Texture {
|
func (r *Renderer) DefaultTarget() ui.Texture {
|
||||||
return &texture{r.disp.Target()}
|
return &texture{r.disp.Target(), nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) Display() *allg5.Display { return r.disp }
|
func (r *Renderer) Display() *allg5.Display { return r.disp }
|
||||||
@ -191,7 +238,7 @@ func (r *Renderer) Font(name string) ui.Font {
|
|||||||
func (r *Renderer) mustGetBitmap(t ui.Texture) *allg5.Bitmap {
|
func (r *Renderer) mustGetBitmap(t ui.Texture) *allg5.Bitmap {
|
||||||
texture, ok := t.(*texture)
|
texture, ok := t.(*texture)
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("image must be created on same renderer")
|
panic("texture must be created on same renderer")
|
||||||
}
|
}
|
||||||
return texture.bmp
|
return texture.bmp
|
||||||
}
|
}
|
||||||
@ -244,7 +291,7 @@ func (r *Renderer) SetIcon(texture ui.Texture) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
|
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
|
||||||
r.newCursor = c
|
r.cursor = c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) {
|
func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) {
|
||||||
@ -256,7 +303,7 @@ func (r *Renderer) SetWindowTitle(t string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) Target() ui.Texture {
|
func (r *Renderer) Target() ui.Texture {
|
||||||
return &texture{allg5.CurrentTarget()}
|
return &texture{allg5.CurrentTarget(), nil}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) text(p geom.PointF32, font string, c color.Color, t string, align allg5.HorizontalAlignment) {
|
func (r *Renderer) text(p geom.PointF32, font string, c color.Color, t string, align allg5.HorizontalAlignment) {
|
||||||
|
@ -18,6 +18,7 @@ func (f rendererFactory) New(title string, width, height int) (ui.Renderer, erro
|
|||||||
func (f rendererFactory) NewOptions(title string, width, height int, opts ui.NewRendererOptions) (ui.Renderer, error) {
|
func (f rendererFactory) NewOptions(title string, width, height int, opts ui.NewRendererOptions) (ui.Renderer, error) {
|
||||||
renderer, err := NewRenderer(width, height, allg5.NewDisplayOptions{
|
renderer, err := NewRenderer(width, height, allg5.NewDisplayOptions{
|
||||||
Resizable: opts.Resizable,
|
Resizable: opts.Resizable,
|
||||||
|
Vsync: opts.VSync,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -8,21 +8,27 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ ui.Texture = &texture{}
|
var _ ui.Texture = &texture{}
|
||||||
|
var _ ui.ImageSource = &texture{}
|
||||||
|
|
||||||
type texture struct {
|
type texture struct {
|
||||||
bmp *allg5.Bitmap
|
bmp *allg5.Bitmap
|
||||||
|
source ui.ImageSource
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *texture) Destroy() {
|
func (t *texture) Destroy() error {
|
||||||
t.bmp.Destroy()
|
t.bmp.Destroy()
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *texture) Height() float32 {
|
func (t *texture) Height() float32 {
|
||||||
return float32(t.bmp.Height())
|
return float32(t.bmp.Height())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *texture) Texture() image.Image {
|
func (t *texture) CreateImage() (image.Image, error) {
|
||||||
return t.bmp.Image()
|
if t.source == nil {
|
||||||
|
return t.bmp.Image(), nil
|
||||||
|
}
|
||||||
|
return t.source.CreateImage()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *texture) Width() float32 {
|
func (t *texture) Width() float32 {
|
||||||
|
11
sdlui/color.go
Normal file
11
sdlui/color.go
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ColorSDL(c color.Color) sdl.Color {
|
||||||
|
return sdl.Color(color.RGBAModel.Convert(c).(color.RGBA))
|
||||||
|
}
|
526
sdlui/events.go
Normal file
526
sdlui/events.go
Normal file
@ -0,0 +1,526 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func eventBase(e sdl.Event) ui.EventBase {
|
||||||
|
return ui.EventBase{StampInSeconds: .001 * float64(e.GetTimestamp())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func key(code sdl.Keycode) ui.Key {
|
||||||
|
switch code {
|
||||||
|
case sdl.K_UNKNOWN:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_RETURN:
|
||||||
|
return ui.KeyEnter
|
||||||
|
case sdl.K_ESCAPE:
|
||||||
|
return ui.KeyEscape
|
||||||
|
case sdl.K_BACKSPACE:
|
||||||
|
return ui.KeyBackspace
|
||||||
|
case sdl.K_TAB:
|
||||||
|
return ui.KeyTab
|
||||||
|
case sdl.K_SPACE:
|
||||||
|
return ui.KeySpace
|
||||||
|
// case sdl.K_EXCLAIM:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_QUOTEDBL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_HASH:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_PERCENT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_DOLLAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AMPERSAND:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_QUOTE:
|
||||||
|
return ui.KeyQuote
|
||||||
|
// case sdl.K_LEFTPAREN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_RIGHTPAREN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_ASTERISK:
|
||||||
|
return ui.KeyPadAsterisk
|
||||||
|
case sdl.K_PLUS:
|
||||||
|
return ui.KeyPadPlus
|
||||||
|
case sdl.K_COMMA:
|
||||||
|
return ui.KeyComma
|
||||||
|
case sdl.K_MINUS:
|
||||||
|
return ui.KeyMinus
|
||||||
|
case sdl.K_PERIOD:
|
||||||
|
return ui.KeyFullstop
|
||||||
|
case sdl.K_SLASH:
|
||||||
|
return ui.KeySlash
|
||||||
|
case sdl.K_0:
|
||||||
|
return ui.Key0
|
||||||
|
case sdl.K_1:
|
||||||
|
return ui.Key1
|
||||||
|
case sdl.K_2:
|
||||||
|
return ui.Key2
|
||||||
|
case sdl.K_3:
|
||||||
|
return ui.Key3
|
||||||
|
case sdl.K_4:
|
||||||
|
return ui.Key4
|
||||||
|
case sdl.K_5:
|
||||||
|
return ui.Key5
|
||||||
|
case sdl.K_6:
|
||||||
|
return ui.Key6
|
||||||
|
case sdl.K_7:
|
||||||
|
return ui.Key7
|
||||||
|
case sdl.K_8:
|
||||||
|
return ui.Key8
|
||||||
|
case sdl.K_9:
|
||||||
|
return ui.Key9
|
||||||
|
// case sdl.K_COLON:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_SEMICOLON:
|
||||||
|
return ui.KeySemicolon
|
||||||
|
// case sdl.K_LESS:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_EQUALS:
|
||||||
|
return ui.KeyEquals
|
||||||
|
// case sdl.K_GREATER:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_QUESTION:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_LEFTBRACKET:
|
||||||
|
return ui.KeyOpenBrace
|
||||||
|
case sdl.K_BACKSLASH:
|
||||||
|
return ui.KeyBackslash
|
||||||
|
case sdl.K_RIGHTBRACKET:
|
||||||
|
return ui.KeyCloseBrace
|
||||||
|
// case sdl.K_CARET:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_UNDERSCORE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_BACKQUOTE:
|
||||||
|
return ui.KeyBacktick
|
||||||
|
case sdl.K_a:
|
||||||
|
return ui.KeyA
|
||||||
|
case sdl.K_b:
|
||||||
|
return ui.KeyB
|
||||||
|
case sdl.K_c:
|
||||||
|
return ui.KeyC
|
||||||
|
case sdl.K_d:
|
||||||
|
return ui.KeyD
|
||||||
|
case sdl.K_e:
|
||||||
|
return ui.KeyE
|
||||||
|
case sdl.K_f:
|
||||||
|
return ui.KeyF
|
||||||
|
case sdl.K_g:
|
||||||
|
return ui.KeyG
|
||||||
|
case sdl.K_h:
|
||||||
|
return ui.KeyH
|
||||||
|
case sdl.K_i:
|
||||||
|
return ui.KeyI
|
||||||
|
case sdl.K_j:
|
||||||
|
return ui.KeyJ
|
||||||
|
case sdl.K_k:
|
||||||
|
return ui.KeyK
|
||||||
|
case sdl.K_l:
|
||||||
|
return ui.KeyL
|
||||||
|
case sdl.K_m:
|
||||||
|
return ui.KeyM
|
||||||
|
case sdl.K_n:
|
||||||
|
return ui.KeyN
|
||||||
|
case sdl.K_o:
|
||||||
|
return ui.KeyO
|
||||||
|
case sdl.K_p:
|
||||||
|
return ui.KeyP
|
||||||
|
case sdl.K_q:
|
||||||
|
return ui.KeyQ
|
||||||
|
case sdl.K_r:
|
||||||
|
return ui.KeyR
|
||||||
|
case sdl.K_s:
|
||||||
|
return ui.KeyS
|
||||||
|
case sdl.K_t:
|
||||||
|
return ui.KeyT
|
||||||
|
case sdl.K_u:
|
||||||
|
return ui.KeyU
|
||||||
|
case sdl.K_v:
|
||||||
|
return ui.KeyV
|
||||||
|
case sdl.K_w:
|
||||||
|
return ui.KeyW
|
||||||
|
case sdl.K_x:
|
||||||
|
return ui.KeyX
|
||||||
|
case sdl.K_y:
|
||||||
|
return ui.KeyY
|
||||||
|
case sdl.K_z:
|
||||||
|
return ui.KeyZ
|
||||||
|
case sdl.K_CAPSLOCK:
|
||||||
|
return ui.KeyCapsLock
|
||||||
|
case sdl.K_F1:
|
||||||
|
return ui.KeyF1
|
||||||
|
case sdl.K_F2:
|
||||||
|
return ui.KeyF2
|
||||||
|
case sdl.K_F3:
|
||||||
|
return ui.KeyF3
|
||||||
|
case sdl.K_F4:
|
||||||
|
return ui.KeyF4
|
||||||
|
case sdl.K_F5:
|
||||||
|
return ui.KeyF5
|
||||||
|
case sdl.K_F6:
|
||||||
|
return ui.KeyF6
|
||||||
|
case sdl.K_F7:
|
||||||
|
return ui.KeyF7
|
||||||
|
case sdl.K_F8:
|
||||||
|
return ui.KeyF8
|
||||||
|
case sdl.K_F9:
|
||||||
|
return ui.KeyF9
|
||||||
|
case sdl.K_F10:
|
||||||
|
return ui.KeyF10
|
||||||
|
case sdl.K_F11:
|
||||||
|
return ui.KeyF11
|
||||||
|
case sdl.K_F12:
|
||||||
|
return ui.KeyF12
|
||||||
|
case sdl.K_PRINTSCREEN:
|
||||||
|
return ui.KeyPrintScreen
|
||||||
|
case sdl.K_SCROLLLOCK:
|
||||||
|
return ui.KeyScrollLock
|
||||||
|
case sdl.K_PAUSE:
|
||||||
|
return ui.KeyPause
|
||||||
|
case sdl.K_INSERT:
|
||||||
|
return ui.KeyInsert
|
||||||
|
case sdl.K_HOME:
|
||||||
|
return ui.KeyHome
|
||||||
|
case sdl.K_PAGEUP:
|
||||||
|
return ui.KeyPageUp
|
||||||
|
case sdl.K_DELETE:
|
||||||
|
return ui.KeyDelete
|
||||||
|
case sdl.K_END:
|
||||||
|
return ui.KeyEnd
|
||||||
|
case sdl.K_PAGEDOWN:
|
||||||
|
return ui.KeyPageDown
|
||||||
|
case sdl.K_RIGHT:
|
||||||
|
return ui.KeyRight
|
||||||
|
case sdl.K_LEFT:
|
||||||
|
return ui.KeyLeft
|
||||||
|
case sdl.K_DOWN:
|
||||||
|
return ui.KeyDown
|
||||||
|
case sdl.K_UP:
|
||||||
|
return ui.KeyUp
|
||||||
|
// case sdl.K_NUMLOCKCLEAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_KP_DIVIDE:
|
||||||
|
return ui.KeyPadSlash
|
||||||
|
case sdl.K_KP_MULTIPLY:
|
||||||
|
return ui.KeyPadAsterisk
|
||||||
|
case sdl.K_KP_MINUS:
|
||||||
|
return ui.KeyPadMinus
|
||||||
|
case sdl.K_KP_PLUS:
|
||||||
|
return ui.KeyPadPlus
|
||||||
|
case sdl.K_KP_ENTER:
|
||||||
|
return ui.KeyPadEnter
|
||||||
|
case sdl.K_KP_1:
|
||||||
|
return ui.KeyPad1
|
||||||
|
case sdl.K_KP_2:
|
||||||
|
return ui.KeyPad2
|
||||||
|
case sdl.K_KP_3:
|
||||||
|
return ui.KeyPad3
|
||||||
|
case sdl.K_KP_4:
|
||||||
|
return ui.KeyPad4
|
||||||
|
case sdl.K_KP_5:
|
||||||
|
return ui.KeyPad5
|
||||||
|
case sdl.K_KP_6:
|
||||||
|
return ui.KeyPad6
|
||||||
|
case sdl.K_KP_7:
|
||||||
|
return ui.KeyPad7
|
||||||
|
case sdl.K_KP_8:
|
||||||
|
return ui.KeyPad8
|
||||||
|
case sdl.K_KP_9:
|
||||||
|
return ui.KeyPad9
|
||||||
|
case sdl.K_KP_0:
|
||||||
|
return ui.KeyPad0
|
||||||
|
// case sdl.K_KP_PERIOD:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_APPLICATION:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_POWER:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_KP_EQUALS:
|
||||||
|
return ui.KeyPadEquals
|
||||||
|
// case sdl.K_F13:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F14:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F15:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F16:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F17:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F18:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F19:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F20:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F21:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F22:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F23:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_F24:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_EXECUTE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_HELP:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_MENU:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_SELECT:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_STOP:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_AGAIN:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_UNDO:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_CUT:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_COPY:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_PASTE:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_FIND:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_MUTE:
|
||||||
|
return ui.KeyNone
|
||||||
|
case sdl.K_VOLUMEUP:
|
||||||
|
return ui.KeyVolumeUp
|
||||||
|
case sdl.K_VOLUMEDOWN:
|
||||||
|
return ui.KeyVolumeDown
|
||||||
|
// case sdl.K_KP_COMMA:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_EQUALSAS400:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_ALTERASE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_SYSREQ:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CANCEL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CLEAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_PRIOR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_RETURN2:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_SEPARATOR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_OUT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_OPER:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CLEARAGAIN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CRSEL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_EXSEL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_00:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_000:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_THOUSANDSSEPARATOR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_DECIMALSEPARATOR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CURRENCYUNIT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CURRENCYSUBUNIT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_LEFTPAREN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_RIGHTPAREN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_KP_LEFTBRACE:
|
||||||
|
return ui.KeyOpenBrace // generic equivalent
|
||||||
|
case sdl.K_KP_RIGHTBRACE:
|
||||||
|
return ui.KeyCloseBrace // generic equivalent
|
||||||
|
case sdl.K_KP_TAB:
|
||||||
|
return ui.KeyTab // generic equivalent
|
||||||
|
case sdl.K_KP_BACKSPACE:
|
||||||
|
return ui.KeyBackspace // generic equivalent
|
||||||
|
case sdl.K_KP_A:
|
||||||
|
return ui.KeyA // generic equivalent
|
||||||
|
case sdl.K_KP_B:
|
||||||
|
return ui.KeyB // generic equivalent
|
||||||
|
case sdl.K_KP_C:
|
||||||
|
return ui.KeyC // generic equivalent
|
||||||
|
case sdl.K_KP_D:
|
||||||
|
return ui.KeyD // generic equivalent
|
||||||
|
case sdl.K_KP_E:
|
||||||
|
return ui.KeyE // generic equivalent
|
||||||
|
case sdl.K_KP_F:
|
||||||
|
return ui.KeyF // generic equivalent
|
||||||
|
// case sdl.K_KP_XOR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_POWER:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_PERCENT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_LESS:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_GREATER:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_AMPERSAND:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_DBLAMPERSAND:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_VERTICALBAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_DBLVERTICALBAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_COLON:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_HASH:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_KP_SPACE:
|
||||||
|
return ui.KeySpace // generic equivalent
|
||||||
|
// case sdl.K_KP_AT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_EXCLAM:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMSTORE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMRECALL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMCLEAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMADD:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMSUBTRACT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMMULTIPLY:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_MEMDIVIDE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_PLUSMINUS:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_CLEAR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_CLEARENTRY:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_BINARY:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_OCTAL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_DECIMAL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KP_HEXADECIMAL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
case sdl.K_LCTRL:
|
||||||
|
return ui.KeyLeftControl
|
||||||
|
case sdl.K_LSHIFT:
|
||||||
|
return ui.KeyLeftShift
|
||||||
|
case sdl.K_LALT:
|
||||||
|
return ui.KeyAlt
|
||||||
|
case sdl.K_LGUI:
|
||||||
|
return ui.KeyLeftWin
|
||||||
|
case sdl.K_RCTRL:
|
||||||
|
return ui.KeyRightControl
|
||||||
|
case sdl.K_RSHIFT:
|
||||||
|
return ui.KeyRightShift
|
||||||
|
case sdl.K_RALT:
|
||||||
|
return ui.KeyAltGr
|
||||||
|
case sdl.K_RGUI:
|
||||||
|
return ui.KeyRightWin
|
||||||
|
// case sdl.K_MODE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AUDIONEXT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AUDIOPREV:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AUDIOSTOP:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AUDIOPLAY:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AUDIOMUTE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_MEDIASELECT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_WWW:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_MAIL:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_CALCULATOR:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_COMPUTER:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_SEARCH:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_HOME:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_BACK:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_FORWARD:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_STOP:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_REFRESH:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_AC_BOOKMARKS:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_BRIGHTNESSDOWN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_BRIGHTNESSUP:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_DISPLAYSWITCH:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KBDILLUMTOGGLE:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KBDILLUMDOWN:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_KBDILLUMUP:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_EJECT:
|
||||||
|
// return ui.KeyNone
|
||||||
|
// case sdl.K_SLEEP:
|
||||||
|
// return ui.KeyNone
|
||||||
|
default:
|
||||||
|
return ui.KeyNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func keyModifiers(mod uint16) ui.KeyModifier {
|
||||||
|
var modifiers ui.KeyModifier
|
||||||
|
if mod&uint16(sdl.KMOD_ALT|sdl.KMOD_LALT) != 0 {
|
||||||
|
modifiers |= ui.KeyModifierAlt
|
||||||
|
}
|
||||||
|
if mod&uint16(sdl.KMOD_CTRL|sdl.KMOD_LCTRL) != 0 {
|
||||||
|
modifiers |= ui.KeyModifierControl
|
||||||
|
}
|
||||||
|
if mod&uint16(sdl.KMOD_SHIFT|sdl.KMOD_LSHIFT) != 0 {
|
||||||
|
modifiers |= ui.KeyModifierShift
|
||||||
|
}
|
||||||
|
if mod&uint16(sdl.KMOD_GUI|sdl.KMOD_LGUI) != 0 {
|
||||||
|
modifiers |= ui.KeyModifierOSCommand
|
||||||
|
}
|
||||||
|
return modifiers
|
||||||
|
}
|
||||||
|
|
||||||
|
func mouseButton(b uint8) ui.MouseButton {
|
||||||
|
switch b {
|
||||||
|
case sdl.BUTTON_LEFT:
|
||||||
|
return ui.MouseButtonLeft
|
||||||
|
case sdl.BUTTON_MIDDLE:
|
||||||
|
return ui.MouseButtonMiddle
|
||||||
|
case sdl.BUTTON_RIGHT:
|
||||||
|
return ui.MouseButtonRight
|
||||||
|
}
|
||||||
|
return ui.MouseButtonLeft
|
||||||
|
}
|
||||||
|
|
||||||
|
func mouseEvent(e sdl.Event, x, y int32) ui.MouseEvent {
|
||||||
|
return ui.MouseEvent{
|
||||||
|
X: float32(x),
|
||||||
|
Y: float32(y),
|
||||||
|
EventBase: eventBase(e),
|
||||||
|
}
|
||||||
|
}
|
13
sdlui/events_test.go
Normal file
13
sdlui/events_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestKeyModifiers(t *testing.T) {
|
||||||
|
var mod uint16 = 4097
|
||||||
|
assert.Equal(t, ui.KeyModifier(ui.KeyModifierShift), keyModifiers(mod))
|
||||||
|
}
|
24
sdlui/font.go
Normal file
24
sdlui/font.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Font struct {
|
||||||
|
*ttf.Font
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) Height() float32 {
|
||||||
|
return float32(f.Font.Height())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) Measure(t string) geom.RectangleF32 {
|
||||||
|
w, h, _ := f.SizeUTF8(t)
|
||||||
|
return geom.RectF32(0, 0, float32(w), float32(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Font) WidthOf(t string) float32 {
|
||||||
|
w, _, _ := f.SizeUTF8(t)
|
||||||
|
return float32(w)
|
||||||
|
}
|
16
sdlui/image.go
Normal file
16
sdlui/image.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/draw"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RGBAImage(m image.Image) *image.RGBA {
|
||||||
|
rgba, ok := m.(*image.RGBA)
|
||||||
|
if ok {
|
||||||
|
return rgba
|
||||||
|
}
|
||||||
|
rgba = image.NewRGBA(m.Bounds())
|
||||||
|
draw.Draw(rgba, rgba.Bounds(), m, image.ZP, draw.Over)
|
||||||
|
return rgba
|
||||||
|
}
|
38
sdlui/rectangle.go
Normal file
38
sdlui/rectangle.go
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Rect(x, y, w, h int32) sdl.Rect {
|
||||||
|
return sdl.Rect{X: x, Y: y, W: w, H: h}
|
||||||
|
}
|
||||||
|
|
||||||
|
func RectAbs(x1, y1, x2, y2 int32) sdl.Rect {
|
||||||
|
if x1 > x2 {
|
||||||
|
x1, x2 = x2, x1
|
||||||
|
}
|
||||||
|
if y1 > y2 {
|
||||||
|
y1, y2 = y2, y1
|
||||||
|
}
|
||||||
|
return Rect(x1, y1, x2-x1, y2-y1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RectAbsPtr(x1, y1, x2, y2 int32) *sdl.Rect {
|
||||||
|
rect := RectAbs(x1, y1, x2, y2)
|
||||||
|
return &rect
|
||||||
|
}
|
||||||
|
|
||||||
|
func RectPtr(x, y, w, h int32) *sdl.Rect {
|
||||||
|
return &sdl.Rect{X: x, Y: y, W: w, H: h}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SDLRectangle(r geom.RectangleF32) sdl.Rect {
|
||||||
|
return sdl.Rect{X: int32(r.Min.X), Y: int32(r.Min.Y), W: int32(r.Dx()), H: int32(r.Dy())}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SDLRectanglePtr(r geom.RectangleF32) *sdl.Rect {
|
||||||
|
rect := SDLRectangle(r)
|
||||||
|
return &rect
|
||||||
|
}
|
421
sdlui/renderer.go
Normal file
421
sdlui/renderer.go
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
_ "image/jpeg" // add JPEG for CreateSurfacePath
|
||||||
|
_ "image/png" // add PNG for CreateSurfacePath
|
||||||
|
"math"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errNotImplemented = errors.New(`not implemented`)
|
||||||
|
|
||||||
|
type Renderer struct {
|
||||||
|
window *sdl.Window
|
||||||
|
renderer *sdl.Renderer
|
||||||
|
refresh uint32
|
||||||
|
fonts map[string]*Font
|
||||||
|
|
||||||
|
cursor ui.MouseCursor
|
||||||
|
cursors map[sdl.SystemCursor]*sdl.Cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ui.Renderer = &Renderer{}
|
||||||
|
var _ ui.Texture = &Renderer{}
|
||||||
|
|
||||||
|
type NewRendererOptions struct {
|
||||||
|
Location sdl.Point
|
||||||
|
Resizable bool
|
||||||
|
VSync bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRenderer(title string, width, height int32, opts NewRendererOptions) (*Renderer, error) {
|
||||||
|
var clean zntg.Actions
|
||||||
|
defer func() { clean.Do() }()
|
||||||
|
|
||||||
|
if err := sdl.Init(sdl.INIT_EVERYTHING); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clean = clean.Add(sdl.Quit)
|
||||||
|
|
||||||
|
if err := ttf.Init(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clean = clean.Add(ttf.Quit)
|
||||||
|
|
||||||
|
if opts.VSync {
|
||||||
|
sdl.SetHint(sdl.HINT_RENDER_VSYNC, "1")
|
||||||
|
}
|
||||||
|
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
|
||||||
|
windowFlags := uint32(sdl.WINDOW_SHOWN)
|
||||||
|
if opts.Resizable {
|
||||||
|
windowFlags |= sdl.WINDOW_RESIZABLE
|
||||||
|
}
|
||||||
|
window, err := sdl.CreateWindow(title, opts.Location.X, opts.Location.Y, width, height, windowFlags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
clean = clean.AddErr(window.Destroy)
|
||||||
|
|
||||||
|
rendererFlags := uint32(sdl.RENDERER_ACCELERATED)
|
||||||
|
if opts.VSync {
|
||||||
|
rendererFlags |= sdl.RENDERER_PRESENTVSYNC
|
||||||
|
}
|
||||||
|
renderer, err := sdl.CreateRenderer(window, -1, rendererFlags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
renderer.SetDrawBlendMode(sdl.BLENDMODE_BLEND)
|
||||||
|
clean = clean.AddErr(renderer.Destroy)
|
||||||
|
|
||||||
|
refresh := sdl.RegisterEvents(1)
|
||||||
|
if refresh == math.MaxUint32 {
|
||||||
|
return nil, errors.New("couldn't register user event")
|
||||||
|
}
|
||||||
|
clean = nil
|
||||||
|
|
||||||
|
return &Renderer{
|
||||||
|
window: window,
|
||||||
|
renderer: renderer,
|
||||||
|
refresh: refresh,
|
||||||
|
fonts: map[string]*Font{},
|
||||||
|
cursors: map[sdl.SystemCursor]*sdl.Cursor{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Events
|
||||||
|
|
||||||
|
func (r *Renderer) WindowBounds() geom.RectangleF32 {
|
||||||
|
x, y := r.window.GetPosition()
|
||||||
|
w, h := r.window.GetSize()
|
||||||
|
return geom.RectF32(float32(x), float32(y), float32(x+w), float32(y+h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
||||||
|
r.renderer.Present()
|
||||||
|
|
||||||
|
waitOrPoll := func() sdl.Event {
|
||||||
|
if wait {
|
||||||
|
return sdl.WaitEvent()
|
||||||
|
}
|
||||||
|
return sdl.PollEvent()
|
||||||
|
}
|
||||||
|
|
||||||
|
cursor := r.cursor
|
||||||
|
for event := waitOrPoll(); event != nil; event = sdl.PollEvent() {
|
||||||
|
r.cursor = ui.MouseCursorDefault
|
||||||
|
var unhandled bool
|
||||||
|
|
||||||
|
// TODO: simulate ui.MouseEnter & ui.MouseLeave?
|
||||||
|
switch e := event.(type) {
|
||||||
|
case *sdl.WindowEvent:
|
||||||
|
switch e.Event {
|
||||||
|
case sdl.WINDOWEVENT_CLOSE:
|
||||||
|
t.Handle(&ui.DisplayCloseEvent{EventBase: eventBase(e)})
|
||||||
|
case sdl.WINDOWEVENT_RESIZED:
|
||||||
|
t.Handle(&ui.DisplayResizeEvent{EventBase: eventBase(e), Bounds: r.WindowBounds()})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
case *sdl.KeyboardEvent:
|
||||||
|
if e.Type == sdl.KEYDOWN {
|
||||||
|
t.Handle(&ui.KeyDownEvent{EventBase: eventBase(e), Key: key(e.Keysym.Sym), Modifiers: keyModifiers(e.Keysym.Mod)})
|
||||||
|
} else if e.Type == sdl.KEYUP {
|
||||||
|
t.Handle(&ui.KeyDownEvent{EventBase: eventBase(e), Key: 0, Modifiers: 0})
|
||||||
|
} else {
|
||||||
|
unhandled = true
|
||||||
|
}
|
||||||
|
case *sdl.TextInputEvent:
|
||||||
|
if e.Type == sdl.TEXTINPUT {
|
||||||
|
text := e.GetText()
|
||||||
|
for _, character := range text {
|
||||||
|
t.Handle(&ui.TextInputEvent{
|
||||||
|
EventBase: eventBase(e),
|
||||||
|
Character: character,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unhandled = true
|
||||||
|
}
|
||||||
|
|
||||||
|
case *sdl.MouseButtonEvent:
|
||||||
|
if e.Type == sdl.MOUSEBUTTONDOWN {
|
||||||
|
t.Handle(&ui.MouseButtonDownEvent{MouseEvent: mouseEvent(e, e.X, e.Y), Button: mouseButton(e.Button)})
|
||||||
|
} else {
|
||||||
|
t.Handle(&ui.MouseButtonUpEvent{MouseEvent: mouseEvent(e, e.X, e.Y), Button: mouseButton(e.Button)})
|
||||||
|
}
|
||||||
|
case *sdl.MouseMotionEvent:
|
||||||
|
t.Handle(&ui.MouseMoveEvent{MouseEvent: ui.MouseEvent{EventBase: eventBase(e), X: float32(e.X), Y: float32(e.Y)}, MouseWheel: 0})
|
||||||
|
case *sdl.UserEvent:
|
||||||
|
if r.refresh == e.Type {
|
||||||
|
t.Handle(&ui.RefreshEvent{EventBase: eventBase(e)})
|
||||||
|
} else {
|
||||||
|
unhandled = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
unhandled = true // not handled by EventTarget.Handle
|
||||||
|
}
|
||||||
|
|
||||||
|
if unhandled {
|
||||||
|
r.cursor = cursor
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.cursor != cursor {
|
||||||
|
switch r.cursor {
|
||||||
|
case ui.MouseCursorDefault:
|
||||||
|
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_ARROW))
|
||||||
|
case ui.MouseCursorNotAllowed:
|
||||||
|
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_NO))
|
||||||
|
case ui.MouseCursorPointer:
|
||||||
|
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_HAND))
|
||||||
|
case ui.MouseCursorText:
|
||||||
|
sdl.SetCursor(r.SystemCursor(sdl.SYSTEM_CURSOR_IBEAM))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Refresh() {
|
||||||
|
windowID, _ := r.window.GetID()
|
||||||
|
e := &sdl.UserEvent{
|
||||||
|
Type: r.refresh,
|
||||||
|
WindowID: windowID,
|
||||||
|
}
|
||||||
|
sdl.PushEvent(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lifetime
|
||||||
|
|
||||||
|
func (r *Renderer) Destroy() error {
|
||||||
|
for _, f := range r.fonts {
|
||||||
|
f.Close()
|
||||||
|
}
|
||||||
|
r.renderer.Destroy()
|
||||||
|
r.window.Destroy()
|
||||||
|
ttf.Quit()
|
||||||
|
sdl.Quit()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drawing
|
||||||
|
|
||||||
|
func (r *Renderer) Clear(c color.Color) {
|
||||||
|
if c == color.Transparent {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.SetDrawColorGo(c)
|
||||||
|
r.renderer.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) createTexture(source ui.ImageSource, keepSource bool) (ui.Texture, error) {
|
||||||
|
m, err := source.CreateImage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rgba := RGBAImage(m)
|
||||||
|
width := int32(rgba.Bounds().Dx())
|
||||||
|
height := int32(rgba.Bounds().Dy())
|
||||||
|
surface, err := sdl.CreateRGBSurfaceWithFormatFrom(
|
||||||
|
unsafe.Pointer(&rgba.Pix[0]),
|
||||||
|
width, height, 32, int32(rgba.Stride), sdl.PIXELFORMAT_ABGR8888)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer surface.Free()
|
||||||
|
texture, err := r.renderer.CreateTextureFromSurface(surface)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if keepSource {
|
||||||
|
return &TextureImageSource{&Texture{texture}, source}, nil
|
||||||
|
}
|
||||||
|
return &Texture{texture}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) CreateTexture(source ui.ImageSource) (ui.Texture, error) {
|
||||||
|
return r.createTexture(source, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) CreateTextureGo(m image.Image, source bool) (ui.Texture, error) {
|
||||||
|
return r.createTexture(ui.ImageGoSource{Image: m}, source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) CreateTexturePath(path string, source bool) (ui.Texture, error) {
|
||||||
|
return r.createTexture(ui.ImageFileSource(path), source)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) CreateTextureTarget(w, h float32) (ui.Texture, error) {
|
||||||
|
format, err := r.window.GetPixelFormat()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
texture, err := r.renderer.CreateTexture(format, sdl.TEXTUREACCESS_TARGET, int32(w), int32(h))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
texture.SetBlendMode(sdl.BLENDMODE_BLEND)
|
||||||
|
return &Texture{texture}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) DefaultTarget() ui.Texture { return r }
|
||||||
|
|
||||||
|
func (r *Renderer) DrawTexture(t ui.Texture, p geom.PointF32) {
|
||||||
|
r.DrawTextureOptions(t, p, ui.DrawOptions{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) DrawTextureOptions(t ui.Texture, p geom.PointF32, opts ui.DrawOptions) {
|
||||||
|
texture, ok := t.(sdlTexture)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if opts.Tint != nil {
|
||||||
|
texture.SetColor(opts.Tint)
|
||||||
|
}
|
||||||
|
width, height, err := texture.Size()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dst := RectPtr(int32(p.X), int32(p.Y), width, height)
|
||||||
|
if opts.Scale != nil {
|
||||||
|
dst.W = int32(float32(width) * opts.Scale.X)
|
||||||
|
dst.H = int32(float32(height) * opts.Scale.Y)
|
||||||
|
}
|
||||||
|
r.renderer.Copy(texture.Native(), RectPtr(0, 0, width, height), dst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) FillRectangle(rect geom.RectangleF32, c color.Color) {
|
||||||
|
r.SetDrawColorGo(c)
|
||||||
|
r.renderer.FillRect(SDLRectanglePtr(rect))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Font(name string) ui.Font {
|
||||||
|
return r.fonts[name]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Rectangle(rect geom.RectangleF32, c color.Color, thickness float32) {
|
||||||
|
r.SetDrawColorGo(c)
|
||||||
|
if rect.Dx() == 0 { // SDL doesn't draw a 1 px wide line when Dx() == 0 && thickness == 1
|
||||||
|
offset := int32(rect.Min.X - .5*thickness)
|
||||||
|
for thick := int32(thickness); thick > 0; thick-- {
|
||||||
|
r.renderer.DrawLine(offset, int32(rect.Min.Y), offset, int32(rect.Max.Y))
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
} else if rect.Dy() == 0 {
|
||||||
|
offset := int32(rect.Min.Y - .5*thickness)
|
||||||
|
for thick := int32(thickness); thick > 0; thick-- {
|
||||||
|
r.renderer.DrawLine(int32(rect.Min.X), offset, int32(rect.Max.X), offset)
|
||||||
|
offset++
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for thick := int32(thickness); thick > 0; thick-- {
|
||||||
|
r.renderer.DrawRect(SDLRectanglePtr(rect))
|
||||||
|
rect = rect.Inset(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) RegisterFont(name, path string, size int) error {
|
||||||
|
font, err := ttf.OpenFont(path, size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
r.fonts[name] = &Font{font}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) RenderTo(t ui.Texture) {
|
||||||
|
texture, ok := t.(sdlTexture)
|
||||||
|
if ok {
|
||||||
|
err := r.renderer.SetRenderTarget(texture.Native())
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
r.RenderToDisplay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) RenderToDisplay() {
|
||||||
|
r.renderer.SetRenderTarget(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) SetDrawColor(c sdl.Color) {
|
||||||
|
r.renderer.SetDrawColor(c.R, c.G, c.B, c.A)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) SetDrawColorGo(c color.Color) {
|
||||||
|
r.SetDrawColor(ColorSDL(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) { r.cursor = c }
|
||||||
|
|
||||||
|
func (r *Renderer) Size() geom.PointF32 {
|
||||||
|
w, h, err := r.renderer.GetOutputSize()
|
||||||
|
if err != nil {
|
||||||
|
return geom.PtF32(geom.NaN32(), geom.NaN32())
|
||||||
|
}
|
||||||
|
return geom.PtF32(float32(w), float32(h))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) SystemCursor(id sdl.SystemCursor) *sdl.Cursor {
|
||||||
|
if cursor, ok := r.cursors[id]; ok {
|
||||||
|
return cursor
|
||||||
|
}
|
||||||
|
cursor := sdl.CreateSystemCursor(id)
|
||||||
|
r.cursors[id] = cursor
|
||||||
|
return cursor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Target() ui.Texture {
|
||||||
|
target := r.renderer.GetRenderTarget()
|
||||||
|
if target == nil {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
return &Texture{target}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) Text(p geom.PointF32, font string, color color.Color, text string) {
|
||||||
|
f := r.Font(font).(*Font)
|
||||||
|
|
||||||
|
surface, err := f.RenderUTF8Blended(text, ColorSDL(color))
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer surface.Free()
|
||||||
|
texture, err := r.renderer.CreateTextureFromSurface(surface)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer texture.Destroy()
|
||||||
|
|
||||||
|
r.DrawTexture(&Texture{texture}, p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Renderer) TextAlign(p geom.PointF32, font string, color color.Color, text string, align ui.HorizontalAlignment) {
|
||||||
|
switch align {
|
||||||
|
case ui.AlignLeft:
|
||||||
|
r.Text(p, font, color, text)
|
||||||
|
case ui.AlignCenter:
|
||||||
|
width := r.Font(font).(*Font).WidthOf(text)
|
||||||
|
r.Text(p.Add2D(-.5*width, 0), font, color, text)
|
||||||
|
case ui.AlignRight:
|
||||||
|
width := r.Font(font).(*Font).WidthOf(text)
|
||||||
|
r.Text(p.Add2D(-width, 0), font, color, text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Texture
|
||||||
|
|
||||||
|
func (r *Renderer) Image() image.Image { return nil }
|
||||||
|
|
||||||
|
func (r *Renderer) Height() float32 { return r.Size().Y }
|
||||||
|
|
||||||
|
func (r *Renderer) Width() float32 { return r.Size().X }
|
24
sdlui/rendererfactory.go
Normal file
24
sdlui/rendererfactory.go
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ui.SetRendererFactory(&rendererFactory{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type rendererFactory struct{}
|
||||||
|
|
||||||
|
func (f rendererFactory) New(title string, width, height int) (ui.Renderer, error) {
|
||||||
|
return f.NewOptions(title, width, height, ui.NewRendererOptions{Resizable: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f rendererFactory) NewOptions(title string, width, height int, opts ui.NewRendererOptions) (ui.Renderer, error) {
|
||||||
|
return NewRenderer(title, int32(width), int32(height), NewRendererOptions{
|
||||||
|
Location: sdl.Point{X: sdl.WINDOWPOS_UNDEFINED, Y: sdl.WINDOWPOS_UNDEFINED},
|
||||||
|
Resizable: opts.Resizable,
|
||||||
|
VSync: opts.VSync,
|
||||||
|
})
|
||||||
|
}
|
65
sdlui/texture.go
Normal file
65
sdlui/texture.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
package sdlui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"image/color"
|
||||||
|
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"opslag.de/schobers/geom"
|
||||||
|
"opslag.de/schobers/zntg/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
type sdlTexture interface {
|
||||||
|
Native() *sdl.Texture
|
||||||
|
SetColor(color.Color)
|
||||||
|
Size() (int32, int32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type Texture struct {
|
||||||
|
*sdl.Texture
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ui.Texture = &Texture{}
|
||||||
|
|
||||||
|
func (t *Texture) Height() float32 {
|
||||||
|
_, _, _, height, err := t.Texture.Query()
|
||||||
|
if err != nil {
|
||||||
|
return geom.NaN32()
|
||||||
|
}
|
||||||
|
return float32(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Texture) Native() *sdl.Texture { return t.Texture }
|
||||||
|
|
||||||
|
func (t *Texture) SetColor(c color.Color) {
|
||||||
|
color := ColorSDL(c)
|
||||||
|
t.SetColorMod(color.R, color.G, color.B)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Texture) Size() (int32, int32, error) {
|
||||||
|
_, _, width, height, err := t.Texture.Query()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return width, height, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Texture) Width() float32 {
|
||||||
|
_, _, width, _, err := t.Texture.Query()
|
||||||
|
if err != nil {
|
||||||
|
return geom.NaN32()
|
||||||
|
}
|
||||||
|
return float32(width)
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ui.ImageSource = &TextureImageSource{}
|
||||||
|
|
||||||
|
type TextureImageSource struct {
|
||||||
|
*Texture
|
||||||
|
|
||||||
|
source ui.ImageSource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s TextureImageSource) CreateImage() (image.Image, error) {
|
||||||
|
return s.source.CreateImage()
|
||||||
|
}
|
@ -18,7 +18,7 @@ func (b *Buffer) Update(ctx Context, size geom.PointF32) error {
|
|||||||
b.texture = nil
|
b.texture = nil
|
||||||
b.size = geom.ZeroPtF32
|
b.size = geom.ZeroPtF32
|
||||||
}
|
}
|
||||||
texture, err := ctx.Renderer().CreateTextureSize(size.X, size.Y)
|
texture, err := ctx.Renderer().CreateTextureTarget(size.X, size.Y)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
23
ui/button.go
23
ui/button.go
@ -11,7 +11,6 @@ type Button struct {
|
|||||||
|
|
||||||
HoverColor color.Color
|
HoverColor color.Color
|
||||||
Icon Texture
|
Icon Texture
|
||||||
IconScale float32
|
|
||||||
Text string
|
Text string
|
||||||
Type ButtonType
|
Type ButtonType
|
||||||
}
|
}
|
||||||
@ -43,7 +42,7 @@ func (b *Button) desiredSize(ctx Context) geom.PointF32 {
|
|||||||
w += pad + font.WidthOf(b.Text)
|
w += pad + font.WidthOf(b.Text)
|
||||||
}
|
}
|
||||||
if b.Icon != nil && b.Icon.Height() > 0 {
|
if b.Icon != nil && b.Icon.Height() > 0 {
|
||||||
iconW := b.scale(b.Icon.Width() * h / b.Icon.Height())
|
iconW := b.Icon.Width() * h / b.Icon.Height()
|
||||||
w += pad + iconW
|
w += pad + iconW
|
||||||
}
|
}
|
||||||
if w == 0 {
|
if w == 0 {
|
||||||
@ -91,13 +90,6 @@ func (b *Button) fillColor(p *Palette) color.Color {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Button) scale(f float32) float32 {
|
|
||||||
if b.IconScale == 0 {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
return b.IconScale * f
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *Button) textColor(p *Palette) color.Color {
|
func (b *Button) textColor(p *Palette) color.Color {
|
||||||
if b.Font.Color != nil {
|
if b.Font.Color != nil {
|
||||||
return b.Font.Color
|
return b.Font.Color
|
||||||
@ -131,18 +123,21 @@ func (b *Button) Render(ctx Context) {
|
|||||||
|
|
||||||
var pad = style.Dimensions.TextPadding
|
var pad = style.Dimensions.TextPadding
|
||||||
bounds = bounds.Inset(pad)
|
bounds = bounds.Inset(pad)
|
||||||
|
boundsH := bounds.Dy()
|
||||||
pos := bounds.Min
|
pos := bounds.Min
|
||||||
if b.Icon != nil && b.Icon.Height() > 0 {
|
if b.Icon != nil && b.Icon.Height() > 0 {
|
||||||
icon, _ := ctx.Textures().ScaledHeight(b.Icon, b.scale(bounds.Dy()))
|
icon, _ := ctx.Textures().ScaledHeight(b.Icon, boundsH) // try to pre-scale icon
|
||||||
if icon != nil {
|
if icon == nil { // let the renderer scale
|
||||||
ctx.Renderer().DrawTextureOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: textColor})
|
icon = b.Icon
|
||||||
pos.X += icon.Width() + pad
|
|
||||||
}
|
}
|
||||||
|
scale, iconWidth := ScaleToHeight(SizeOfTexture(icon), boundsH)
|
||||||
|
ctx.Renderer().DrawTextureOptions(icon, geom.PtF32(pos.X, pos.Y), DrawOptions{Tint: textColor, Scale: scale})
|
||||||
|
pos.X += iconWidth + pad
|
||||||
}
|
}
|
||||||
if len(b.Text) != 0 {
|
if len(b.Text) != 0 {
|
||||||
var fontName = b.FontName(ctx)
|
var fontName = b.FontName(ctx)
|
||||||
var font = ctx.Renderer().Font(fontName)
|
var font = ctx.Renderer().Font(fontName)
|
||||||
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-font.Height())), fontName, textColor, b.Text)
|
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fontName, textColor, b.Text)
|
||||||
}
|
}
|
||||||
|
|
||||||
if b.Type == ButtonTypeOutlined {
|
if b.Type == ButtonTypeOutlined {
|
||||||
|
@ -30,8 +30,9 @@ func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 {
|
|||||||
if len(c.Text) != 0 {
|
if len(c.Text) != 0 {
|
||||||
w += pad + font.WidthOf(c.Text)
|
w += pad + font.WidthOf(c.Text)
|
||||||
}
|
}
|
||||||
icon, _ := ctx.Textures().ScaledHeight(c.getOrCreateNormalIcon(ctx), h)
|
icon := c.getOrCreateNormalIcon(ctx)
|
||||||
w += pad + icon.Width()
|
_, iconWidth := ScaleToHeight(SizeOfTexture(icon), h)
|
||||||
|
w += pad + iconWidth
|
||||||
return geom.PtF32(w+pad, pad+h+pad)
|
return geom.PtF32(w+pad, pad+h+pad)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,47 +49,32 @@ func (c *Checkbox) getOrCreateNormalIcon(ctx Context) Texture {
|
|||||||
return GetOrCreateIcon(ctx, "ui-default-checkbox", c.normalIcon)
|
return GetOrCreateIcon(ctx, "ui-default-checkbox", c.normalIcon)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkbox) iconBorder() geom.PolygonF32 {
|
var checkBoxIconBorder = geom.PolF32(
|
||||||
return geom.PolF32(
|
geom.PtF32(48, 80),
|
||||||
geom.PtF32(48, 80),
|
geom.PtF32(400, 80),
|
||||||
geom.PtF32(400, 80),
|
geom.PtF32(400, 432),
|
||||||
geom.PtF32(400, 432),
|
geom.PtF32(48, 432),
|
||||||
geom.PtF32(48, 432),
|
)
|
||||||
)
|
|
||||||
|
var checkBoxCheckMark = geom.PointsF32{
|
||||||
|
geom.PtF32(96, 256),
|
||||||
|
geom.PtF32(180, 340),
|
||||||
|
geom.PtF32(340, 150),
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkbox) checkMark() geom.PointsF32 {
|
func (c *Checkbox) hoverIcon(pt geom.PointF32) bool {
|
||||||
return geom.PointsF32{
|
return (pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)) || pt.DistanceToLines(checkBoxCheckMark) < 24
|
||||||
geom.PtF32(96, 256),
|
|
||||||
geom.PtF32(180, 340),
|
|
||||||
geom.PtF32(340, 150),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkbox) hoverIcon() ImagePixelTestFn {
|
func (c *Checkbox) normalIcon(pt geom.PointF32) bool {
|
||||||
border := c.iconBorder()
|
return pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)
|
||||||
check := c.checkMark()
|
|
||||||
return func(pt geom.PointF32) bool {
|
|
||||||
return (pt.DistanceToPolygon(border) < 48 && !pt.InPolygon(border)) || pt.DistanceToLines(check) < 24
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkbox) normalIcon() ImagePixelTestFn {
|
func (c *Checkbox) selectedIcon(pt geom.PointF32) bool {
|
||||||
border := c.iconBorder()
|
if pt.DistanceToPolygon(checkBoxIconBorder) < 48 || pt.InPolygon(checkBoxIconBorder) {
|
||||||
return func(pt geom.PointF32) bool {
|
return pt.DistanceToLines(checkBoxCheckMark) > 24
|
||||||
return pt.DistanceToPolygon(border) < 48 && !pt.InPolygon(border)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Checkbox) selectedIcon() ImagePixelTestFn {
|
|
||||||
border := c.iconBorder()
|
|
||||||
check := c.checkMark()
|
|
||||||
return func(pt geom.PointF32) bool {
|
|
||||||
if pt.DistanceToPolygon(border) < 48 || pt.InPolygon(border) {
|
|
||||||
return pt.DistanceToLines(check) > 24
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checkbox) DesiredSize(ctx Context) geom.PointF32 { return c.desiredSize(ctx) }
|
func (c *Checkbox) DesiredSize(ctx Context) geom.PointF32 { return c.desiredSize(ctx) }
|
||||||
@ -124,19 +110,25 @@ func (c *Checkbox) Render(ctx Context) {
|
|||||||
|
|
||||||
var pad = style.Dimensions.TextPadding
|
var pad = style.Dimensions.TextPadding
|
||||||
bounds = bounds.Inset(pad)
|
bounds = bounds.Inset(pad)
|
||||||
|
boundsH := bounds.Dy()
|
||||||
pos := bounds.Min
|
pos := bounds.Min
|
||||||
icon, _ := ctx.Textures().ScaledHeight(c.icon(ctx), bounds.Dy())
|
icon := c.icon(ctx)
|
||||||
if icon != nil {
|
if icon != nil {
|
||||||
iconColor := fore
|
iconColor := fore
|
||||||
if c.Selected && c.Font.Color == nil {
|
if c.Selected && c.Font.Color == nil {
|
||||||
iconColor = palette.Primary
|
iconColor = palette.Primary
|
||||||
}
|
}
|
||||||
ctx.Renderer().DrawTextureOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: iconColor})
|
scaledIcon, _ := ctx.Textures().ScaledHeight(icon, boundsH) // try to pre-scale icon
|
||||||
pos.X += icon.Width() + pad
|
if scaledIcon == nil { // let the renderer scale
|
||||||
|
scaledIcon = icon
|
||||||
|
}
|
||||||
|
scale, iconWidth := ScaleToHeight(SizeOfTexture(scaledIcon), boundsH)
|
||||||
|
ctx.Renderer().DrawTextureOptions(scaledIcon, geom.PtF32(pos.X, pos.Y), DrawOptions{Tint: iconColor, Scale: scale})
|
||||||
|
pos.X += iconWidth + pad
|
||||||
}
|
}
|
||||||
if len(c.Text) != 0 {
|
if len(c.Text) != 0 {
|
||||||
var fontName = c.FontName(ctx)
|
var fontName = c.FontName(ctx)
|
||||||
var font = ctx.Renderer().Font(fontName)
|
var font = ctx.Renderer().Font(fontName)
|
||||||
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-font.Height())), fontName, fore, c.Text)
|
ctx.Renderer().Text(geom.PtF32(pos.X, pos.Y+.5*(boundsH-font.Height())), fontName, fore, c.Text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@ func (c *context) Renderer() Renderer { return c.r }
|
|||||||
func (c *context) Style() *Style { return c.style }
|
func (c *context) Style() *Style { return c.style }
|
||||||
|
|
||||||
// Handle implement EventTarget
|
// Handle implement EventTarget
|
||||||
|
|
||||||
func (c *context) Handle(e Event) {
|
func (c *context) Handle(e Event) {
|
||||||
switch e.(type) {
|
switch e.(type) {
|
||||||
case *DisplayCloseEvent:
|
case *DisplayCloseEvent:
|
||||||
|
@ -10,3 +10,11 @@ type DrawOptions struct {
|
|||||||
Tint color.Color
|
Tint color.Color
|
||||||
Scale *geom.PointF32
|
Scale *geom.PointF32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ScaleToHeight(size geom.PointF32, height float32) (*geom.PointF32, float32) {
|
||||||
|
if size.Y == height {
|
||||||
|
return nil, size.X
|
||||||
|
}
|
||||||
|
factor := height / size.Y
|
||||||
|
return &geom.PointF32{X: factor, Y: factor}, factor * size.X
|
||||||
|
}
|
||||||
|
15
ui/event.go
15
ui/event.go
@ -30,13 +30,19 @@ const (
|
|||||||
KeyModifierShift = 1 << iota
|
KeyModifierShift = 1 << iota
|
||||||
KeyModifierControl
|
KeyModifierControl
|
||||||
KeyModifierAlt
|
KeyModifierAlt
|
||||||
|
KeyModifierOSCommand
|
||||||
)
|
)
|
||||||
|
|
||||||
type KeyPressEvent struct {
|
type KeyDownEvent struct {
|
||||||
|
EventBase
|
||||||
|
Key Key
|
||||||
|
Modifiers KeyModifier
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeyUpEvent struct {
|
||||||
EventBase
|
EventBase
|
||||||
Key Key
|
Key Key
|
||||||
Modifiers KeyModifier
|
Modifiers KeyModifier
|
||||||
Character rune
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type MouseButton int
|
type MouseButton int
|
||||||
@ -82,3 +88,8 @@ type MouseMoveEvent struct {
|
|||||||
type RefreshEvent struct {
|
type RefreshEvent struct {
|
||||||
EventBase
|
EventBase
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TextInputEvent struct {
|
||||||
|
EventBase
|
||||||
|
Character rune
|
||||||
|
}
|
||||||
|
@ -4,7 +4,8 @@ import (
|
|||||||
"image/color"
|
"image/color"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
_ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI
|
_ "opslag.de/schobers/zntg/sdlui" // import the renderer for the UI
|
||||||
|
// _ "opslag.de/schobers/zntg/allg5ui" // import the renderer for the UI
|
||||||
|
|
||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
"opslag.de/schobers/zntg/ui"
|
"opslag.de/schobers/zntg/ui"
|
||||||
@ -73,7 +74,7 @@ func run() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
plus, err := render.CreateTexturePath("../resources/images/plus.png")
|
plus, err := render.CreateTexturePath("../resources/images/plus.png", true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
52
ui/icon.go
52
ui/icon.go
@ -7,11 +7,30 @@ import (
|
|||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ImagePixelTestFn func(geom.PointF32) bool
|
type AlphaPixelImageSource struct {
|
||||||
|
ImageAlphaPixelTestFn
|
||||||
|
|
||||||
|
Size geom.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *AlphaPixelImageSource) CreateImage() (image.Image, error) {
|
||||||
|
return DrawImageAlpha(s.Size, s.ImageAlphaPixelTestFn), nil
|
||||||
|
}
|
||||||
|
|
||||||
type ImageAlphaPixelTestFn func(geom.PointF32) uint8
|
type ImageAlphaPixelTestFn func(geom.PointF32) uint8
|
||||||
|
|
||||||
func createTexture(ctx Context, image image.Image) Texture {
|
func (f ImageAlphaPixelTestFn) CreateImageSource(size geom.Point) ImageSource {
|
||||||
texture, err := ctx.Renderer().CreateTexture(image)
|
return &AlphaPixelImageSource{f, size}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImagePixelTestFn func(geom.PointF32) bool
|
||||||
|
|
||||||
|
func (f ImagePixelTestFn) CreateImageSource(size geom.Point) ImageSource {
|
||||||
|
return &PixelImageSource{f, size}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTexture(ctx Context, source ImageSource) Texture {
|
||||||
|
texture, err := ctx.Renderer().CreateTexture(source)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -19,18 +38,15 @@ func createTexture(ctx Context, image image.Image) Texture {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CreateIcon(ctx Context, test ImagePixelTestFn) Texture {
|
func CreateIcon(ctx Context, test ImagePixelTestFn) Texture {
|
||||||
icon := DrawIcon(test)
|
return createTexture(ctx, test.CreateImageSource(IconSize()))
|
||||||
return createTexture(ctx, icon)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTexture(ctx Context, size geom.Point, test ImagePixelTestFn) Texture {
|
func CreateTexture(ctx Context, size geom.Point, test ImagePixelTestFn) Texture {
|
||||||
image := DrawImage(size, test)
|
return createTexture(ctx, test.CreateImageSource(size))
|
||||||
return createTexture(ctx, image)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateTextureAlpha(ctx Context, size geom.Point, test ImageAlphaPixelTestFn) Texture {
|
func CreateTextureAlpha(ctx Context, size geom.Point, test ImageAlphaPixelTestFn) Texture {
|
||||||
image := DrawImageAlpha(size, test)
|
return createTexture(ctx, test.CreateImageSource(size))
|
||||||
return createTexture(ctx, image)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func DrawIcon(test ImagePixelTestFn) image.Image {
|
func DrawIcon(test ImagePixelTestFn) image.Image {
|
||||||
@ -66,20 +82,28 @@ func DrawImageAlpha(size geom.Point, test ImageAlphaPixelTestFn) image.Image {
|
|||||||
return icon
|
return icon
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOrCreateIcon(ctx Context, name string, testFactory func() ImagePixelTestFn) Texture {
|
func GetOrCreateIcon(ctx Context, name string, test ImagePixelTestFn) Texture {
|
||||||
texture := ctx.Textures().Texture(name)
|
texture := ctx.Textures().Texture(name)
|
||||||
if texture != nil {
|
if texture != nil {
|
||||||
return texture
|
return texture
|
||||||
}
|
}
|
||||||
test := testFactory()
|
texture, err := ctx.Textures().CreateTexture(name, test.CreateImageSource(IconSize()))
|
||||||
texture = CreateIcon(ctx, test)
|
if err != nil {
|
||||||
if texture == nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ctx.Textures().AddTexture(name, texture)
|
|
||||||
return texture
|
return texture
|
||||||
}
|
}
|
||||||
|
|
||||||
func IconSize() geom.Point {
|
func IconSize() geom.Point {
|
||||||
return geom.Pt(448, 512)
|
return geom.Pt(448, 512)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PixelImageSource struct {
|
||||||
|
ImagePixelTestFn
|
||||||
|
|
||||||
|
Size geom.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PixelImageSource) CreateImage() (image.Image, error) {
|
||||||
|
return DrawImage(s.Size, s.ImagePixelTestFn), nil
|
||||||
|
}
|
||||||
|
37
ui/imagesource.go
Normal file
37
ui/imagesource.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ImageSource interface {
|
||||||
|
CreateImage() (image.Image, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageFileSource string
|
||||||
|
|
||||||
|
var _ ImageSource = ImageFileSource("")
|
||||||
|
|
||||||
|
func (s ImageFileSource) CreateImage() (image.Image, error) {
|
||||||
|
f, err := os.Open(string(s))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
m, _, err := image.Decode(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageGoSource struct {
|
||||||
|
image.Image
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ ImageSource = ImageGoSource{}
|
||||||
|
|
||||||
|
func (s ImageGoSource) CreateImage() (image.Image, error) {
|
||||||
|
return s.Image, nil
|
||||||
|
}
|
20
ui/key.go
20
ui/key.go
@ -48,6 +48,7 @@ const (
|
|||||||
KeyDPadUp
|
KeyDPadUp
|
||||||
KeyE
|
KeyE
|
||||||
KeyEnd
|
KeyEnd
|
||||||
|
KeyEnter
|
||||||
KeyEquals
|
KeyEquals
|
||||||
KeyEscape
|
KeyEscape
|
||||||
KeyF
|
KeyF
|
||||||
@ -135,3 +136,22 @@ const (
|
|||||||
KeyY
|
KeyY
|
||||||
KeyZ
|
KeyZ
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type KeyState map[Key]bool
|
||||||
|
|
||||||
|
func (s KeyState) Modifiers() KeyModifier {
|
||||||
|
var mods KeyModifier
|
||||||
|
if s[KeyAlt] || s[KeyAltGr] {
|
||||||
|
mods |= KeyModifierAlt
|
||||||
|
}
|
||||||
|
if s[KeyLeftControl] || s[KeyRightControl] {
|
||||||
|
mods |= KeyModifierControl
|
||||||
|
}
|
||||||
|
if s[KeyLeftShift] || s[KeyRightShift] {
|
||||||
|
mods |= KeyModifierShift
|
||||||
|
}
|
||||||
|
if s[KeyLeftWin] || s[KeyRightWin] || s[KeyCommand] {
|
||||||
|
mods |= KeyModifierOSCommand
|
||||||
|
}
|
||||||
|
return mods
|
||||||
|
}
|
||||||
|
@ -17,9 +17,10 @@ type Renderer interface {
|
|||||||
|
|
||||||
// Drawing
|
// Drawing
|
||||||
Clear(c color.Color)
|
Clear(c color.Color)
|
||||||
CreateTexture(m image.Image) (Texture, error)
|
CreateTexture(m ImageSource) (Texture, error)
|
||||||
CreateTexturePath(path string) (Texture, error)
|
CreateTextureGo(m image.Image, source bool) (Texture, error)
|
||||||
CreateTextureSize(w, h float32) (Texture, error)
|
CreateTexturePath(path string, source bool) (Texture, error)
|
||||||
|
CreateTextureTarget(w, h float32) (Texture, error)
|
||||||
DefaultTarget() Texture
|
DefaultTarget() Texture
|
||||||
DrawTexture(t Texture, p geom.PointF32)
|
DrawTexture(t Texture, p geom.PointF32)
|
||||||
DrawTextureOptions(t Texture, p geom.PointF32, opts DrawOptions)
|
DrawTextureOptions(t Texture, p geom.PointF32, opts DrawOptions)
|
||||||
|
@ -28,4 +28,5 @@ func SetRendererFactory(factory RendererFactory) {
|
|||||||
type NewRendererOptions struct {
|
type NewRendererOptions struct {
|
||||||
Location *geom.PointF32
|
Location *geom.PointF32
|
||||||
Resizable bool
|
Resizable bool
|
||||||
|
VSync bool
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package ui
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/color"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"unicode"
|
"unicode"
|
||||||
@ -144,7 +143,7 @@ func (b *TextBox) Handle(ctx Context, e Event) {
|
|||||||
b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent)
|
b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent)
|
||||||
b.Selection.End = b.Selection.Caret
|
b.Selection.End = b.Selection.Caret
|
||||||
}
|
}
|
||||||
case *KeyPressEvent:
|
case *KeyDownEvent:
|
||||||
if !b.Focus {
|
if !b.Focus {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -217,14 +216,12 @@ func (b *TextBox) Handle(ctx Context, e Event) {
|
|||||||
case KeyX:
|
case KeyX:
|
||||||
DefaultClipboard.WriteText(b.cut())
|
DefaultClipboard.WriteText(b.cut())
|
||||||
}
|
}
|
||||||
default:
|
|
||||||
if e.Modifiers == KeyModifierNone || e.Modifiers&KeyModifierShift == KeyModifierShift {
|
|
||||||
caret := b.Selection.Caret
|
|
||||||
b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:])
|
|
||||||
b.Selection.Caret = caret + 1
|
|
||||||
b.Selection.SetSelectionToCaret()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
case *TextInputEvent:
|
||||||
|
caret := b.Selection.Caret
|
||||||
|
b.Text = fmt.Sprintf("%s%c%s", b.Text[:caret], e.Character, b.Text[caret:])
|
||||||
|
b.Selection.Caret = caret + 1
|
||||||
|
b.Selection.SetSelectionToCaret()
|
||||||
}
|
}
|
||||||
if b.over {
|
if b.over {
|
||||||
ctx.Renderer().SetMouseCursor(MouseCursorText)
|
ctx.Renderer().SetMouseCursor(MouseCursorText)
|
||||||
@ -242,7 +239,11 @@ func (b *TextBox) Render(ctx Context) {
|
|||||||
var caretWidth float32 = 1
|
var caretWidth float32 = 1
|
||||||
b.box.RenderFn(ctx, func(_ Context, size geom.PointF32) {
|
b.box.RenderFn(ctx, func(_ Context, size geom.PointF32) {
|
||||||
var renderer = ctx.Renderer()
|
var renderer = ctx.Renderer()
|
||||||
renderer.Clear(color.Transparent)
|
back := b.Background
|
||||||
|
if back == nil {
|
||||||
|
back = ctx.Style().Palette.Background
|
||||||
|
}
|
||||||
|
renderer.Clear(back)
|
||||||
if b.Selection.Start != b.Selection.End {
|
if b.Selection.Start != b.Selection.End {
|
||||||
left, right := renderer.Font(f).WidthOf(b.Text[:b.Selection.Start]), renderer.Font(f).WidthOf(b.Text[:b.Selection.End])
|
left, right := renderer.Font(f).WidthOf(b.Text[:b.Selection.Start]), renderer.Font(f).WidthOf(b.Text[:b.Selection.End])
|
||||||
renderer.FillRectangle(geom.RectF32(left, 0, right, size.Y), style.Palette.PrimaryHighlight)
|
renderer.FillRectangle(geom.RectF32(left, 0, right, size.Y), style.Palette.PrimaryHighlight)
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package ui
|
package ui
|
||||||
|
|
||||||
import "image"
|
import "opslag.de/schobers/geom"
|
||||||
|
|
||||||
type Texture interface {
|
type Texture interface {
|
||||||
Destroy()
|
Destroy() error
|
||||||
Height() float32
|
Height() float32
|
||||||
Texture() image.Image
|
|
||||||
Width() float32
|
Width() float32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SizeOfTexture(t Texture) geom.PointF32 { return geom.PtF32(t.Width(), t.Height()) }
|
||||||
|
@ -7,15 +7,21 @@ import (
|
|||||||
"opslag.de/schobers/geom"
|
"opslag.de/schobers/geom"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateImageFn func() (image.Image, error)
|
|
||||||
|
|
||||||
func ScaleTexture(render Renderer, texture Texture, scale float32) Texture {
|
func ScaleTexture(render Renderer, texture Texture, scale float32) Texture {
|
||||||
w := uint(texture.Width() * scale)
|
w := uint(texture.Width() * scale)
|
||||||
if w == 0 {
|
if w == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
scaled := resize.Resize(w, 0, texture.Texture(), resize.Bilinear)
|
source, ok := texture.(ImageSource)
|
||||||
res, err := render.CreateTexture(scaled)
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
image, err := source.CreateImage()
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
scaled := resize.Resize(w, 0, image, resize.Bilinear)
|
||||||
|
res, err := render.CreateTextureGo(scaled, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -40,21 +46,31 @@ func (t *Textures) AddTexture(name string, texture Texture) {
|
|||||||
t.textures[name] = texture
|
t.textures[name] = texture
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Textures) AddTextureFn(name string, create CreateImageFn) error {
|
func (t *Textures) createTexture(name string, create func() (Texture, error)) (Texture, error) {
|
||||||
im, err := create()
|
texture, err := create()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
|
||||||
return t.AddTextureNative(name, im)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Textures) AddTextureNative(name string, im image.Image) error {
|
|
||||||
texture, err := t.render.CreateTexture(im)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
t.AddTexture(name, texture)
|
t.AddTexture(name, texture)
|
||||||
return nil
|
return texture, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Textures) CreateTexture(name string, source ImageSource) (Texture, error) {
|
||||||
|
return t.createTexture(name, func() (Texture, error) {
|
||||||
|
return t.render.CreateTexture(source)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Textures) CreateTextureGo(name string, im image.Image, source bool) (Texture, error) {
|
||||||
|
return t.createTexture(name, func() (Texture, error) {
|
||||||
|
return t.render.CreateTextureGo(im, source)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Textures) CreateTexturePath(name string, path string, source bool) (Texture, error) {
|
||||||
|
return t.createTexture(name, func() (Texture, error) {
|
||||||
|
return t.render.CreateTexturePath(path, source)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *Textures) Destroy() {
|
func (t *Textures) Destroy() {
|
||||||
|
Loading…
Reference in New Issue
Block a user