├── go-window_03.JPG ├── 04 - Loading an image ├── doom.JPG ├── doom.png ├── README.md └── main.go ├── 06 - 3D stars simulation ├── star3d.gif ├── README.md └── main.go ├── .gitignore ├── 01 - setup inital window ├── README.md └── main.go ├── 05 - Simple Animation ├── README.md └── main.go ├── 02 - handling events basics ├── README.md └── main.go ├── LICENSE ├── 03 - getting a buffer ├── README.md └── main.go └── README.md /go-window_03.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MickDuprez/go-window/HEAD/go-window_03.JPG -------------------------------------------------------------------------------- /04 - Loading an image/doom.JPG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MickDuprez/go-window/HEAD/04 - Loading an image/doom.JPG -------------------------------------------------------------------------------- /04 - Loading an image/doom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MickDuprez/go-window/HEAD/04 - Loading an image/doom.png -------------------------------------------------------------------------------- /06 - 3D stars simulation/star3d.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MickDuprez/go-window/HEAD/06 - 3D stars simulation/star3d.gif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, build with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | -------------------------------------------------------------------------------- /01 - setup inital window/README.md: -------------------------------------------------------------------------------- 1 | ## Initial setup and display 2 | The code here is the kickoff point for the rest of the project. 3 | It sets up the basic size and title for the window and handles some basic events 4 | using an infinite for loop typical of most window applications. -------------------------------------------------------------------------------- /05 - Simple Animation/README.md: -------------------------------------------------------------------------------- 1 | ## Simple Animation 2 | The goal of this section is to create a reasonable 'ticker' to set a reasonable 3 | framerate for rendering realtime graphics or animations. 4 | 5 | To get this working I had to use a paint.Event to flush the system and make it re-paint the screen 6 | but it works ok until I find a better way. 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /06 - 3D stars simulation/README.md: -------------------------------------------------------------------------------- 1 | ## 3D Stars animation 2 | This is a Go version of the 3D starfield by thebennybox on YouTube using the basic app built so far. 3 | https://www.youtube.com/watch?v=v7nrzvd9A5c&list=PLEETnX-uPtBUbVOok816vTl1K9vV1GgH5&index=5 4 | 5 | ### Sample of output 6 | ![sample output](https://github.com/MickDuprez/go-window/blob/master/06%20-%203D%20stars%20simulation/star3d.gif) 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /02 - handling events basics/README.md: -------------------------------------------------------------------------------- 1 | ## Handling basic window events 2 | 3 | The code in this folder continues on from the previous code and demonstrates 4 | most of the event handling methods you're likely to need. 5 | 6 | There were a few anomolies as documented in the code but I think this is mainly 7 | to do with the OS you are using. 8 | 9 | The paint.Paint event needs a lot more investigation but it's early days still 10 | and we're likely to work out what we need to refresh the screen view in the 11 | next section/s. 12 | 13 | As mentioned on the main page of this repo, things are not obvious to the new Go 14 | coder coming from other environments/frameworks. Maybe it's just how it's 15 | documented but it really isn't obvious how all the different pieces fit together. 16 | -------------------------------------------------------------------------------- /04 - Loading an image/README.md: -------------------------------------------------------------------------------- 1 | ## Loading an image 2 | In this sections we'll load a classic image and display it. 3 | We'll also add a line to the same frame buffer that draws over the image as expected. 4 | This will be handy for loading textures at a later date I think. 5 | 6 | I've also added some timeing code to measure how milliseconds per frame per second. 7 | An ideal target is around 16.666 which equates to 60 frames per second. 8 | I'm not happy with this yet though and it will nedd more work. 9 | 10 | There's also a sleep line of code to make the loop sleep for 16ms (60fps) to try 11 | and set a base speed for the loop. This is placed after all rendering is done although 12 | it probably doesn't matter that much. 13 | 14 | ### Sample of output 15 | ![sample output](https://github.com/MickDuprez/go-window/blob/master/04%20-%20Loading%20an%20image/doom.JPG) 16 | 17 | 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Michael Duprez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /03 - getting a buffer/README.md: -------------------------------------------------------------------------------- 1 | ## Creating the screen and pixel buffers 2 | This is as bare bones as you can get to have a simple window to draw pixels to. 3 | 4 | ### The Process - Get Screen Buffer -> Get a Pixel Buffer -> Draw, Upload and Publish 5 | Once you have set up the main window you need to get one or more screen Buffers. 6 | From what I can tell, the screen buffer is what the window calls to display the image. 7 | The screen buffer/s can be of arbitrary sizes and could be arranged to fit within 8 | the main window to create multiple panels of different graphics buffers. These can be 9 | used for things like different views or scales of the same image. 10 | The screen buffer/s needs to be renewed on resizing of the main window to resize itself. 11 | There doesn't appear to be any methods to resize it which makes sense as it would 12 | have to return a new buffer anyway to organise memory. 13 | There is also a Texture you can grab from the screen which I haven't looked into yet. 14 | 15 | You can then get a pixel buffer (*image.RGBA) from the screen buffer, this will be the same size as 16 | the screen buffer until a resize is called so you will need a new one of these as well on resize. 17 | This is where you draw your pixels to. 18 | All x, y values start at the top left corner of the pixel buffer and are positive in the right 19 | and down directions. 20 | 21 | Once you are done drawing pixels you tell the window to upload the screen buffer and publish 22 | the results. 23 | 24 | So far this is working well but I would love any feedback on how to do this better. The examples 25 | mentioned in the doc's are a bit ambitious to take in just to get a basic window and they use 26 | different libraries than used here. 27 | 28 | 29 | -------------------------------------------------------------------------------- /01 - setup inital window/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/mobile/event/lifecycle" 7 | 8 | "golang.org/x/exp/shiny/driver" 9 | "golang.org/x/exp/shiny/screen" 10 | ) 11 | 12 | func main() { 13 | // the driver package calls its Main to provide access to the screen 14 | // through the OS drivers abstracting away the underlying system. 15 | driver.Main(func(s screen.Screen) { 16 | // using the screen, create a top level double buffered window: 17 | w, err := s.NewWindow(&screen.NewWindowOptions{ 18 | Title: "Simple Window for Graphics", 19 | Width: 800, 20 | Height: 650, 21 | }) 22 | if err != nil { 23 | fmt.Printf("Failed to create Window - %v", err) 24 | return 25 | } 26 | defer w.Release() 27 | 28 | // We have a window, now we need a loop to handle window events 29 | // in regards to other windows in the OS 30 | var cnt int // counter to help with messages 31 | for { 32 | switch e := w.NextEvent().(type) { 33 | 34 | case lifecycle.Event: 35 | cnt++ 36 | fmt.Printf("Event %d: From %s To %s\n", cnt, e.From, e.To) 37 | if e.To == lifecycle.StageDead { 38 | fmt.Println("lifecycle is StageDead, goodbye!") 39 | return // quit the application. 40 | } 41 | 42 | // the following 2 Stages seem to be the only ones that work 43 | // as you would expect. StageInvisible doesn't work on Windows OS 44 | // but this may be different on other systems. 45 | if e.To == lifecycle.StageFocused { 46 | fmt.Println("window now has the focus") 47 | } 48 | if e.From == lifecycle.StageFocused { 49 | fmt.Println("window has lost the focus") 50 | } 51 | // we'll add other events as they are discovered... 52 | } 53 | } 54 | 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-window 2 | 3 | ### Creating an empty gui window in go for software rendering of 2D/3D graphics 4 | This project creates a bare bones GUI window for any Windows/Linux/Mac with basic event handlers 5 | and graphics buffer/s ready to draw pixels to. It can be used to create simple 2D/3D graphics 6 | from 'absolute' scratch (i.e. pixels!) for games or image viewing and editing etc. 7 | Yes, there definitely libraries out there ready to go for building games or GUI's but they abstract 8 | away all the very things I want to learn. 9 | 10 | The project uses libraries from golang.org/x/exp/shiny and golang.org/x/mobile/event and produce a 11 | blank native styled window with title bar and menu, there are no options for border styles etc. 12 | The GoDoc's, while very good have some unusual terminologies for things if you come from say a win32 background 13 | and can seem more confusing than enlightening so I thought I'd document my findings as I build up this basic 14 | windowing library from as low a level as possible. 15 | I'm also learning Go so this is as good a project as any to stretch the old grey cells with and I welcome any 16 | comments/suggestions/corrections :) 17 | 18 | ### Sample of output 19 | ![simple window](https://github.com/MickDuprez/go-window/blob/master/go-window_03.JPG) 20 | ![3d star field](https://github.com/MickDuprez/go-window/blob/master/06%20-%203D%20stars%20simulation/star3d.gif) 21 | 22 | ### What this project won't or can't provide 23 | Due to limitations in the go libraries and my lack of knowledge of said lib's there will 24 | be no bells and whistles like title bar icons, message boxes, UI widgets and other things you 25 | would require for a 'proper' desktop application. For these I'd recomend one of the Go wrapped 26 | GUI frameworks such as QT, GTK or WxWidgets et al. 27 | This project is built purely for low overhead with good performance and to develop and display 28 | graphics using software rendering in Go so I wanted this to be as lean as possible. 29 | 30 | ### What about OpenGL/DirectX et al? 31 | The whole purpose of this windowing library is for learning and experimentation and to discover how these 32 | graphics libraries work under the hood. While there are good arguments for using these driver API's it's still good 33 | to know how to do basic graphics at the lower level, not only for learning but for things like micro controllers 34 | with a simple LCD screen say to build an old style arcade cabinet or an equipment monitoring console etc. 35 | Anyway, if you're reading this and you love Go and want to learn graphics, hopefully this repo will be of some use. 36 | 37 | ## Repo Structure and Usage 38 | This repo is structured in lesson like folders that start from displaying a bare bones empty window with 39 | very basic low level events up to a fully working example library ready to go with all of the previous steps 40 | being abstracted away and ready to use. 41 | 42 | Study the README in each folder for some more detailed explanations and observations I had while coding 43 | up the source. 44 | 45 | ## Important! 46 | For futher documentation, see the imported lib's and look for them in GoDoc, particulalry golang.org/x/exp/shiny, 47 | golang.org/x/exp/shiny/screen and mobile/event to get started with. 48 | Reading them along while reading this code will be very helpful! 49 | 50 | __NOTE: This repo is being developed on a Windows OS, as I get time I will confirm/update differences 51 | I find with other OS's__ 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /04 - Loading an image/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "image/draw" 8 | "image/png" 9 | "log" 10 | "os" 11 | "time" 12 | 13 | "golang.org/x/mobile/event/lifecycle" 14 | 15 | "golang.org/x/mobile/event/key" 16 | "golang.org/x/mobile/event/size" 17 | 18 | "golang.org/x/exp/shiny/driver" 19 | "golang.org/x/exp/shiny/screen" 20 | ) 21 | 22 | var ( 23 | // set up some global helper var's 24 | winWidth, winHeight = 600, 400 25 | 26 | // We can get info from the event.Size() function along with other 27 | // helpful functions and data. 28 | sizeEvent size.Event 29 | 30 | screenSize = image.Point{winWidth, winHeight} 31 | screenBuffer screen.Buffer 32 | pixBuffer *image.RGBA 33 | s screen.Screen 34 | ) 35 | 36 | func main() { 37 | driver.Main(func(s screen.Screen) { 38 | w, err := s.NewWindow(&screen.NewWindowOptions{ 39 | Title: "Simple Window for Graphics", 40 | Width: winWidth, 41 | Height: winHeight, 42 | }) 43 | if err != nil { 44 | fmt.Printf("Failed to create Window - %v", err) 45 | return 46 | } 47 | defer w.Release() 48 | 49 | screenBuffer, err = s.NewBuffer(screenSize) 50 | if err != nil { 51 | log.Fatalf("%v - failed to create screen buffer", err) 52 | } 53 | defer screenBuffer.Release() 54 | pixBuffer = screenBuffer.RGBA() 55 | 56 | var frames = 0 57 | var startTime time.Time 58 | var currTime = time.Now() 59 | for { 60 | drawScene(w) 61 | w.Upload(image.Point{0, 0}, screenBuffer, screenBuffer.Bounds()) 62 | w.Publish() 63 | time.Sleep(time.Millisecond * 16) 64 | 65 | // print out the ms/frame value 66 | frames++ 67 | currTime = time.Now() 68 | if currTime.Sub(startTime).Seconds() >= 1.0 { 69 | fmt.Printf("\rRendering at %.5f \tms/frame", 1000.0/float64(frames)) 70 | frames = 0 71 | startTime = currTime 72 | } 73 | 74 | // Handle window events: 75 | switch e := w.NextEvent().(type) { 76 | 77 | case key.Event: 78 | if e.Code == key.CodeEscape { 79 | return // quit app 80 | } 81 | 82 | case lifecycle.Event: 83 | if e.To == lifecycle.StageDead { 84 | // Do any final cleanup or saving here: 85 | return // quit the application. 86 | } 87 | } 88 | } 89 | }) 90 | } 91 | 92 | func drawScene(w screen.Window) { 93 | // load the DOOM image: 94 | // Read image from file that already exists 95 | existingImageFile, err := os.Open("doom.png") 96 | if err != nil { 97 | log.Fatalf("%v - failed to open image", err) 98 | } 99 | defer existingImageFile.Close() 100 | loadedImage, err := png.Decode(existingImageFile) 101 | if err != nil { 102 | log.Fatalf("%v - failed to decode image", err) 103 | } 104 | 105 | // calculate the values to set the image to the center of the screen buffer. 106 | // NOTE: this doesn't seem right and needs more investigation! 107 | // I think it has a bit to do with the actual image size compared to the 108 | // actual image displayed but still not sure... 109 | imgRect := loadedImage.Bounds() 110 | imgX := ((pixBuffer.Rect.Dx() - imgRect.Dx()) / 2) - imgRect.Dx() 111 | imgY := ((pixBuffer.Rect.Dy() - imgRect.Dy()) / 2) - imgRect.Dy() 112 | 113 | // to draw the image we need to 'Draw' it to the pixel buffer. 114 | draw.Draw(pixBuffer, pixBuffer.Bounds(), loadedImage, image.Point{imgX, imgY}, draw.Src) 115 | 116 | // now let's draw a red line using SetRGBA hex values. 117 | // this line should draw over the pixels in the previous loaded image. 118 | for x := 50; x < 550; x++ { 119 | pixBuffer.SetRGBA(x, x, color.RGBA{0xff, 0x00, 0x00, 0xff}) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /05 - Simple Animation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "log" 8 | "time" 9 | 10 | "golang.org/x/mobile/event/lifecycle" 11 | "golang.org/x/mobile/event/paint" 12 | 13 | "golang.org/x/mobile/event/key" 14 | "golang.org/x/mobile/event/size" 15 | 16 | "golang.org/x/exp/shiny/driver" 17 | "golang.org/x/exp/shiny/screen" 18 | ) 19 | 20 | var ( 21 | // set up some global helper var's 22 | winWidth, winHeight = 600, 400 23 | 24 | // We can get info from the event.Size() function along with other 25 | // helpful functions and data. 26 | sizeEvent size.Event 27 | 28 | screenSize = image.Point{winWidth, winHeight} 29 | screenBuffer screen.Buffer 30 | pixBuffer *image.RGBA 31 | s screen.Screen 32 | ) 33 | 34 | func main() { 35 | driver.Main(func(s screen.Screen) { 36 | w, err := s.NewWindow(&screen.NewWindowOptions{ 37 | Title: "Simple Window for Graphics", 38 | Width: winWidth, 39 | Height: winHeight, 40 | }) 41 | if err != nil { 42 | fmt.Printf("Failed to create Window - %v", err) 43 | return 44 | } 45 | defer w.Release() 46 | 47 | screenBuffer, err = s.NewBuffer(screenSize) 48 | if err != nil { 49 | log.Fatalf("%v - failed to create screen buffer", err) 50 | } 51 | defer screenBuffer.Release() 52 | pixBuffer = screenBuffer.RGBA() 53 | 54 | var frames = 0 55 | var startTime time.Time 56 | var currTime = time.Now() 57 | for { 58 | clearBuffer(color.Black) 59 | drawToBuffer(w) 60 | w.Upload(image.Point{0, 0}, screenBuffer, screenBuffer.Bounds()) 61 | w.Publish() 62 | time.Sleep(time.Millisecond * 5) // slow it down a bit 63 | 64 | // print out the ms/frame value 65 | frames++ 66 | currTime = time.Now() 67 | if currTime.Sub(startTime).Seconds() >= 1.0 { 68 | fmt.Printf("\rRendering at %.3f \tms/frame\t", 1000.0/float64(frames)) 69 | frames = 0 70 | startTime = currTime 71 | } // 72 | 73 | // Handle window events: 74 | switch e := w.NextEvent().(type) { 75 | 76 | case key.Event: 77 | if e.Code == key.CodeEscape { 78 | return // quit app 79 | } 80 | 81 | case lifecycle.Event: 82 | if e.To == lifecycle.StageDead { 83 | // Do any final cleanup or saving here: 84 | return // quit the application. 85 | } 86 | } 87 | } 88 | }) 89 | } 90 | 91 | var ( 92 | // create the bounds variables 93 | boxSize = 50 94 | top = 0 95 | btm = screenSize.Y - boxSize 96 | left = 0 97 | right = screenSize.X - boxSize 98 | 99 | // we'll be drawing a simple 50x50 pixel square that will go from 100 | // top/left to approx. btm/right. Once it reaches the bottom it will 101 | // start at the top at the last X position and continue. 102 | // We'll try moving at a set distance of pixels per frame and fine tune to suit. 103 | pX = left // start pixel in X 104 | pY = top // start pixel in Y 105 | ) 106 | 107 | // This is where we draw the pixels of our image, this will be refactored out soon. 108 | func drawToBuffer(w screen.Window) { 109 | 110 | // check starting pixels are within bounds. 111 | if pX >= right { 112 | pX = left 113 | } 114 | if pY >= btm { 115 | pY = top 116 | } 117 | 118 | // draw the box 119 | for x := 0; x < 50; x++ { 120 | for y := 0; y < 50; y++ { 121 | pixBuffer.SetRGBA(pX+x, pY+y, color.RGBA{0xff, 0x00, 0x00, 0xff}) 122 | } 123 | } 124 | // move the starting pixel 125 | pX += 2 126 | pY += 2 127 | 128 | // force the window to paint itself. 129 | w.Send(paint.Event{External: true}) 130 | } 131 | 132 | // clearBuffer - clears the screen to a standard color, can be changed 133 | // to take an RGBA and a flag for gradient perhaps. 134 | func clearBuffer(c color.Color) { 135 | for x := 0; x < pixBuffer.Bounds().Dx(); x++ { 136 | for y := 0; y < pixBuffer.Bounds().Dy(); y++ { 137 | pixBuffer.Set(x, y, c) 138 | } 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /02 - handling events basics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "golang.org/x/mobile/event/paint" 7 | 8 | "golang.org/x/mobile/event/mouse" 9 | 10 | "golang.org/x/mobile/event/key" 11 | "golang.org/x/mobile/event/lifecycle" 12 | "golang.org/x/mobile/event/size" 13 | 14 | "golang.org/x/exp/shiny/driver" 15 | "golang.org/x/exp/shiny/screen" 16 | ) 17 | 18 | // set up some Global var's 19 | var winWidth, winHeight = 800, 650 20 | 21 | func main() { 22 | driver.Main(func(s screen.Screen) { 23 | w, err := s.NewWindow(&screen.NewWindowOptions{ 24 | Title: "Simple Window for Graphics", 25 | Width: winWidth, 26 | Height: winHeight, 27 | }) 28 | if err != nil { 29 | fmt.Printf("Failed to create Window - %v", err) 30 | return 31 | } 32 | defer w.Release() 33 | 34 | var cnt int // counter to help with messages 35 | for { 36 | // Handle some main window events to do any saving of resources 37 | // and final clean up etc as required. 38 | switch e := w.NextEvent().(type) { 39 | 40 | case lifecycle.Event: 41 | if e.To == lifecycle.StageDead { 42 | // Do any final cleanup or saving here: 43 | // --- 44 | 45 | fmt.Println("Thanks for playing, goodbye!") 46 | return // quit the application. 47 | } 48 | 49 | // let's handle the window resize event, we'll need this to update 50 | // the global winWidth/winHeight var's for use in building buffers 51 | // to suit the window. 52 | case size.Event: 53 | cnt++ 54 | 55 | fmt.Printf("Event %3d: size.Event Size = %v\n", cnt, e.Size()) 56 | winWidth = e.Size().X 57 | winHeight = e.Size().Y 58 | 59 | // handle keyboard events: 60 | case key.Event: 61 | cnt++ 62 | // print out the current key being pressed, notice it sends 2 messages 63 | // one for press, one for release. For holding a key press it sends 'none' 64 | // uncomment the below to test and see. 65 | fmt.Printf("Event %3d: key.Event - Key %v event was %v\n", cnt, e.Code, e.Direction) 66 | 67 | // lets handle some specific scenarios: 68 | switch e.Code { 69 | case key.CodeEscape: 70 | // let's use this to quit for now: 71 | fmt.Println("\n\nYou have escaped the window, bye!") 72 | return 73 | 74 | case key.CodeA: 75 | if e.Direction == key.DirPress { 76 | fmt.Println(" moving one step left.") 77 | } 78 | if e.Direction == key.DirNone { 79 | fmt.Println(" he's still going!") 80 | } 81 | if e.Direction == key.DirRelease { 82 | fmt.Println(" Phew!, stopped going left.") 83 | } 84 | } // --- end key switch 85 | 86 | // handle mouse events, can we see a pattern here? ;) 87 | case mouse.Event: 88 | switch e.Button { 89 | case mouse.ButtonLeft: 90 | if e.Direction == mouse.DirPress { 91 | fmt.Println("left mouse button down") 92 | } 93 | // this doesn't fire for some reason?? 94 | if e.Direction == mouse.DirNone { 95 | fmt.Println("left mouse button being held") 96 | } 97 | if e.Direction == mouse.DirRelease { 98 | fmt.Println("left mouse button released") 99 | } 100 | 101 | case mouse.ButtonRight: 102 | // handle press as single step (not working in Windows??): 103 | if e.Direction == mouse.DirStep { 104 | fmt.Println("right mouse button pressed and released in single step.") 105 | } 106 | if e.Direction == mouse.DirPress { 107 | fmt.Println("right mouse button pressed.") 108 | } 109 | case mouse.ButtonMiddle: 110 | if e.Direction == mouse.DirPress { 111 | fmt.Println("middle mouse button pressed.") 112 | } 113 | // doesn't work if you have set mouse wheel to middle button. 114 | case mouse.ButtonWheelDown: 115 | if e.Direction == mouse.DirPress { 116 | fmt.Println("mouse wheel button pressed.") 117 | } 118 | case mouse.ButtonWheelUp: 119 | fmt.Println("mouse wheel direction UP.") 120 | // let's try calling an event manually, the Paint event 121 | // could well be useful in future: 122 | w.Send(paint.Event{true}) 123 | } // --- end mouse switch 124 | 125 | case paint.Event: 126 | // may not need/be able to use this, see below for details 127 | // https://godoc.org/golang.org/x/mobile/event/paint#Event 128 | fmt.Printf("paint.Event called: %v\n", e.External) 129 | 130 | } // --- end events 131 | } // --- end app loop 132 | }) 133 | } 134 | -------------------------------------------------------------------------------- /06 - 3D stars simulation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "log" 8 | "math/rand" 9 | "time" 10 | 11 | "golang.org/x/mobile/event/lifecycle" 12 | "golang.org/x/mobile/event/paint" 13 | 14 | "golang.org/x/mobile/event/key" 15 | "golang.org/x/mobile/event/size" 16 | 17 | "golang.org/x/exp/shiny/driver" 18 | "golang.org/x/exp/shiny/screen" 19 | ) 20 | 21 | var ( 22 | // set up some global helper var's 23 | winWidth, winHeight = 600, 400 24 | 25 | // We can get info from the event.Size() function along with other 26 | // helpful functions and data. 27 | sizeEvent size.Event 28 | 29 | screenSize = image.Point{winWidth, winHeight} 30 | screenBuffer screen.Buffer 31 | pixBuffer *image.RGBA 32 | s screen.Screen 33 | 34 | delta float64 = 0.0 // time delta 35 | ) 36 | 37 | func main() { 38 | driver.Main(func(s screen.Screen) { 39 | w, err := s.NewWindow(&screen.NewWindowOptions{ 40 | Title: "Simple Window for Graphics", 41 | Width: winWidth, 42 | Height: winHeight, 43 | }) 44 | if err != nil { 45 | fmt.Printf("Failed to create Window - %v", err) 46 | return 47 | } 48 | defer w.Release() 49 | 50 | screenBuffer, err = s.NewBuffer(screenSize) 51 | if err != nil { 52 | log.Fatalf("%v - failed to create screen buffer", err) 53 | } 54 | defer screenBuffer.Release() 55 | pixBuffer = screenBuffer.RGBA() 56 | 57 | // Create the starfield object 58 | stars := Stars3d{ 59 | numStars: 4096, 60 | spread: 64.0, 61 | speed: 20.0, 62 | } 63 | // we have to init to set the array sizes etc. 64 | stars.InitStars() 65 | 66 | //var frames = 0 67 | var previousTime = time.Now() 68 | for { 69 | // render the star field 70 | stars.UpdateAndRender(w, pixBuffer, delta) 71 | 72 | w.Upload(image.Point{0, 0}, screenBuffer, screenBuffer.Bounds()) 73 | w.Publish() 74 | time.Sleep(time.Millisecond * 6) // slow it down a bit 75 | 76 | // print out the ms/frame value 77 | currTime := time.Now() 78 | delta = float64(currTime.Sub(previousTime).Nanoseconds()) / 1000000000.0 79 | fmt.Printf("\rRendering at %.5f \tms/frame\t", delta*1000) 80 | previousTime = currTime 81 | 82 | // Handle window events: 83 | switch e := w.NextEvent().(type) { 84 | 85 | case key.Event: 86 | if e.Code == key.CodeEscape { 87 | return // quit app 88 | } 89 | 90 | case lifecycle.Event: 91 | if e.To == lifecycle.StageDead { 92 | // Do any final cleanup or saving here: 93 | return // quit the application. 94 | } 95 | } 96 | } 97 | }) 98 | } 99 | 100 | // clearBuffer - clears the screen to a standard color, can be changed 101 | // to take an RGBA and a flag for gradient perhaps. 102 | func clearBuffer(c color.RGBA) { 103 | for x := 0; x < pixBuffer.Bounds().Dx(); x++ { 104 | for y := 0; y < pixBuffer.Bounds().Dy(); y++ { 105 | pixBuffer.Set(x, y, c) 106 | } 107 | } 108 | } 109 | 110 | type Stars3d struct { 111 | numStars int 112 | spread float64 113 | speed float64 114 | starX []float64 115 | starY []float64 116 | starZ []float64 117 | } 118 | 119 | // UpdateAndRender - draws a field of stars to the pixel buffer. 120 | func (s *Stars3d) UpdateAndRender(w screen.Window, img *image.RGBA, delta float64) { 121 | clearBuffer(color.RGBA{0x00, 0x00, 0x00, 0xff}) 122 | 123 | // draw the stars 124 | halfWidth := float64(pixBuffer.Bounds().Dx()) / 2.0 125 | halfHeight := float64(pixBuffer.Bounds().Dy()) / 2.0 126 | for i := 0; i < s.numStars; i++ { 127 | s.starZ[i] -= delta * s.speed 128 | if s.starZ[i] <= 0 { 129 | s.initStar(i) 130 | } 131 | x := int((s.starX[i]/s.starZ[i])*halfWidth + halfWidth) 132 | y := int((s.starY[i]/s.starZ[i])*halfHeight + halfHeight) 133 | 134 | if (x < 0 || x >= pixBuffer.Bounds().Dx()) || 135 | (y < 0 || y >= pixBuffer.Bounds().Dy()) { 136 | s.initStar(i) 137 | } else { 138 | pixBuffer.Set(x, y, color.White) 139 | } 140 | } 141 | // force the window to paint itself. 142 | w.Send(paint.Event{External: true}) 143 | } 144 | 145 | // InitStars - set up the starfield 146 | func (s *Stars3d) InitStars() { 147 | s.starX = make([]float64, s.numStars) 148 | s.starY = make([]float64, s.numStars) 149 | s.starZ = make([]float64, s.numStars) 150 | 151 | for i := 0; i < s.numStars; i++ { 152 | s.initStar(i) 153 | } 154 | } 155 | 156 | func (s *Stars3d) initStar(i int) { 157 | s.starX[i] = (2*rand.Float64() - 0.5) * s.spread 158 | s.starY[i] = (2*rand.Float64() - 0.5) * s.spread 159 | s.starZ[i] = (rand.Float64() + 0.00001) * s.spread 160 | } 161 | -------------------------------------------------------------------------------- /03 - getting a buffer/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "image" 6 | "image/color" 7 | "log" 8 | 9 | "golang.org/x/mobile/event/lifecycle" 10 | "golang.org/x/mobile/event/paint" 11 | 12 | "golang.org/x/mobile/event/mouse" 13 | 14 | "golang.org/x/mobile/event/key" 15 | "golang.org/x/mobile/event/size" 16 | 17 | "golang.org/x/exp/shiny/driver" 18 | "golang.org/x/exp/shiny/screen" 19 | ) 20 | 21 | var ( 22 | // set up some global helper var's 23 | winWidth, winHeight = 800, 650 24 | 25 | // We can get info from the event.Size() function along with other 26 | // helpful functions and data. 27 | sizeEvent size.Event 28 | ) 29 | 30 | func main() { 31 | driver.Main(func(s screen.Screen) { 32 | w, err := s.NewWindow(&screen.NewWindowOptions{ 33 | Title: "Simple Window for Graphics", 34 | Width: winWidth, 35 | Height: winHeight, 36 | }) 37 | if err != nil { 38 | fmt.Printf("Failed to create Window - %v", err) 39 | return 40 | } 41 | defer w.Release() 42 | 43 | // As the size.Event might not be called at startup 44 | // set them the screenSize the same as our winHeight/winWidth 45 | screenSize := image.Point{} 46 | if sizeEvent.Bounds().Max.X == 0 { 47 | screenSize = image.Point{winWidth, winHeight} 48 | } else { 49 | screenSize = image.Point{sizeEvent.WidthPx, sizeEvent.HeightPx} 50 | } 51 | screenBuffer, err := s.NewBuffer(screenSize) 52 | if err != nil { 53 | log.Fatalf("%v - failed to create screen buffer", err) 54 | } 55 | defer screenBuffer.Release() 56 | // pixBuffer is like a 'back bufffer' and it's what we do our drawing to. 57 | pixBuffer := screenBuffer.RGBA() 58 | 59 | for { 60 | // Handle window events: 61 | switch e := w.NextEvent().(type) { 62 | 63 | case size.Event: 64 | sizeEvent = e 65 | // we need to create a new screen buffer, there's no way to resize the old one 66 | screenBuffer.Release() 67 | screenBuffer, err = s.NewBuffer(image.Point{e.WidthPx, e.HeightPx}) 68 | 69 | if err != nil { 70 | log.Fatalf("couldn't create new buffer at size.Event - %v", err) 71 | } 72 | // we need to get a new pixel buffer here so we get a buffer of 73 | // the right size 74 | pixBuffer = screenBuffer.RGBA() 75 | 76 | case key.Event: 77 | if e.Code == key.CodeEscape { 78 | return // quit app 79 | } 80 | handleKeyEvents(e) 81 | 82 | case mouse.Event: 83 | handleMouseEvents(e) 84 | 85 | case paint.Event: 86 | // fill the background, comment one or the other out below to see 87 | // the difference when we don't use our updated window sizes. 88 | // w.Fill(image.Rect(0, 0, 800, 650), color.Black, screen.Src) 89 | w.Fill(sizeEvent.Bounds(), color.Black, screen.Src) 90 | 91 | // ---- START DRAWING CODE ----- 92 | // 256 pixel square at 100 down and 100 across from window edge 93 | // using the 'Set' method, limited colors only. 94 | x_start, x_finish, y_start, y_finish := 100, 256, 100, 256 95 | for x := x_start; x < x_finish; x++ { 96 | for y := y_start; y < y_finish; y++ { 97 | pixBuffer.Set(x, y, color.White) 98 | } 99 | } 100 | 101 | // 256 pixel square at 100 down and 300 across from window edge 102 | // using the 'SetRGBA' method, colors calculated in loop. 103 | // NOTE: see https://lodev.org/cgtutor/quickcg.html 104 | x_start, x_finish, y_start, y_finish = 300, 556, 100, 356 105 | var ciX, ciY uint8 // color index counter 106 | for x := x_start; x < x_finish; x++ { 107 | for y := y_start; y < y_finish; y++ { 108 | pixBuffer.SetRGBA(x, y, color.RGBA{ciX, ciY, uint8(128), 0xff}) 109 | 110 | ciY++ 111 | } 112 | ciX++ 113 | } 114 | 115 | // now let's draw a red line using SetRGBA hex values. 116 | for x := 400; x < 550; x++ { 117 | pixBuffer.SetRGBA(x, x, color.RGBA{0xff, 0x00, 0x00, 0xff}) 118 | } 119 | // ---- END DRAWING CODE ---------- 120 | 121 | // upload the pixBuffer (or SwapBuffers if you like) 122 | w.Upload(image.Point{0, 0}, screenBuffer, sizeEvent.Bounds()) 123 | // finfished drawing etc, swap back buffer to front: 124 | w.Publish() 125 | 126 | case lifecycle.Event: 127 | if e.To == lifecycle.StageDead { 128 | // Do any final cleanup or saving here: 129 | return // quit the application. 130 | } 131 | 132 | } 133 | } 134 | }) 135 | } 136 | 137 | func handleKeyEvents(e key.Event) { 138 | switch e.Code { 139 | 140 | case key.CodeA: 141 | if e.Direction == key.DirPress { 142 | fmt.Println(" moving one step left.") 143 | } 144 | if e.Direction == key.DirNone { 145 | fmt.Println(" he's still going!") 146 | } 147 | if e.Direction == key.DirRelease { 148 | fmt.Println(" Phew!, stopped going left.") 149 | } 150 | } 151 | } 152 | 153 | func handleMouseEvents(e mouse.Event) { 154 | switch e.Button { 155 | case mouse.ButtonLeft: 156 | if e.Direction == mouse.DirPress { 157 | fmt.Println("left mouse button down") 158 | } 159 | 160 | if e.Direction == mouse.DirNone { 161 | fmt.Println("left mouse button being held") 162 | } 163 | if e.Direction == mouse.DirRelease { 164 | fmt.Println("left mouse button released") 165 | } 166 | } 167 | } 168 | --------------------------------------------------------------------------------