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
|
||||
case allg5.KeyEnd:
|
||||
return ui.KeyEnd
|
||||
case allg5.KeyEnter:
|
||||
return ui.KeyEnter
|
||||
case allg5.KeyEquals:
|
||||
return ui.KeyEquals
|
||||
case allg5.KeyEscape:
|
||||
|
@ -4,6 +4,9 @@ import (
|
||||
"image"
|
||||
"image/color"
|
||||
"math"
|
||||
"unicode"
|
||||
|
||||
"opslag.de/schobers/zntg"
|
||||
|
||||
"opslag.de/schobers/allg5"
|
||||
"opslag.de/schobers/geom"
|
||||
@ -13,20 +16,25 @@ import (
|
||||
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 {
|
||||
disp.Destroy()
|
||||
return nil, err
|
||||
}
|
||||
clean = clean.Add(eq.Destroy)
|
||||
|
||||
user := allg5.NewUserEventSource()
|
||||
eq.RegisterKeyboard()
|
||||
@ -34,18 +42,25 @@ func NewRenderer(w, h int, opts allg5.NewDisplayOptions) (*Renderer, error) {
|
||||
eq.RegisterDisplay(disp)
|
||||
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.
|
||||
type Renderer struct {
|
||||
disp *allg5.Display
|
||||
eq *allg5.EventQueue
|
||||
unh func(allg5.Event)
|
||||
ft map[string]*font
|
||||
user *allg5.UserEventSource
|
||||
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
|
||||
newCursor ui.MouseCursor
|
||||
}
|
||||
|
||||
// Renderer implementation (events)
|
||||
@ -53,11 +68,14 @@ type Renderer struct {
|
||||
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
|
||||
}
|
||||
|
||||
cursor := r.cursor
|
||||
r.cursor = ui.MouseCursorDefault
|
||||
var unhandled bool
|
||||
for ev != nil {
|
||||
switch e := ev.(type) {
|
||||
case *allg5.DisplayCloseEvent:
|
||||
@ -65,7 +83,21 @@ func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
||||
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})
|
||||
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:
|
||||
@ -82,12 +114,12 @@ func (r *Renderer) PushEvents(t ui.EventTarget, wait bool) {
|
||||
if r.unh != nil {
|
||||
r.unh(e)
|
||||
}
|
||||
unhandled = true
|
||||
}
|
||||
ev = r.eq.Get()
|
||||
}
|
||||
|
||||
if r.newCursor != r.cursor {
|
||||
r.cursor = r.newCursor
|
||||
if !unhandled && cursor != r.cursor {
|
||||
switch r.cursor {
|
||||
case ui.MouseCursorNone:
|
||||
r.disp.SetMouseCursor(allg5.MouseCursorNone)
|
||||
@ -130,32 +162,47 @@ func (r *Renderer) Clear(c color.Color) {
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
if err != nil {
|
||||
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))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &texture{bmp}, nil
|
||||
return &texture{bmp, nil}, nil
|
||||
}
|
||||
|
||||
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 }
|
||||
@ -191,7 +238,7 @@ func (r *Renderer) Font(name string) ui.Font {
|
||||
func (r *Renderer) mustGetBitmap(t ui.Texture) *allg5.Bitmap {
|
||||
texture, ok := t.(*texture)
|
||||
if !ok {
|
||||
panic("image must be created on same renderer")
|
||||
panic("texture must be created on same renderer")
|
||||
}
|
||||
return texture.bmp
|
||||
}
|
||||
@ -244,7 +291,7 @@ func (r *Renderer) SetIcon(texture ui.Texture) {
|
||||
}
|
||||
|
||||
func (r *Renderer) SetMouseCursor(c ui.MouseCursor) {
|
||||
r.newCursor = c
|
||||
r.cursor = c
|
||||
}
|
||||
|
||||
func (r *Renderer) SetUnhandledEventHandler(handler func(allg5.Event)) {
|
||||
@ -256,7 +303,7 @@ func (r *Renderer) SetWindowTitle(t string) {
|
||||
}
|
||||
|
||||
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) {
|
||||
|
@ -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) {
|
||||
renderer, err := NewRenderer(width, height, allg5.NewDisplayOptions{
|
||||
Resizable: opts.Resizable,
|
||||
Vsync: opts.VSync,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -8,21 +8,27 @@ import (
|
||||
)
|
||||
|
||||
var _ ui.Texture = &texture{}
|
||||
var _ ui.ImageSource = &texture{}
|
||||
|
||||
type texture struct {
|
||||
bmp *allg5.Bitmap
|
||||
bmp *allg5.Bitmap
|
||||
source ui.ImageSource
|
||||
}
|
||||
|
||||
func (t *texture) Destroy() {
|
||||
func (t *texture) Destroy() error {
|
||||
t.bmp.Destroy()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *texture) Height() float32 {
|
||||
return float32(t.bmp.Height())
|
||||
}
|
||||
|
||||
func (t *texture) Texture() image.Image {
|
||||
return t.bmp.Image()
|
||||
func (t *texture) CreateImage() (image.Image, error) {
|
||||
if t.source == nil {
|
||||
return t.bmp.Image(), nil
|
||||
}
|
||||
return t.source.CreateImage()
|
||||
}
|
||||
|
||||
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.size = geom.ZeroPtF32
|
||||
}
|
||||
texture, err := ctx.Renderer().CreateTextureSize(size.X, size.Y)
|
||||
texture, err := ctx.Renderer().CreateTextureTarget(size.X, size.Y)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
23
ui/button.go
23
ui/button.go
@ -11,7 +11,6 @@ type Button struct {
|
||||
|
||||
HoverColor color.Color
|
||||
Icon Texture
|
||||
IconScale float32
|
||||
Text string
|
||||
Type ButtonType
|
||||
}
|
||||
@ -43,7 +42,7 @@ func (b *Button) desiredSize(ctx Context) geom.PointF32 {
|
||||
w += pad + font.WidthOf(b.Text)
|
||||
}
|
||||
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
|
||||
}
|
||||
if w == 0 {
|
||||
@ -91,13 +90,6 @@ func (b *Button) fillColor(p *Palette) color.Color {
|
||||
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 {
|
||||
if b.Font.Color != nil {
|
||||
return b.Font.Color
|
||||
@ -131,18 +123,21 @@ func (b *Button) Render(ctx Context) {
|
||||
|
||||
var pad = style.Dimensions.TextPadding
|
||||
bounds = bounds.Inset(pad)
|
||||
boundsH := bounds.Dy()
|
||||
pos := bounds.Min
|
||||
if b.Icon != nil && b.Icon.Height() > 0 {
|
||||
icon, _ := ctx.Textures().ScaledHeight(b.Icon, b.scale(bounds.Dy()))
|
||||
if icon != nil {
|
||||
ctx.Renderer().DrawTextureOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: textColor})
|
||||
pos.X += icon.Width() + pad
|
||||
icon, _ := ctx.Textures().ScaledHeight(b.Icon, boundsH) // try to pre-scale icon
|
||||
if icon == nil { // let the renderer scale
|
||||
icon = b.Icon
|
||||
}
|
||||
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 {
|
||||
var fontName = b.FontName(ctx)
|
||||
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 {
|
||||
|
@ -30,8 +30,9 @@ func (c *Checkbox) desiredSize(ctx Context) geom.PointF32 {
|
||||
if len(c.Text) != 0 {
|
||||
w += pad + font.WidthOf(c.Text)
|
||||
}
|
||||
icon, _ := ctx.Textures().ScaledHeight(c.getOrCreateNormalIcon(ctx), h)
|
||||
w += pad + icon.Width()
|
||||
icon := c.getOrCreateNormalIcon(ctx)
|
||||
_, iconWidth := ScaleToHeight(SizeOfTexture(icon), h)
|
||||
w += pad + iconWidth
|
||||
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)
|
||||
}
|
||||
|
||||
func (c *Checkbox) iconBorder() geom.PolygonF32 {
|
||||
return geom.PolF32(
|
||||
geom.PtF32(48, 80),
|
||||
geom.PtF32(400, 80),
|
||||
geom.PtF32(400, 432),
|
||||
geom.PtF32(48, 432),
|
||||
)
|
||||
var checkBoxIconBorder = geom.PolF32(
|
||||
geom.PtF32(48, 80),
|
||||
geom.PtF32(400, 80),
|
||||
geom.PtF32(400, 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 {
|
||||
return geom.PointsF32{
|
||||
geom.PtF32(96, 256),
|
||||
geom.PtF32(180, 340),
|
||||
geom.PtF32(340, 150),
|
||||
}
|
||||
func (c *Checkbox) hoverIcon(pt geom.PointF32) bool {
|
||||
return (pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)) || pt.DistanceToLines(checkBoxCheckMark) < 24
|
||||
}
|
||||
|
||||
func (c *Checkbox) hoverIcon() ImagePixelTestFn {
|
||||
border := c.iconBorder()
|
||||
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(pt geom.PointF32) bool {
|
||||
return pt.DistanceToPolygon(checkBoxIconBorder) < 48 && !pt.InPolygon(checkBoxIconBorder)
|
||||
}
|
||||
|
||||
func (c *Checkbox) normalIcon() ImagePixelTestFn {
|
||||
border := c.iconBorder()
|
||||
return func(pt geom.PointF32) bool {
|
||||
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
|
||||
func (c *Checkbox) selectedIcon(pt geom.PointF32) bool {
|
||||
if pt.DistanceToPolygon(checkBoxIconBorder) < 48 || pt.InPolygon(checkBoxIconBorder) {
|
||||
return pt.DistanceToLines(checkBoxCheckMark) > 24
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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
|
||||
bounds = bounds.Inset(pad)
|
||||
boundsH := bounds.Dy()
|
||||
pos := bounds.Min
|
||||
icon, _ := ctx.Textures().ScaledHeight(c.icon(ctx), bounds.Dy())
|
||||
icon := c.icon(ctx)
|
||||
if icon != nil {
|
||||
iconColor := fore
|
||||
if c.Selected && c.Font.Color == nil {
|
||||
iconColor = palette.Primary
|
||||
}
|
||||
ctx.Renderer().DrawTextureOptions(icon, geom.PtF32(pos.X, pos.Y+.5*(bounds.Dy()-icon.Height())), DrawOptions{Tint: iconColor})
|
||||
pos.X += icon.Width() + pad
|
||||
scaledIcon, _ := ctx.Textures().ScaledHeight(icon, boundsH) // try to pre-scale icon
|
||||
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 {
|
||||
var fontName = c.FontName(ctx)
|
||||
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 }
|
||||
|
||||
// Handle implement EventTarget
|
||||
|
||||
func (c *context) Handle(e Event) {
|
||||
switch e.(type) {
|
||||
case *DisplayCloseEvent:
|
||||
|
@ -10,3 +10,11 @@ type DrawOptions struct {
|
||||
Tint color.Color
|
||||
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
|
||||
KeyModifierControl
|
||||
KeyModifierAlt
|
||||
KeyModifierOSCommand
|
||||
)
|
||||
|
||||
type KeyPressEvent struct {
|
||||
type KeyDownEvent struct {
|
||||
EventBase
|
||||
Key Key
|
||||
Modifiers KeyModifier
|
||||
}
|
||||
|
||||
type KeyUpEvent struct {
|
||||
EventBase
|
||||
Key Key
|
||||
Modifiers KeyModifier
|
||||
Character rune
|
||||
}
|
||||
|
||||
type MouseButton int
|
||||
@ -82,3 +88,8 @@ type MouseMoveEvent struct {
|
||||
type RefreshEvent struct {
|
||||
EventBase
|
||||
}
|
||||
|
||||
type TextInputEvent struct {
|
||||
EventBase
|
||||
Character rune
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import (
|
||||
"image/color"
|
||||
"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/zntg/ui"
|
||||
@ -73,7 +74,7 @@ func run() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
plus, err := render.CreateTexturePath("../resources/images/plus.png")
|
||||
plus, err := render.CreateTexturePath("../resources/images/plus.png", true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
52
ui/icon.go
52
ui/icon.go
@ -7,11 +7,30 @@ import (
|
||||
"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
|
||||
|
||||
func createTexture(ctx Context, image image.Image) Texture {
|
||||
texture, err := ctx.Renderer().CreateTexture(image)
|
||||
func (f ImageAlphaPixelTestFn) CreateImageSource(size geom.Point) ImageSource {
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -19,18 +38,15 @@ func createTexture(ctx Context, image image.Image) Texture {
|
||||
}
|
||||
|
||||
func CreateIcon(ctx Context, test ImagePixelTestFn) Texture {
|
||||
icon := DrawIcon(test)
|
||||
return createTexture(ctx, icon)
|
||||
return createTexture(ctx, test.CreateImageSource(IconSize()))
|
||||
}
|
||||
|
||||
func CreateTexture(ctx Context, size geom.Point, test ImagePixelTestFn) Texture {
|
||||
image := DrawImage(size, test)
|
||||
return createTexture(ctx, image)
|
||||
return createTexture(ctx, test.CreateImageSource(size))
|
||||
}
|
||||
|
||||
func CreateTextureAlpha(ctx Context, size geom.Point, test ImageAlphaPixelTestFn) Texture {
|
||||
image := DrawImageAlpha(size, test)
|
||||
return createTexture(ctx, image)
|
||||
return createTexture(ctx, test.CreateImageSource(size))
|
||||
}
|
||||
|
||||
func DrawIcon(test ImagePixelTestFn) image.Image {
|
||||
@ -66,20 +82,28 @@ func DrawImageAlpha(size geom.Point, test ImageAlphaPixelTestFn) image.Image {
|
||||
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)
|
||||
if texture != nil {
|
||||
return texture
|
||||
}
|
||||
test := testFactory()
|
||||
texture = CreateIcon(ctx, test)
|
||||
if texture == nil {
|
||||
texture, err := ctx.Textures().CreateTexture(name, test.CreateImageSource(IconSize()))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
ctx.Textures().AddTexture(name, texture)
|
||||
return texture
|
||||
}
|
||||
|
||||
func IconSize() geom.Point {
|
||||
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
|
||||
KeyE
|
||||
KeyEnd
|
||||
KeyEnter
|
||||
KeyEquals
|
||||
KeyEscape
|
||||
KeyF
|
||||
@ -135,3 +136,22 @@ const (
|
||||
KeyY
|
||||
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
|
||||
Clear(c color.Color)
|
||||
CreateTexture(m image.Image) (Texture, error)
|
||||
CreateTexturePath(path string) (Texture, error)
|
||||
CreateTextureSize(w, h float32) (Texture, error)
|
||||
CreateTexture(m ImageSource) (Texture, error)
|
||||
CreateTextureGo(m image.Image, source bool) (Texture, error)
|
||||
CreateTexturePath(path string, source bool) (Texture, error)
|
||||
CreateTextureTarget(w, h float32) (Texture, error)
|
||||
DefaultTarget() Texture
|
||||
DrawTexture(t Texture, p geom.PointF32)
|
||||
DrawTextureOptions(t Texture, p geom.PointF32, opts DrawOptions)
|
||||
|
@ -28,4 +28,5 @@ func SetRendererFactory(factory RendererFactory) {
|
||||
type NewRendererOptions struct {
|
||||
Location *geom.PointF32
|
||||
Resizable bool
|
||||
VSync bool
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image/color"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@ -144,7 +143,7 @@ func (b *TextBox) Handle(ctx Context, e Event) {
|
||||
b.Selection.Caret = b.mousePosToCaretPos(ctx, e.MouseEvent)
|
||||
b.Selection.End = b.Selection.Caret
|
||||
}
|
||||
case *KeyPressEvent:
|
||||
case *KeyDownEvent:
|
||||
if !b.Focus {
|
||||
break
|
||||
}
|
||||
@ -217,14 +216,12 @@ func (b *TextBox) Handle(ctx Context, e Event) {
|
||||
case KeyX:
|
||||
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 {
|
||||
ctx.Renderer().SetMouseCursor(MouseCursorText)
|
||||
@ -242,7 +239,11 @@ func (b *TextBox) Render(ctx Context) {
|
||||
var caretWidth float32 = 1
|
||||
b.box.RenderFn(ctx, func(_ Context, size geom.PointF32) {
|
||||
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 {
|
||||
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)
|
||||
|
@ -1,10 +1,11 @@
|
||||
package ui
|
||||
|
||||
import "image"
|
||||
import "opslag.de/schobers/geom"
|
||||
|
||||
type Texture interface {
|
||||
Destroy()
|
||||
Destroy() error
|
||||
Height() float32
|
||||
Texture() image.Image
|
||||
Width() float32
|
||||
}
|
||||
|
||||
func SizeOfTexture(t Texture) geom.PointF32 { return geom.PtF32(t.Width(), t.Height()) }
|
||||
|
@ -7,15 +7,21 @@ import (
|
||||
"opslag.de/schobers/geom"
|
||||
)
|
||||
|
||||
type CreateImageFn func() (image.Image, error)
|
||||
|
||||
func ScaleTexture(render Renderer, texture Texture, scale float32) Texture {
|
||||
w := uint(texture.Width() * scale)
|
||||
if w == 0 {
|
||||
return nil
|
||||
}
|
||||
scaled := resize.Resize(w, 0, texture.Texture(), resize.Bilinear)
|
||||
res, err := render.CreateTexture(scaled)
|
||||
source, ok := texture.(ImageSource)
|
||||
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 {
|
||||
return nil
|
||||
}
|
||||
@ -40,21 +46,31 @@ func (t *Textures) AddTexture(name string, texture Texture) {
|
||||
t.textures[name] = texture
|
||||
}
|
||||
|
||||
func (t *Textures) AddTextureFn(name string, create CreateImageFn) error {
|
||||
im, err := create()
|
||||
func (t *Textures) createTexture(name string, create func() (Texture, error)) (Texture, error) {
|
||||
texture, err := create()
|
||||
if err != nil {
|
||||
return 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
|
||||
return nil, err
|
||||
}
|
||||
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() {
|
||||
|
Loading…
Reference in New Issue
Block a user