diff --git a/default.go b/default.go index 2ac9a638..47732101 100644 --- a/default.go +++ b/default.go @@ -67,54 +67,12 @@ func SetScreen(x, y int) { defaultWindow.SetScreen(x, y) } -// MoveWindow calls MoveWindow on the default window. -func MoveWindow(x, y, w, h int) error { - initDefaultWindow() - return defaultWindow.MoveWindow(x, y, w, h) -} - // UpdateViewSize calls UpdateViewSize on the default window. func UpdateViewSize(w, h int) error { initDefaultWindow() return defaultWindow.UpdateViewSize(w, h) } -// SetFullScreen calls SetFullScreen on the default window. -func SetFullScreen(fs bool) error { - initDefaultWindow() - return defaultWindow.SetFullScreen(fs) -} - -// SetBorderless calls SetBorderless on the default window. -func SetBorderless(bs bool) error { - initDefaultWindow() - return defaultWindow.SetBorderless(bs) -} - -// SetTopMost calls SetTopMost on the default window. -func SetTopMost(on bool) error { - initDefaultWindow() - return defaultWindow.SetTopMost(on) -} - -// SetTitle calls SetTitle on the default window. -func SetTitle(title string) error { - initDefaultWindow() - return defaultWindow.SetTitle(title) -} - -// SetTrayIcon calls SetTrayIcon on the default window. -func SetTrayIcon(icon string) error { - initDefaultWindow() - return defaultWindow.SetTrayIcon(icon) -} - -// ShowNotification calls ShowNotification on the default window. -func ShowNotification(title, msg string, icon bool) error { - initDefaultWindow() - return defaultWindow.ShowNotification(title, msg, icon) -} - // ScreenShot calls ScreenShot on the default window. func ScreenShot() *image.RGBA { initDefaultWindow() @@ -156,15 +114,3 @@ func Height() int { initDefaultWindow() return defaultWindow.Height() } - -// HideCursor calls HideCursor on the default window. -func HideCursor() error { - initDefaultWindow() - return defaultWindow.HideCursor() -} - -// GetCursorPosition calls GetCursorPosition on the default window. -func GetCursorPosition() (x, y float64, err error) { - initDefaultWindow() - return defaultWindow.GetCursorPosition() -} diff --git a/default_desktop.go b/default_desktop.go new file mode 100644 index 00000000..d822ec8a --- /dev/null +++ b/default_desktop.go @@ -0,0 +1,59 @@ +//go:build (windows || linux || osx) && !js && !android && !nooswindow +// +build windows linux osx +// +build !js +// +build !android +// +build !nooswindow + +package oak + +import ( + "image" +) + +// MoveWindow calls MoveWindow on the default window. +func MoveWindow(x, y, w, h int) error { + initDefaultWindow() + return defaultWindow.MoveWindow(x, y, w, h) +} + +// SetFullScreen calls SetFullScreen on the default window. +func SetFullScreen(fs bool) error { + initDefaultWindow() + return defaultWindow.SetFullScreen(fs) +} + +// SetBorderless calls SetBorderless on the default window. +func SetBorderless(bs bool) error { + initDefaultWindow() + return defaultWindow.SetBorderless(bs) +} + +// SetTopMost calls SetTopMost on the default window. +func SetTopMost(on bool) error { + initDefaultWindow() + return defaultWindow.SetTopMost(on) +} + +// SetTitle calls SetTitle on the default window. +func SetTitle(title string) error { + initDefaultWindow() + return defaultWindow.SetTitle(title) +} + +// SetIcon calls SetIcon on the default window. +func SetIcon(icon image.Image) error { + initDefaultWindow() + return defaultWindow.SetIcon(icon) +} + +// HideCursor calls HideCursor on the default window. +func HideCursor() error { + initDefaultWindow() + return defaultWindow.HideCursor() +} + +// GetCursorPosition calls GetCursorPosition on the default window. +func GetCursorPosition() (x, y float64) { + initDefaultWindow() + return defaultWindow.GetCursorPosition() +} diff --git a/drawLoop.go b/drawLoop.go index e251763d..c76d49c9 100644 --- a/drawLoop.go +++ b/drawLoop.go @@ -117,8 +117,8 @@ func (w *Window) drawLoop() { func (w *Window) publish() { w.prePublish(w, w.windowTextures[w.bufferIdx]) w.windowTextures[w.bufferIdx].Upload(zeroPoint, w.winBuffers[w.bufferIdx], w.winBuffers[w.bufferIdx].Bounds()) - w.windowControl.Scale(w.windowRect, w.windowTextures[w.bufferIdx], w.windowTextures[w.bufferIdx].Bounds(), draw.Src) - w.windowControl.Publish() + w.Window.Scale(w.windowRect, w.windowTextures[w.bufferIdx], w.windowTextures[w.bufferIdx].Bounds(), draw.Src) + w.Window.Publish() // every frame, swap buffers. This enables drivers which might hold on to the rgba buffers we publish as if they // were immutable. w.bufferIdx = (w.bufferIdx + 1) % bufferCount diff --git a/event/bind.go b/event/bind.go index 11fa7d63..4e66fc38 100644 --- a/event/bind.go +++ b/event/bind.go @@ -1,6 +1,10 @@ package event -import "sync/atomic" +import ( + "sync/atomic" + + "github.com/oakmound/oak/v3/dlog" +) // Q: Why do Bind / Unbind / etc not immediately take effect? // A: For concurrent safety, most operations on a bus lock the bus. Triggers acquire a read lock on the bus, @@ -33,7 +37,7 @@ type Binding struct { // Unbind unbinds the callback associated with this binding from it's own event handler. If this binding // does not belong to its handler or has already been unbound, this will do nothing. -func (b Binding) Unbind() chan struct{} { +func (b Binding) Unbind() <-chan struct{} { return b.Handler.Unbind(b) } @@ -89,7 +93,7 @@ func (bus *Bus) PersistentBind(eventID UnsafeEventID, callerID CallerID, fn Unsa // Unbind unregisters a binding from a bus concurrently. Once complete, triggers that would // have previously caused the Bindable callback to execute will no longer do so. -func (bus *Bus) Unbind(loc Binding) chan struct{} { +func (bus *Bus) Unbind(loc Binding) <-chan struct{} { ch := make(chan struct{}) go func() { bus.mutex.Lock() @@ -113,6 +117,9 @@ type Bindable[C any, Payload any] func(C, Payload) Response // will be called with the provided caller as its first argument, and will also be called when the provided event is specifically // triggered on the caller's ID. func Bind[C Caller, Payload any](h Handler, ev EventID[Payload], caller C, fn Bindable[C, Payload]) Binding { + if caller.CID() == 0 { + dlog.Error("Bind called with CallerID 0; is this entity registered and set?") + } return h.UnsafeBind(ev.UnsafeEventID, caller.CID(), func(cid CallerID, h Handler, payload interface{}) Response { typedPayload := payload.(Payload) ent := h.GetCallerMap().GetEntity(cid) @@ -136,7 +143,7 @@ func GlobalBind[Payload any](h Handler, ev EventID[Payload], fn GlobalBindable[P type UnsafeBindable func(CallerID, Handler, interface{}) Response // UnbindAllFrom unbinds all bindings currently bound to the provided caller via ID. -func (bus *Bus) UnbindAllFrom(c CallerID) chan struct{} { +func (bus *Bus) UnbindAllFrom(c CallerID) <-chan struct{} { ch := make(chan struct{}) go func() { bus.mutex.Lock() diff --git a/event/handler.go b/event/handler.go index af59e3ea..5537121d 100644 --- a/event/handler.go +++ b/event/handler.go @@ -9,11 +9,11 @@ var ( // by alternative event handlers. type Handler interface { Reset() - TriggerForCaller(cid CallerID, event UnsafeEventID, data interface{}) chan struct{} - Trigger(event UnsafeEventID, data interface{}) chan struct{} + TriggerForCaller(cid CallerID, event UnsafeEventID, data interface{}) <-chan struct{} + Trigger(event UnsafeEventID, data interface{}) <-chan struct{} UnsafeBind(UnsafeEventID, CallerID, UnsafeBindable) Binding - Unbind(Binding) chan struct{} - UnbindAllFrom(CallerID) chan struct{} + Unbind(Binding) <-chan struct{} + UnbindAllFrom(CallerID) <-chan struct{} SetCallerMap(*CallerMap) GetCallerMap() *CallerMap } diff --git a/event/trigger.go b/event/trigger.go index c65c966a..cf842fec 100644 --- a/event/trigger.go +++ b/event/trigger.go @@ -1,7 +1,7 @@ package event // TriggerForCaller acts like Trigger, but will only trigger for the given caller. -func (bus *Bus) TriggerForCaller(callerID CallerID, eventID UnsafeEventID, data interface{}) chan struct{} { +func (bus *Bus) TriggerForCaller(callerID CallerID, eventID UnsafeEventID, data interface{}) <-chan struct{} { if callerID == Global { return bus.Trigger(eventID, data) } @@ -21,7 +21,7 @@ func (bus *Bus) TriggerForCaller(callerID CallerID, eventID UnsafeEventID, data // Trigger will scan through the event bus and call all bindables found attached // to the given event, with the passed in data. -func (bus *Bus) Trigger(eventID UnsafeEventID, data interface{}) chan struct{} { +func (bus *Bus) Trigger(eventID UnsafeEventID, data interface{}) <-chan struct{} { ch := make(chan struct{}) go func() { bus.mutex.RLock() @@ -35,11 +35,11 @@ func (bus *Bus) Trigger(eventID UnsafeEventID, data interface{}) chan struct{} { } // TriggerOn calls Trigger with a strongly typed event. -func TriggerOn[T any](b Handler, ev EventID[T], data T) chan struct{} { +func TriggerOn[T any](b Handler, ev EventID[T], data T) <-chan struct{} { return b.Trigger(ev.UnsafeEventID, data) } // TriggerForCallerOn calls TriggerForCaller with a strongly typed event. -func TriggerForCallerOn[T any](b Handler, cid CallerID, ev EventID[T], data T) chan struct{} { +func TriggerForCallerOn[T any](b Handler, cid CallerID, ev EventID[T], data T) <-chan struct{} { return b.TriggerForCaller(cid, ev.UnsafeEventID, data) } diff --git a/examples/bezier/README.md b/examples/bezier/README.md new file mode 100644 index 00000000..8c055322 --- /dev/null +++ b/examples/bezier/README.md @@ -0,0 +1,5 @@ +# Bezier Rendering +Use a mouse or debug commands to create points. +The points will be used to draw bézier curve. + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/blank/README.md b/examples/blank/README.md new file mode 100644 index 00000000..8181d097 --- /dev/null +++ b/examples/blank/README.md @@ -0,0 +1,4 @@ +# Blank Scene +Starts a pprof server and sets up a blank scene. +Useful for benchmarking and as a minimal base to copy from. +For less minimalist copying point see [the basic game template](https://github.com/oakmound/game-template). \ No newline at end of file diff --git a/examples/clipboard/go.mod b/examples/clipboard/go.mod index 15d5fc95..8aa951b5 100644 --- a/examples/clipboard/go.mod +++ b/examples/clipboard/go.mod @@ -5,7 +5,25 @@ go 1.18 require ( github.com/atotto/clipboard v0.1.4 github.com/oakmound/oak/v3 v3.0.0-alpha.1 - golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 +) + +require ( + dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 // indirect + github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect + github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect + github.com/disintegration/gift v1.2.1 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/hajimehoshi/go-mp3 v0.3.2 // indirect + github.com/oakmound/alsa v0.0.2 // indirect + github.com/oakmound/libudev v0.2.1 // indirect + github.com/oakmound/w32 v2.1.0+incompatible // indirect + github.com/oov/directsound-go v0.0.0-20141101201356-e53e59c700bf // indirect + golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 // indirect + golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect + golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb // indirect ) replace github.com/oakmound/oak/v3 => ../.. diff --git a/examples/clipboard/go.sum b/examples/clipboard/go.sum index a91dcd3d..5a212221 100644 --- a/examples/clipboard/go.sum +++ b/examples/clipboard/go.sum @@ -7,19 +7,16 @@ github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= -github.com/disintegration/gift v1.2.0 h1:VMQeei2F+ZtsHjMgP6Sdt1kFjRhs2lGz8ljEOPeIR50= -github.com/disintegration/gift v1.2.0/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= -github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d/go.mod h1:CHkHWWZ4kbGY6jEy1+qlitDaCtRgNvCOQdakj/1Yl/Q= -github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1/go.mod h1:frG94byMNy+1CgGrQ25dZ+17tf98EN+OYBQL4Zh612M= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 h1:TL70PMkdPCt9cRhKTqsm+giRpgrd0IGEj763nNr2VFY= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKcokao= -github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= +github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s= +github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jfreymuth/pulse v0.1.0 h1:KN38/9hoF9PJvP5DpEVhMRKNuwnJUonc8c9ARorRXUA= -github.com/jfreymuth/pulse v0.1.0/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= github.com/oakmound/alsa v0.0.2 h1:JbOUckkJqVvhABth7qy2JgAjqsWuBPggyoYOk1L6eK0= github.com/oakmound/alsa v0.0.2/go.mod h1:wx+ehwqFnNL7foTwxxu2bKQlaUmD2oXd4ka1UBSgWAo= github.com/oakmound/libudev v0.2.1 h1:gaXuw7Pbt3RSRxbUakAjl0dSW6Wo3TZWpwS5aMq8+EA= @@ -33,16 +30,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 h1:ywNGLBFk8tKaiu+GYZeoXWzrFoJ/a1LHYKy1lb3R9cM= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 h1:ZDL7hDvJEQEcHVkoZawKmRUgbqn1pOIzb8EinBh5csU= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -59,8 +57,8 @@ golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/examples/collision-demo/README.md b/examples/collision-demo/README.md new file mode 100644 index 00000000..b06bb7b8 --- /dev/null +++ b/examples/collision-demo/README.md @@ -0,0 +1,5 @@ +# Collision Demo + +Controllable box that colors itself based on what zones it collides with. + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/collision-demo/shake-extension-demo/README.md b/examples/collision-demo/shake-extension-demo/README.md new file mode 100644 index 00000000..a2f2390b --- /dev/null +++ b/examples/collision-demo/shake-extension-demo/README.md @@ -0,0 +1,9 @@ +# Shaker Demo + +Extension of the collision demo to illustrate the default shaker. +In addition to changing color 3 of the sectors will show a different shaking paradigm. + + + +This demo probably wont stay as is for long as it is a thin wrapper on collision-demo. + diff --git a/examples/collision-demo/shake-extension-demo/main.go b/examples/collision-demo/shake-extension-demo/main.go new file mode 100644 index 00000000..a9e2c614 --- /dev/null +++ b/examples/collision-demo/shake-extension-demo/main.go @@ -0,0 +1,159 @@ +package main + +import ( + "image/color" + "time" + + oak "github.com/oakmound/oak/v3" + "github.com/oakmound/oak/v3/collision" + "github.com/oakmound/oak/v3/entities" + "github.com/oakmound/oak/v3/event" + "github.com/oakmound/oak/v3/key" + "github.com/oakmound/oak/v3/render" + "github.com/oakmound/oak/v3/scene" + "github.com/oakmound/oak/v3/shake" +) + +const ( + _ = iota + RED collision.Label = iota + GREEN + BLUE + TEAL +) + +func main() { + oak.AddScene("demo", scene.Scene{Start: func(ctx *scene.Context) { + act := &AttachCollisionTest{} + act.Solid = entities.NewSolid(50, 50, 50, 50, render.NewColorBox(50, 50, color.RGBA{0, 0, 0, 255}), nil, ctx.CallerMap.Register(act)) + + collision.Attach(act.Vector, act.Space, nil, 0, 0) + + event.Bind(ctx, event.Enter, act, func(act *AttachCollisionTest, ev event.EnterPayload) event.Response { + if act.ShouldUpdate { + act.ShouldUpdate = false + act.R.Undraw() + act.R = act.nextR + render.Draw(act.R, 0, 1) + } + if oak.IsDown(key.A) { + // We could use attachment here to not have to shift both + // R and act but that is made more difficult by constantly + // changing the act's R + act.ShiftX(-3) + act.R.ShiftX(-3) + } else if oak.IsDown(key.D) { + act.ShiftX(3) + act.R.ShiftX(3) + } + if oak.IsDown(key.W) { + act.ShiftY(-3) + act.R.ShiftY(-3) + } else if oak.IsDown(key.S) { + act.ShiftY(3) + act.R.ShiftY(3) + } + return 0 + }) + + render.Draw(act.R, 0, 1) + + collision.PhaseCollision(act.Space, nil) + + upleft := entities.NewSolid(0, 0, 320, 240, render.NewColorBox(320, 240, color.RGBA{100, 0, 0, 100}), nil, 0) + upleft.Space.UpdateLabel(RED) + upleft.R.SetLayer(0) + render.Draw(upleft.R, 0, 0) + + upright := entities.NewSolid(320, 0, 320, 240, render.NewColorBox(320, 240, color.RGBA{0, 100, 0, 100}), nil, 0) + upright.Space.UpdateLabel(GREEN) + upright.R.SetLayer(0) + render.Draw(upright.R, 0, 0) + + botleft := entities.NewSolid(0, 240, 320, 240, render.NewColorBox(320, 240, color.RGBA{0, 0, 100, 100}), nil, 0) + botleft.Space.UpdateLabel(BLUE) + botleft.R.SetLayer(0) + render.Draw(botleft.R, 0, 0) + + botright := entities.NewSolid(320, 240, 320, 240, render.NewColorBox(320, 240, color.RGBA{0, 100, 100, 100}), nil, 0) + botright.Space.UpdateLabel(TEAL) + botright.R.SetLayer(0) + render.Draw(botright.R, 0, 0) + + event.Bind(ctx, collision.Start, act, func(act *AttachCollisionTest, l collision.Label) event.Response { + switch l { + case RED: + act.r += 125 + act.UpdateR() + case GREEN: + act.g += 125 + act.UpdateR() + shake.DefaultShaker.Shake(upleft, time.Second) + shake.DefaultShaker.Shake(botleft, time.Second) + shake.DefaultShaker.Shake(botright, time.Second) + case BLUE: + act.b += 125 + act.UpdateR() + shake.DefaultShaker.Shake(act, time.Second*2) + case TEAL: + act.b += 125 + act.g += 125 + act.UpdateR() + shake.DefaultShaker.ShakeScreen(ctx, time.Second) + } + + return 0 + }) + event.Bind(ctx, collision.Stop, act, func(act *AttachCollisionTest, l collision.Label) event.Response { + switch l { + case RED: + act.r -= 125 + act.UpdateR() + case GREEN: + act.g -= 125 + act.UpdateR() + case BLUE: + act.b -= 125 + act.UpdateR() + case TEAL: + act.b -= 125 + act.g -= 125 + act.UpdateR() + } + + return 0 + }) + }}) + render.SetDrawStack( + render.NewDynamicHeap(), + ) + oak.Init("demo") +} + +type AttachCollisionTest struct { + *entities.Solid + // AttachSpace is a composable struct that allows + // spaces to be attached to vectors + collision.AttachSpace + // Phase is a composable struct that enables the call + // collision.CollisionPhase on this struct's space, + // which will start sending signals when that space + // starts and stops touching given labels + collision.Phase + r, g, b int + ShouldUpdate bool + nextR render.Renderable +} + +// CID returns the event.CallerID so that this can be bound to. +func (act *AttachCollisionTest) CID() event.CallerID { + return act.CallerID +} + +// UpdateR with the rgb set on the act. +func (act *AttachCollisionTest) UpdateR() { + act.nextR = render.NewColorBox(50, 50, color.RGBA{uint8(act.r), uint8(act.g), uint8(act.b), 255}) + act.nextR.SetPos(act.X(), act.Y()) + act.nextR.SetLayer(1) + act.ShouldUpdate = true +} diff --git a/examples/custom-cursor/README.md b/examples/custom-cursor/README.md new file mode 100644 index 00000000..d1cdb8df --- /dev/null +++ b/examples/custom-cursor/README.md @@ -0,0 +1,4 @@ +# Custom Cursor +An example of replacing default cursor. + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/error-scene/README.md b/examples/error-scene/README.md new file mode 100644 index 00000000..18e0cecd --- /dev/null +++ b/examples/error-scene/README.md @@ -0,0 +1,4 @@ +# Error Scene +Add a catchall error scene in case an unknown scene is specified. + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/fallback-font/README.md b/examples/fallback-font/README.md new file mode 100644 index 00000000..3524c96d --- /dev/null +++ b/examples/fallback-font/README.md @@ -0,0 +1,4 @@ +# Fallback Fonts +For when a user might not have a font installed. + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/fallback-font/go.mod b/examples/fallback-font/go.mod index bc069ee2..1494e286 100644 --- a/examples/fallback-font/go.mod +++ b/examples/fallback-font/go.mod @@ -7,4 +7,23 @@ require ( github.com/oakmound/oak/v3 v3.0.0-alpha.1 ) +require ( + dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 // indirect + github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect + github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect + github.com/disintegration/gift v1.2.1 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/hajimehoshi/go-mp3 v0.3.2 // indirect + github.com/oakmound/alsa v0.0.2 // indirect + github.com/oakmound/libudev v0.2.1 // indirect + github.com/oakmound/w32 v2.1.0+incompatible // indirect + github.com/oov/directsound-go v0.0.0-20141101201356-e53e59c700bf // indirect + golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 // indirect + golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect + golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 // indirect + golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect + golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb // indirect +) + replace github.com/oakmound/oak/v3 => ../.. diff --git a/examples/fallback-font/go.sum b/examples/fallback-font/go.sum index fe44f8be..2e0baca0 100644 --- a/examples/fallback-font/go.sum +++ b/examples/fallback-font/go.sum @@ -5,21 +5,18 @@ github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA= github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= -github.com/disintegration/gift v1.2.0 h1:VMQeei2F+ZtsHjMgP6Sdt1kFjRhs2lGz8ljEOPeIR50= -github.com/disintegration/gift v1.2.0/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= -github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d/go.mod h1:CHkHWWZ4kbGY6jEy1+qlitDaCtRgNvCOQdakj/1Yl/Q= -github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1/go.mod h1:frG94byMNy+1CgGrQ25dZ+17tf98EN+OYBQL4Zh612M= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= github.com/flopp/go-findfont v0.0.0-20201114153133-e7393a00c15b h1:/wqXgpZNTP8qV1dPEApjJXlDQd5N/F9U/WEvy5SawUI= github.com/flopp/go-findfont v0.0.0-20201114153133-e7393a00c15b/go.mod h1:wKKxRDjD024Rh7VMwoU90i6ikQRCr+JTHB5n4Ejkqvw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 h1:TL70PMkdPCt9cRhKTqsm+giRpgrd0IGEj763nNr2VFY= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKcokao= -github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= +github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s= +github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jfreymuth/pulse v0.1.0 h1:KN38/9hoF9PJvP5DpEVhMRKNuwnJUonc8c9ARorRXUA= -github.com/jfreymuth/pulse v0.1.0/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= github.com/oakmound/alsa v0.0.2 h1:JbOUckkJqVvhABth7qy2JgAjqsWuBPggyoYOk1L6eK0= github.com/oakmound/alsa v0.0.2/go.mod h1:wx+ehwqFnNL7foTwxxu2bKQlaUmD2oXd4ka1UBSgWAo= github.com/oakmound/libudev v0.2.1 h1:gaXuw7Pbt3RSRxbUakAjl0dSW6Wo3TZWpwS5aMq8+EA= @@ -33,16 +30,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 h1:ywNGLBFk8tKaiu+GYZeoXWzrFoJ/a1LHYKy1lb3R9cM= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 h1:ZDL7hDvJEQEcHVkoZawKmRUgbqn1pOIzb8EinBh5csU= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -59,8 +57,8 @@ golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/examples/flappy-bird/README.md b/examples/flappy-bird/README.md new file mode 100644 index 00000000..f837af86 --- /dev/null +++ b/examples/flappy-bird/README.md @@ -0,0 +1,4 @@ +# Flappy Bird +A simple implementation of Flappy Bird + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/multi-window/README.md b/examples/multi-window/README.md new file mode 100644 index 00000000..b3b932f5 --- /dev/null +++ b/examples/multi-window/README.md @@ -0,0 +1,4 @@ +# Multi Window +An example of managing multiple windows. + +![text](./example.PNG) \ No newline at end of file diff --git a/examples/piano/main.go b/examples/piano/main.go index ef045b8e..e57b540c 100644 --- a/examples/piano/main.go +++ b/examples/piano/main.go @@ -230,6 +230,8 @@ func main() { key.P: src.PulsePCM(2), } for kc, synfn := range codeKinds { + synfn := synfn + kc := kc event.GlobalBind(ctx, key.Down(kc), func(ev key.Event) event.Response { if ev.Modifiers&key.ModShift == key.ModShift { synthKind = synfn diff --git a/examples/screenopts/main.go b/examples/screenopts/main.go index d0dd3bde..5ce3e691 100644 --- a/examples/screenopts/main.go +++ b/examples/screenopts/main.go @@ -2,6 +2,10 @@ package main import ( "fmt" + "image" + "image/color" + "math/rand" + "strconv" oak "github.com/oakmound/oak/v3" "github.com/oakmound/oak/v3/event" @@ -10,20 +14,51 @@ import ( "github.com/oakmound/oak/v3/scene" ) -const ( - borderlessAtStart = false - fullscreenAtStart = false -) - func main() { + const ( + borderlessAtStart = false + fullscreenAtStart = false + topMostAtStart = false + ) + oak.AddScene("demo", scene.Scene{Start: func(ctx *scene.Context) { - txt := render.NewText("Press F to toggle fullscreen. Press B to toggle borderless.", 50, 50) + txt := render.NewText("Press F to toggle fullscreen. Press B to toggle borderless. Press T to toggle topmost", 50, 50) render.Draw(txt) + line2 := render.NewText("Press Q to change window title. Press C to change the window icon", 50, 70) + render.Draw(line2) borderless := borderlessAtStart fullscreen := fullscreenAtStart - event.GlobalBind(ctx, key.Down(key.W), func(k key.Event) event.Response { + topMost := topMostAtStart + + event.GlobalBind(ctx, key.Down(key.C), func(k key.Event) event.Response { + colors := []color.RGBA{ + {255, 255, 0, 255}, + {255, 0, 255, 255}, + {0, 255, 255, 255}, + {255, 0, 0, 255}, + {0, 255, 0, 255}, + {0, 0, 255, 255}, + } + c := colors[rand.Intn(len(colors))] + rgba := image.NewRGBA(image.Rect(0, 0, 32, 32)) + for x := 0; x < 32; x++ { + for y := 0; y < 32; y++ { + rgba.SetRGBA(x, y, c) + } + } + + // TODO: this is silly + err := ctx.Window.(*oak.Window).Window.SetIcon(rgba) + if err != nil { + fmt.Println(err) + } + return 0 + }) + + event.GlobalBind(ctx, key.Down(key.F), func(k key.Event) event.Response { fullscreen = !fullscreen + fmt.Println("Setting fullscreen:", fullscreen) err := oak.SetFullScreen(fullscreen) if err != nil { fullscreen = !fullscreen @@ -33,6 +68,7 @@ func main() { }) event.GlobalBind(ctx, key.Down(key.B), func(k key.Event) event.Response { borderless = !borderless + fmt.Println("Setting borderless:", borderless) err := oak.SetBorderless(borderless) if err != nil { borderless = !borderless @@ -40,10 +76,27 @@ func main() { } return 0 }) + event.GlobalBind(ctx, key.Down(key.T), func(k key.Event) event.Response { + topMost = !topMost + fmt.Println("Setting top most:", topMost) + err := oak.SetTopMost(topMost) + if err != nil { + topMost = !topMost + fmt.Println(err) + } + return 0 + }) + titleCt := 0 + event.GlobalBind(ctx, key.Down(key.Q), func(k key.Event) event.Response { + titleCt++ + oak.SetTitle("window title " + strconv.Itoa(titleCt)) + return 0 + }) }}) oak.Init("demo", func(c oak.Config) (oak.Config, error) { + c.TopMost = topMostAtStart // Both cannot be true at once! c.Borderless = borderlessAtStart c.Fullscreen = fullscreenAtStart diff --git a/examples/svg/go.mod b/examples/svg/go.mod index 0f091b98..119d0686 100644 --- a/examples/svg/go.mod +++ b/examples/svg/go.mod @@ -12,20 +12,20 @@ require ( dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 // indirect github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc // indirect github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 // indirect - github.com/disintegration/gift v1.2.0 // indirect - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb // indirect + github.com/disintegration/gift v1.2.1 // indirect + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/hajimehoshi/go-mp3 v0.3.1 // indirect + github.com/hajimehoshi/go-mp3 v0.3.2 // indirect github.com/oakmound/alsa v0.0.2 // indirect github.com/oakmound/libudev v0.2.1 // indirect github.com/oakmound/w32 v2.1.0+incompatible // indirect github.com/oov/directsound-go v0.0.0-20141101201356-e53e59c700bf // indirect - golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect - golang.org/x/image v0.0.0-20210504121937-7319ad40d33e // indirect - golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 // indirect + golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 // indirect + golang.org/x/image v0.0.0-20220321031419-a8550c1d254a // indirect + golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 // indirect golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d // indirect golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect - golang.org/x/sys v0.0.0-20220111092808-5a964db01320 // indirect + golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb // indirect golang.org/x/text v0.3.6 // indirect ) diff --git a/examples/svg/go.sum b/examples/svg/go.sum index 8ec8315c..987e79b7 100644 --- a/examples/svg/go.sum +++ b/examples/svg/go.sum @@ -5,14 +5,14 @@ github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA= github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= -github.com/disintegration/gift v1.2.0 h1:VMQeei2F+ZtsHjMgP6Sdt1kFjRhs2lGz8ljEOPeIR50= -github.com/disintegration/gift v1.2.0/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 h1:TL70PMkdPCt9cRhKTqsm+giRpgrd0IGEj763nNr2VFY= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKcokao= -github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= +github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s= +github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jfreymuth/pulse v0.1.0 h1:KN38/9hoF9PJvP5DpEVhMRKNuwnJUonc8c9ARorRXUA= github.com/oakmound/alsa v0.0.2 h1:JbOUckkJqVvhABth7qy2JgAjqsWuBPggyoYOk1L6eK0= @@ -32,16 +32,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 h1:ywNGLBFk8tKaiu+GYZeoXWzrFoJ/a1LHYKy1lb3R9cM= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20210504121937-7319ad40d33e h1:PzJMNfFQx+QO9hrC1GwZ4BoPGeNGhfeQEgcQFArEjPk= -golang.org/x/image v0.0.0-20210504121937-7319ad40d33e/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 h1:ZDL7hDvJEQEcHVkoZawKmRUgbqn1pOIzb8EinBh5csU= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -59,8 +60,8 @@ golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= diff --git a/examples/text-demo-2/README.md b/examples/text-demo-2/README.md deleted file mode 100644 index aec48a90..00000000 --- a/examples/text-demo-2/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Text Creation -Continually draw text on screen with a random color - -![text](./example.gif) \ No newline at end of file diff --git a/examples/text-demos/README.md b/examples/text-demos/README.md new file mode 100644 index 00000000..1b8e2514 --- /dev/null +++ b/examples/text-demos/README.md @@ -0,0 +1,2 @@ +# Text Creation +Examples of drawing text and changing their text / content while on the screen. \ No newline at end of file diff --git a/examples/text-demo-1/README.md b/examples/text-demos/color-changing-text-demo/README.md similarity index 80% rename from examples/text-demo-1/README.md rename to examples/text-demos/color-changing-text-demo/README.md index 14f8d568..a79cecf3 100644 --- a/examples/text-demo-1/README.md +++ b/examples/text-demos/color-changing-text-demo/README.md @@ -1,4 +1,2 @@ # Text Creation Draw text and update some of it to change its color and display the rgb value - -![text](./example.gif) \ No newline at end of file diff --git a/examples/text-demo-1/assets/font/luxisbi.ttf b/examples/text-demos/color-changing-text-demo/assets/font/luxisbi.ttf similarity index 100% rename from examples/text-demo-1/assets/font/luxisbi.ttf rename to examples/text-demos/color-changing-text-demo/assets/font/luxisbi.ttf diff --git a/examples/text-demo-1/main.go b/examples/text-demos/color-changing-text-demo/main.go similarity index 100% rename from examples/text-demo-1/main.go rename to examples/text-demos/color-changing-text-demo/main.go diff --git a/examples/text-demos/continual-text-demo/README.md b/examples/text-demos/continual-text-demo/README.md new file mode 100644 index 00000000..3ad111e2 --- /dev/null +++ b/examples/text-demos/continual-text-demo/README.md @@ -0,0 +1,2 @@ +# Text Creation +Continually draw text on screen with a random color \ No newline at end of file diff --git a/examples/text-demo-2/main.go b/examples/text-demos/continual-text-demo/main.go similarity index 100% rename from examples/text-demo-2/main.go rename to examples/text-demos/continual-text-demo/main.go diff --git a/go.mod b/go.mod index f8b12964..3cbf9b85 100644 --- a/go.mod +++ b/go.mod @@ -6,23 +6,23 @@ require ( dmitri.shuralyov.com/gpu/mtl v0.0.0-20201218220906-28db891af037 github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 - github.com/disintegration/gift v1.2.0 + github.com/disintegration/gift v1.2.1 github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1 - github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb + github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 - github.com/hajimehoshi/go-mp3 v0.3.1 + github.com/hajimehoshi/go-mp3 v0.3.2 github.com/jfreymuth/pulse v0.1.0 github.com/oakmound/alsa v0.0.2 github.com/oakmound/libudev v0.2.1 github.com/oakmound/w32 v2.1.0+incompatible github.com/oov/directsound-go v0.0.0-20141101201356-e53e59c700bf - golang.org/x/image v0.0.0-20201208152932-35266b937fa6 - golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 + golang.org/x/image v0.0.0-20220321031419-a8550c1d254a + golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/sys v0.0.0-20220111092808-5a964db01320 + golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb ) require ( github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d // indirect - golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect + golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 // indirect ) diff --git a/go.sum b/go.sum index f8cf8ae7..d79d3828 100644 --- a/go.sum +++ b/go.sum @@ -5,18 +5,18 @@ github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc h1:7D+Bh06CRPCJO3gr github.com/BurntSushi/xgb v0.0.0-20210121224620-deaf085860bc/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046 h1:O/r2Sj+8QcMF7V5IcmiE2sMFV2q3J47BEirxbXJAdzA= github.com/BurntSushi/xgbutil v0.0.0-20190907113008-ad855c713046/go.mod h1:uw9h2sd4WWHOPdJ13MQpwK5qYWKYDumDqxWWIknEQ+k= -github.com/disintegration/gift v1.2.0 h1:VMQeei2F+ZtsHjMgP6Sdt1kFjRhs2lGz8ljEOPeIR50= -github.com/disintegration/gift v1.2.0/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= +github.com/disintegration/gift v1.2.1 h1:Y005a1X4Z7Uc+0gLpSAsKhWi4qLtsdEcMIbbdvdZ6pc= +github.com/disintegration/gift v1.2.1/go.mod h1:Jh2i7f7Q2BM7Ezno3PhfezbR1xpUg9dUg3/RlKGr4HI= github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d h1:HB5J9+f1xpkYLgWQ/RqEcbp3SEufyOIMYLoyKNKiG7E= github.com/eaburns/bit v0.0.0-20131029213740-7bd5cd37375d/go.mod h1:CHkHWWZ4kbGY6jEy1+qlitDaCtRgNvCOQdakj/1Yl/Q= github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1 h1:wl/ggSfTHqAy46hyzw1IlrUYwjqmXYvbJyPdH3rT7YE= github.com/eaburns/flac v0.0.0-20171003200620-9a6fb92396d1/go.mod h1:frG94byMNy+1CgGrQ25dZ+17tf98EN+OYBQL4Zh612M= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb h1:T6gaWBvRzJjuOrdCtg8fXXjKai2xSDqWTcKFUPuw8Tw= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20210410170116-ea3d685f79fb/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958 h1:TL70PMkdPCt9cRhKTqsm+giRpgrd0IGEj763nNr2VFY= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20220320163800-277f93cfa958/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= -github.com/hajimehoshi/go-mp3 v0.3.1 h1:pn/SKU1+/rfK8KaZXdGEC2G/KCB2aLRjbTCrwKcokao= -github.com/hajimehoshi/go-mp3 v0.3.1/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= +github.com/hajimehoshi/go-mp3 v0.3.2 h1:xSYNE2F3lxtOu9BRjCWHHceg7S91IHfXfXp5+LYQI7s= +github.com/hajimehoshi/go-mp3 v0.3.2/go.mod h1:qMJj/CSDxx6CGHiZeCgbiq2DSUkbK0UbtXShQcnfyMM= github.com/hajimehoshi/oto v0.6.1/go.mod h1:0QXGEkbuJRohbJaxr7ZQSxnju7hEhseiPx2hrh6raOI= github.com/jfreymuth/pulse v0.1.0 h1:KN38/9hoF9PJvP5DpEVhMRKNuwnJUonc8c9ARorRXUA= github.com/jfreymuth/pulse v0.1.0/go.mod h1:cpYspI6YljhkUf1WLXLLDmeaaPFc3CnGLjDZf9dZ4no= @@ -33,16 +33,17 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4 h1:ywNGLBFk8tKaiu+GYZeoXWzrFoJ/a1LHYKy1lb3R9cM= +golang.org/x/exp/shiny v0.0.0-20220407100705-7b9b53b0aca4/go.mod h1:VjAR7z0ngyATZTELrBSkxOOHhhlnVUxDye4mcjx5h/8= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= -golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a h1:LnH9RNcpPv5Kzi15lXg42lYMPUf0x8CuPv1YnvBWZAg= +golang.org/x/image v0.0.0-20220321031419-a8550c1d254a/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190415191353-3e0bab5405d6/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816 h1:jhDgkcu3yQ4tasBZ+1YwDmK7eFmuVf1w1k+NGGGxfmE= -golang.org/x/mobile v0.0.0-20220112015953-858099ff7816/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3 h1:ZDL7hDvJEQEcHVkoZawKmRUgbqn1pOIzb8EinBh5csU= +golang.org/x/mobile v0.0.0-20220325161704-447654d348e3/go.mod h1:pe2sM7Uk+2Su1y7u/6Z8KJ24D7lepUjFZbhFOrmDfuQ= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -59,8 +60,8 @@ golang.org/x/sys v0.0.0-20190429190828-d89cdac9e872/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY= -golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= +golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/inputLoop.go b/inputLoop.go index 9ad0bfe1..d3654b96 100644 --- a/inputLoop.go +++ b/inputLoop.go @@ -27,7 +27,7 @@ var ( func (w *Window) inputLoop() { for { - switch e := w.windowControl.NextEvent().(type) { + switch e := w.Window.NextEvent().(type) { // We only currently respond to death lifecycle events. case lifecycle.Event: switch e.To { diff --git a/inputLoop_test.go b/inputLoop_test.go index cc2093f4..8ef6703b 100644 --- a/inputLoop_test.go +++ b/inputLoop_test.go @@ -12,15 +12,15 @@ import ( func TestInputLoop(t *testing.T) { c1 := blankScene(t) c1.SetLogicHandler(event.NewBus(nil)) - c1.windowControl.Send(okey.Event{ + c1.Window.Send(okey.Event{ Direction: key.DirPress, Code: key.Code0, }) - c1.windowControl.Send(okey.Event{ + c1.Window.Send(okey.Event{ Direction: key.DirNone, Code: key.Code0, }) - c1.windowControl.Send(okey.Event{ + c1.Window.Send(okey.Event{ Direction: key.DirRelease, Code: key.Code0, }) diff --git a/inputTracker.go b/inputTracker.go index 067b2455..d5080ded 100644 --- a/inputTracker.go +++ b/inputTracker.go @@ -54,11 +54,8 @@ type joyHandler struct { handler event.Handler } -func (jh *joyHandler) Trigger(eventID event.UnsafeEventID, data interface{}) chan struct{} { - jh.handler.Trigger(trackingJoystickChange.UnsafeEventID, struct{}{}) - ch := make(chan struct{}) - close(ch) - return ch +func (jh *joyHandler) Trigger(eventID event.UnsafeEventID, data interface{}) <-chan struct{} { + return event.TriggerOn(jh.handler, trackingJoystickChange, struct{}{}) } func trackJoystickChanges(handler event.Handler) { diff --git a/joystick/joystick.go b/joystick/joystick.go index 1500dc15..e2f12513 100644 --- a/joystick/joystick.go +++ b/joystick/joystick.go @@ -52,7 +52,7 @@ func Init() error { // A Triggerer can either be an event bus or event CID, allowing // joystick triggers to be listened to globally or sent to particular entities. type Triggerer interface { - Trigger(eventID event.UnsafeEventID, data interface{}) chan struct{} + Trigger(eventID event.UnsafeEventID, data interface{}) <-chan struct{} } // A Joystick represents a (usually) physical controller connected to the machine. diff --git a/lifecycle.go b/lifecycle.go index 49511503..7df9106a 100644 --- a/lifecycle.go +++ b/lifecycle.go @@ -23,8 +23,8 @@ func (w *Window) lifecycleLoop(s screen.Screen) { // Apply that factor to the scale err = w.newWindow( - int32(w.config.Screen.X), - int32(w.config.Screen.Y), + w.config.Screen.X, + w.config.Screen.Y, int(float64(w.ScreenWidth)*w.config.Screen.Scale), int(float64(w.ScreenHeight)*w.config.Screen.Scale), ) @@ -37,7 +37,7 @@ func (w *Window) lifecycleLoop(s screen.Screen) { go w.inputLoop() <-w.quitCh - w.windowControl.Release() + w.Window.Release() } // Quit sends a signal to the window to close itself, closing the window and @@ -45,24 +45,24 @@ func (w *Window) lifecycleLoop(s screen.Screen) { // it must not be called again. func (w *Window) Quit() { // We could have hit this before the window was created - if w.windowControl == nil { + if w.Window == nil { close(w.quitCh) } else { - w.windowControl.Send(lifecycle.Event{To: lifecycle.StageDead}) + w.Window.Send(lifecycle.Event{To: lifecycle.StageDead}) } if w.config.EnableDebugConsole { debugstream.DefaultCommands.RemoveScope(w.ControllerID) } } -func (w *Window) newWindow(x, y int32, width, height int) error { +func (w *Window) newWindow(x, y, width, height int) error { // The window controller handles incoming hardware or platform events and // publishes image data to the screen. wC, err := w.windowController(w.screenControl, x, y, width, height) if err != nil { return err } - w.windowControl = wC + w.Window = wC return w.ChangeWindow(width, height) } @@ -81,7 +81,7 @@ func (w *Window) ChangeWindow(width, height int) error { buff, err := w.screenControl.NewImage(image.Point{width, height}) if err == nil { draw.Draw(buff.RGBA(), buff.Bounds(), w.bkgFn(), zeroPoint, draw.Src) - w.windowControl.Upload(zeroPoint, buff, buff.Bounds()) + w.Window.Upload(zeroPoint, buff, buff.Bounds()) } else { return err } diff --git a/scene/context.go b/scene/context.go index 79e28aee..3ba0a48d 100644 --- a/scene/context.go +++ b/scene/context.go @@ -7,7 +7,6 @@ import ( "github.com/oakmound/oak/v3/event" "github.com/oakmound/oak/v3/key" "github.com/oakmound/oak/v3/render" - "github.com/oakmound/oak/v3/window" ) // A Context contains all transient engine components used in a scene, including @@ -24,7 +23,7 @@ type Context struct { event.Handler PreviousScene string SceneInput interface{} - Window window.Window + Window Window DrawStack *render.DrawStack diff --git a/scene/context_desktop.go b/scene/context_desktop.go new file mode 100644 index 00000000..6f624c35 --- /dev/null +++ b/scene/context_desktop.go @@ -0,0 +1,8 @@ +//go:build !js && !android && !nooswindow +// +build !js,!android,!nooswindow + +package scene + +import "github.com/oakmound/oak/v3/window" + +type Window = window.Window diff --git a/scene/context_other.go b/scene/context_other.go new file mode 100644 index 00000000..2572c27a --- /dev/null +++ b/scene/context_other.go @@ -0,0 +1,8 @@ +//go:build js || android || nooswindow +// +build js android nooswindow + +package scene + +import "github.com/oakmound/oak/v3/window" + +type Window = window.App diff --git a/screenOpts.go b/screenOpts.go deleted file mode 100644 index 9e76788e..00000000 --- a/screenOpts.go +++ /dev/null @@ -1,135 +0,0 @@ -package oak - -import "github.com/oakmound/oak/v3/oakerr" - -type fullScreenable interface { - SetFullScreen(bool) error -} - -// SetFullScreen attempts to set the local oak window to be full screen. -// If the window does not support this functionality, it will report as such. -func (w *Window) SetFullScreen(on bool) error { - if fs, ok := w.windowControl.(fullScreenable); ok { - return fs.SetFullScreen(on) - } - return oakerr.UnsupportedPlatform{ - Operation: "SetFullScreen", - } -} - -type movableWindow interface { - MoveWindow(x, y, w, h int32) error -} - -// MoveWindow sets the position of a window to be x,y and it's dimensions to w,h -// If the window does not support being positioned, it will report as such. -func (w *Window) MoveWindow(x, y, wd, h int) error { - if mw, ok := w.windowControl.(movableWindow); ok { - return mw.MoveWindow(int32(x), int32(y), int32(wd), int32(h)) - } - return oakerr.UnsupportedPlatform{ - Operation: "MoveWindow", - } -} - -type borderlesser interface { - SetBorderless(bool) error -} - -// SetBorderless attempts to set the local oak window to have no border. -// If the window does not support this functionaltiy, it will report as such. -func (w *Window) SetBorderless(on bool) error { - if bs, ok := w.windowControl.(borderlesser); ok { - return bs.SetBorderless(on) - } - return oakerr.UnsupportedPlatform{ - Operation: "SetBorderless", - } -} - -type topMoster interface { - SetTopMost(bool) error -} - -// SetTopMost attempts to set the local oak window to stay on top of other windows. -// If the window does not support this functionality, it will report as such. -func (w *Window) SetTopMost(on bool) error { - if tm, ok := w.windowControl.(topMoster); ok { - return tm.SetTopMost(on) - } - return oakerr.UnsupportedPlatform{ - Operation: "SetTopMost", - } -} - -type titler interface { - SetTitle(string) error -} - -// SetTitle sets this window's title. -func (w *Window) SetTitle(title string) error { - if t, ok := w.windowControl.(titler); ok { - return t.SetTitle(title) - } - return oakerr.UnsupportedPlatform{ - Operation: "SetTitle", - } -} - -type trayIconer interface { - SetTrayIcon(string) error -} - -// SetTrayIcon sets a application tray icon for this program. -func (w *Window) SetTrayIcon(icon string) error { - if t, ok := w.windowControl.(trayIconer); ok { - return t.SetTrayIcon(icon) - } - return oakerr.UnsupportedPlatform{ - Operation: "SetTrayIcon", - } -} - -type trayNotifier interface { - ShowNotification(title, msg string, icon bool) error -} - -// ShowNotification shows a text notification, optionally using a previously set -// tray icon. -func (w *Window) ShowNotification(title, msg string, icon bool) error { - if t, ok := w.windowControl.(trayNotifier); ok { - return t.ShowNotification(title, msg, icon) - } - return oakerr.UnsupportedPlatform{ - Operation: "ShowNotification", - } -} - -type cursorHider interface { - HideCursor() error -} - -// HideCursor disables showing the cursor when it is over this window. -func (w *Window) HideCursor() error { - if t, ok := w.windowControl.(cursorHider); ok { - return t.HideCursor() - } - return oakerr.UnsupportedPlatform{ - Operation: "HideCursor", - } -} - -type getCursorPositioner interface { - GetCursorPosition() (x, y float64) -} - -// GetCursorPosition returns the cusor position relative to the top left corner of this window. -func (w *Window) GetCursorPosition() (x, y float64, err error) { - if wp, ok := w.windowControl.(getCursorPositioner); ok { - x, y := wp.GetCursorPosition() - return x, y, nil - } - return 0, 0, oakerr.UnsupportedPlatform{ - Operation: "GetCursorPosition", - } -} diff --git a/screenOpts_test.go b/screenOpts_test.go deleted file mode 100644 index be8dca28..00000000 --- a/screenOpts_test.go +++ /dev/null @@ -1,22 +0,0 @@ -package oak - -import "testing" - -func TestScreenOpts(t *testing.T) { - // What these functions do (and error presence) depends on the operating - // system / build tags, which we can't configure at test time without - // making a new driver just for this test. - c1 := blankScene(t) - c1.SetFullScreen(true) - c1.SetFullScreen(false) - c1.MoveWindow(10, 10, 20, 20) - c1.SetBorderless(true) - c1.SetBorderless(false) - c1.SetTopMost(true) - c1.SetTopMost(false) - c1.SetTitle("testScreenOpts") - c1.SetTrayIcon("icon.ico") - c1.ShowNotification("testnotification", "testmessge", true) - c1.ShowNotification("testnotification", "testmessge", false) - c1.HideCursor() -} diff --git a/entities/x/shake/shake.go b/shake/shake.go similarity index 100% rename from entities/x/shake/shake.go rename to shake/shake.go diff --git a/shiny/driver/androiddriver/image.go b/shiny/driver/androiddriver/image.go index e5d58786..c8fa2134 100644 --- a/shiny/driver/androiddriver/image.go +++ b/shiny/driver/androiddriver/image.go @@ -11,7 +11,7 @@ import ( ) type imageImpl struct { - screen *screenImpl + screen *Screen size image.Point img *glutil.Image deadLock sync.Mutex diff --git a/shiny/driver/androiddriver/main.go b/shiny/driver/androiddriver/main.go index 3f2e8aaf..20b9a5d9 100644 --- a/shiny/driver/androiddriver/main.go +++ b/shiny/driver/androiddriver/main.go @@ -20,7 +20,7 @@ import ( func Main(f func(screen.Screen)) { app.Main(func(a app.App) { - s := &screenImpl{ + s := &Screen{ app: a, } screenOnce := sync.Once{} diff --git a/shiny/driver/androiddriver/screen.go b/shiny/driver/androiddriver/screen.go index 8ab94970..2e80dc5d 100644 --- a/shiny/driver/androiddriver/screen.go +++ b/shiny/driver/androiddriver/screen.go @@ -5,12 +5,10 @@ package androiddriver import ( "image" - "image/color" "github.com/oakmound/oak/v3/shiny/driver/internal/event" "github.com/oakmound/oak/v3/shiny/screen" "golang.org/x/image/draw" - "golang.org/x/image/math/f64" "golang.org/x/mobile/app" "golang.org/x/mobile/event/size" "golang.org/x/mobile/exp/gl/glutil" @@ -18,9 +16,9 @@ import ( "golang.org/x/mobile/gl" ) -var _ screen.Screen = &screenImpl{} +var _ screen.Screen = &Screen{} -type screenImpl struct { +type Screen struct { event.Deque app app.App @@ -32,7 +30,7 @@ type screenImpl struct { lastSz size.Event } -func (s *screenImpl) NewImage(size image.Point) (screen.Image, error) { +func (s *Screen) NewImage(size image.Point) (screen.Image, error) { img := &imageImpl{ screen: s, size: size, @@ -42,28 +40,22 @@ func (s *screenImpl) NewImage(size image.Point) (screen.Image, error) { return img, nil } -func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) { +func (s *Screen) NewTexture(size image.Point) (screen.Texture, error) { return NewTexture(s, size), nil } -var _ screen.Window = &screenImpl{} +var _ screen.Window = &Screen{} -func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, error) { +func (s *Screen) NewWindow(opts screen.WindowGenerator) (screen.Window, error) { // android does not support multiple windows return s, nil } -func (w *screenImpl) Publish() screen.PublishResult { - return screen.PublishResult{} -} +func (w *Screen) Publish() {} -func (w *screenImpl) Release() {} -func (w *screenImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) {} -func (w *screenImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {} -func (w *screenImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (w *screenImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) {} -func (w *screenImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (w *screenImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Screen) Release() {} +func (w *Screen) Upload(dp image.Point, src screen.Image, sr image.Rectangle) {} +func (w *Screen) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { t := src.(*textureImpl) t.img.img.Draw( w.lastSz, diff --git a/shiny/driver/androiddriver/texture.go b/shiny/driver/androiddriver/texture.go index 25748d6c..10ecc4d9 100644 --- a/shiny/driver/androiddriver/texture.go +++ b/shiny/driver/androiddriver/texture.go @@ -12,12 +12,12 @@ import ( ) type textureImpl struct { - screen *screenImpl + screen *Screen size image.Point img *imageImpl } -func NewTexture(s *screenImpl, size image.Point) *textureImpl { +func NewTexture(s *Screen, size image.Point) *textureImpl { return &textureImpl{ screen: s, size: size, diff --git a/shiny/driver/driver_android.go b/shiny/driver/driver_android.go index 1645acc5..706e0f88 100644 --- a/shiny/driver/driver_android.go +++ b/shiny/driver/driver_android.go @@ -22,3 +22,5 @@ func monitorSize() (int, int) { // GetSystemMetrics syscall return 0, 0 } + +type Window = androiddriver.Screen diff --git a/shiny/driver/driver_darwin.go b/shiny/driver/driver_darwin.go deleted file mode 100644 index b561f207..00000000 --- a/shiny/driver/driver_darwin.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build darwin && darwingl && !nooswindow && !android -// +build darwin,darwingl,!nooswindow,!android - -package driver - -import ( - "bytes" - "fmt" - "os/exec" - "regexp" - "strconv" - - "github.com/oakmound/oak/v3/shiny/driver/gldriver" - "github.com/oakmound/oak/v3/shiny/screen" -) - -func main(f func(screen.Screen)) { - gldriver.Main(f) -} - -var ( - sysProfRegex = regexp.MustCompile(`Resolution: (\d)* x (\d)*`) -) - -func monitorSize() (int, int) { - out, err := exec.Command("system_profiler", "SPDisplaysDataType").CombinedOutput() - if err != nil { - return 0, 0 - } - found := sysProfRegex.FindAll(out, -1) - if len(found) == 0 { - return 0, 0 - } - if len(found) != 1 { - fmt.Println("Found multiple screens", len(found)) - } - first := found[0] - first = bytes.TrimPrefix(first, []byte("Resolution: ")) - dims := bytes.Split(first, []byte(" x ")) - if len(dims) != 2 { - return 0, 0 - } - w, err := strconv.Atoi(string(dims[0])) - if err != nil { - return 0, 0 - } - h, err := strconv.Atoi(string(dims[1])) - if err != nil { - return 0, 0 - } - return w, h -} diff --git a/shiny/driver/driver_fallback.go b/shiny/driver/driver_fallback.go index c086af73..d44ed868 100644 --- a/shiny/driver/driver_fallback.go +++ b/shiny/driver/driver_fallback.go @@ -21,3 +21,5 @@ func main(f func(screen.Screen)) { func monitorSize() (int, int) { return 0, 0 } + +type Window = struct{} \ No newline at end of file diff --git a/shiny/driver/driver_js.go b/shiny/driver/driver_js.go index 53e73e30..2e661e61 100644 --- a/shiny/driver/driver_js.go +++ b/shiny/driver/driver_js.go @@ -1,5 +1,5 @@ //go:build js && !nooswindow && !windows && !darwin && !linux -// +build js,!nooswindow,!windows,!darwin,!linux,!android +// +build js,!nooswindow,!windows,!darwin,!linux package driver @@ -15,3 +15,5 @@ func main(f func(screen.Screen)) { func monitorSize() (int, int) { return 0, 0 } + +type Window = jsdriver.Window diff --git a/shiny/driver/driver_noop.go b/shiny/driver/driver_noop.go index 38516784..1d5d1e4a 100644 --- a/shiny/driver/driver_noop.go +++ b/shiny/driver/driver_noop.go @@ -1,3 +1,4 @@ +//go:build nooswindow // +build nooswindow package driver @@ -14,3 +15,5 @@ func main(f func(screen.Screen)) { func monitorSize() (int, int) { return 0, 0 } + +type Window = noop.Window diff --git a/shiny/driver/driver_windows.go b/shiny/driver/driver_windows.go index 15cd3e5e..19d1ad27 100644 --- a/shiny/driver/driver_windows.go +++ b/shiny/driver/driver_windows.go @@ -20,3 +20,5 @@ func monitorSize() (int, int) { // GetSystemMetrics syscall return 0, 0 } + +type Window = windriver.Window diff --git a/shiny/driver/driver_x11.go b/shiny/driver/driver_x11.go index c2169238..87320635 100644 --- a/shiny/driver/driver_x11.go +++ b/shiny/driver/driver_x11.go @@ -19,3 +19,5 @@ func main(f func(screen.Screen)) { func monitorSize() (int, int) { return 0, 0 } + +type Window = x11driver.Window diff --git a/shiny/driver/gldriver/buffer.go b/shiny/driver/gldriver/buffer.go deleted file mode 100644 index 38df52a0..00000000 --- a/shiny/driver/gldriver/buffer.go +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gldriver - -import "image" - -type bufferImpl struct { - // buf should always be equal to (i.e. the same ptr, len, cap as) rgba.Pix. - // It is a separate, redundant field in order to detect modifications to - // the rgba field that are invalid as per the screen.Image documentation. - buf []byte - rgba image.RGBA - size image.Point -} - -func (b *bufferImpl) Release() {} -func (b *bufferImpl) Size() image.Point { return b.size } -func (b *bufferImpl) Bounds() image.Rectangle { return image.Rectangle{Max: b.size} } -func (b *bufferImpl) RGBA() *image.RGBA { return &b.rgba } - -func (b *bufferImpl) preUpload() { - // Check that the program hasn't tried to modify the rgba field via the - // pointer returned by the bufferImpl.RGBA method. This check doesn't catch - // 100% of all cases; it simply tries to detect some invalid uses of a - // screen.Image such as: - // *buffer.RGBA() = anotherImageRGBA - if len(b.buf) != 0 && len(b.rgba.Pix) != 0 && &b.buf[0] != &b.rgba.Pix[0] { - panic("gldriver: invalid Buffer.RGBA modification") - } -} diff --git a/shiny/driver/gldriver/cocoa.go b/shiny/driver/gldriver/cocoa.go deleted file mode 100644 index fb7e82f3..00000000 --- a/shiny/driver/gldriver/cocoa.go +++ /dev/null @@ -1,676 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin -// +build 386 amd64 -// +build !ios - -package gldriver - -/* -#cgo CFLAGS: -x objective-c -DGL_SILENCE_DEPRECATION -#cgo LDFLAGS: -framework Cocoa -framework OpenGL -#include -#import // for HIToolbox/Events.h -#import -#include -#include -#include - -void startDriver(); -void stopDriver(); -void makeCurrentContext(uintptr_t ctx); -void flushContext(uintptr_t ctx); -uintptr_t doNewWindow(int width, int height, char* title); -void doShowWindow(uintptr_t id); -void doCloseWindow(uintptr_t id); -uint64_t threadID(); -*/ -import "C" - -import ( - "errors" - "fmt" - "log" - "runtime" - "unsafe" - - "github.com/oakmound/oak/v3/shiny/driver/internal/lifecycler" - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/mobile/event/key" - "golang.org/x/mobile/event/mouse" - "golang.org/x/mobile/event/paint" - "golang.org/x/mobile/event/size" - "golang.org/x/mobile/geom" - "golang.org/x/mobile/gl" -) - -const useLifecycler = true - -// TODO: change this to true, after manual testing on OS X. -const handleSizeEventsAtChannelReceive = false - -var initThreadID C.uint64_t - -func init() { - // Lock the goroutine responsible for initialization to an OS thread. - // This means the goroutine running main (and calling startDriver below) - // is locked to the OS thread that started the program. This is - // necessary for the correct delivery of Cocoa events to the process. - // - // A discussion on this topic: - // https://groups.google.com/forum/#!msg/golang-nuts/IiWZ2hUuLDA/SNKYYZBelsYJ - runtime.LockOSThread() - initThreadID = C.threadID() -} - -func newWindow(opts screen.WindowGenerator) (uintptr, error) { - width, height := optsSize(opts) - - title := C.CString(opts.Title) - defer C.free(unsafe.Pointer(title)) - - return uintptr(C.doNewWindow(C.int(width), C.int(height), title)), nil -} - -func moveWindow(w *windowImpl, opts screen.WindowGenerator) error { - // todo - return nil -} - -func initWindow(w *windowImpl) { - w.glctx, w.worker = gl.NewContext() -} - -func showWindow(w *windowImpl) { - C.doShowWindow(C.uintptr_t(w.id)) -} - -//export preparedOpenGL -func preparedOpenGL(id, ctx, vba uintptr) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - w.ctx = ctx - go drawLoop(w, vba) -} - -func closeWindow(id uintptr) { - C.doCloseWindow(C.uintptr_t(id)) -} - -var mainCallback func(screen.Screen) - -func main(f func(screen.Screen)) error { - if tid := C.threadID(); tid != initThreadID { - log.Fatalf("gldriver.Main called on thread %d, but gldriver.init ran on %d", tid, initThreadID) - } - - mainCallback = f - C.startDriver() - return nil -} - -//export driverStarted -func driverStarted() { - go func() { - mainCallback(theScreen) - C.stopDriver() - }() -} - -//export drawgl -func drawgl(id uintptr) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return // closing window - } - - // TODO: is this necessary? - w.lifecycler.SetVisible(true) - w.lifecycler.SendEvent(w, w.glctx) - - w.Send(paint.Event{External: true}) - <-w.drawDone -} - -// drawLoop is the primary drawing loop. -// -// After Cocoa has created an NSWindow and called prepareOpenGL, -// it starts drawLoop on a locked goroutine to handle OpenGL calls. -// -// The screen is drawn every time a paint.Event is received, which can be -// triggered either by the user or by Cocoa via drawgl (for example, when -// the window is resized). -func drawLoop(w *windowImpl, vba uintptr) { - runtime.LockOSThread() - C.makeCurrentContext(C.uintptr_t(w.ctx.(uintptr))) - - // Starting in OS X 10.11 (El Capitan), the vertex array is - // occasionally getting unbound when the context changes threads. - // - // Avoid this by binding it again. - C.glBindVertexArray(C.GLuint(vba)) - if errno := C.glGetError(); errno != 0 { - panic(fmt.Sprintf("gldriver: glBindVertexArray failed: %d", errno)) - } - - workAvailable := w.worker.WorkAvailable() - - // TODO(crawshaw): exit this goroutine on Release. - for { - select { - case <-workAvailable: - w.worker.DoWork() - case <-w.publish: - loop: - for { - select { - case <-workAvailable: - w.worker.DoWork() - default: - break loop - } - } - C.flushContext(C.uintptr_t(w.ctx.(uintptr))) - w.publishDone <- screen.PublishResult{} - } - } -} - -//export setGeom -func setGeom(id uintptr, ppp float32, widthPx, heightPx int) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return // closing window - } - - sz := size.Event{ - WidthPx: widthPx, - HeightPx: heightPx, - WidthPt: geom.Pt(float32(widthPx) / ppp), - HeightPt: geom.Pt(float32(heightPx) / ppp), - PixelsPerPt: ppp, - } - - if !handleSizeEventsAtChannelReceive { - w.szMu.Lock() - w.sz = sz - w.szMu.Unlock() - } - - w.Send(sz) -} - -//export windowClosing -func windowClosing(id uintptr) { - sendLifecycle(id, (*lifecycler.State).SetDead, true) -} - -func sendWindowEvent(id uintptr, e interface{}) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return // closing window - } - w.Send(e) -} - -var mods = [...]struct { - flags uint32 - code uint16 - mod key.Modifiers -}{ - // Left and right variants of modifier keys have their own masks, - // but they are not documented. These were determined empirically. - {1<<17 | 0x102, C.kVK_Shift, key.ModShift}, - {1<<17 | 0x104, C.kVK_RightShift, key.ModShift}, - {1<<18 | 0x101, C.kVK_Control, key.ModControl}, - {33<<13 | 0x100, C.kVK_RightControl, key.ModControl}, - {1<<19 | 0x120, C.kVK_Option, key.ModAlt}, - {1<<19 | 0x140, C.kVK_RightOption, key.ModAlt}, - {1<<20 | 0x108, C.kVK_Command, key.ModMeta}, - {1<<20 | 0x110, 0x36 /* kVK_RightCommand */, key.ModMeta}, -} - -func cocoaMods(flags uint32) (m key.Modifiers) { - for _, mod := range mods { - if flags&mod.flags == mod.flags { - m |= mod.mod - } - } - return m -} - -func cocoaMouseDir(ty int32) mouse.Direction { - switch ty { - case C.NSLeftMouseDown, C.NSRightMouseDown, C.NSOtherMouseDown: - return mouse.DirPress - case C.NSLeftMouseUp, C.NSRightMouseUp, C.NSOtherMouseUp: - return mouse.DirRelease - default: // dragged - return mouse.DirNone - } -} - -func cocoaMouseButton(button int32) mouse.Button { - switch button { - case 0: - return mouse.ButtonLeft - case 1: - return mouse.ButtonRight - case 2: - return mouse.ButtonMiddle - default: - return mouse.ButtonNone - } -} - -//export mouseEvent -func mouseEvent(id uintptr, x, y, dx, dy float32, ty, button int32, flags uint32) { - cmButton := mouse.ButtonNone - switch ty { - default: - cmButton = cocoaMouseButton(button) - case C.NSMouseMoved, C.NSLeftMouseDragged, C.NSRightMouseDragged, C.NSOtherMouseDragged: - // No-op. - case C.NSScrollWheel: - // Note that the direction of scrolling is inverted by default - // on OS X by the "natural scrolling" setting. At the Cocoa - // level this inversion is applied to trackpads and mice behind - // the scenes, and the value of dy goes in the direction the OS - // wants scrolling to go. - // - // This means the same trackpad/mouse motion on OS X and Linux - // can produce wheel events in opposite directions, but the - // direction matches what other programs on the OS do. - // - // If we wanted to expose the physical device motion in the - // event we could use [NSEvent isDirectionInvertedFromDevice] - // to know if "natural scrolling" is enabled. - // - // TODO: On a trackpad, a scroll can be a drawn-out affair with a - // distinct beginning and end. Should the intermediate events be - // DirNone? - // - // TODO: handle horizontal scrolling - button := mouse.ButtonWheelUp - if dy < 0 { - dy = -dy - button = mouse.ButtonWheelDown - } - e := mouse.Event{ - X: x, - Y: y, - Button: button, - Direction: mouse.DirStep, - Modifiers: cocoaMods(flags), - } - for delta := int(dy); delta != 0; delta-- { - sendWindowEvent(id, e) - } - return - } - sendWindowEvent(id, mouse.Event{ - X: x, - Y: y, - Button: cmButton, - Direction: cocoaMouseDir(ty), - Modifiers: cocoaMods(flags), - }) -} - -//export keyEvent -func keyEvent(id uintptr, runeVal rune, dir uint8, code uint16, flags uint32) { - sendWindowEvent(id, key.Event{ - Rune: cocoaRune(runeVal), - Direction: key.Direction(dir), - Code: cocoaKeyCode(code), - Modifiers: cocoaMods(flags), - }) -} - -//export flagEvent -func flagEvent(id uintptr, flags uint32) { - for _, mod := range mods { - if flags&mod.flags == mod.flags && lastFlags&mod.flags != mod.flags { - keyEvent(id, -1, C.NSKeyDown, mod.code, flags) - } - if lastFlags&mod.flags == mod.flags && flags&mod.flags != mod.flags { - keyEvent(id, -1, C.NSKeyUp, mod.code, flags) - } - } - lastFlags = flags -} - -var lastFlags uint32 - -func sendLifecycle(id uintptr, setter func(*lifecycler.State, bool), val bool) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - setter(&w.lifecycler, val) - w.lifecycler.SendEvent(w, w.glctx) -} - -func sendLifecycleAll(dead bool) { - windows := []*windowImpl{} - - theScreen.mu.Lock() - for _, w := range theScreen.windows { - windows = append(windows, w) - } - theScreen.mu.Unlock() - - for _, w := range windows { - w.lifecycler.SetFocused(false) - w.lifecycler.SetVisible(false) - if dead { - w.lifecycler.SetDead(true) - } - w.lifecycler.SendEvent(w, w.glctx) - } -} - -//export lifecycleDeadAll -func lifecycleDeadAll() { sendLifecycleAll(true) } - -//export lifecycleHideAll -func lifecycleHideAll() { sendLifecycleAll(false) } - -//export lifecycleVisible -func lifecycleVisible(id uintptr, val bool) { - sendLifecycle(id, (*lifecycler.State).SetVisible, val) -} - -//export lifecycleFocused -func lifecycleFocused(id uintptr, val bool) { - sendLifecycle(id, (*lifecycler.State).SetFocused, val) -} - -// cocoaRune marks the Carbon/Cocoa private-range unicode rune representing -// a non-unicode key event to -1, used for Rune in the key package. -// -// http://www.unicode.org/Public/MAPPINGS/VENDORS/APPLE/CORPCHAR.TXT -func cocoaRune(r rune) rune { - if '\uE000' <= r && r <= '\uF8FF' { - return -1 - } - return r -} - -// cocoaKeyCode converts a Carbon/Cocoa virtual key code number -// into the standard keycodes used by the key package. -// -// To get a sense of the key map, see the diagram on -// http://boredzo.org/blog/archives/2007-05-22/virtual-key-codes -func cocoaKeyCode(vkcode uint16) key.Code { - switch vkcode { - case C.kVK_ANSI_A: - return key.CodeA - case C.kVK_ANSI_B: - return key.CodeB - case C.kVK_ANSI_C: - return key.CodeC - case C.kVK_ANSI_D: - return key.CodeD - case C.kVK_ANSI_E: - return key.CodeE - case C.kVK_ANSI_F: - return key.CodeF - case C.kVK_ANSI_G: - return key.CodeG - case C.kVK_ANSI_H: - return key.CodeH - case C.kVK_ANSI_I: - return key.CodeI - case C.kVK_ANSI_J: - return key.CodeJ - case C.kVK_ANSI_K: - return key.CodeK - case C.kVK_ANSI_L: - return key.CodeL - case C.kVK_ANSI_M: - return key.CodeM - case C.kVK_ANSI_N: - return key.CodeN - case C.kVK_ANSI_O: - return key.CodeO - case C.kVK_ANSI_P: - return key.CodeP - case C.kVK_ANSI_Q: - return key.CodeQ - case C.kVK_ANSI_R: - return key.CodeR - case C.kVK_ANSI_S: - return key.CodeS - case C.kVK_ANSI_T: - return key.CodeT - case C.kVK_ANSI_U: - return key.CodeU - case C.kVK_ANSI_V: - return key.CodeV - case C.kVK_ANSI_W: - return key.CodeW - case C.kVK_ANSI_X: - return key.CodeX - case C.kVK_ANSI_Y: - return key.CodeY - case C.kVK_ANSI_Z: - return key.CodeZ - case C.kVK_ANSI_1: - return key.Code1 - case C.kVK_ANSI_2: - return key.Code2 - case C.kVK_ANSI_3: - return key.Code3 - case C.kVK_ANSI_4: - return key.Code4 - case C.kVK_ANSI_5: - return key.Code5 - case C.kVK_ANSI_6: - return key.Code6 - case C.kVK_ANSI_7: - return key.Code7 - case C.kVK_ANSI_8: - return key.Code8 - case C.kVK_ANSI_9: - return key.Code9 - case C.kVK_ANSI_0: - return key.Code0 - // TODO: move the rest of these codes to constants in key.go - // if we are happy with them. - case C.kVK_Return: - return key.CodeReturnEnter - case C.kVK_Escape: - return key.CodeEscape - case C.kVK_Delete: - return key.CodeDeleteBackspace - case C.kVK_Tab: - return key.CodeTab - case C.kVK_Space: - return key.CodeSpacebar - case C.kVK_ANSI_Minus: - return key.CodeHyphenMinus - case C.kVK_ANSI_Equal: - return key.CodeEqualSign - case C.kVK_ANSI_LeftBracket: - return key.CodeLeftSquareBracket - case C.kVK_ANSI_RightBracket: - return key.CodeRightSquareBracket - case C.kVK_ANSI_Backslash: - return key.CodeBackslash - // 50: Keyboard Non-US "#" and ~ - case C.kVK_ANSI_Semicolon: - return key.CodeSemicolon - case C.kVK_ANSI_Quote: - return key.CodeApostrophe - case C.kVK_ANSI_Grave: - return key.CodeGraveAccent - case C.kVK_ANSI_Comma: - return key.CodeComma - case C.kVK_ANSI_Period: - return key.CodeFullStop - case C.kVK_ANSI_Slash: - return key.CodeSlash - case C.kVK_CapsLock: - return key.CodeCapsLock - case C.kVK_F1: - return key.CodeF1 - case C.kVK_F2: - return key.CodeF2 - case C.kVK_F3: - return key.CodeF3 - case C.kVK_F4: - return key.CodeF4 - case C.kVK_F5: - return key.CodeF5 - case C.kVK_F6: - return key.CodeF6 - case C.kVK_F7: - return key.CodeF7 - case C.kVK_F8: - return key.CodeF8 - case C.kVK_F9: - return key.CodeF9 - case C.kVK_F10: - return key.CodeF10 - case C.kVK_F11: - return key.CodeF11 - case C.kVK_F12: - return key.CodeF12 - // 70: PrintScreen - // 71: Scroll Lock - // 72: Pause - // 73: Insert - case C.kVK_Home: - return key.CodeHome - case C.kVK_PageUp: - return key.CodePageUp - case C.kVK_ForwardDelete: - return key.CodeDeleteForward - case C.kVK_End: - return key.CodeEnd - case C.kVK_PageDown: - return key.CodePageDown - case C.kVK_RightArrow: - return key.CodeRightArrow - case C.kVK_LeftArrow: - return key.CodeLeftArrow - case C.kVK_DownArrow: - return key.CodeDownArrow - case C.kVK_UpArrow: - return key.CodeUpArrow - case C.kVK_ANSI_KeypadClear: - return key.CodeKeypadNumLock - case C.kVK_ANSI_KeypadDivide: - return key.CodeKeypadSlash - case C.kVK_ANSI_KeypadMultiply: - return key.CodeKeypadAsterisk - case C.kVK_ANSI_KeypadMinus: - return key.CodeKeypadHyphenMinus - case C.kVK_ANSI_KeypadPlus: - return key.CodeKeypadPlusSign - case C.kVK_ANSI_KeypadEnter: - return key.CodeKeypadEnter - case C.kVK_ANSI_Keypad1: - return key.CodeKeypad1 - case C.kVK_ANSI_Keypad2: - return key.CodeKeypad2 - case C.kVK_ANSI_Keypad3: - return key.CodeKeypad3 - case C.kVK_ANSI_Keypad4: - return key.CodeKeypad4 - case C.kVK_ANSI_Keypad5: - return key.CodeKeypad5 - case C.kVK_ANSI_Keypad6: - return key.CodeKeypad6 - case C.kVK_ANSI_Keypad7: - return key.CodeKeypad7 - case C.kVK_ANSI_Keypad8: - return key.CodeKeypad8 - case C.kVK_ANSI_Keypad9: - return key.CodeKeypad9 - case C.kVK_ANSI_Keypad0: - return key.CodeKeypad0 - case C.kVK_ANSI_KeypadDecimal: - return key.CodeKeypadFullStop - case C.kVK_ANSI_KeypadEquals: - return key.CodeKeypadEqualSign - case C.kVK_F13: - return key.CodeF13 - case C.kVK_F14: - return key.CodeF14 - case C.kVK_F15: - return key.CodeF15 - case C.kVK_F16: - return key.CodeF16 - case C.kVK_F17: - return key.CodeF17 - case C.kVK_F18: - return key.CodeF18 - case C.kVK_F19: - return key.CodeF19 - case C.kVK_F20: - return key.CodeF20 - // 116: Keyboard Execute - case C.kVK_Help: - return key.CodeHelp - // 118: Keyboard Menu - // 119: Keyboard Select - // 120: Keyboard Stop - // 121: Keyboard Again - // 122: Keyboard Undo - // 123: Keyboard Cut - // 124: Keyboard Copy - // 125: Keyboard Paste - // 126: Keyboard Find - case C.kVK_Mute: - return key.CodeMute - case C.kVK_VolumeUp: - return key.CodeVolumeUp - case C.kVK_VolumeDown: - return key.CodeVolumeDown - // 130: Keyboard Locking Caps Lock - // 131: Keyboard Locking Num Lock - // 132: Keyboard Locking Scroll Lock - // 133: Keyboard Comma - // 134: Keyboard Equal Sign - // ...: Bunch of stuff - case C.kVK_Control: - return key.CodeLeftControl - case C.kVK_Shift: - return key.CodeLeftShift - case C.kVK_Option: - return key.CodeLeftAlt - case C.kVK_Command: - return key.CodeLeftGUI - case C.kVK_RightControl: - return key.CodeRightControl - case C.kVK_RightShift: - return key.CodeRightShift - case C.kVK_RightOption: - return key.CodeRightAlt - // TODO key.CodeRightGUI - default: - return key.CodeUnknown - } -} - -func surfaceCreate() error { - return errors.New("gldriver: surface creation not implemented on darwin") -} diff --git a/shiny/driver/gldriver/cocoa.m b/shiny/driver/gldriver/cocoa.m deleted file mode 100644 index 8f9f7a3b..00000000 --- a/shiny/driver/gldriver/cocoa.m +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright 2014 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build darwin -// +build 386 amd64 -// +build !ios - -#include "_cgo_export.h" -#include -#include - -#import -#import -#import - -// The variables did not exist on older OS X releases, -// we use the old variables deprecated on macOS to define them. -#if __MAC_OS_X_VERSION_MAX_ALLOWED < 101200 -enum -{ - NSEventTypeScrollWheel = NSScrollWheel, - NSEventTypeKeyDown = NSKeyDown -}; -enum -{ - NSWindowStyleMaskTitled = NSTitledWindowMask, - NSWindowStyleMaskResizable = NSResizableWindowMask, - NSWindowStyleMaskMiniaturizable = NSMiniaturizableWindowMask, - NSWindowStyleMaskClosable = NSClosableWindowMask -}; -#endif - -void makeCurrentContext(uintptr_t context) { - NSOpenGLContext* ctx = (NSOpenGLContext*)context; - [ctx makeCurrentContext]; - [ctx update]; -} - -void flushContext(uintptr_t context) { - NSOpenGLContext* ctx = (NSOpenGLContext*)context; - [ctx flushBuffer]; -} - -uint64 threadID() { - uint64 id; - if (pthread_threadid_np(pthread_self(), &id)) { - abort(); - } - return id; -} - -@interface ScreenGLView : NSOpenGLView -{ -} -@end - -@implementation ScreenGLView -- (void)prepareOpenGL { - [self setWantsBestResolutionOpenGLSurface:YES]; - GLint swapInt = 1; - NSOpenGLContext *ctx = [self openGLContext]; - [ctx setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; - - // Using attribute arrays in OpenGL 3.3 requires the use of a VBA. - // But VBAs don't exist in ES 2. So we bind a default one. - GLuint vba; - glGenVertexArrays(1, &vba); - glBindVertexArray(vba); - - preparedOpenGL((GoUintptr)self, (GoUintptr)ctx, (GoUintptr)vba); -} - -- (void)callSetGeom { - // Calculate screen PPI. - // - // Note that the backingScaleFactor converts from logical - // pixels to actual pixels, but both of these units vary - // independently from real world size. E.g. - // - // 13" Retina Macbook Pro, 2560x1600, 227ppi, backingScaleFactor=2, scale=3.15 - // 15" Retina Macbook Pro, 2880x1800, 220ppi, backingScaleFactor=2, scale=3.06 - // 27" iMac, 2560x1440, 109ppi, backingScaleFactor=1, scale=1.51 - // 27" Retina iMac, 5120x2880, 218ppi, backingScaleFactor=2, scale=3.03 - NSScreen *screen = self.window.screen; - double screenPixW = [screen frame].size.width * [screen backingScaleFactor]; - - CGDirectDisplayID display = (CGDirectDisplayID)[[[screen deviceDescription] valueForKey:@"NSScreenNumber"] intValue]; - CGSize screenSizeMM = CGDisplayScreenSize(display); // in millimeters - float ppi = 25.4 * screenPixW / screenSizeMM.width; - float pixelsPerPt = ppi/72.0; - - // The width and height reported to the geom package are the - // bounds of the OpenGL view. Several steps are necessary. - // First, [self bounds] gives us the number of logical pixels - // in the view. Multiplying this by the backingScaleFactor - // gives us the number of actual pixels. - NSRect r = [self bounds]; - int w = r.size.width * [screen backingScaleFactor]; - int h = r.size.height * [screen backingScaleFactor]; - - setGeom((GoUintptr)self, pixelsPerPt, w, h); -} - -- (void)reshape { - [super reshape]; - [self callSetGeom]; -} - -- (void)drawRect:(NSRect)theRect { - // Called during resize. Do an extra draw if we are visible. - // This gets rid of flicker when resizing. - drawgl((GoUintptr)self); -} - -- (void)mouseEventNS:(NSEvent *)theEvent { - NSPoint p = [theEvent locationInWindow]; - double h = self.frame.size.height; - - // Both h and p are measured in Cocoa pixels, which are a fraction of - // physical pixels, so we multiply by backingScaleFactor. - double scale = [self.window.screen backingScaleFactor]; - - double x = p.x * scale; - double y = (h - p.y) * scale - 1; // flip origin from bottom-left to top-left. - - double dx, dy; - if (theEvent.type == NSEventTypeScrollWheel) { - dx = theEvent.scrollingDeltaX; - dy = theEvent.scrollingDeltaY; - } - - mouseEvent((GoUintptr)self, x, y, dx, dy, theEvent.type, theEvent.buttonNumber, theEvent.modifierFlags); -} - -- (void)mouseMoved:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)mouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)mouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)mouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)rightMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)rightMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)rightMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)otherMouseDown:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)otherMouseUp:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)otherMouseDragged:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } -- (void)scrollWheel:(NSEvent *)theEvent { [self mouseEventNS:theEvent]; } - -// raw modifier key presses -- (void)flagsChanged:(NSEvent *)theEvent { - flagEvent((GoUintptr)self, theEvent.modifierFlags); -} - -// overrides special handling of escape and tab -- (BOOL)performKeyEquivalent:(NSEvent *)theEvent { - [self key:theEvent]; - return YES; -} - -- (void)keyDown:(NSEvent *)theEvent { [self key:theEvent]; } -- (void)keyUp:(NSEvent *)theEvent { [self key:theEvent]; } - -- (void)key:(NSEvent *)theEvent { - NSRange range = [theEvent.characters rangeOfComposedCharacterSequenceAtIndex:0]; - - uint8_t buf[4] = {0, 0, 0, 0}; - if (![theEvent.characters getBytes:buf - maxLength:4 - usedLength:nil - encoding:NSUTF32LittleEndianStringEncoding - options:NSStringEncodingConversionAllowLossy - range:range - remainingRange:nil]) { - NSLog(@"failed to read key event %@", theEvent); - return; - } - - uint32_t rune = (uint32_t)buf[0]<<0 | (uint32_t)buf[1]<<8 | (uint32_t)buf[2]<<16 | (uint32_t)buf[3]<<24; - - uint8_t direction; - if ([theEvent isARepeat]) { - direction = 0; - } else if (theEvent.type == NSEventTypeKeyDown) { - direction = 1; - } else { - direction = 2; - } - keyEvent((GoUintptr)self, (int32_t)rune, direction, theEvent.keyCode, theEvent.modifierFlags); -} - -- (void)windowDidChangeScreenProfile:(NSNotification *)notification { - [self callSetGeom]; -} - -// TODO: catch windowDidMiniaturize? - -- (void)windowDidExpose:(NSNotification *)notification { - lifecycleVisible((GoUintptr)self, true); -} - -- (void)windowDidBecomeKey:(NSNotification *)notification { - lifecycleFocused((GoUintptr)self, true); -} - -- (void)windowDidResignKey:(NSNotification *)notification { - lifecycleFocused((GoUintptr)self, false); - if ([NSApp isHidden]) { - lifecycleVisible((GoUintptr)self, false); - } -} - -- (void)windowWillClose:(NSNotification *)notification { - windowClosing((GoUintptr)self); - - if (self.window.nextResponder != NULL) { - [self.window.nextResponder release]; - self.window.nextResponder = NULL; - } -} -@end - -@interface AppDelegate : NSObject -{ -} -@end - -@implementation AppDelegate -- (void)applicationDidFinishLaunching:(NSNotification *)aNotification { - driverStarted(); - [[NSRunningApplication currentApplication] activateWithOptions:(NSApplicationActivateAllWindows | NSApplicationActivateIgnoringOtherApps)]; -} - -- (void)applicationWillTerminate:(NSNotification *)aNotification { - lifecycleDeadAll(); -} - -- (void)applicationWillHide:(NSNotification *)aNotification { - lifecycleHideAll(); -} -@end - -uintptr_t doNewWindow(int width, int height, char* title) { - NSScreen *screen = [NSScreen mainScreen]; - double w = (double)width / [screen backingScaleFactor]; - double h = (double)height / [screen backingScaleFactor]; - __block ScreenGLView* view = NULL; - - dispatch_sync(dispatch_get_main_queue(), ^{ - id menuBar = [NSMenu new]; - id menuItem = [NSMenuItem new]; - [menuBar addItem:menuItem]; - [NSApp setMainMenu:menuBar]; - - id menu = [NSMenu new]; - NSString* name = [[NSString alloc] initWithUTF8String:title]; - - id hideMenuItem = [[NSMenuItem alloc] initWithTitle:@"Hide" - action:@selector(hide:) keyEquivalent:@"h"]; - [menu addItem:hideMenuItem]; - - id quitMenuItem = [[NSMenuItem alloc] initWithTitle:@"Quit" - action:@selector(terminate:) keyEquivalent:@"q"]; - [menu addItem:quitMenuItem]; - [menuItem setSubmenu:menu]; - - NSRect rect = NSMakeRect(0, 0, w, h); - - NSWindow* window = [[NSWindow alloc] initWithContentRect:rect - styleMask:NSWindowStyleMaskTitled - backing:NSBackingStoreBuffered - defer:NO]; - window.styleMask |= NSWindowStyleMaskResizable; - window.styleMask |= NSWindowStyleMaskMiniaturizable; - window.styleMask |= NSWindowStyleMaskClosable; - window.title = name; - window.displaysWhenScreenProfileChanges = YES; - [window cascadeTopLeftFromPoint:NSMakePoint(20,20)]; - [window setAcceptsMouseMovedEvents:YES]; - - NSOpenGLPixelFormatAttribute attr[] = { - NSOpenGLPFAOpenGLProfile, NSOpenGLProfileVersion3_2Core, - NSOpenGLPFAColorSize, 24, - NSOpenGLPFAAlphaSize, 8, - NSOpenGLPFADepthSize, 16, - NSOpenGLPFADoubleBuffer, - NSOpenGLPFAAllowOfflineRenderers, - 0 - }; - id pixFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attr]; - view = [[ScreenGLView alloc] initWithFrame:rect pixelFormat:pixFormat]; - [window setContentView:view]; - [window setDelegate:view]; - [window makeFirstResponder:view]; - }); - - return (uintptr_t)view; -} - -void doShowWindow(uintptr_t viewID) { - ScreenGLView* view = (ScreenGLView*)viewID; - dispatch_async(dispatch_get_main_queue(), ^{ - [view.window makeKeyAndOrderFront:view.window]; - }); -} - -void doCloseWindow(uintptr_t viewID) { - ScreenGLView* view = (ScreenGLView*)viewID; - dispatch_sync(dispatch_get_main_queue(), ^{ - [view.window performClose:view]; - }); -} - -void startDriver() { - [NSAutoreleasePool new]; - [NSApplication sharedApplication]; - [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; - AppDelegate* delegate = [[AppDelegate alloc] init]; - [NSApp setDelegate:delegate]; - [NSApp run]; -} - -void stopDriver() { - dispatch_async(dispatch_get_main_queue(), ^{ - [NSApp terminate:nil]; - }); -} diff --git a/shiny/driver/gldriver/context.go b/shiny/driver/gldriver/context.go deleted file mode 100644 index 197be350..00000000 --- a/shiny/driver/gldriver/context.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !android - -package gldriver - -import ( - "runtime" - - "golang.org/x/mobile/gl" -) - -// NewContext creates an OpenGL ES context with a dedicated processing thread. -func NewContext() (gl.Context, error) { - glctx, worker := gl.NewContext() - - errCh := make(chan error) - workAvailable := worker.WorkAvailable() - go func() { - runtime.LockOSThread() - err := surfaceCreate() - errCh <- err - if err != nil { - return - } - - for range workAvailable { - worker.DoWork() - } - }() - if err := <-errCh; err != nil { - return nil, err - } - return glctx, nil -} diff --git a/shiny/driver/gldriver/egl.go b/shiny/driver/gldriver/egl.go deleted file mode 100644 index 6f5d3d7b..00000000 --- a/shiny/driver/gldriver/egl.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gldriver - -// These constants match the values found in the EGL 1.4 headers, -// egl.h, eglext.h, and eglplatform.h. -const ( - _EGL_DONT_CARE = -1 - - _EGL_NO_SURFACE = 0 - _EGL_NO_CONTEXT = 0 - _EGL_NO_DISPLAY = 0 - - _EGL_OPENGL_ES2_BIT = 0x04 // EGL_RENDERABLE_TYPE mask - _EGL_WINDOW_BIT = 0x04 // EGL_SURFACE_TYPE mask - - _EGL_OPENGL_ES_API = 0x30A0 - _EGL_RENDERABLE_TYPE = 0x3040 - _EGL_SURFACE_TYPE = 0x3033 - _EGL_BUFFER_SIZE = 0x3020 - _EGL_ALPHA_SIZE = 0x3021 - _EGL_BLUE_SIZE = 0x3022 - _EGL_GREEN_SIZE = 0x3023 - _EGL_RED_SIZE = 0x3024 - _EGL_DEPTH_SIZE = 0x3025 - _EGL_STENCIL_SIZE = 0x3026 - _EGL_SAMPLE_BUFFERS = 0x3032 - _EGL_CONFIG_CAVEAT = 0x3027 - _EGL_NONE = 0x3038 - - _EGL_CONTEXT_CLIENT_VERSION = 0x3098 -) - -// ANGLE specific options found in eglext.h -const ( - _EGL_PLATFORM_ANGLE_ANGLE = 0x3202 - _EGL_PLATFORM_ANGLE_TYPE_ANGLE = 0x3203 - _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE = 0x3204 - _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE = 0x3205 - _EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE = 0x3206 - - _EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE = 0x3207 - _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE = 0x3208 - _EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE = 0x3209 - _EGL_PLATFORM_ANGLE_DEVICE_TYPE_HARDWARE_ANGLE = 0x320A - _EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE = 0x320B - - _EGL_PLATFORM_ANGLE_TYPE_OPENGL_ANGLE = 0x320D - _EGL_PLATFORM_ANGLE_TYPE_OPENGLES_ANGLE = 0x320E -) - -const ( - _EGL_SUCCESS = 0x3000 - _EGL_NOT_INITIALIZED = 0x3001 - _EGL_BAD_ACCESS = 0x3002 - _EGL_BAD_ALLOC = 0x3003 - _EGL_BAD_ATTRIBUTE = 0x3004 - _EGL_BAD_CONFIG = 0x3005 - _EGL_BAD_CONTEXT = 0x3006 - _EGL_BAD_CURRENT_SURFACE = 0x3007 - _EGL_BAD_DISPLAY = 0x3008 - _EGL_BAD_MATCH = 0x3009 - _EGL_BAD_NATIVE_PIXMAP = 0x300A - _EGL_BAD_NATIVE_WINDOW = 0x300B - _EGL_BAD_PARAMETER = 0x300C - _EGL_BAD_SURFACE = 0x300D - _EGL_CONTEXT_LOST = 0x300E -) - -func eglErrString(errno uintptr) string { - switch errno { - case _EGL_SUCCESS: - return "EGL_SUCCESS" - case _EGL_NOT_INITIALIZED: - return "EGL_NOT_INITIALIZED" - case _EGL_BAD_ACCESS: - return "EGL_BAD_ACCESS" - case _EGL_BAD_ALLOC: - return "EGL_BAD_ALLOC" - case _EGL_BAD_ATTRIBUTE: - return "EGL_BAD_ATTRIBUTE" - case _EGL_BAD_CONFIG: - return "EGL_BAD_CONFIG" - case _EGL_BAD_CONTEXT: - return "EGL_BAD_CONTEXT" - case _EGL_BAD_CURRENT_SURFACE: - return "EGL_BAD_CURRENT_SURFACE" - case _EGL_BAD_DISPLAY: - return "EGL_BAD_DISPLAY" - case _EGL_BAD_MATCH: - return "EGL_BAD_MATCH" - case _EGL_BAD_NATIVE_PIXMAP: - return "EGL_BAD_NATIVE_PIXMAP" - case _EGL_BAD_NATIVE_WINDOW: - return "EGL_BAD_NATIVE_WINDOW" - case _EGL_BAD_PARAMETER: - return "EGL_BAD_PARAMETER" - case _EGL_BAD_SURFACE: - return "EGL_BAD_SURFACE" - case _EGL_CONTEXT_LOST: - return "EGL_CONTEXT_LOST" - } - return "EGL: unknown error" -} diff --git a/shiny/driver/gldriver/gldriver.go b/shiny/driver/gldriver/gldriver.go deleted file mode 100644 index f9b0b27f..00000000 --- a/shiny/driver/gldriver/gldriver.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// Package gldriver provides an OpenGL driver for accessing a screen. -package gldriver - -import ( - "encoding/binary" - "fmt" - "math" - - "github.com/oakmound/oak/v3/shiny/driver/internal/errscreen" - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/image/math/f64" - "golang.org/x/mobile/gl" -) - -// Main is called by the program's main function to run the graphical -// application. -// -// It calls f on the Screen, possibly in a separate goroutine, as some OS- -// specific libraries require being on 'the main thread'. It returns when f -// returns. -func Main(f func(screen.Screen)) { - if err := main(f); err != nil { - f(errscreen.Stub(err)) - } -} - -// writeAff3 must only be called while holding windowImpl.glctxMu. -func writeAff3(glctx gl.Context, u gl.Uniform, a f64.Aff3) { - var m [9]float32 - m[0*3+0] = float32(a[0*3+0]) - m[0*3+1] = float32(a[1*3+0]) - m[0*3+2] = 0 - m[1*3+0] = float32(a[0*3+1]) - m[1*3+1] = float32(a[1*3+1]) - m[1*3+2] = 0 - m[2*3+0] = float32(a[0*3+2]) - m[2*3+1] = float32(a[1*3+2]) - m[2*3+2] = 1 - glctx.UniformMatrix3fv(u, m[:]) -} - -// f32Bytes returns the byte representation of float32 values in the given byte -// order. byteOrder must be either binary.BigEndian or binary.LittleEndian. -func f32Bytes(byteOrder binary.ByteOrder, values ...float32) []byte { - le := false - switch byteOrder { - case binary.BigEndian: - case binary.LittleEndian: - le = true - default: - panic(fmt.Sprintf("invalid byte order %v", byteOrder)) - } - - b := make([]byte, 4*len(values)) - for i, v := range values { - u := math.Float32bits(v) - if le { - b[4*i+0] = byte(u >> 0) - b[4*i+1] = byte(u >> 8) - b[4*i+2] = byte(u >> 16) - b[4*i+3] = byte(u >> 24) - } else { - b[4*i+0] = byte(u >> 24) - b[4*i+1] = byte(u >> 16) - b[4*i+2] = byte(u >> 8) - b[4*i+3] = byte(u >> 0) - } - } - return b -} - -// compileProgram must only be called while holding windowImpl.glctxMu. -func compileProgram(glctx gl.Context, vSrc, fSrc string) (gl.Program, error) { - program := glctx.CreateProgram() - if program.Value == 0 { - return gl.Program{}, fmt.Errorf("gldriver: no programs available") - } - - vertexShader, err := compileShader(glctx, gl.VERTEX_SHADER, vSrc) - if err != nil { - return gl.Program{}, err - } - fragmentShader, err := compileShader(glctx, gl.FRAGMENT_SHADER, fSrc) - if err != nil { - glctx.DeleteShader(vertexShader) - return gl.Program{}, err - } - - glctx.AttachShader(program, vertexShader) - glctx.AttachShader(program, fragmentShader) - glctx.LinkProgram(program) - - // Flag shaders for deletion when program is unlinked. - glctx.DeleteShader(vertexShader) - glctx.DeleteShader(fragmentShader) - - if glctx.GetProgrami(program, gl.LINK_STATUS) == 0 { - defer glctx.DeleteProgram(program) - return gl.Program{}, fmt.Errorf("gldriver: program compile: %s", glctx.GetProgramInfoLog(program)) - } - return program, nil -} - -// compileShader must only be called while holding windowImpl.glctxMu. -func compileShader(glctx gl.Context, shaderType gl.Enum, src string) (gl.Shader, error) { - shader := glctx.CreateShader(shaderType) - if shader.Value == 0 { - return gl.Shader{}, fmt.Errorf("gldriver: could not create shader (type %v)", shaderType) - } - glctx.ShaderSource(shader, src) - glctx.CompileShader(shader) - if glctx.GetShaderi(shader, gl.COMPILE_STATUS) == 0 { - defer glctx.DeleteShader(shader) - return gl.Shader{}, fmt.Errorf("gldriver: shader compile: %s", glctx.GetShaderInfoLog(shader)) - } - return shader, nil -} diff --git a/shiny/driver/gldriver/other.go b/shiny/driver/gldriver/other.go deleted file mode 100644 index 538feb36..00000000 --- a/shiny/driver/gldriver/other.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build !darwin !386,!amd64 ios -// +build !linux android -// +build !windows -// +build !openbsd - -package gldriver - -import ( - "fmt" - "runtime" - - "github.com/oakmound/oak/v3/shiny/screen" -) - -func newWindow(opts screen.WindowGenerator) (uintptr, error) { return 0, nil } - -func moveWindow(w *windowImpl, opts screen.WindowGenerator) error { return nil } - -const useLifecycler = true -const handleSizeEventsAtChannelReceive = true - -func initWindow(id *windowImpl) {} -func showWindow(id *windowImpl) {} -func closeWindow(id uintptr) {} -func drawLoop(w *windowImpl) {} - -func main(f func(screen.Screen)) error { - return fmt.Errorf("gldriver: unsupported GOOS/GOARCH %s/%s", runtime.GOOS, runtime.GOARCH) -} diff --git a/shiny/driver/gldriver/screen.go b/shiny/driver/gldriver/screen.go deleted file mode 100644 index 4e01ac7d..00000000 --- a/shiny/driver/gldriver/screen.go +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gldriver - -import ( - "fmt" - "image" - "sync" - - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/mobile/gl" -) - -var theScreen = &screenImpl{ - windows: make(map[uintptr]*windowImpl), -} - -type screenImpl struct { - texture struct { - program gl.Program - pos gl.Attrib - mvp gl.Uniform - uvp gl.Uniform - inUV gl.Attrib - sample gl.Uniform - quad gl.Buffer - } - fill struct { - program gl.Program - pos gl.Attrib - mvp gl.Uniform - color gl.Uniform - quad gl.Buffer - } - - mu sync.Mutex - windows map[uintptr]*windowImpl -} - -func (s *screenImpl) NewImage(size image.Point) (retBuf screen.Image, retErr error) { - m := image.NewRGBA(image.Rectangle{Max: size}) - return &bufferImpl{ - buf: m.Pix, - rgba: *m, - size: size, - }, nil -} - -func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) { - // TODO: can we compile these programs eagerly instead of lazily? - - // Find a GL context for this texture. - // TODO: this might be correct. Some GL objects can be shared - // across contexts. But this needs a review of the spec to make - // sure it's correct, and some testing would be nice. - var w *windowImpl - - s.mu.Lock() - for _, window := range s.windows { - w = window - break - } - s.mu.Unlock() - - if w == nil { - return nil, fmt.Errorf("gldriver: no window available") - } - - w.glctxMu.Lock() - defer w.glctxMu.Unlock() - glctx := w.glctx - if glctx == nil { - return nil, fmt.Errorf("gldriver: no GL context available") - } - - if !glctx.IsProgram(s.texture.program) { - p, err := compileProgram(glctx, textureVertexSrc, textureFragmentSrc) - if err != nil { - return nil, err - } - s.texture.program = p - s.texture.pos = glctx.GetAttribLocation(p, "pos") - s.texture.mvp = glctx.GetUniformLocation(p, "mvp") - s.texture.uvp = glctx.GetUniformLocation(p, "uvp") - s.texture.inUV = glctx.GetAttribLocation(p, "inUV") - s.texture.sample = glctx.GetUniformLocation(p, "sample") - s.texture.quad = glctx.CreateBuffer() - - glctx.BindBuffer(gl.ARRAY_BUFFER, s.texture.quad) - glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW) - } - - t := &textureImpl{ - w: w, - id: glctx.CreateTexture(), - size: size, - } - - glctx.BindTexture(gl.TEXTURE_2D, t.id) - glctx.TexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, size.X, size.Y, gl.RGBA, gl.UNSIGNED_BYTE, nil) - glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR) - glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR) - glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE) - glctx.TexParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE) - - return t, nil -} - -func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, error) { - id, err := newWindow(opts) - if err != nil { - return nil, err - } - w := &windowImpl{ - s: s, - id: id, - publish: make(chan struct{}), - publishDone: make(chan screen.PublishResult), - drawDone: make(chan struct{}), - } - initWindow(w) - - s.mu.Lock() - s.windows[id] = w - s.mu.Unlock() - - if useLifecycler { - w.lifecycler.SendEvent(w, nil) - } - - showWindow(w) - - moveWindow(w, opts) - - return w, nil -} diff --git a/shiny/driver/gldriver/texture.go b/shiny/driver/gldriver/texture.go deleted file mode 100644 index 21b734ea..00000000 --- a/shiny/driver/gldriver/texture.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gldriver - -import ( - "encoding/binary" - "image" - "image/color" - "image/draw" - - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/mobile/gl" -) - -type textureImpl struct { - w *windowImpl - id gl.Texture - fb gl.Framebuffer - size image.Point -} - -func (t *textureImpl) Size() image.Point { return t.size } -func (t *textureImpl) Bounds() image.Rectangle { return image.Rectangle{Max: t.size} } - -func (t *textureImpl) Release() { - t.w.glctxMu.Lock() - defer t.w.glctxMu.Unlock() - - if t.fb.Value != 0 { - t.w.glctx.DeleteFramebuffer(t.fb) - t.fb = gl.Framebuffer{} - } - t.w.glctx.DeleteTexture(t.id) - t.id = gl.Texture{} -} - -func (t *textureImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { - buf := src.(*bufferImpl) - buf.preUpload() - - // src2dst is added to convert from the src coordinate space to the dst - // coordinate space. It is subtracted to convert the other way. - src2dst := dp.Sub(sr.Min) - - // Clip to the source. - sr = sr.Intersect(buf.Bounds()) - - // Clip to the destination. - dr := sr.Add(src2dst) - dr = dr.Intersect(t.Bounds()) - if dr.Empty() { - return - } - - // Bring dr.Min in dst-space back to src-space to get the pixel buffer offset. - pix := buf.rgba.Pix[buf.rgba.PixOffset(dr.Min.X-src2dst.X, dr.Min.Y-src2dst.Y):] - - t.w.glctxMu.Lock() - defer t.w.glctxMu.Unlock() - - t.w.glctx.BindTexture(gl.TEXTURE_2D, t.id) - - width := dr.Dx() - if width*4 == buf.rgba.Stride { - t.w.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, dr.Min.X, dr.Min.Y, width, dr.Dy(), gl.RGBA, gl.UNSIGNED_BYTE, pix) - return - } - // TODO: can we use GL_UNPACK_ROW_LENGTH with glPixelStorei for stride in - // ES 3.0, instead of uploading the pixels row-by-row? - for y, p := dr.Min.Y, 0; y < dr.Max.Y; y++ { - t.w.glctx.TexSubImage2D(gl.TEXTURE_2D, 0, dr.Min.X, y, width, 1, gl.RGBA, gl.UNSIGNED_BYTE, pix[p:]) - p += buf.rgba.Stride - } -} - -func (t *textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - minX := float64(dr.Min.X) - minY := float64(dr.Min.Y) - maxX := float64(dr.Max.X) - maxY := float64(dr.Max.Y) - mvp := calcMVP( - t.size.X, t.size.Y, - minX, minY, - maxX, minY, - minX, maxY, - ) - - glctx := t.w.glctx - - t.w.glctxMu.Lock() - defer t.w.glctxMu.Unlock() - - create := t.fb.Value == 0 - if create { - t.fb = glctx.CreateFramebuffer() - } - glctx.BindFramebuffer(gl.FRAMEBUFFER, t.fb) - if create { - glctx.FramebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, t.id, 0) - } - - glctx.Viewport(0, 0, t.size.X, t.size.Y) - doFill(t.w.s, t.w.glctx, mvp, src, op) - - // We can't restore the GL state (i.e. bind the back buffer, also known as - // gl.Framebuffer{Value: 0}) right away, since we don't necessarily know - // the right viewport size yet. It is valid to call textureImpl.Fill before - // we've gotten our first size.Event. We bind it lazily instead. - t.w.backBufferBound = false -} - -var quadCoords = f32Bytes(binary.LittleEndian, - 0, 0, // top left - 1, 0, // top right - 0, 1, // bottom left - 1, 1, // bottom right -) - -const textureVertexSrc = `#version 100 -uniform mat3 mvp; -uniform mat3 uvp; -attribute vec3 pos; -attribute vec2 inUV; -varying vec2 uv; -void main() { - vec3 p = pos; - p.z = 1.0; - gl_Position = vec4(mvp * p, 1); - uv = (uvp * vec3(inUV, 1)).xy; -} -` - -const textureFragmentSrc = `#version 100 -precision mediump float; -varying vec2 uv; -uniform sampler2D sample; -void main() { - gl_FragColor = texture2D(sample, uv); -} -` - -const fillVertexSrc = `#version 100 -uniform mat3 mvp; -attribute vec3 pos; -void main() { - vec3 p = pos; - p.z = 1.0; - gl_Position = vec4(mvp * p, 1); -} -` - -const fillFragmentSrc = `#version 100 -precision mediump float; -uniform vec4 color; -void main() { - gl_FragColor = color; -} -` diff --git a/shiny/driver/gldriver/win32.go b/shiny/driver/gldriver/win32.go deleted file mode 100644 index d26fe143..00000000 --- a/shiny/driver/gldriver/win32.go +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build windows - -package gldriver - -import ( - "errors" - "fmt" - "runtime" - "unsafe" - - "github.com/oakmound/oak/v3/shiny/driver/internal/win32" - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/mobile/event/key" - "golang.org/x/mobile/event/lifecycle" - "golang.org/x/mobile/event/mouse" - "golang.org/x/mobile/event/paint" - "golang.org/x/mobile/event/size" - "golang.org/x/mobile/gl" -) - -const useLifecycler = true -const handleSizeEventsAtChannelReceive = true - -var screenHWND win32.HWND - -func main(f func(screen.Screen)) error { - var err error - screenHWND, err = win32.NewScreen() - if err != nil { - return err - } - return win32.Main(screenHWND, func() { f(theScreen) }) -} - -var ( - eglGetPlatformDisplayEXT = gl.LibEGL.NewProc("eglGetPlatformDisplayEXT") - eglInitialize = gl.LibEGL.NewProc("eglInitialize") - eglChooseConfig = gl.LibEGL.NewProc("eglChooseConfig") - eglGetError = gl.LibEGL.NewProc("eglGetError") - eglBindAPI = gl.LibEGL.NewProc("eglBindAPI") - eglCreateWindowSurface = gl.LibEGL.NewProc("eglCreateWindowSurface") - eglCreateContext = gl.LibEGL.NewProc("eglCreateContext") - eglMakeCurrent = gl.LibEGL.NewProc("eglMakeCurrent") - eglSwapInterval = gl.LibEGL.NewProc("eglSwapInterval") - eglDestroySurface = gl.LibEGL.NewProc("eglDestroySurface") - eglSwapBuffers = gl.LibEGL.NewProc("eglSwapBuffers") -) - -type eglConfig uintptr // void* - -type eglInt int32 - -var rgb888 = [...]eglInt{ - _EGL_RENDERABLE_TYPE, _EGL_OPENGL_ES2_BIT, - _EGL_SURFACE_TYPE, _EGL_WINDOW_BIT, - _EGL_BLUE_SIZE, 8, - _EGL_GREEN_SIZE, 8, - _EGL_RED_SIZE, 8, - _EGL_DEPTH_SIZE, 16, - _EGL_STENCIL_SIZE, 8, - _EGL_NONE, -} - -type ctxWin32 struct { - ctx uintptr - display uintptr // EGLDisplay - surface uintptr // EGLSurface -} - -func newWindow(opts screen.WindowGenerator) (uintptr, error) { - w, err := win32.NewWindow(screenHWND, opts) - if err != nil { - return 0, err - } - - return uintptr(w), nil -} - -func moveWindow(w *windowImpl, opts screen.WindowGenerator) error { - return win32.ResizeClientRect(win32.HWND(w.id), opts) -} - -func initWindow(w *windowImpl) { - w.glctx, w.worker = gl.NewContext() -} - -func showWindow(w *windowImpl) { - // Show makes an initial call to sizeEvent (via win32.SizeEvent), where - // we setup the EGL surface and GL context. - win32.Show(win32.HWND(w.id)) -} - -func closeWindow(id uintptr) {} // TODO - -func drawLoop(w *windowImpl) { - runtime.LockOSThread() - - display := w.ctx.(ctxWin32).display - surface := w.ctx.(ctxWin32).surface - ctx := w.ctx.(ctxWin32).ctx - - if ret, _, _ := eglMakeCurrent.Call(display, surface, surface, ctx); ret == 0 { - panic(fmt.Sprintf("eglMakeCurrent failed: %v", eglErr())) - } - - // TODO(crawshaw): exit this goroutine on Release. - workAvailable := w.worker.WorkAvailable() - for { - select { - case <-workAvailable: - w.worker.DoWork() - case <-w.publish: - loop: - for { - select { - case <-workAvailable: - w.worker.DoWork() - default: - break loop - } - } - if ret, _, _ := eglSwapBuffers.Call(display, surface); ret == 0 { - panic(fmt.Sprintf("eglSwapBuffers failed: %v", eglErr())) - } - w.publishDone <- screen.PublishResult{} - } - } -} - -func init() { - win32.SizeEvent = sizeEvent - win32.PaintEvent = paintEvent - win32.MouseEvent = mouseEvent - win32.KeyEvent = keyEvent - win32.LifecycleEvent = lifecycleEvent -} - -func lifecycleEvent(hwnd win32.HWND, to lifecycle.Stage) { - theScreen.mu.Lock() - w := theScreen.windows[uintptr(hwnd)] - theScreen.mu.Unlock() - - if w.lifecycleStage == to { - return - } - w.Send(lifecycle.Event{ - From: w.lifecycleStage, - To: to, - DrawContext: w.glctx, - }) - w.lifecycleStage = to -} - -func mouseEvent(hwnd win32.HWND, e mouse.Event) { - theScreen.mu.Lock() - w := theScreen.windows[uintptr(hwnd)] - theScreen.mu.Unlock() - - w.Send(e) -} - -func keyEvent(hwnd win32.HWND, e key.Event) { - theScreen.mu.Lock() - w := theScreen.windows[uintptr(hwnd)] - theScreen.mu.Unlock() - - w.Send(e) -} - -func paintEvent(hwnd win32.HWND, e paint.Event) { - theScreen.mu.Lock() - w := theScreen.windows[uintptr(hwnd)] - theScreen.mu.Unlock() - - if w.ctx == nil { - // Sometimes a paint event comes in before initial - // window size is set. Ignore it. - return - } - - // TODO: the paint.Event should have External: true. - w.Send(paint.Event{}) -} - -func sizeEvent(hwnd win32.HWND, e size.Event) { - theScreen.mu.Lock() - w := theScreen.windows[uintptr(hwnd)] - theScreen.mu.Unlock() - - if w.ctx == nil { - // This is the initial size event on window creation. - // Create an EGL surface and spin up a GL context. - if err := createEGLSurface(hwnd, w); err != nil { - panic(err) - } - go drawLoop(w) - } - - if !handleSizeEventsAtChannelReceive { - w.szMu.Lock() - w.sz = e - w.szMu.Unlock() - } - - w.Send(e) - - if handleSizeEventsAtChannelReceive { - return - } - - // Screen is dirty, generate a paint event. - // - // The sizeEvent function is called on the goroutine responsible for - // calling the GL worker.DoWork. When compiling with -tags gldebug, - // these GL calls are blocking (so we can read the error message), so - // to make progress they need to happen on another goroutine. - go func() { - // TODO: this call to Viewport is not right, but is very hard to - // do correctly with our async events channel model. We want - // the call to Viewport to be made the instant before the - // paint.Event is received. - w.glctxMu.Lock() - w.glctx.Viewport(0, 0, e.WidthPx, e.HeightPx) - w.glctx.ClearColor(0, 0, 0, 1) - w.glctx.Clear(gl.COLOR_BUFFER_BIT) - w.glctxMu.Unlock() - - w.Send(paint.Event{}) - }() -} - -func eglErr() error { - if ret, _, _ := eglGetError.Call(); ret != _EGL_SUCCESS { - return errors.New(eglErrString(ret)) - } - return nil -} - -func createEGLSurface(hwnd win32.HWND, w *windowImpl) error { - var displayAttribPlatforms = [][]eglInt{ - // Default - { - _EGL_PLATFORM_ANGLE_TYPE_ANGLE, - _EGL_PLATFORM_ANGLE_TYPE_DEFAULT_ANGLE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, - _EGL_NONE, - }, - // Direct3D 11 - { - _EGL_PLATFORM_ANGLE_TYPE_ANGLE, - _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, - _EGL_NONE, - }, - // Direct3D 9 - { - _EGL_PLATFORM_ANGLE_TYPE_ANGLE, - _EGL_PLATFORM_ANGLE_TYPE_D3D9_ANGLE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, - _EGL_NONE, - }, - // Direct3D 11 with WARP - // https://msdn.microsoft.com/en-us/library/windows/desktop/gg615082.aspx - { - _EGL_PLATFORM_ANGLE_TYPE_ANGLE, - _EGL_PLATFORM_ANGLE_TYPE_D3D11_ANGLE, - _EGL_PLATFORM_ANGLE_DEVICE_TYPE_ANGLE, - _EGL_PLATFORM_ANGLE_DEVICE_TYPE_WARP_ANGLE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MAJOR_ANGLE, _EGL_DONT_CARE, - _EGL_PLATFORM_ANGLE_MAX_VERSION_MINOR_ANGLE, _EGL_DONT_CARE, - _EGL_NONE, - }, - } - - dc, err := win32.GetDC(hwnd) - if err != nil { - return fmt.Errorf("win32.GetDC failed: %v", err) - } - - var display uintptr = _EGL_NO_DISPLAY - for i, displayAttrib := range displayAttribPlatforms { - lastTry := i == len(displayAttribPlatforms)-1 - - display, _, _ = eglGetPlatformDisplayEXT.Call( - _EGL_PLATFORM_ANGLE_ANGLE, - uintptr(dc), - uintptr(unsafe.Pointer(&displayAttrib[0])), - ) - - if display == _EGL_NO_DISPLAY { - if !lastTry { - continue - } - return fmt.Errorf("eglGetPlatformDisplayEXT failed: %v", eglErr()) - } - - if ret, _, _ := eglInitialize.Call(display, 0, 0); ret == 0 { - if !lastTry { - continue - } - return fmt.Errorf("eglInitialize failed: %v", eglErr()) - } - } - - eglBindAPI.Call(_EGL_OPENGL_ES_API) - if err := eglErr(); err != nil { - return err - } - - var numConfigs eglInt - var config eglConfig - ret, _, _ := eglChooseConfig.Call( - display, - uintptr(unsafe.Pointer(&rgb888[0])), - uintptr(unsafe.Pointer(&config)), - 1, - uintptr(unsafe.Pointer(&numConfigs)), - ) - if ret == 0 { - return fmt.Errorf("eglChooseConfig failed: %v", eglErr()) - } - if numConfigs <= 0 { - return errors.New("eglChooseConfig found no valid config") - } - - surface, _, _ := eglCreateWindowSurface.Call(display, uintptr(config), uintptr(hwnd), 0, 0) - if surface == _EGL_NO_SURFACE { - return fmt.Errorf("eglCreateWindowSurface failed: %v", eglErr()) - } - - contextAttribs := [...]eglInt{ - _EGL_CONTEXT_CLIENT_VERSION, 2, - _EGL_NONE, - } - context, _, _ := eglCreateContext.Call( - display, - uintptr(config), - _EGL_NO_CONTEXT, - uintptr(unsafe.Pointer(&contextAttribs[0])), - ) - if context == _EGL_NO_CONTEXT { - return fmt.Errorf("eglCreateContext failed: %v", eglErr()) - } - - eglSwapInterval.Call(display, 1) - - w.ctx = ctxWin32{ - ctx: context, - display: display, - surface: surface, - } - - return nil -} - -func surfaceCreate() error { - return errors.New("gldriver: surface creation not implemented on windows") -} diff --git a/shiny/driver/gldriver/window.go b/shiny/driver/gldriver/window.go deleted file mode 100644 index ea614f88..00000000 --- a/shiny/driver/gldriver/window.go +++ /dev/null @@ -1,398 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package gldriver - -import ( - "image" - "image/color" - "image/draw" - "sync" - - "github.com/oakmound/oak/v3/shiny/driver/internal/drawer" - "github.com/oakmound/oak/v3/shiny/driver/internal/event" - "github.com/oakmound/oak/v3/shiny/driver/internal/lifecycler" - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/image/math/f64" - "golang.org/x/mobile/event/lifecycle" - "golang.org/x/mobile/event/size" - "golang.org/x/mobile/gl" -) - -type windowImpl struct { - s *screenImpl - - // id is an OS-specific data structure for the window. - // - Cocoa: ScreenGLView* - // - X11: Window - // - Windows: win32.HWND - id uintptr - - // ctx is a C data structure for the GL context. - // - Cocoa: uintptr holding a NSOpenGLContext*. - // - X11: uintptr holding an EGLSurface. - // - Windows: ctxWin32 - ctx interface{} - - lifecycler lifecycler.State - // TODO: Delete the field below (and the useLifecycler constant), and use - // the field above for cocoa and win32. - lifecycleStage lifecycle.Stage // current stage - - event.Deque - publish chan struct{} - publishDone chan screen.PublishResult - drawDone chan struct{} - - // glctxMu is a mutex that enforces the atomicity of methods like - // Texture.Upload or Window.Draw that are conceptually one operation - // but are implemented by multiple OpenGL calls. OpenGL is a stateful - // API, so interleaving OpenGL calls from separate higher-level - // operations causes inconsistencies. - glctxMu sync.Mutex - glctx gl.Context - worker gl.Worker - // backBufferBound is whether the default Framebuffer, with ID 0, also - // known as the back buffer or the window's Framebuffer, is bound and its - // viewport is known to equal the window size. It can become false when we - // bind to a texture's Framebuffer or when the window size changes. - backBufferBound bool - - // szMu protects only sz. If you need to hold both glctxMu and szMu, the - // lock ordering is to lock glctxMu first (and unlock it last). - szMu sync.Mutex - sz size.Event -} - -// NextEvent implements the screen.EventDeque interface. -func (w *windowImpl) NextEvent() interface{} { - e := w.Deque.NextEvent() - if handleSizeEventsAtChannelReceive { - if sz, ok := e.(size.Event); ok { - w.glctxMu.Lock() - w.backBufferBound = false - w.szMu.Lock() - w.sz = sz - w.szMu.Unlock() - w.glctxMu.Unlock() - } - } - return e -} - -func (w *windowImpl) Release() { - // There are two ways a window can be closed: the Operating System or - // Desktop Environment can initiate (e.g. in response to a user clicking a - // red button), or the Go app can programatically close the window (by - // calling Window.Release). - // - // When the OS closes a window: - // - Cocoa: Obj-C's windowWillClose calls Go's windowClosing. - // - X11: the X11 server sends a WM_DELETE_WINDOW message. - // - Windows: TODO: implement and document this. - // - // This should send a lifecycle event (To: StageDead) to the Go app's event - // loop, which should respond by calling Window.Release (this method). - // Window.Release is where system resources are actually cleaned up. - // - // When Window.Release is called, the closeWindow call below: - // - Cocoa: calls Obj-C's performClose, which emulates the red button - // being clicked. (TODO: document how this actually cleans up - // resources??) - // - X11: calls C's XDestroyWindow. - // - Windows: TODO: implement and document this. - // - // On Cocoa, if these two approaches race, experiments suggest that the - // race is won by performClose (which is called serially on the main - // thread). Even if that isn't true, the windowWillClose handler is - // idempotent. - - theScreen.mu.Lock() - delete(theScreen.windows, w.id) - theScreen.mu.Unlock() - - closeWindow(w.id) -} - -func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { - originalSRMin := sr.Min - sr = sr.Intersect(src.Bounds()) - if sr.Empty() { - return - } - dp = dp.Add(sr.Min.Sub(originalSRMin)) - // TODO: keep a texture around for this purpose? - t, err := w.s.NewTexture(sr.Size()) - if err != nil { - panic(err) - } - t.Upload(image.Point{}, src, sr) - w.Draw(f64.Aff3{ - 1, 0, float64(dp.X), - 0, 1, float64(dp.Y), - }, t, t.Bounds(), draw.Src) - t.Release() -} - -func useOp(glctx gl.Context, op draw.Op) { - if op == draw.Over { - glctx.Enable(gl.BLEND) - glctx.BlendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA) - } else { - glctx.Disable(gl.BLEND) - } -} - -func (w *windowImpl) bindBackBuffer() { - w.szMu.Lock() - sz := w.sz - w.szMu.Unlock() - - w.backBufferBound = true - w.glctx.BindFramebuffer(gl.FRAMEBUFFER, gl.Framebuffer{Value: 0}) - w.glctx.Viewport(0, 0, sz.WidthPx, sz.HeightPx) -} - -func (w *windowImpl) fill(mvp f64.Aff3, src color.Color, op draw.Op) { - w.glctxMu.Lock() - defer w.glctxMu.Unlock() - - if !w.backBufferBound { - w.bindBackBuffer() - } - - doFill(w.s, w.glctx, mvp, src, op) -} - -func doFill(s *screenImpl, glctx gl.Context, mvp f64.Aff3, src color.Color, op draw.Op) { - useOp(glctx, op) - if !glctx.IsProgram(s.fill.program) { - p, err := compileProgram(glctx, fillVertexSrc, fillFragmentSrc) - if err != nil { - // TODO: initialize this somewhere else we can better handle the error. - panic(err.Error()) - } - s.fill.program = p - s.fill.pos = glctx.GetAttribLocation(p, "pos") - s.fill.mvp = glctx.GetUniformLocation(p, "mvp") - s.fill.color = glctx.GetUniformLocation(p, "color") - s.fill.quad = glctx.CreateBuffer() - - glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad) - glctx.BufferData(gl.ARRAY_BUFFER, quadCoords, gl.STATIC_DRAW) - } - glctx.UseProgram(s.fill.program) - - writeAff3(glctx, s.fill.mvp, mvp) - - r, g, b, a := src.RGBA() - glctx.Uniform4f( - s.fill.color, - float32(r)/65535, - float32(g)/65535, - float32(b)/65535, - float32(a)/65535, - ) - - glctx.BindBuffer(gl.ARRAY_BUFFER, s.fill.quad) - glctx.EnableVertexAttribArray(s.fill.pos) - glctx.VertexAttribPointer(s.fill.pos, 2, gl.FLOAT, false, 0, 0) - - glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) - - glctx.DisableVertexAttribArray(s.fill.pos) -} - -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - minX := float64(dr.Min.X) - minY := float64(dr.Min.Y) - maxX := float64(dr.Max.X) - maxY := float64(dr.Max.Y) - w.fill(w.mvp( - minX, minY, - maxX, minY, - minX, maxY, - ), src, op) -} - -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - minX := float64(sr.Min.X) - minY := float64(sr.Min.Y) - maxX := float64(sr.Max.X) - maxY := float64(sr.Max.Y) - w.fill(w.mvp( - src2dst[0]*minX+src2dst[1]*minY+src2dst[2], - src2dst[3]*minX+src2dst[4]*minY+src2dst[5], - src2dst[0]*maxX+src2dst[1]*minY+src2dst[2], - src2dst[3]*maxX+src2dst[4]*minY+src2dst[5], - src2dst[0]*minX+src2dst[1]*maxY+src2dst[2], - src2dst[3]*minX+src2dst[4]*maxY+src2dst[5], - ), src, op) -} - -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { - t := src.(*textureImpl) - sr = sr.Intersect(t.Bounds()) - if sr.Empty() { - return - } - - w.glctxMu.Lock() - defer w.glctxMu.Unlock() - - if !w.backBufferBound { - w.bindBackBuffer() - } - - useOp(w.glctx, op) - w.glctx.UseProgram(w.s.texture.program) - - // Start with src-space left, top, right and bottom. - srcL := float64(sr.Min.X) - srcT := float64(sr.Min.Y) - srcR := float64(sr.Max.X) - srcB := float64(sr.Max.Y) - // Transform to dst-space via the src2dst matrix, then to a MVP matrix. - writeAff3(w.glctx, w.s.texture.mvp, w.mvp( - src2dst[0]*srcL+src2dst[1]*srcT+src2dst[2], - src2dst[3]*srcL+src2dst[4]*srcT+src2dst[5], - src2dst[0]*srcR+src2dst[1]*srcT+src2dst[2], - src2dst[3]*srcR+src2dst[4]*srcT+src2dst[5], - src2dst[0]*srcL+src2dst[1]*srcB+src2dst[2], - src2dst[3]*srcL+src2dst[4]*srcB+src2dst[5], - )) - - // OpenGL's fragment shaders' UV coordinates run from (0,0)-(1,1), - // unlike vertex shaders' XY coordinates running from (-1,+1)-(+1,-1). - // - // We are drawing a rectangle PQRS, defined by two of its - // corners, onto the entire texture. The two quads may actually - // be equal, but in the general case, PQRS can be smaller. - // - // (0,0) +---------------+ (1,0) - // | P +-----+ Q | - // | | | | - // | S +-----+ R | - // (0,1) +---------------+ (1,1) - // - // The PQRS quad is always axis-aligned. First of all, convert - // from pixel space to texture space. - tw := float64(t.size.X) - th := float64(t.size.Y) - px := float64(sr.Min.X-0) / tw - py := float64(sr.Min.Y-0) / th - qx := float64(sr.Max.X-0) / tw - sy := float64(sr.Max.Y-0) / th - // Due to axis alignment, qy = py and sx = px. - // - // The simultaneous equations are: - // 0 + 0 + a02 = px - // 0 + 0 + a12 = py - // a00 + 0 + a02 = qx - // a10 + 0 + a12 = qy = py - // 0 + a01 + a02 = sx = px - // 0 + a11 + a12 = sy - writeAff3(w.glctx, w.s.texture.uvp, f64.Aff3{ - qx - px, 0, px, - 0, sy - py, py, - }) - - w.glctx.ActiveTexture(gl.TEXTURE0) - w.glctx.BindTexture(gl.TEXTURE_2D, t.id) - w.glctx.Uniform1i(w.s.texture.sample, 0) - - w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad) - w.glctx.EnableVertexAttribArray(w.s.texture.pos) - w.glctx.VertexAttribPointer(w.s.texture.pos, 2, gl.FLOAT, false, 0, 0) - - w.glctx.BindBuffer(gl.ARRAY_BUFFER, w.s.texture.quad) - w.glctx.EnableVertexAttribArray(w.s.texture.inUV) - w.glctx.VertexAttribPointer(w.s.texture.inUV, 2, gl.FLOAT, false, 0, 0) - - w.glctx.DrawArrays(gl.TRIANGLE_STRIP, 0, 4) - - w.glctx.DisableVertexAttribArray(w.s.texture.pos) - w.glctx.DisableVertexAttribArray(w.s.texture.inUV) -} - -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Copy(w, dp, src, sr, op) -} - -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Scale(w, dr, src, sr, op) -} - -func (w *windowImpl) mvp(tlx, tly, trx, try, blx, bly float64) f64.Aff3 { - w.szMu.Lock() - sz := w.sz - w.szMu.Unlock() - - return calcMVP(sz.WidthPx, sz.HeightPx, tlx, tly, trx, try, blx, bly) -} - -// calcMVP returns the Model View Projection matrix that maps the quadCoords -// unit square, (0, 0) to (1, 1), to a quad QV, such that QV in vertex shader -// space corresponds to the quad QP in pixel space, where QP is defined by -// three of its four corners - the arguments to this function. The three -// corners are nominally the top-left, top-right and bottom-left, but there is -// no constraint that e.g. tlx < trx. -// -// In pixel space, the window ranges from (0, 0) to (widthPx, heightPx). The -// Y-axis points downwards. -// -// In vertex shader space, the window ranges from (-1, +1) to (+1, -1), which -// is a 2-unit by 2-unit square. The Y-axis points upwards. -func calcMVP(widthPx, heightPx int, tlx, tly, trx, try, blx, bly float64) f64.Aff3 { - // Convert from pixel coords to vertex shader coords. - invHalfWidth := +2 / float64(widthPx) - invHalfHeight := -2 / float64(heightPx) - tlx = tlx*invHalfWidth - 1 - tly = tly*invHalfHeight + 1 - trx = trx*invHalfWidth - 1 - try = try*invHalfHeight + 1 - blx = blx*invHalfWidth - 1 - bly = bly*invHalfHeight + 1 - - // The resultant affine matrix: - // - maps (0, 0) to (tlx, tly). - // - maps (1, 0) to (trx, try). - // - maps (0, 1) to (blx, bly). - return f64.Aff3{ - trx - tlx, blx - tlx, tlx, - try - tly, bly - tly, tly, - } -} - -func (w *windowImpl) Publish() screen.PublishResult { - // gl.Flush is a lightweight (on modern GL drivers) blocking call - // that ensures all GL functions pending in the gl package have - // been passed onto the GL driver before the app package attempts - // to swap the screen buffer. - // - // This enforces that the final receive (for this paint cycle) on - // gl.WorkAvailable happens before the send on publish. - w.glctxMu.Lock() - w.glctx.Flush() - w.glctxMu.Unlock() - - w.publish <- struct{}{} - res := <-w.publishDone - - select { - case w.drawDone <- struct{}{}: - default: - } - - return res -} - -func (w *windowImpl) MoveWindow(x, y, width, height int32) error { - return moveWindow(w, screen.WindowGenerator{ - X: x, - Y: y, - Width: int(width), - Height: int(height), - }) -} diff --git a/shiny/driver/gldriver/x11.c b/shiny/driver/gldriver/x11.c deleted file mode 100644 index 4edd6640..00000000 --- a/shiny/driver/gldriver/x11.c +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux,!android openbsd - -#include "_cgo_export.h" -#include -#include -#include -#include - -Atom net_wm_name; -Atom utf8_string; -Atom wm_delete_window; -Atom wm_protocols; -Atom wm_take_focus; - -EGLConfig e_config; -EGLContext e_ctx; -EGLDisplay e_dpy; -Colormap x_colormap; -Display *x_dpy; -XVisualInfo *x_visual_info; -Window x_root; - -// TODO: share code with eglErrString -char * -eglGetErrorStr() { - switch (eglGetError()) { - case EGL_SUCCESS: - return "EGL_SUCCESS"; - case EGL_NOT_INITIALIZED: - return "EGL_NOT_INITIALIZED"; - case EGL_BAD_ACCESS: - return "EGL_BAD_ACCESS"; - case EGL_BAD_ALLOC: - return "EGL_BAD_ALLOC"; - case EGL_BAD_ATTRIBUTE: - return "EGL_BAD_ATTRIBUTE"; - case EGL_BAD_CONFIG: - return "EGL_BAD_CONFIG"; - case EGL_BAD_CONTEXT: - return "EGL_BAD_CONTEXT"; - case EGL_BAD_CURRENT_SURFACE: - return "EGL_BAD_CURRENT_SURFACE"; - case EGL_BAD_DISPLAY: - return "EGL_BAD_DISPLAY"; - case EGL_BAD_MATCH: - return "EGL_BAD_MATCH"; - case EGL_BAD_NATIVE_PIXMAP: - return "EGL_BAD_NATIVE_PIXMAP"; - case EGL_BAD_NATIVE_WINDOW: - return "EGL_BAD_NATIVE_WINDOW"; - case EGL_BAD_PARAMETER: - return "EGL_BAD_PARAMETER"; - case EGL_BAD_SURFACE: - return "EGL_BAD_SURFACE"; - case EGL_CONTEXT_LOST: - return "EGL_CONTEXT_LOST"; - } - return "unknown EGL error"; -} - -void -startDriver() { - x_dpy = XOpenDisplay(NULL); - if (!x_dpy) { - fprintf(stderr, "XOpenDisplay failed\n"); - exit(1); - } - e_dpy = eglGetDisplay(x_dpy); - if (!e_dpy) { - fprintf(stderr, "eglGetDisplay failed: %s\n", eglGetErrorStr()); - exit(1); - } - EGLint e_major, e_minor; - if (!eglInitialize(e_dpy, &e_major, &e_minor)) { - fprintf(stderr, "eglInitialize failed: %s\n", eglGetErrorStr()); - exit(1); - } - if (!eglBindAPI(EGL_OPENGL_ES_API)) { - fprintf(stderr, "eglBindAPI failed: %s\n", eglGetErrorStr()); - exit(1); - } - - static const EGLint attribs[] = { - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SURFACE_TYPE, EGL_WINDOW_BIT, - EGL_BLUE_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_RED_SIZE, 8, - EGL_DEPTH_SIZE, 16, - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_NONE - }; - EGLint num_configs; - if (!eglChooseConfig(e_dpy, attribs, &e_config, 1, &num_configs)) { - fprintf(stderr, "eglChooseConfig failed: %s\n", eglGetErrorStr()); - exit(1); - } - EGLint vid; - if (!eglGetConfigAttrib(e_dpy, e_config, EGL_NATIVE_VISUAL_ID, &vid)) { - fprintf(stderr, "eglGetConfigAttrib failed: %s\n", eglGetErrorStr()); - exit(1); - } - - XVisualInfo visTemplate; - visTemplate.visualid = vid; - int num_visuals; - x_visual_info = XGetVisualInfo(x_dpy, VisualIDMask, &visTemplate, &num_visuals); - if (!x_visual_info) { - fprintf(stderr, "XGetVisualInfo failed\n"); - exit(1); - } - - x_root = RootWindow(x_dpy, DefaultScreen(x_dpy)); - x_colormap = XCreateColormap(x_dpy, x_root, x_visual_info->visual, AllocNone); - if (!x_colormap) { - fprintf(stderr, "XCreateColormap failed\n"); - exit(1); - } - - static const EGLint ctx_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 3, - EGL_NONE - }; - e_ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs); - if (!e_ctx) { - fprintf(stderr, "eglCreateContext failed: %s\n", eglGetErrorStr()); - exit(1); - } - - net_wm_name = XInternAtom(x_dpy, "_NET_WM_NAME", False); - utf8_string = XInternAtom(x_dpy, "UTF8_STRING", False); - wm_delete_window = XInternAtom(x_dpy, "WM_DELETE_WINDOW", False); - wm_protocols = XInternAtom(x_dpy, "WM_PROTOCOLS", False); - wm_take_focus = XInternAtom(x_dpy, "WM_TAKE_FOCUS", False); - - const int key_lo = 8; - const int key_hi = 255; - int keysyms_per_keycode; - KeySym *keysyms = XGetKeyboardMapping(x_dpy, key_lo, key_hi-key_lo+1, &keysyms_per_keycode); - if (keysyms_per_keycode < 2) { - fprintf(stderr, "XGetKeyboardMapping returned too few keysyms per keycode: %d\n", keysyms_per_keycode); - exit(1); - } - int k; - for (k = key_lo; k <= key_hi; k++) { - onKeysym(k, - keysyms[(k-key_lo)*keysyms_per_keycode + 0], - keysyms[(k-key_lo)*keysyms_per_keycode + 1]); - } - //TODO: use GetModifierMapping to figure out which modifier is the numlock modifier. -} - -void -processEvents() { - while (XPending(x_dpy)) { - XEvent ev; - XNextEvent(x_dpy, &ev); - switch (ev.type) { - case KeyPress: - case KeyRelease: - onKey(ev.xkey.window, ev.xkey.state, ev.xkey.keycode, ev.type == KeyPress ? 1 : 2); - break; - case ButtonPress: - case ButtonRelease: - onMouse(ev.xbutton.window, ev.xbutton.x, ev.xbutton.y, ev.xbutton.state, ev.xbutton.button, - ev.type == ButtonPress ? 1 : 2); - break; - case MotionNotify: - onMouse(ev.xmotion.window, ev.xmotion.x, ev.xmotion.y, ev.xmotion.state, 0, 0); - break; - case FocusIn: - case FocusOut: - onFocus(ev.xmotion.window, ev.type == FocusIn); - break; - case Expose: - // A non-zero Count means that there are more expose events coming. For - // example, a non-rectangular exposure (e.g. from a partially overlapped - // window) will result in multiple expose events whose dirty rectangles - // combine to define the dirty region. Go's paint events do not provide - // dirty regions, so we only pass on the final X11 expose event. - if (ev.xexpose.count == 0) { - onExpose(ev.xexpose.window); - } - break; - case ConfigureNotify: - onConfigure(ev.xconfigure.window, ev.xconfigure.x, ev.xconfigure.y, - ev.xconfigure.width, ev.xconfigure.height, - DisplayWidth(x_dpy, DefaultScreen(x_dpy)), - DisplayWidthMM(x_dpy, DefaultScreen(x_dpy))); - break; - case ClientMessage: - if ((ev.xclient.message_type != wm_protocols) || (ev.xclient.format != 32)) { - break; - } - Atom a = ev.xclient.data.l[0]; - if (a == wm_delete_window) { - onDeleteWindow(ev.xclient.window); - } else if (a == wm_take_focus) { - XSetInputFocus(x_dpy, ev.xclient.window, RevertToParent, ev.xclient.data.l[1]); - } - break; - } - } -} - -void -makeCurrent(uintptr_t surface) { - EGLSurface surf = (EGLSurface)(surface); - if (!eglMakeCurrent(e_dpy, surf, surf, e_ctx)) { - fprintf(stderr, "eglMakeCurrent failed: %s\n", eglGetErrorStr()); - exit(1); - } -} - -void -swapBuffers(uintptr_t surface) { - EGLSurface surf = (EGLSurface)(surface); - if (!eglSwapBuffers(e_dpy, surf)) { - fprintf(stderr, "eglSwapBuffers failed: %s\n", eglGetErrorStr()); - exit(1); - } -} - -void -doCloseWindow(uintptr_t id) { - Window win = (Window)(id); - XDestroyWindow(x_dpy, win); -} - -uintptr_t -doNewWindow(int x, int y, int width, int height, char* title, int title_len) { - XSetWindowAttributes attr; - attr.colormap = x_colormap; - attr.event_mask = - KeyPressMask | - KeyReleaseMask | - ButtonPressMask | - ButtonReleaseMask | - PointerMotionMask | - ExposureMask | - StructureNotifyMask | - FocusChangeMask; - - Window win = XCreateWindow( - x_dpy, x_root, x, y, width, height, 0, x_visual_info->depth, InputOutput, - x_visual_info->visual, CWColormap | CWEventMask, &attr); - - XSizeHints sizehints; - sizehints.width = width; - sizehints.height = height; - sizehints.flags = USSize; - XSetNormalHints(x_dpy, win, &sizehints); - - Atom atoms[2]; - atoms[0] = wm_delete_window; - atoms[1] = wm_take_focus; - XSetWMProtocols(x_dpy, win, atoms, 2); - - XSetStandardProperties(x_dpy, win, "", "App", None, (char **)NULL, 0, &sizehints); - XChangeProperty(x_dpy, win, net_wm_name, utf8_string, 8, PropModeReplace, title, title_len); - - return win; -} - -void -doConfigureWindow(uintptr_t id, int x, int y, int width, int height) { - Window win = (Window)(id); - unsigned int mask = CWX | CWY | CWWidth | CWHeight; - XWindowChanges values = {x,y,width,height,0,0,0}; - - XConfigureWindow(x_dpy, win, mask, &values); -} - -uintptr_t -doShowWindow(uintptr_t id) { - Window win = (Window)(id); - XMapWindow(x_dpy, win); - EGLSurface surf = eglCreateWindowSurface(e_dpy, e_config, win, NULL); - if (!surf) { - fprintf(stderr, "eglCreateWindowSurface failed: %s\n", eglGetErrorStr()); - exit(1); - } - return (uintptr_t)(surf); -} - -uintptr_t -surfaceCreate() { - static const EGLint ctx_attribs[] = { - EGL_CONTEXT_CLIENT_VERSION, 3, - EGL_NONE - }; - EGLContext ctx = eglCreateContext(e_dpy, e_config, EGL_NO_CONTEXT, ctx_attribs); - if (!ctx) { - fprintf(stderr, "surface eglCreateContext failed: %s\n", eglGetErrorStr()); - return 0; - } - - static const EGLint cfg_attribs[] = { - EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL_SURFACE_TYPE, EGL_PBUFFER_BIT, - EGL_BLUE_SIZE, 8, - EGL_GREEN_SIZE, 8, - EGL_RED_SIZE, 8, - EGL_DEPTH_SIZE, 16, - EGL_CONFIG_CAVEAT, EGL_NONE, - EGL_NONE - }; - EGLConfig cfg; - EGLint num_configs; - if (!eglChooseConfig(e_dpy, cfg_attribs, &cfg, 1, &num_configs)) { - fprintf(stderr, "gldriver: surface eglChooseConfig failed: %s\n", eglGetErrorStr()); - return 0; - } - - // TODO: use the size of the monitor as a bound for texture size. - static const EGLint attribs[] = { - EGL_WIDTH, 4096, - EGL_HEIGHT, 3072, - EGL_NONE - }; - EGLSurface surface = eglCreatePbufferSurface(e_dpy, cfg, attribs); - if (!surface) { - fprintf(stderr, "gldriver: surface eglCreatePbufferSurface failed: %s\n", eglGetErrorStr()); - return 0; - } - - if (!eglMakeCurrent(e_dpy, surface, surface, ctx)) { - fprintf(stderr, "gldriver: surface eglMakeCurrent failed: %s\n", eglGetErrorStr()); - return 0; - } - - return (uintptr_t)surface; -} diff --git a/shiny/driver/gldriver/x11.go b/shiny/driver/gldriver/x11.go deleted file mode 100644 index 23e6cc81..00000000 --- a/shiny/driver/gldriver/x11.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2015 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -// +build linux,!android openbsd - -package gldriver - -/* -#cgo LDFLAGS: -lEGL -lGLESv2 -lX11 - -#include -#include -#include - -char *eglGetErrorStr(); -void startDriver(); -void processEvents(); -void makeCurrent(uintptr_t ctx); -void swapBuffers(uintptr_t ctx); -void doCloseWindow(uintptr_t id); -void doConfigureWindow(uintptr_t id, int x, int y, int width, int height); -uintptr_t doNewWindow(int x, int y, int width, int height, char* title, int title_len); -uintptr_t doShowWindow(uintptr_t id); -uintptr_t surfaceCreate(); -*/ -import "C" -import ( - "errors" - "runtime" - "time" - "unsafe" - - "github.com/oakmound/oak/v3/shiny/driver/internal/x11key" - "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/mobile/event/key" - "golang.org/x/mobile/event/mouse" - "golang.org/x/mobile/event/paint" - "golang.org/x/mobile/event/size" - "golang.org/x/mobile/geom" - "golang.org/x/mobile/gl" -) - -const useLifecycler = true - -const handleSizeEventsAtChannelReceive = true - -var theKeysyms x11key.KeysymTable - -func init() { - // It might not be necessary, but it probably doesn't hurt to try to make - // 'the main thread' be 'the X11 / OpenGL thread'. - runtime.LockOSThread() -} - -func newWindow(opts screen.WindowGenerator) (uintptr, error) { - width, height := optsSize(opts) - - title := opts.Title - ctitle := C.CString(title) - defer C.free(unsafe.Pointer(ctitle)) - - retc := make(chan uintptr) - uic <- uiClosure{ - f: func() uintptr { - return uintptr(C.doNewWindow(C.int(opts.X), C.int(opts.Y), C.int(width), C.int(height), ctitle, C.int(len(title)))) - }, - retc: retc, - } - return <-retc, nil -} - -func moveWindow(w *windowImpl, opts screen.WindowGenerator) error { - width, height := optsSize(opts) - C.doConfigureWindow(C.uintptr_t(w.id), C.int(opts.X), C.int(opts.Y), C.int(width), C.int(height)) - return nil -} - -func initWindow(w *windowImpl) { - w.glctx, w.worker = glctx, worker -} - -func showWindow(w *windowImpl) { - retc := make(chan uintptr) - uic <- uiClosure{ - f: func() uintptr { - return uintptr(C.doShowWindow(C.uintptr_t(w.id))) - }, - retc: retc, - } - w.ctx = <-retc - go drawLoop(w) -} - -func closeWindow(id uintptr) { - uic <- uiClosure{ - f: func() uintptr { - C.doCloseWindow(C.uintptr_t(id)) - return 0 - }, - } -} - -func drawLoop(w *windowImpl) { - glcontextc <- w.ctx.(uintptr) - go func() { - for range w.publish { - publishc <- w - } - }() -} - -var ( - glcontextc = make(chan uintptr) - publishc = make(chan *windowImpl) - uic = make(chan uiClosure) - - // TODO: don't assume that there is only one window, and hence only - // one (global) GL context. - // - // TODO: should we be able to make a shiny.Texture before having a - // shiny.Window's GL context? Should something like gl.IsProgram be a - // method instead of a function, and have each shiny.Window have its own - // gl.Context? - glctx gl.Context - worker gl.Worker -) - -// uiClosure is a closure to be run on C's UI thread. -type uiClosure struct { - f func() uintptr - retc chan uintptr -} - -func main(f func(screen.Screen)) error { - if gl.Version() == "GL_ES_2_0" { - return errors.New("gldriver: ES 3 required on X11") - } - C.startDriver() - glctx, worker = gl.NewContext() - - closec := make(chan struct{}) - go func() { - f(theScreen) - close(closec) - }() - - // heartbeat is a channel that, at regular intervals, directs the select - // below to also consider X11 events, not just Go events (channel - // communications). - // - // TODO: select instead of poll. Note that knowing whether to call - // C.processEvents needs to select on a file descriptor, and the other - // cases below select on Go channels. - heartbeat := time.NewTicker(time.Second / 60) - workAvailable := worker.WorkAvailable() - - for { - select { - case <-closec: - return nil - case ctx := <-glcontextc: - // TODO: do we need to synchronize with seeing a size event for - // this window's context before or after calling makeCurrent? - // Otherwise, are we racing with the gl.Viewport call? I've - // occasionally seen a stale viewport, if the window manager sets - // the window width and height to something other than that - // requested by XCreateWindow, but it's not easily reproducible. - C.makeCurrent(C.uintptr_t(ctx)) - case w := <-publishc: - C.swapBuffers(C.uintptr_t(w.ctx.(uintptr))) - w.publishDone <- screen.PublishResult{} - case req := <-uic: - ret := req.f() - if req.retc != nil { - req.retc <- ret - } - case <-heartbeat.C: - C.processEvents() - case <-workAvailable: - worker.DoWork() - } - } -} - -//export onExpose -func onExpose(id uintptr) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - - w.Send(paint.Event{External: true}) -} - -//export onKeysym -func onKeysym(k, unshifted, shifted uint32) { - theKeysyms[k][0] = unshifted - theKeysyms[k][1] = shifted -} - -//export onKey -func onKey(id uintptr, state uint16, detail, dir uint8) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - - r, c := theKeysyms.Lookup(detail, state, 0) - w.Send(key.Event{ - Rune: r, - Code: c, - Modifiers: x11key.KeyModifiers(state), - Direction: key.Direction(dir), - }) -} - -//export onMouse -func onMouse(id uintptr, x, y int32, state uint16, button, dir uint8) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - - // TODO: should a mouse.Event have a separate MouseModifiers field, for - // which buttons are pressed during a mouse move? - btn := mouse.Button(button) - switch btn { - case 4: - btn = mouse.ButtonWheelUp - case 5: - btn = mouse.ButtonWheelDown - case 6: - btn = mouse.ButtonWheelLeft - case 7: - btn = mouse.ButtonWheelRight - } - if btn.IsWheel() { - if dir != uint8(mouse.DirPress) { - return - } - dir = uint8(mouse.DirStep) - } - w.Send(mouse.Event{ - X: float32(x), - Y: float32(y), - Button: btn, - Modifiers: x11key.KeyModifiers(state), - Direction: mouse.Direction(dir), - }) -} - -//export onFocus -func onFocus(id uintptr, focused bool) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - - w.lifecycler.SetFocused(focused) - w.lifecycler.SendEvent(w, w.glctx) -} - -//export onConfigure -func onConfigure(id uintptr, x, y, width, height, displayWidth, displayWidthMM int32) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - - w.lifecycler.SetVisible(x+width > 0 && y+height > 0) - w.lifecycler.SendEvent(w, w.glctx) - - const ( - mmPerInch = 25.4 - ptPerInch = 72 - ) - pixelsPerMM := float32(displayWidth) / float32(displayWidthMM) - w.Send(size.Event{ - WidthPx: int(width), - HeightPx: int(height), - WidthPt: geom.Pt(width), - HeightPt: geom.Pt(height), - PixelsPerPt: pixelsPerMM * mmPerInch / ptPerInch, - }) -} - -//export onDeleteWindow -func onDeleteWindow(id uintptr) { - theScreen.mu.Lock() - w := theScreen.windows[id] - theScreen.mu.Unlock() - - if w == nil { - return - } - - w.lifecycler.SetDead(true) - w.lifecycler.SendEvent(w, w.glctx) -} - -func surfaceCreate() error { - if C.surfaceCreate() == 0 { - return errors.New("gldriver: surface creation failed") - } - return nil -} diff --git a/shiny/driver/internal/drawer/drawer.go b/shiny/driver/internal/drawer/drawer.go index e981eb3f..c9bd4d21 100644 --- a/shiny/driver/internal/drawer/drawer.go +++ b/shiny/driver/internal/drawer/drawer.go @@ -15,7 +15,7 @@ import ( // Copy implements the Copy method of the screen.Drawer interface by calling // the Draw method of that same interface. -func Copy(dst screen.Drawer, dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { +func Copy(dst screen.SimpleDrawer, dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { dst.Draw(f64.Aff3{ 1, 0, float64(dp.X - sr.Min.X), 0, 1, float64(dp.Y - sr.Min.Y), @@ -24,7 +24,7 @@ func Copy(dst screen.Drawer, dp image.Point, src screen.Texture, sr image.Rectan // Scale implements the Scale method of the screen.Drawer interface by calling // the Draw method of that same interface. -func Scale(dst screen.Drawer, dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func Scale(dst screen.SimpleDrawer, dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { rx := float64(dr.Dx()) / float64(sr.Dx()) ry := float64(dr.Dy()) / float64(sr.Dy()) dst.Draw(f64.Aff3{ diff --git a/shiny/driver/internal/win32/cursor.go b/shiny/driver/internal/win32/cursor.go index ecf0ef1e..61e75628 100644 --- a/shiny/driver/internal/win32/cursor.go +++ b/shiny/driver/internal/win32/cursor.go @@ -1,3 +1,6 @@ +//go:build windows +// +build windows + package win32 import "sync" diff --git a/shiny/driver/internal/win32/win32.go b/shiny/driver/internal/win32/win32.go index 9dfa55ec..8cd3dc7a 100644 --- a/shiny/driver/internal/win32/win32.go +++ b/shiny/driver/internal/win32/win32.go @@ -126,11 +126,11 @@ func ResizeClientRect(hwnd HWND, opts screen.WindowGenerator) error { h := (wr.Bottom - wr.Top) - (cr.Bottom - int32(opts.Height)) x := wr.Left if opts.X != 0 { - x = opts.X + x = int32(opts.X) } y := wr.Top if opts.Y != 0 { - y = opts.Y + y = int32(opts.Y) } return MoveWindow(hwnd, x, y, w, h, false) } diff --git a/shiny/driver/internal/win32/zsyscall_windows.go b/shiny/driver/internal/win32/zsyscall_windows.go index 26814887..9f2d2e4d 100644 --- a/shiny/driver/internal/win32/zsyscall_windows.go +++ b/shiny/driver/internal/win32/zsyscall_windows.go @@ -42,7 +42,7 @@ var ( ) var ( - procShell_NotifyIconW = modshell32.NewProc("Shell_NotifyIconW") + procShell_NotifyIconW = modshell32.NewProc("Shell_NotifyIconW") procRegisterClass = moduser32.NewProc("RegisterClassW") procIsZoomed = moduser32.NewProc("IsZoomed") procLoadIcon = moduser32.NewProc("LoadIconW") @@ -60,6 +60,7 @@ var ( procPostMessage = moduser32.NewProc("PostMessageW") procSetWindowText = moduser32.NewProc("SetWindowTextW") procGetWindowRect = moduser32.NewProc("GetWindowRect") + procGetWindow = moduser32.NewProc("GetWindow") procMoveWindow = moduser32.NewProc("MoveWindow") procScreenToClient = moduser32.NewProc("ScreenToClient") procSetWindowLong = moduser32.NewProc("SetWindowLongW") @@ -180,6 +181,11 @@ func DestroyWindow(hwnd HWND) bool { return ret != 0 } +const ( + ICON_BIG = 1 + ICON_SMALL = 0 +) + func DefWindowProc(hwnd HWND, msg uint32, wParam, lParam uintptr) (uintptr, error) { ret, _, err := procDefWindowProc.Call( uintptr(hwnd), diff --git a/shiny/driver/internal/x11/x11.go b/shiny/driver/internal/x11/x11.go index 3584f00b..b85f17c0 100644 --- a/shiny/driver/internal/x11/x11.go +++ b/shiny/driver/internal/x11/x11.go @@ -11,7 +11,7 @@ import ( "github.com/BurntSushi/xgbutil/motif" ) -func MoveWindow(xc *xgb.Conn, xw xproto.Window, x, y, width, height int32) (int32, int32, int32, int32) { +func MoveWindow(xc *xgb.Conn, xw xproto.Window, x, y, width, height int) (int, int, int, int) { vals := []uint32{} flags := xproto.ConfigWindowHeight | @@ -51,10 +51,22 @@ func SetFullScreen(xutil *xgbutil.XUtil, win xproto.Window, fullscreen bool) err return ewmh.WmStateReq(xutil, win, action, "_NET_WM_STATE_FULLSCREEN") } +func ToggleTopMost(xutil *xgbutil.XUtil, win xproto.Window) error { + return ewmh.WmStateReq(xutil, win, ewmh.StateToggle, "_NET_WM_STATE_ABOVE") +} + +func SetTopMost(xutil *xgbutil.XUtil, win xproto.Window, topMost bool) error { + action := ewmh.StateRemove + if topMost { + action = ewmh.StateAdd + } + return ewmh.WmStateReq(xutil, win, action, "_NET_WM_STATE_ABOVE") +} + func SetBorderless(xutil *xgbutil.XUtil, win xproto.Window, borderless bool) error { hints := &motif.Hints{ - Flags: motif.HintDecorations, - Decoration: motif.DecorationNone, + Flags: motif.HintDecorations, + Decoration: motif.DecorationNone, } if !borderless { hints.Decoration = motif.DecorationAll diff --git a/shiny/driver/jsdriver/screen.go b/shiny/driver/jsdriver/screen.go index 806cfaaf..1b8b379c 100644 --- a/shiny/driver/jsdriver/screen.go +++ b/shiny/driver/jsdriver/screen.go @@ -18,7 +18,7 @@ func Main(f func(screen.Screen)) { } type screenImpl struct { - windows []*windowImpl + windows []*Window } func (s *screenImpl) NewImage(size image.Point) (screen.Image, error) { @@ -42,7 +42,7 @@ func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, erro } cvs := NewCanvas2d(opts.Width, opts.Height) - w := &windowImpl{ + w := &Window{ cvs: cvs, screen: s, } diff --git a/shiny/driver/jsdriver/window.go b/shiny/driver/jsdriver/window.go index 111f24fc..78389d8a 100644 --- a/shiny/driver/jsdriver/window.go +++ b/shiny/driver/jsdriver/window.go @@ -5,41 +5,33 @@ package jsdriver import ( "image" - "image/color" "image/draw" "syscall/js" "github.com/oakmound/oak/v3/shiny/driver/internal/event" "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/image/math/f64" "golang.org/x/mobile/event/key" "golang.org/x/mobile/event/mouse" ) -type windowImpl struct { +type Window struct { screen *screenImpl cvs *Canvas2D event.Deque } -func (w *windowImpl) Release() {} -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) {} -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Release() {} +func (w *Window) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { rgba := src.(*textureImpl).rgba js.CopyBytesToJS(w.cvs.copybuff, rgba.Pix) w.cvs.imgData.Get("data").Call("set", w.cvs.copybuff) w.cvs.ctx.Call("putImageData", w.cvs.imgData, 0, 0) } -func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) {} -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {} +func (w *Window) Upload(dp image.Point, src screen.Image, sr image.Rectangle) {} -func (w *windowImpl) Publish() screen.PublishResult { - return screen.PublishResult{} -} +func (w *Window) Publish() {} -func (w *windowImpl) sendMouseEvent(mouseEvent js.Value, dir mouse.Direction) { +func (w *Window) sendMouseEvent(mouseEvent js.Value, dir mouse.Direction) { x, y := mouseEvent.Get("offsetX"), mouseEvent.Get("offsetY") button := mouseEvent.Get("button") var mButton mouse.Button @@ -61,7 +53,7 @@ func (w *windowImpl) sendMouseEvent(mouseEvent js.Value, dir mouse.Direction) { }) } -func (w *windowImpl) sendKeyEvent(keyEvent js.Value, dir key.Direction) { +func (w *Window) sendKeyEvent(keyEvent js.Value, dir key.Direction) { var mods key.Modifiers if keyEvent.Get("shiftKey").Bool() { mods |= key.ModShift diff --git a/shiny/driver/mtldriver/mtldriver.go b/shiny/driver/mtldriver/mtldriver.go index 68cebe79..93cd724e 100644 --- a/shiny/driver/mtldriver/mtldriver.go +++ b/shiny/driver/mtldriver/mtldriver.go @@ -106,10 +106,15 @@ func main(f func(screen.Screen)) error { case req := <-glfwChans.updateCh: // this is not functionalized to prevent methods from accidentally // calling this outside of the main thread + if req.title != nil { + req.window.SetTitle(*req.title) + } + for _, atr := range req.attribs { + req.window.SetAttrib(atr.key, atr.val) + } if req.setPos { req.window.SetPos(int(req.x), int(req.y)) req.window.SetSize(int(req.width), int(req.height)) - req.respCh <- struct{}{} } if req.setBorderless != nil { if *req.setBorderless { @@ -162,6 +167,8 @@ type updateWindowReq struct { setBorderless *bool setPos bool x, y, width, height int + title *string + attribs []attribPair respCh chan struct{} } @@ -199,7 +206,7 @@ func newWindow(device mtl.Device, chans windowRequestChannels, opts screen.Windo window.SetAttrib(glfw.Decorated, 0) } - w := &windowImpl{ + w := &Window{ device: device, window: window, chans: chans, diff --git a/shiny/driver/mtldriver/window.go b/shiny/driver/mtldriver/window.go index 30121c9e..11ecfc39 100644 --- a/shiny/driver/mtldriver/window.go +++ b/shiny/driver/mtldriver/window.go @@ -16,12 +16,11 @@ import ( "github.com/oakmound/oak/v3/shiny/driver/internal/event" "github.com/oakmound/oak/v3/shiny/driver/internal/lifecycler" "github.com/oakmound/oak/v3/shiny/driver/mtldriver/internal/coreanim" - "github.com/oakmound/oak/v3/shiny/screen" "golang.org/x/mobile/event/size" ) -// windowImpl implements screen.Window. -type windowImpl struct { +// Window implements screen.Window. +type Window struct { device mtl.Device window *glfw.Window chans windowRequestChannels @@ -42,12 +41,12 @@ type windowImpl struct { x, y int } -func (w *windowImpl) HideCursor() error { +func (w *Window) HideCursor() error { w.window.SetInputMode(glfw.CursorMode, glfw.CursorHidden) return nil } -func (w *windowImpl) SetBorderless(borderless bool) error { +func (w *Window) SetBorderless(borderless bool) error { if w.borderless == borderless { return nil } @@ -67,7 +66,7 @@ func (w *windowImpl) SetBorderless(borderless bool) error { return nil } -func (w *windowImpl) SetFullScreen(full bool) error { +func (w *Window) SetFullScreen(full bool) error { if w.fullscreen == full { return nil } @@ -90,12 +89,12 @@ func (w *windowImpl) SetFullScreen(full bool) error { return nil } -func (w *windowImpl) MoveWindow(x, y, width, height int32) error { +func (w *Window) MoveWindow(x, y, width, height int) error { respCh := make(chan struct{}) - w.x = int(x) - w.y = int(y) - w.w = int(width) - w.h = int(height) + w.x = x + w.y = y + w.w = width + w.h = height w.chans.updateCh <- updateWindowReq{ window: w.window, setPos: true, @@ -110,11 +109,11 @@ func (w *windowImpl) MoveWindow(x, y, width, height int32) error { return nil } -func (w *windowImpl) GetCursorPosition() (x, y float64) { +func (w *Window) GetCursorPosition() (x, y float64) { return w.window.GetCursorPos() } -func (w *windowImpl) Release() { +func (w *Window) Release() { respCh := make(chan struct{}) w.chans.releaseCh <- releaseWindowReq{ window: w.window, @@ -124,7 +123,49 @@ func (w *windowImpl) Release() { <-respCh } -func (w *windowImpl) NextEvent() interface{} { +func (w *Window) SetTitle(title string) error { + respCh := make(chan struct{}) + w.chans.updateCh <- updateWindowReq{ + window: w.window, + title: &title, + respCh: respCh, + } + glfw.PostEmptyEvent() // Break main loop out of glfw.WaitEvents so it can receive on releaseWindowCh. + <-respCh + return nil +} + +type attribPair struct { + key glfw.Hint + val int +} + +func (w *Window) SetTopMost(topMost bool) error { + respCh := make(chan struct{}) + val := glfw.True + if !topMost { + val = glfw.False + } + w.chans.updateCh <- updateWindowReq{ + window: w.window, + attribs: []attribPair{{ + key: glfw.Floating, + val: val, + }}, + respCh: respCh, + } + glfw.PostEmptyEvent() // Break main loop out of glfw.WaitEvents so it can receive on releaseWindowCh. + <-respCh + return nil +} + +// BUG: this doesn't work, and it doesn't error either +func (w *Window) SetIcon(img image.Image) error { + w.window.SetIcon([]image.Image{img}) + return nil +} + +func (w *Window) NextEvent() interface{} { e := w.Deque.NextEvent() if sz, ok := e.(size.Event); ok { // TODO(dmitshur): this is the best place/time/frequency to do this @@ -143,7 +184,7 @@ func (w *windowImpl) NextEvent() interface{} { return e } -func (w *windowImpl) Publish() screen.PublishResult { +func (w *Window) Publish() { // Copy w.rgba pixels into a texture. region := mtl.RegionMake2D(0, 0, w.texture.Width, w.texture.Height) bytesPerRow := 4 * w.texture.Width @@ -152,7 +193,7 @@ func (w *windowImpl) Publish() screen.PublishResult { drawable, err := w.ml.NextDrawable() if err != nil { log.Println("Window.Publish: couldn't get the next drawable:", err) - return screen.PublishResult{} + return } cb := w.cq.MakeCommandBuffer() @@ -171,5 +212,5 @@ func (w *windowImpl) Publish() screen.PublishResult { cb.PresentDrawable(drawable) cb.Commit() - return screen.PublishResult{} + return } diff --git a/shiny/driver/mtldriver/window_amd64.go b/shiny/driver/mtldriver/window_amd64.go index f8dafad9..ba06d764 100644 --- a/shiny/driver/mtldriver/window_amd64.go +++ b/shiny/driver/mtldriver/window_amd64.go @@ -5,7 +5,6 @@ package mtldriver import ( "image" - "image/color" "github.com/oakmound/oak/v3/shiny/driver/internal/drawer" "github.com/oakmound/oak/v3/shiny/screen" @@ -13,27 +12,15 @@ import ( "golang.org/x/image/math/f64" ) -func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { +func (w *Window) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { draw.Draw(w.bgra, sr.Sub(sr.Min).Add(dp), src.RGBA(), sr.Min, draw.Src) } -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - draw.Draw(w.bgra, dr, &image.Uniform{src}, image.Point{}, op) -} - -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { draw.NearestNeighbor.Transform(w.bgra, src2dst, src.(*textureImpl).rgba, sr, op, nil) } -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - draw.NearestNeighbor.Transform(w.bgra, src2dst, &image.Uniform{src}, sr, op, nil) -} - -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Copy(w, dp, src, sr, op) -} - -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { drawer.Scale(w, dr, src, sr, op) } diff --git a/shiny/driver/mtldriver/window_arm64.go b/shiny/driver/mtldriver/window_arm64.go index ea5818dc..cf64640f 100644 --- a/shiny/driver/mtldriver/window_arm64.go +++ b/shiny/driver/mtldriver/window_arm64.go @@ -5,7 +5,6 @@ package mtldriver import ( "image" - "image/color" "github.com/oakmound/oak/v3/shiny/driver/internal/drawer" "github.com/oakmound/oak/v3/shiny/screen" @@ -13,7 +12,7 @@ import ( "golang.org/x/image/math/f64" ) -func (w *windowImpl) Upload(dp image.Point, srcImg screen.Image, sr image.Rectangle) { +func (w *Window) Upload(dp image.Point, srcImg screen.Image, sr image.Rectangle) { dst := w.bgra r := sr.Sub(sr.Min).Add(dp) src := srcImg.RGBA() @@ -45,22 +44,10 @@ func (w *windowImpl) Upload(dp image.Point, srcImg screen.Image, sr image.Rectan } } -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - // Unimplemented -} - -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { nnInterpolator{}.Transform(w.bgra, src2dst, src.(*textureImpl).rgba, sr) } -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - // Unimplemented -} - -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Copy(w, dp, src, sr, draw.Over) -} - -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { drawer.Scale(w, dr, src, sr, draw.Over) } diff --git a/shiny/driver/mtldriver_darwin.go b/shiny/driver/mtldriver_darwin.go index 59618ed7..9035c9f1 100644 --- a/shiny/driver/mtldriver_darwin.go +++ b/shiny/driver/mtldriver_darwin.go @@ -2,8 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin -// +build !noop +//go:build darwin && !noop +// +build darwin,!noop package driver @@ -54,3 +54,5 @@ func monitorSize() (int, int) { } return w, h } + +type Window = mtldriver.Window diff --git a/shiny/driver/noop/noop.go b/shiny/driver/noop/noop.go index 145e0e30..9f0f127a 100644 --- a/shiny/driver/noop/noop.go +++ b/shiny/driver/noop/noop.go @@ -8,7 +8,6 @@ import ( "github.com/oakmound/oak/v3/shiny/driver/internal/event" "github.com/oakmound/oak/v3/shiny/screen" - "golang.org/x/image/math/f64" ) func Main(f func(screen.Screen)) { @@ -31,7 +30,7 @@ func (screenImpl) NewTexture(size image.Point) (screen.Texture, error) { } func (screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, error) { - return &windowImpl{}, nil + return &Window{}, nil } type imageImpl struct { @@ -69,18 +68,12 @@ func (textureImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) func (textureImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {} func (textureImpl) Release() {} -type windowImpl struct { +type Window struct { event.Deque } -func (*windowImpl) Release() {} -func (*windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (*windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) {} -func (*windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (*windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) {} -func (*windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) {} -func (*windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) {} +func (*Window) Release() {} +func (*Window) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) {} +func (*Window) Upload(dp image.Point, src screen.Image, sr image.Rectangle) {} -func (*windowImpl) Publish() screen.PublishResult { - return screen.PublishResult{} -} +func (*Window) Publish() {} diff --git a/shiny/driver/windriver/ico.go b/shiny/driver/windriver/ico.go new file mode 100644 index 00000000..f6f4d38a --- /dev/null +++ b/shiny/driver/windriver/ico.go @@ -0,0 +1,74 @@ +package windriver + +import ( + "bufio" + "bytes" + "encoding/binary" + "image" + "image/draw" + "image/png" + "io" +) + +// adapted from https://github.com/Kodeworks/golang-image-ico + +type icondir struct { + reserved uint16 + imageType uint16 + numImages uint16 +} + +type icondirentry struct { + imageWidth uint8 + imageHeight uint8 + numColors uint8 + reserved uint8 + colorPlanes uint16 + bitsPerPixel uint16 + sizeInBytes uint32 + offset uint32 +} + +func newIcondir() icondir { + var id icondir + id.imageType = 1 + id.numImages = 1 + return id +} + +func newIcondirentry() icondirentry { + var ide icondirentry + ide.colorPlanes = 1 // windows is supposed to not mind 0 or 1, but other icon files seem to have 1 here + ide.bitsPerPixel = 32 // can be 24 for bitmap or 24/32 for png. Set to 32 for now + ide.offset = 22 //6 icondir + 16 icondirentry, next image will be this image size + 16 icondirentry, etc + return ide +} + +func encodeIco(w io.Writer, im image.Image) error { + b := im.Bounds() + m := image.NewRGBA(b) + draw.Draw(m, b, im, b.Min, draw.Src) + + id := newIcondir() + ide := newIcondirentry() + + pngbb := new(bytes.Buffer) + pngwriter := bufio.NewWriter(pngbb) + png.Encode(pngwriter, m) + pngwriter.Flush() + ide.sizeInBytes = uint32(len(pngbb.Bytes())) + + bounds := m.Bounds() + ide.imageWidth = uint8(bounds.Dx()) + ide.imageHeight = uint8(bounds.Dy()) + bb := new(bytes.Buffer) + + var e error + binary.Write(bb, binary.LittleEndian, id) + binary.Write(bb, binary.LittleEndian, ide) + + w.Write(bb.Bytes()) + w.Write(pngbb.Bytes()) + + return e +} diff --git a/shiny/driver/windriver/screen.go b/shiny/driver/windriver/screen.go index e4be68be..4f9ca956 100644 --- a/shiny/driver/windriver/screen.go +++ b/shiny/driver/windriver/screen.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package windriver @@ -61,7 +62,7 @@ func (s *screenImpl) NewTexture(size image.Point) (screen.Texture, error) { } func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, error) { - w := &windowImpl{} + w := &Window{} var err error w.hwnd, err = win32.NewWindow(s.screenHWND, opts) @@ -69,6 +70,7 @@ func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, erro w.exStyle = win32.WS_EX_WINDOWEDGE if opts.TopMost { w.exStyle |= win32.WS_EX_TOPMOST + w.topMost = true } if err != nil { diff --git a/shiny/driver/windriver/window.go b/shiny/driver/windriver/window.go index 0062213a..8297d1fb 100644 --- a/shiny/driver/windriver/window.go +++ b/shiny/driver/windriver/window.go @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +//go:build windows // +build windows package windriver @@ -15,6 +16,10 @@ import ( "image/color" "image/draw" "math" + "math/rand" + "os" + "path/filepath" + "strconv" "sync" "syscall" "unsafe" @@ -34,12 +39,14 @@ import ( var ( windowLock sync.RWMutex - allWindows = make(map[win32.HWND]*windowImpl) + allWindows = make(map[win32.HWND]*Window) ) -type windowImpl struct { +type Window struct { hwnd win32.HWND + changeLock sync.RWMutex + event.Deque sz size.Event @@ -50,6 +57,7 @@ type windowImpl struct { fullscreen bool borderless bool maximized bool + topMost bool windowRect *win32.RECT clientRect *win32.RECT @@ -59,7 +67,7 @@ type windowImpl struct { trayGUID *win32.GUID } -func (w *windowImpl) Release() { +func (w *Window) Release() { if w.trayGUID != nil { iconData := win32.NOTIFYICONDATA{} iconData.CbSize = uint32(unsafe.Sizeof(iconData)) @@ -71,7 +79,7 @@ func (w *windowImpl) Release() { win32.Release(win32.HWND(w.hwnd)) } -func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { +func (w *Window) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { src.(*bufferImpl).preUpload() defer src.(*bufferImpl).postUpload() @@ -83,16 +91,7 @@ func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle }) } -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { - w.execCmd(&cmd{ - id: cmdFill, - dr: dr, - color: src, - op: op, - }) -} - -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { if op != draw.Src && op != draw.Over { // TODO: return @@ -106,25 +105,12 @@ func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectang }) } -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { - if op != draw.Src && op != draw.Over { - return - } - w.execCmd(&cmd{ - id: cmdDrawUniform, - src2dst: src2dst, - color: src, - sr: sr, - op: op, - }) -} - -func (w *windowImpl) SetTitle(title string) error { +func (w *Window) SetTitle(title string) error { win32.SetWindowText(w.hwnd, title) return nil } -func (w *windowImpl) SetBorderless(borderless bool) error { +func (w *Window) SetBorderless(borderless bool) error { // Don't set borderless if currently fullscreen. if !w.fullscreen && borderless != w.borderless { if !w.borderless { @@ -169,7 +155,7 @@ func (w *windowImpl) SetBorderless(borderless bool) error { return nil } -func (w *windowImpl) SetFullScreen(fullscreen bool) error { +func (w *Window) SetFullScreen(fullscreen bool) error { if w.borderless { return errors.New("cannot combine borderless and fullscreen") } @@ -232,7 +218,7 @@ func (w *windowImpl) SetFullScreen(fullscreen bool) error { } // HideCursor turns the OS cursor into a 1x1 transparent image. -func (w *windowImpl) HideCursor() error { +func (w *Window) HideCursor() error { emptyCursor := win32.GetEmptyCursor() success := win32.SetClassLongPtr(w.hwnd, win32.GCLP_HCURSOR, uintptr(emptyCursor)) if !success { @@ -241,74 +227,67 @@ func (w *windowImpl) HideCursor() error { return nil } -func (w *windowImpl) SetTrayIcon(iconPath string) error { - if w.trayGUID == nil { - if err := w.createTrayItem(); err != nil { - return err - } +// SetIcon sets this window's taskbar (and top left corner) icon +func (w *Window) SetIcon(icon image.Image) error { + // windows supports four modes of setting icons: + // 1. loading internal resources embedded into binaries in a windows-specific fashion + // 2. loading from file + // 3. using windows-os built in icons like question marks + // 4. hand crafting black and white icons via combining AND and XOR masks + // + // note, none of these are 'use an icon held in application memory' + // + // 1 is not an option for a multiplatform app. + // 3 is not an option because icons are usually not built in windows icons. + // 4 is not an option because icons are usually colorful. + // + // so we're left with 2: take the image given, write it as an icon to a temporary + // file, load that file, set it as the icon, delete that file. + iconPath := filepath.Join(os.TempDir(), "oakicon"+strconv.Itoa(rand.Int())+".ico") + f, err := os.Create(iconPath) + if err != nil { + return fmt.Errorf("failed to create icon: %w", err) } - iconData := win32.NOTIFYICONDATA{} - iconData.CbSize = uint32(unsafe.Sizeof(iconData)) - iconData.UFlags = win32.NIF_GUID | win32.NIF_MESSAGE - iconData.HWnd = w.hwnd - iconData.GUIDItem = *w.trayGUID - iconData.UFlags = win32.NIF_GUID | win32.NIF_ICON - var err error - iconData.HIcon, err = win32.LoadImage( + defer os.Remove(iconPath) + err = encodeIco(f, icon) + if err != nil { + return err + } + f.Close() + + hicon, err := win32.LoadImage( 0, windows.StringToUTF16Ptr(iconPath), win32.IMAGE_ICON, 0, 0, win32.LR_DEFAULTSIZE|win32.LR_LOADFROMFILE) if err != nil { - return fmt.Errorf("failed to load icon: %w", err) - } - if !win32.Shell_NotifyIcon(win32.NIM_MODIFY, &iconData) { - return fmt.Errorf("failed to create notification icon") - } - return nil -} - -func (w *windowImpl) ShowNotification(title, msg string, icon bool) error { - if w.trayGUID == nil { - if err := w.createTrayItem(); err != nil { - return err + if isWindowsSuccessError(err) { + return fmt.Errorf("failed to reload image") } - } - iconData := win32.NOTIFYICONDATA{} - iconData.CbSize = uint32(unsafe.Sizeof(iconData)) - iconData.UFlags = win32.NIF_GUID | win32.NIF_INFO - iconData.HWnd = w.hwnd - iconData.GUIDItem = *w.trayGUID - copy(iconData.SzInfoTitle[:], windows.StringToUTF16(title)) - copy(iconData.SzInfo[:], windows.StringToUTF16(msg)) - if icon { - iconData.DwInfoFlags = win32.NIIF_USER | win32.NIIF_LARGE_ICON + return fmt.Errorf("failed to reload image: %v", err) } - if !win32.Shell_NotifyIcon(win32.NIM_MODIFY, &iconData) { - return fmt.Errorf("failed to create notification icon") - } + win32.SendMessage(w.hwnd, win32.WM_SETICON, win32.ICON_SMALL, hicon) + win32.SendMessage(w.hwnd, win32.WM_SETICON, win32.ICON_BIG, hicon) return nil } -func (w *windowImpl) createTrayItem() error { - w.trayGUID = new(win32.GUID) - *w.trayGUID = win32.MakeGUID(w.guid) - iconData := win32.NOTIFYICONDATA{} - iconData.CbSize = uint32(unsafe.Sizeof(iconData)) - iconData.UFlags = win32.NIF_GUID | win32.NIF_MESSAGE - iconData.HWnd = w.hwnd - iconData.GUIDItem = *w.trayGUID - iconData.UCallbackMessage = win32.WM_APP + 1 - if !win32.Shell_NotifyIcon(win32.NIM_ADD, &iconData) { - return fmt.Errorf("failed to create notification") +func isWindowsSuccessError(err error) bool { + var errno syscall.Errno + if errors.As(err, &errno) { + if errno == 0 { + // we got a confusing 'this operation completed successfully' + // no, this does not actually mean the operation necessarily succeeded + // no, win32.GetLastError will not necessarily return a real error to clarify things + return true + } } - return nil + return false } -func (w *windowImpl) MoveWindow(x, y, wd, ht int32) error { - return win32.MoveWindow(w.hwnd, x, y, wd, ht, true) +func (w *Window) MoveWindow(x, y, wd, ht int) error { + return win32.MoveWindow(w.hwnd, int32(x), int32(y), int32(wd), int32(ht), true) } func drawWindow(dc win32.HDC, src2dst f64.Aff3, src interface{}, sr image.Rectangle, op draw.Op) (retErr error) { @@ -377,18 +356,11 @@ func drawWindow(dc win32.HDC, src2dst f64.Aff3, src interface{}, sr image.Rectan return fmt.Errorf("unsupported type %T", src) } -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { - drawer.Copy(w, dp, src, sr, op) -} - -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { drawer.Scale(w, dr, src, sr, op) } -func (w *windowImpl) Publish() screen.PublishResult { - // TODO - return screen.PublishResult{} -} +func (w *Window) Publish() {} func init() { send := func(hwnd win32.HWND, e interface{}) { @@ -462,7 +434,7 @@ const ( // msgCmd is the stored value for our handleCmd function for syscalls. var msgCmd = win32.AddWindowMsg(handleCmd) -func (w *windowImpl) execCmd(c *cmd) { +func (w *Window) execCmd(c *cmd) { win32.SendMessage(win32.HWND(w.hwnd), msgCmd, 0, uintptr(unsafe.Pointer(c))) if c.err != nil { panic(fmt.Sprintf("execCmd faild for cmd.id=%d: %v", c.id, c.err)) // TODO handle errors @@ -495,8 +467,37 @@ func handleCmd(hwnd win32.HWND, uMsg uint32, wParam, lParam uintptr) { } } -func (w *windowImpl) GetCursorPosition() (x, y float64) { +func (w *Window) GetCursorPosition() (x, y float64) { + w.changeLock.RLock() + defer w.changeLock.RUnlock() + w.windowRect, _ = win32.GetWindowRect(w.hwnd) xint, yint, _ := win32.GetCursorPos() return float64(xint) - float64(w.windowRect.Left), float64(yint) - float64(w.windowRect.Top) } + +func (w *Window) SetTopMost(topMost bool) error { + w.changeLock.Lock() + defer w.changeLock.Unlock() + + if w.topMost == topMost { + return nil + } + + // Note: although you can change a window's ex style to include EX_TOPMOST + // this will not work after window creation. The following is what you need to + // do instead. + + var ok bool + if topMost { + ok = win32.SetWindowPos(w.hwnd, win32.HWND_TOPMOST, 0, 0, 0, 0, win32.SWP_NOMOVE|win32.SWP_NOSIZE) + } else { + ok = win32.SetWindowPos(w.hwnd, win32.HWND_NOTOPMOST, 0, 0, 0, 0, win32.SWP_NOMOVE|win32.SWP_NOSIZE) + } + if !ok { + // TODO: extract and parse os error + return fmt.Errorf("failed to set top most") + } + w.topMost = topMost + return nil +} diff --git a/shiny/driver/x11driver/screen.go b/shiny/driver/x11driver/screen.go index fa15a388..ab55b5d6 100644 --- a/shiny/driver/x11driver/screen.go +++ b/shiny/driver/x11driver/screen.go @@ -57,7 +57,7 @@ type screenImpl struct { mu sync.Mutex buffers map[shm.Seg]*bufferImpl uploads map[uint16]chan struct{} - windows map[xproto.Window]*windowImpl + windows map[xproto.Window]*Window nPendingUploads int completionKeys []uint16 } @@ -69,6 +69,7 @@ var ( "WM_DELETE_WINDOW", "WM_PROTOCOLS", "WM_TAKE_FOCUS", + "_NET_WM_ICON", } ) @@ -85,7 +86,7 @@ func newScreenImpl(xutil *xgbutil.XUtil) (s *screenImpl, err error) { xsi: xutil.Setup().DefaultScreen(xutil.Conn()), buffers: map[shm.Seg]*bufferImpl{}, uploads: map[uint16]chan struct{}{}, - windows: map[xproto.Window]*windowImpl{}, + windows: map[xproto.Window]*Window{}, } for _, atom := range initialAtoms { s.atoms[atom], err = xprop.Atm(s.XUtil, atom) @@ -294,7 +295,7 @@ func (s *screenImpl) findBuffer(key shm.Seg) *bufferImpl { return b } -func (s *screenImpl) findWindow(key xproto.Window) *windowImpl { +func (s *screenImpl) findWindow(key xproto.Window) *Window { s.mu.Lock() w := s.windows[key] s.mu.Unlock() @@ -447,7 +448,7 @@ func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, erro pictformat = s.pictformat32 } - w := &windowImpl{ + w := &Window{ s: s, xw: xw, xg: xg, @@ -489,7 +490,7 @@ func (s *screenImpl) NewWindow(opts screen.WindowGenerator) (screen.Window, erro render.CreatePicture(s.xc, xp, xproto.Drawable(xw), pictformat, 0, nil) xproto.MapWindow(s.xc, xw) - err = w.MoveWindow(opts.X, opts.Y, int32(width), int32(height)) + err = w.MoveWindow(opts.X, opts.Y, width, height) if opts.Fullscreen { err = w.SetFullScreen(true) if err != nil { diff --git a/shiny/driver/x11driver/window.go b/shiny/driver/x11driver/window.go index 84e6c4da..cede7306 100644 --- a/shiny/driver/x11driver/window.go +++ b/shiny/driver/x11driver/window.go @@ -30,7 +30,7 @@ import ( "golang.org/x/mobile/geom" ) -type windowImpl struct { +type Window struct { s *screenImpl xw xproto.Window @@ -48,11 +48,13 @@ type windowImpl struct { mu sync.Mutex + lastMouseX, lastMouseY int16 + x, y uint32 released bool } -func (w *windowImpl) Release() { +func (w *Window) Release() { w.mu.Lock() released := w.released w.released = true @@ -69,31 +71,31 @@ func (w *windowImpl) Release() { xproto.DestroyWindow(w.s.xc, w.xw) } -func (w *windowImpl) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { +func (w *Window) Upload(dp image.Point, src screen.Image, sr image.Rectangle) { src.(*bufferImpl).upload(xproto.Drawable(w.xw), w.xg, w.s.xsi.RootDepth, dp, sr) } -func (w *windowImpl) Fill(dr image.Rectangle, src color.Color, op draw.Op) { +func (w *Window) Fill(dr image.Rectangle, src color.Color, op draw.Op) { fill(w.s.xc, w.xp, dr, src, op) } -func (w *windowImpl) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { +func (w *Window) DrawUniform(src2dst f64.Aff3, src color.Color, sr image.Rectangle, op draw.Op) { w.s.drawUniform(w.xp, &src2dst, src, sr, op) } -func (w *windowImpl) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Draw(src2dst f64.Aff3, src screen.Texture, sr image.Rectangle, op draw.Op) { src.(*textureImpl).draw(w.xp, &src2dst, sr, op) } -func (w *windowImpl) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Copy(dp image.Point, src screen.Texture, sr image.Rectangle, op draw.Op) { drawer.Copy(w, dp, src, sr, op) } -func (w *windowImpl) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { +func (w *Window) Scale(dr image.Rectangle, src screen.Texture, sr image.Rectangle, op draw.Op) { drawer.Scale(w, dr, src, sr, op) } -func (w *windowImpl) Publish() screen.PublishResult { +func (w *Window) Publish() { // TODO: implement a back buffer, and copy or flip that here to the front // buffer. @@ -105,19 +107,17 @@ func (w *windowImpl) Publish() screen.PublishResult { // client could easily end up sending work at a faster rate than the X11 // server can serve. w.s.xc.Sync() - - return screen.PublishResult{} } -func (w *windowImpl) SetFullScreen(fullscreen bool) error { +func (w *Window) SetFullScreen(fullscreen bool) error { return x11.SetFullScreen(w.s.XUtil, w.xw, fullscreen) } -func (w *windowImpl) SetBorderless(borderless bool) error { +func (w *Window) SetBorderless(borderless bool) error { return x11.SetBorderless(w.s.XUtil, w.xw, borderless) } -func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) { +func (w *Window) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) { // TODO: does the order of these lifecycle and size events matter? Should // they really be a single, atomic event? w.lifecycler.SetVisible((int(ev.X)+int(ev.Width)) > 0 && (int(ev.Y)+int(ev.Height)) > 0) @@ -137,11 +137,11 @@ func (w *windowImpl) handleConfigureNotify(ev xproto.ConfigureNotifyEvent) { }) } -func (w *windowImpl) handleExpose() { +func (w *Window) handleExpose() { w.Send(paint.Event{}) } -func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) { +func (w *Window) handleKey(detail xproto.Keycode, state uint16, dir key.Direction) { r, c := w.s.keysyms.Lookup(uint8(detail), state, w.s.numLockMod) w.Send(key.Event{ Rune: r, @@ -151,7 +151,9 @@ func (w *windowImpl) handleKey(detail xproto.Keycode, state uint16, dir key.Dire }) } -func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) { +func (w *Window) handleMouse(x, y int16, b xproto.Button, state uint16, dir mouse.Direction) { + w.lastMouseX = x + w.lastMouseY = y // TODO: should a mouse.Event have a separate MouseModifiers field, for // which buttons are pressed during a mouse move? btn := mouse.Button(b) @@ -180,7 +182,7 @@ func (w *windowImpl) handleMouse(x, y int16, b xproto.Button, state uint16, dir }) } -func (w *windowImpl) MoveWindow(x, y, width, height int32) error { +func (w *Window) MoveWindow(x, y, width, height int) error { newX, newY, newW, newH := x11.MoveWindow(w.s.xc, w.xw, x, y, width, height) w.x = uint32(newX) w.y = uint32(newY) @@ -195,3 +197,95 @@ func (w *windowImpl) MoveWindow(x, y, width, height int32) error { }) return nil } + +func (w *Window) SetTitle(title string) error { + xproto.ChangeProperty(w.s.xc, xproto.PropModeReplace, w.xw, + w.s.atoms["_NET_WM_NAME"], w.s.atoms["UTF8_STRING"], + 8, uint32(len(title)), []byte(title)) + return nil +} + +func (w *Window) SetTopMost(topMost bool) error { + return x11.SetTopMost(w.s.XUtil, w.xw, topMost) +} + +func (w *Window) HideCursor() error { + // ask X for a pixmap id + px, err := xproto.NewPixmapId(w.s.xc) + if err != nil { + return err + } + + // Create a 1x1 pixmap with that pixmap id + // depth has to be 1, otherwise you get BadMatch + // the drawable has to be this root window. I don't know why. + // You can't make a pixmap with less than 1x1 dimensions. + // I don't even know if this pixmap is black or transparent + xproto.CreatePixmap(w.s.xc, 1, px, xproto.Drawable(w.s.XUtil.RootWin()), 1, 1) + + // ask X for a cursor id + cursorId, err := xproto.NewCursorId(w.s.xc) + if err != nil { + return err + } + + // create a cursor from the pixmap with that cursor id. + // the zeros are colors (r,g,b,r,g,b) and the hotspot of the cursor (x,y) + // the second px is a mask which we ignore. + xproto.CreateCursor(w.s.xc, cursorId, px, px, 0, 0, 0, 0, 0, 0, 0, 0) + + // change the cursor of the window to be the created cursor. + xproto.ChangeWindowAttributes(w.s.xc, w.xw, + xproto.CwBackPixel|xproto.CwCursor, []uint32{0xffffffff, uint32(cursorId)}) + + // free the things we created + // it does not make sense to me that we can free these, and still persist our created + // cursor, but it works + xproto.FreeCursor(w.s.xc, cursorId) + xproto.FreePixmap(w.s.xc, px) + + return nil +} + +func (w *Window) SetIcon(icon image.Image) error { + bds := icon.Bounds() + wd := bds.Max.X + h := bds.Max.Y + u32w := uint32(wd) + u32h := uint32(h) + // 4 bytes, b/g/r/a, per pixel + bgra := make([]byte, 8, 8+wd*h*4) + // prepend width and height + bgra[0] = byte(u32w) + bgra[1] = byte(u32w >> 8) + bgra[2] = byte(u32w >> 16) + bgra[3] = byte(u32w >> 24) + bgra[4] = byte(u32h) + bgra[5] = byte(u32h >> 8) + bgra[6] = byte(u32h >> 16) + bgra[7] = byte(u32h >> 24) + for x := 0; x < wd; x++ { + for y := 0; y < h; y++ { + c := icon.At(x, (h-1)-y) + r, g, b, a := c.RGBA() + bgra = append(bgra, byte(b>>8)) + bgra = append(bgra, byte(g>>8)) + bgra = append(bgra, byte(r>>8)) + bgra = append(bgra, byte(a>>8)) + } + } + const XA_CARDINAL = 6 + // 32 here is the bit size of a cardinal, which is a bgra pixel + // we divide our length by 4 because we're sending a byte slice, + // not a cardinal slice + xproto.ChangeProperty(w.s.xc, xproto.PropModeReplace, w.xw, + w.s.atoms["_NET_WM_ICON"], XA_CARDINAL, + 32, uint32(len(bgra))/4, bgra) + return nil +} + +func (w *Window) GetCursorPosition() (x, y float64) { + // it's really not easy to do this with X + // we're just caching the last values we got + return float64(w.lastMouseX), float64(w.lastMouseY) +} diff --git a/shiny/screen/options.go b/shiny/screen/options.go index 4702241b..47ba2603 100644 --- a/shiny/screen/options.go +++ b/shiny/screen/options.go @@ -30,7 +30,7 @@ type WindowGenerator struct { // X and Y determine the location the new window should be created at. If // either are zero, a driver-dependant default will be used for each zero // value. If Fullscreen is true, these values will be ignored. - X, Y int32 + X, Y int } // A WindowOption is any function that sets up a WindowGenerator. @@ -54,7 +54,7 @@ func Dimensions(w, h int) WindowOption { } // Position sets the starting position of the new window -func Position(x, y int32) WindowOption { +func Position(x, y int) WindowOption { return func(g *WindowGenerator) { g.X = x g.Y = y diff --git a/shiny/screen/screen.go b/shiny/screen/screen.go index 248bc40f..3eca3e4a 100644 --- a/shiny/screen/screen.go +++ b/shiny/screen/screen.go @@ -54,6 +54,9 @@ package screen import ( "image" + "image/draw" + + "golang.org/x/image/math/f64" ) // Screen creates Images, Textures and Windows. @@ -80,16 +83,44 @@ type Window interface { EventDeque - Drawer + // Scale scales the sub-Texture defined by src and sr to the destination + // (the method receiver), such that sr in src-space is mapped to dr in + // dst-space. + Scale(dr image.Rectangle, src Texture, sr image.Rectangle, op draw.Op) + + // Upload uploads the sub-Buffer defined by src and sr to the destination + // (the method receiver), such that sr.Min in src-space aligns with dp in + // dst-space. The destination's contents are overwritten; the draw operator + // is implicitly draw.Src. + // + // It is valid to upload a Buffer while another upload of the same Buffer + // is in progress, but a Buffer's image.RGBA pixel contents should not be + // accessed while it is uploading. A Buffer is re-usable, in that its pixel + // contents can be further modified, once all outstanding calls to Upload + // have returned. + // + // TODO: make it optional that a Buffer's contents is preserved after + // Upload? Undoing a swizzle is a non-trivial amount of work, and can be + // redundant if the next paint cycle starts by clearing the buffer. + // + // When uploading to a Window, there will not be any visible effect until + // Publish is called. + Upload(dp image.Point, src Image, sr image.Rectangle) // Publish flushes any pending Upload and Draw calls to the window, and // swaps the back buffer to the front. - Publish() PublishResult + Publish() } -// PublishResult is the result of an Window.Publish call. -type PublishResult struct { - // BackBufferPreserved is whether the contents of the back buffer was - // preserved. If false, the contents are undefined. - BackBufferPreserved bool +type SimpleDrawer interface { + // Draw draws the sub-Texture defined by src and sr to the destination (the + // method receiver). src2dst defines how to transform src coordinates to + // dst coordinates. For example, if src2dst is the matrix + // + // m00 m01 m02 + // m10 m11 m12 + // + // then the src-space point (sx, sy) maps to the dst-space point + // (m00*sx + m01*sy + m02, m10*sx + m11*sy + m12). + Draw(src2dst f64.Aff3, src Texture, sr image.Rectangle, op draw.Op) } diff --git a/test_examples.sh b/test_examples.sh index 8d3bce7a..636211a5 100755 --- a/test_examples.sh +++ b/test_examples.sh @@ -1,6 +1,7 @@ #!/usr/bin/env bash examples=$(find ./examples | grep main.go$) +root=$(pwd) for ex in $examples do echo $ex @@ -12,5 +13,5 @@ do if [ $retVal -ne 124 ]; then exit 1 fi - cd ../.. + cd $root done \ No newline at end of file diff --git a/tidy_all.sh b/tidy_all.sh new file mode 100644 index 00000000..2623915d --- /dev/null +++ b/tidy_all.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +go mod tidy + +cd examples/fallback-font +go mod tidy + +cd ../clipboard +go mod tidy + +cd ../svg +go mod tidy \ No newline at end of file diff --git a/window.go b/window.go index 9f109d07..9a9fa480 100644 --- a/window.go +++ b/window.go @@ -21,10 +21,11 @@ import ( "github.com/oakmound/oak/v3/window" ) -var _ window.Window = &Window{} +var _ window.App = &Window{} -func (w *Window) windowController(s screen.Screen, x, y int32, width, height int) (screen.Window, error) { - return s.NewWindow(screen.NewWindowGenerator( +func (w *Window) windowController(s screen.Screen, x, y, width, height int) (*driver.Window, error) { + // TODO v4: can we update this interface to return our concrete driver.Window? + dwin, err := s.NewWindow(screen.NewWindowGenerator( screen.Dimensions(width, height), screen.Title(w.config.Title), screen.Position(x, y), @@ -32,6 +33,7 @@ func (w *Window) windowController(s screen.Screen, x, y int32, width, height int screen.Borderless(w.config.Borderless), screen.TopMost(w.config.TopMost), )) + return dwin.(*driver.Window), err } // the number of rgba buffers oak's draw loop swaps between @@ -40,6 +42,10 @@ const bufferCount = 2 type Window struct { key.State + // the driver.Window embedded in this window exposes at compile time the OS level + // options one has to manipulate this. + *driver.Window + // TODO: most of these channels are not closed cleanly transitionCh chan struct{} @@ -79,9 +85,9 @@ type Window struct { // The window buffer represents the subsection of the world which is available to // be shown in a window. - winBuffers [bufferCount]screen.Image - screenControl screen.Screen - windowControl screen.Window + winBuffers [bufferCount]screen.Image + screenControl screen.Screen + windowTextures [bufferCount]screen.Texture bufferIdx uint8 diff --git a/window/window.go b/window/window.go index 926df662..a27fe188 100644 --- a/window/window.go +++ b/window/window.go @@ -2,33 +2,40 @@ package window import ( + "image" + "github.com/oakmound/oak/v3/alg/intgeom" "github.com/oakmound/oak/v3/event" ) -// Window is an interface of methods on an oak.Window used -// to avoid circular imports +// Window is an interface of methods on an oak.Window available on platforms which have distinct app windows +// (osx, linux, windows). It is not available on other platforms (js, android) type Window interface { + App + SetFullScreen(bool) error SetBorderless(bool) error SetTopMost(bool) error SetTitle(string) error - SetTrayIcon(string) error - ShowNotification(title, msg string, icon bool) error + SetIcon(image.Image) error MoveWindow(x, y, w, h int) error HideCursor() error +} +// App is an interface of methods available to all oak programs. +type App interface { Width() int Height() int Viewport() intgeom.Point2 SetViewportBounds(intgeom.Rect2) + ShiftScreen(int, int) + SetScreen(int, int) + NextScene() GoToScene(string) InFocus() bool - ShiftScreen(int, int) - SetScreen(int, int) Quit() EventHandler() event.Handler