From eac0b4b31a1ae323222076dcb31dc7cd4d9402d5 Mon Sep 17 00:00:00 2001 From: Sam Anthony Date: Sat, 24 Aug 2024 14:49:13 -0400 Subject: move win to gui package --- event.go | 83 ++++++++++++++ win.go | 357 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ win/events.go | 89 --------------- win/win.go | 358 ---------------------------------------------------------- 4 files changed, 440 insertions(+), 447 deletions(-) create mode 100644 win.go delete mode 100644 win/events.go delete mode 100644 win/win.go diff --git a/event.go b/event.go index 3b88c00..eff095b 100644 --- a/event.go +++ b/event.go @@ -68,3 +68,86 @@ func makeEventsChan() (<-chan Event, chan<- Event) { return out, in } + +// Button indicates a mouse button in an event. +type Button string + +// List of all mouse buttons. +const ( + ButtonLeft Button = "left" + ButtonRight Button = "right" + ButtonMiddle Button = "middle" +) + +// Key indicates a keyboard key in an event. +type Key string + +// List of all keyboard keys. +const ( + KeyLeft Key = "left" + KeyRight Key = "right" + KeyUp Key = "up" + KeyDown Key = "down" + KeyEscape Key = "escape" + KeySpace Key = "space" + KeyBackspace Key = "backspace" + KeyDelete Key = "delete" + KeyEnter Key = "enter" + KeyTab Key = "tab" + KeyHome Key = "home" + KeyEnd Key = "end" + KeyPageUp Key = "pageup" + KeyPageDown Key = "pagedown" + KeyShift Key = "shift" + KeyCtrl Key = "ctrl" + KeyAlt Key = "alt" +) + +type ( + // WiClose is an event that happens when the user presses the close button on the window. + WiClose struct{} + + // MoMove is an event that happens when the mouse gets moved across the window. + MoMove struct{ image.Point } + + // MoDown is an event that happens when a mouse button gets pressed. + MoDown struct { + image.Point + Button Button + } + + // MoUp is an event that happens when a mouse button gets released. + MoUp struct { + image.Point + Button Button + } + + // MoScroll is an event that happens on scrolling the mouse. + // + // The Point field tells the amount scrolled in each direction. + MoScroll struct{ image.Point } + + // KbType is an event that happens when a Unicode character gets typed on the keyboard. + KbType struct{ Rune rune } + + // KbDown is an event that happens when a key on the keyboard gets pressed. + KbDown struct{ Key Key } + + // KbUp is an event that happens when a key on the keyboard gets released. + KbUp struct{ Key Key } + + // KbRepeat is an event that happens when a key on the keyboard gets repeated. + // + // This happens when its held down for some time. + KbRepeat struct{ Key Key } +) + +func (wc WiClose) String() string { return "wi/close" } +func (mm MoMove) String() string { return fmt.Sprintf("mo/move/%d/%d", mm.X, mm.Y) } +func (md MoDown) String() string { return fmt.Sprintf("mo/down/%d/%d/%s", md.X, md.Y, md.Button) } +func (mu MoUp) String() string { return fmt.Sprintf("mo/up/%d/%d/%s", mu.X, mu.Y, mu.Button) } +func (ms MoScroll) String() string { return fmt.Sprintf("mo/scroll/%d/%d", ms.X, ms.Y) } +func (kt KbType) String() string { return fmt.Sprintf("kb/type/%d", kt.Rune) } +func (kd KbDown) String() string { return fmt.Sprintf("kb/down/%s", kd.Key) } +func (ku KbUp) String() string { return fmt.Sprintf("kb/up/%s", ku.Key) } +func (kr KbRepeat) String() string { return fmt.Sprintf("kb/repeat/%s", kr.Key) } diff --git a/win.go b/win.go new file mode 100644 index 0000000..41c68a3 --- /dev/null +++ b/win.go @@ -0,0 +1,357 @@ +package gui + +import ( + "image" + "image/draw" + "runtime" + "time" + "unsafe" + + "github.com/faiface/mainthread" + "github.com/go-gl/gl/v2.1/gl" + "github.com/go-gl/glfw/v3.2/glfw" +) + +// Option is a functional option to the window constructor New. +type Option func(*options) + +type options struct { + title string + width, height int + resizable bool + borderless bool + maximized bool +} + +// Title option sets the title (caption) of the window. +func Title(title string) Option { + return func(o *options) { + o.title = title + } +} + +// Size option sets the width and height of the window. +func Size(width, height int) Option { + return func(o *options) { + o.width = width + o.height = height + } +} + +// Resizable option makes the window resizable by the user. +func Resizable() Option { + return func(o *options) { + o.resizable = true + } +} + +// Borderless option makes the window borderless. +func Borderless() Option { + return func(o *options) { + o.borderless = true + } +} + +// Maximized option makes the window start maximized. +func Maximized() Option { + return func(o *options) { + o.maximized = true + } +} + +// New creates a new window with all the supplied options. +// +// The default title is empty and the default size is 640x480. +func New(opts ...Option) (*Win, error) { + o := options{ + title: "", + width: 640, + height: 480, + resizable: false, + borderless: false, + maximized: false, + } + for _, opt := range opts { + opt(&o) + } + + eventsOut, eventsIn := makeEventsChan() + + w := &Win{ + eventsOut: eventsOut, + eventsIn: eventsIn, + draw: make(chan func(draw.Image) image.Rectangle), + newSize: make(chan image.Rectangle), + finish: make(chan struct{}), + } + + var err error + mainthread.Call(func() { + w.w, err = makeGLFWWin(&o) + }) + if err != nil { + return nil, err + } + + mainthread.Call(func() { + // hiDPI hack + width, _ := w.w.GetFramebufferSize() + w.ratio = width / o.width + if w.ratio < 1 { + w.ratio = 1 + } + if w.ratio != 1 { + o.width /= w.ratio + o.height /= w.ratio + } + w.w.Destroy() + w.w, err = makeGLFWWin(&o) + }) + if err != nil { + return nil, err + } + + bounds := image.Rect(0, 0, o.width*w.ratio, o.height*w.ratio) + w.img = image.NewRGBA(bounds) + + go func() { + runtime.LockOSThread() + w.openGLThread() + }() + + mainthread.CallNonBlock(w.eventThread) + + return w, nil +} + +func makeGLFWWin(o *options) (*glfw.Window, error) { + err := glfw.Init() + if err != nil { + return nil, err + } + glfw.WindowHint(glfw.DoubleBuffer, glfw.False) + if o.resizable { + glfw.WindowHint(glfw.Resizable, glfw.True) + } else { + glfw.WindowHint(glfw.Resizable, glfw.False) + } + if o.borderless { + glfw.WindowHint(glfw.Decorated, glfw.False) + } + if o.maximized { + glfw.WindowHint(glfw.Maximized, glfw.True) + } + w, err := glfw.CreateWindow(o.width, o.height, o.title, nil, nil) + if err != nil { + return nil, err + } + if o.maximized { + o.width, o.height = w.GetFramebufferSize() // set o.width and o.height to the window size due to the window being maximized + } + return w, nil +} + +// Win is an Env that handles an actual graphical window. +// +// It receives its events from the OS and it draws to the surface of the window. +// +// Warning: only one window can be open at a time. This will be fixed. +type Win struct { + eventsOut <-chan Event + eventsIn chan<- Event + draw chan func(draw.Image) image.Rectangle + + newSize chan image.Rectangle + finish chan struct{} + + w *glfw.Window + img *image.RGBA + ratio int +} + +// Events returns the events channel of the window. +func (w *Win) Events() <-chan Event { return w.eventsOut } + +// Draw returns the draw channel of the window. +func (w *Win) Draw() chan<- func(draw.Image) image.Rectangle { return w.draw } + +var buttons = map[glfw.MouseButton]Button{ + glfw.MouseButtonLeft: ButtonLeft, + glfw.MouseButtonRight: ButtonRight, + glfw.MouseButtonMiddle: ButtonMiddle, +} + +var keys = map[glfw.Key]Key{ + glfw.KeyLeft: KeyLeft, + glfw.KeyRight: KeyRight, + glfw.KeyUp: KeyUp, + glfw.KeyDown: KeyDown, + glfw.KeyEscape: KeyEscape, + glfw.KeySpace: KeySpace, + glfw.KeyBackspace: KeyBackspace, + glfw.KeyDelete: KeyDelete, + glfw.KeyEnter: KeyEnter, + glfw.KeyTab: KeyTab, + glfw.KeyHome: KeyHome, + glfw.KeyEnd: KeyEnd, + glfw.KeyPageUp: KeyPageUp, + glfw.KeyPageDown: KeyPageDown, + glfw.KeyLeftShift: KeyShift, + glfw.KeyRightShift: KeyShift, + glfw.KeyLeftControl: KeyCtrl, + glfw.KeyRightControl: KeyCtrl, + glfw.KeyLeftAlt: KeyAlt, + glfw.KeyRightAlt: KeyAlt, +} + +func (w *Win) eventThread() { + var moX, moY int + + w.w.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { + moX, moY = int(x), int(y) + w.eventsIn <- MoMove{image.Pt(moX*w.ratio, moY*w.ratio)} + }) + + w.w.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) { + b, ok := buttons[button] + if !ok { + return + } + switch action { + case glfw.Press: + w.eventsIn <- MoDown{image.Pt(moX*w.ratio, moY*w.ratio), b} + case glfw.Release: + w.eventsIn <- MoUp{image.Pt(moX*w.ratio, moY*w.ratio), b} + } + }) + + w.w.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { + w.eventsIn <- MoScroll{image.Pt(int(xoff), int(yoff))} + }) + + w.w.SetCharCallback(func(_ *glfw.Window, r rune) { + w.eventsIn <- KbType{r} + }) + + w.w.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, _ int, action glfw.Action, _ glfw.ModifierKey) { + k, ok := keys[key] + if !ok { + return + } + switch action { + case glfw.Press: + w.eventsIn <- KbDown{k} + case glfw.Release: + w.eventsIn <- KbUp{k} + case glfw.Repeat: + w.eventsIn <- KbRepeat{k} + } + }) + + w.w.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { + r := image.Rect(0, 0, width, height) + w.newSize <- r + w.eventsIn <- Resize{Rectangle: r} + }) + + w.w.SetCloseCallback(func(_ *glfw.Window) { + w.eventsIn <- WiClose{} + }) + + r := w.img.Bounds() + w.eventsIn <- Resize{Rectangle: r} + + for { + select { + case <-w.finish: + close(w.eventsIn) + w.w.Destroy() + return + default: + glfw.WaitEventsTimeout(1.0 / 30) + } + } +} + +func (w *Win) openGLThread() { + w.w.MakeContextCurrent() + gl.Init() + + w.openGLFlush(w.img.Bounds()) + +loop: + for { + var totalR image.Rectangle + + select { + case r := <-w.newSize: + img := image.NewRGBA(r) + draw.Draw(img, w.img.Bounds(), w.img, w.img.Bounds().Min, draw.Src) + w.img = img + totalR = totalR.Union(r) + + case d, ok := <-w.draw: + if !ok { + close(w.finish) + return + } + r := d(w.img) + totalR = totalR.Union(r) + } + + for { + select { + case <-time.After(time.Second / 960): + w.openGLFlush(totalR) + totalR = image.ZR + continue loop + + case r := <-w.newSize: + img := image.NewRGBA(r) + draw.Draw(img, w.img.Bounds(), w.img, w.img.Bounds().Min, draw.Src) + w.img = img + totalR = totalR.Union(r) + + case d, ok := <-w.draw: + if !ok { + close(w.finish) + return + } + r := d(w.img) + totalR = totalR.Union(r) + } + } + } +} + +func (w *Win) openGLFlush(r image.Rectangle) { + bounds := w.img.Bounds() + r = r.Intersect(bounds) + if r.Empty() { + return + } + + tmp := image.NewRGBA(r) + draw.Draw(tmp, r, w.img, r.Min, draw.Src) + + gl.DrawBuffer(gl.FRONT) + gl.Viewport( + int32(bounds.Min.X), + int32(bounds.Min.Y), + int32(bounds.Dx()), + int32(bounds.Dy()), + ) + gl.RasterPos2d( + -1+2*float64(r.Min.X)/float64(bounds.Dx()), + +1-2*float64(r.Min.Y)/float64(bounds.Dy()), + ) + gl.PixelZoom(1, -1) + gl.DrawPixels( + int32(r.Dx()), + int32(r.Dy()), + gl.RGBA, + gl.UNSIGNED_BYTE, + unsafe.Pointer(&tmp.Pix[0]), + ) + gl.Flush() +} diff --git a/win/events.go b/win/events.go deleted file mode 100644 index 90fab52..0000000 --- a/win/events.go +++ /dev/null @@ -1,89 +0,0 @@ -package win - -import ( - "fmt" - "image" -) - -// Button indicates a mouse button in an event. -type Button string - -// List of all mouse buttons. -const ( - ButtonLeft Button = "left" - ButtonRight Button = "right" - ButtonMiddle Button = "middle" -) - -// Key indicates a keyboard key in an event. -type Key string - -// List of all keyboard keys. -const ( - KeyLeft Key = "left" - KeyRight Key = "right" - KeyUp Key = "up" - KeyDown Key = "down" - KeyEscape Key = "escape" - KeySpace Key = "space" - KeyBackspace Key = "backspace" - KeyDelete Key = "delete" - KeyEnter Key = "enter" - KeyTab Key = "tab" - KeyHome Key = "home" - KeyEnd Key = "end" - KeyPageUp Key = "pageup" - KeyPageDown Key = "pagedown" - KeyShift Key = "shift" - KeyCtrl Key = "ctrl" - KeyAlt Key = "alt" -) - -type ( - // WiClose is an event that happens when the user presses the close button on the window. - WiClose struct{} - - // MoMove is an event that happens when the mouse gets moved across the window. - MoMove struct{ image.Point } - - // MoDown is an event that happens when a mouse button gets pressed. - MoDown struct { - image.Point - Button Button - } - - // MoUp is an event that happens when a mouse button gets released. - MoUp struct { - image.Point - Button Button - } - - // MoScroll is an event that happens on scrolling the mouse. - // - // The Point field tells the amount scrolled in each direction. - MoScroll struct{ image.Point } - - // KbType is an event that happens when a Unicode character gets typed on the keyboard. - KbType struct{ Rune rune } - - // KbDown is an event that happens when a key on the keyboard gets pressed. - KbDown struct{ Key Key } - - // KbUp is an event that happens when a key on the keyboard gets released. - KbUp struct{ Key Key } - - // KbRepeat is an event that happens when a key on the keyboard gets repeated. - // - // This happens when its held down for some time. - KbRepeat struct{ Key Key } -) - -func (wc WiClose) String() string { return "wi/close" } -func (mm MoMove) String() string { return fmt.Sprintf("mo/move/%d/%d", mm.X, mm.Y) } -func (md MoDown) String() string { return fmt.Sprintf("mo/down/%d/%d/%s", md.X, md.Y, md.Button) } -func (mu MoUp) String() string { return fmt.Sprintf("mo/up/%d/%d/%s", mu.X, mu.Y, mu.Button) } -func (ms MoScroll) String() string { return fmt.Sprintf("mo/scroll/%d/%d", ms.X, ms.Y) } -func (kt KbType) String() string { return fmt.Sprintf("kb/type/%d", kt.Rune) } -func (kd KbDown) String() string { return fmt.Sprintf("kb/down/%s", kd.Key) } -func (ku KbUp) String() string { return fmt.Sprintf("kb/up/%s", ku.Key) } -func (kr KbRepeat) String() string { return fmt.Sprintf("kb/repeat/%s", kr.Key) } diff --git a/win/win.go b/win/win.go deleted file mode 100644 index 1741549..0000000 --- a/win/win.go +++ /dev/null @@ -1,358 +0,0 @@ -package win - -import ( - "image" - "image/draw" - "runtime" - "time" - "unsafe" - - "github.com/faiface/gui" - "github.com/faiface/mainthread" - "github.com/go-gl/gl/v2.1/gl" - "github.com/go-gl/glfw/v3.2/glfw" -) - -// Option is a functional option to the window constructor New. -type Option func(*options) - -type options struct { - title string - width, height int - resizable bool - borderless bool - maximized bool -} - -// Title option sets the title (caption) of the window. -func Title(title string) Option { - return func(o *options) { - o.title = title - } -} - -// Size option sets the width and height of the window. -func Size(width, height int) Option { - return func(o *options) { - o.width = width - o.height = height - } -} - -// Resizable option makes the window resizable by the user. -func Resizable() Option { - return func(o *options) { - o.resizable = true - } -} - -// Borderless option makes the window borderless. -func Borderless() Option { - return func(o *options) { - o.borderless = true - } -} - -// Maximized option makes the window start maximized. -func Maximized() Option { - return func(o *options) { - o.maximized = true - } -} - -// New creates a new window with all the supplied options. -// -// The default title is empty and the default size is 640x480. -func New(opts ...Option) (*Win, error) { - o := options{ - title: "", - width: 640, - height: 480, - resizable: false, - borderless: false, - maximized: false, - } - for _, opt := range opts { - opt(&o) - } - - eventsOut, eventsIn := gui.MakeEventsChan() - - w := &Win{ - eventsOut: eventsOut, - eventsIn: eventsIn, - draw: make(chan func(draw.Image) image.Rectangle), - newSize: make(chan image.Rectangle), - finish: make(chan struct{}), - } - - var err error - mainthread.Call(func() { - w.w, err = makeGLFWWin(&o) - }) - if err != nil { - return nil, err - } - - mainthread.Call(func() { - // hiDPI hack - width, _ := w.w.GetFramebufferSize() - w.ratio = width / o.width - if w.ratio < 1 { - w.ratio = 1 - } - if w.ratio != 1 { - o.width /= w.ratio - o.height /= w.ratio - } - w.w.Destroy() - w.w, err = makeGLFWWin(&o) - }) - if err != nil { - return nil, err - } - - bounds := image.Rect(0, 0, o.width*w.ratio, o.height*w.ratio) - w.img = image.NewRGBA(bounds) - - go func() { - runtime.LockOSThread() - w.openGLThread() - }() - - mainthread.CallNonBlock(w.eventThread) - - return w, nil -} - -func makeGLFWWin(o *options) (*glfw.Window, error) { - err := glfw.Init() - if err != nil { - return nil, err - } - glfw.WindowHint(glfw.DoubleBuffer, glfw.False) - if o.resizable { - glfw.WindowHint(glfw.Resizable, glfw.True) - } else { - glfw.WindowHint(glfw.Resizable, glfw.False) - } - if o.borderless { - glfw.WindowHint(glfw.Decorated, glfw.False) - } - if o.maximized { - glfw.WindowHint(glfw.Maximized, glfw.True) - } - w, err := glfw.CreateWindow(o.width, o.height, o.title, nil, nil) - if err != nil { - return nil, err - } - if o.maximized { - o.width, o.height = w.GetFramebufferSize() // set o.width and o.height to the window size due to the window being maximized - } - return w, nil -} - -// Win is an Env that handles an actual graphical window. -// -// It receives its events from the OS and it draws to the surface of the window. -// -// Warning: only one window can be open at a time. This will be fixed. -type Win struct { - eventsOut <-chan gui.Event - eventsIn chan<- gui.Event - draw chan func(draw.Image) image.Rectangle - - newSize chan image.Rectangle - finish chan struct{} - - w *glfw.Window - img *image.RGBA - ratio int -} - -// Events returns the events channel of the window. -func (w *Win) Events() <-chan gui.Event { return w.eventsOut } - -// Draw returns the draw channel of the window. -func (w *Win) Draw() chan<- func(draw.Image) image.Rectangle { return w.draw } - -var buttons = map[glfw.MouseButton]Button{ - glfw.MouseButtonLeft: ButtonLeft, - glfw.MouseButtonRight: ButtonRight, - glfw.MouseButtonMiddle: ButtonMiddle, -} - -var keys = map[glfw.Key]Key{ - glfw.KeyLeft: KeyLeft, - glfw.KeyRight: KeyRight, - glfw.KeyUp: KeyUp, - glfw.KeyDown: KeyDown, - glfw.KeyEscape: KeyEscape, - glfw.KeySpace: KeySpace, - glfw.KeyBackspace: KeyBackspace, - glfw.KeyDelete: KeyDelete, - glfw.KeyEnter: KeyEnter, - glfw.KeyTab: KeyTab, - glfw.KeyHome: KeyHome, - glfw.KeyEnd: KeyEnd, - glfw.KeyPageUp: KeyPageUp, - glfw.KeyPageDown: KeyPageDown, - glfw.KeyLeftShift: KeyShift, - glfw.KeyRightShift: KeyShift, - glfw.KeyLeftControl: KeyCtrl, - glfw.KeyRightControl: KeyCtrl, - glfw.KeyLeftAlt: KeyAlt, - glfw.KeyRightAlt: KeyAlt, -} - -func (w *Win) eventThread() { - var moX, moY int - - w.w.SetCursorPosCallback(func(_ *glfw.Window, x, y float64) { - moX, moY = int(x), int(y) - w.eventsIn <- MoMove{image.Pt(moX*w.ratio, moY*w.ratio)} - }) - - w.w.SetMouseButtonCallback(func(_ *glfw.Window, button glfw.MouseButton, action glfw.Action, mod glfw.ModifierKey) { - b, ok := buttons[button] - if !ok { - return - } - switch action { - case glfw.Press: - w.eventsIn <- MoDown{image.Pt(moX*w.ratio, moY*w.ratio), b} - case glfw.Release: - w.eventsIn <- MoUp{image.Pt(moX*w.ratio, moY*w.ratio), b} - } - }) - - w.w.SetScrollCallback(func(_ *glfw.Window, xoff, yoff float64) { - w.eventsIn <- MoScroll{image.Pt(int(xoff), int(yoff))} - }) - - w.w.SetCharCallback(func(_ *glfw.Window, r rune) { - w.eventsIn <- KbType{r} - }) - - w.w.SetKeyCallback(func(_ *glfw.Window, key glfw.Key, _ int, action glfw.Action, _ glfw.ModifierKey) { - k, ok := keys[key] - if !ok { - return - } - switch action { - case glfw.Press: - w.eventsIn <- KbDown{k} - case glfw.Release: - w.eventsIn <- KbUp{k} - case glfw.Repeat: - w.eventsIn <- KbRepeat{k} - } - }) - - w.w.SetFramebufferSizeCallback(func(_ *glfw.Window, width, height int) { - r := image.Rect(0, 0, width, height) - w.newSize <- r - w.eventsIn <- gui.Resize{Rectangle: r} - }) - - w.w.SetCloseCallback(func(_ *glfw.Window) { - w.eventsIn <- WiClose{} - }) - - r := w.img.Bounds() - w.eventsIn <- gui.Resize{Rectangle: r} - - for { - select { - case <-w.finish: - close(w.eventsIn) - w.w.Destroy() - return - default: - glfw.WaitEventsTimeout(1.0 / 30) - } - } -} - -func (w *Win) openGLThread() { - w.w.MakeContextCurrent() - gl.Init() - - w.openGLFlush(w.img.Bounds()) - -loop: - for { - var totalR image.Rectangle - - select { - case r := <-w.newSize: - img := image.NewRGBA(r) - draw.Draw(img, w.img.Bounds(), w.img, w.img.Bounds().Min, draw.Src) - w.img = img - totalR = totalR.Union(r) - - case d, ok := <-w.draw: - if !ok { - close(w.finish) - return - } - r := d(w.img) - totalR = totalR.Union(r) - } - - for { - select { - case <-time.After(time.Second / 960): - w.openGLFlush(totalR) - totalR = image.ZR - continue loop - - case r := <-w.newSize: - img := image.NewRGBA(r) - draw.Draw(img, w.img.Bounds(), w.img, w.img.Bounds().Min, draw.Src) - w.img = img - totalR = totalR.Union(r) - - case d, ok := <-w.draw: - if !ok { - close(w.finish) - return - } - r := d(w.img) - totalR = totalR.Union(r) - } - } - } -} - -func (w *Win) openGLFlush(r image.Rectangle) { - bounds := w.img.Bounds() - r = r.Intersect(bounds) - if r.Empty() { - return - } - - tmp := image.NewRGBA(r) - draw.Draw(tmp, r, w.img, r.Min, draw.Src) - - gl.DrawBuffer(gl.FRONT) - gl.Viewport( - int32(bounds.Min.X), - int32(bounds.Min.Y), - int32(bounds.Dx()), - int32(bounds.Dy()), - ) - gl.RasterPos2d( - -1+2*float64(r.Min.X)/float64(bounds.Dx()), - +1-2*float64(r.Min.Y)/float64(bounds.Dy()), - ) - gl.PixelZoom(1, -1) - gl.DrawPixels( - int32(r.Dx()), - int32(r.Dy()), - gl.RGBA, - gl.UNSIGNED_BYTE, - unsafe.Pointer(&tmp.Pix[0]), - ) - gl.Flush() -} -- cgit v1.2.3