From 52a3a96d20ca6dde3f8140fdfe14b1a43193fabb Mon Sep 17 00:00:00 2001
From: faiface <faiface@ksp.sk>
Date: Fri, 25 Nov 2016 22:49:56 +0100
Subject: [PATCH] replace BeginEnder with Doer and migrate everything

---
 graphics.go          |  58 +++++++++++++
 pixelgl/interface.go |  46 +++++------
 pixelgl/shader.go    | 170 +++++++++++++++++++------------------
 pixelgl/texture.go   |  67 ++++++++-------
 pixelgl/vertex.go    | 100 +++++++++++-----------
 window.go            | 193 +++++++++++++++++++++----------------------
 6 files changed, 344 insertions(+), 290 deletions(-)
 create mode 100644 graphics.go

diff --git a/graphics.go b/graphics.go
new file mode 100644
index 0000000..4381671
--- /dev/null
+++ b/graphics.go
@@ -0,0 +1,58 @@
+package pixel
+
+import "github.com/faiface/pixel/pixelgl"
+
+// Warning: technical stuff below.
+
+// VertexFormat is an internal format of the OpenGL vertex data.
+//
+// You can actually change this and all of the Pixel's functions will use the new format.
+// Only change when you're implementing an OpenGL effect or something similar.
+var VertexFormat = DefaultVertexFormat()
+
+// DefaultVertexFormat returns the default vertex format used by Pixel.
+func DefaultVertexFormat() pixelgl.VertexFormat {
+	return pixelgl.VertexFormat{
+		{Purpose: pixelgl.Position, Size: 2},
+		{Purpose: pixelgl.Color, Size: 4},
+		{Purpose: pixelgl.TexCoord, Size: 2},
+	}
+}
+
+// ConvertVertexData converts data in the oldFormat to the newFormat. Vertex attributes in the new format
+// will be copied from the corresponding vertex attributes in the old format. If a vertex attribute in the new format
+// has no corresponding attribute in the old format, it will be filled with zeros.
+func ConvertVertexData(oldFormat, newFormat pixelgl.VertexFormat, data []float64) []float64 {
+	// calculate the mapping between old and new format
+	// if i is a start of a vertex attribute in the new format, then mapping[i] returns
+	// the index where the same attribute starts in the old format
+	mapping := make(map[int]int)
+	i := 0
+	for _, newAttr := range newFormat {
+		j := 0
+		for _, oldAttr := range oldFormat {
+			if newAttr == oldAttr {
+				mapping[i] = j
+				break
+			}
+			j += oldAttr.Size
+		}
+		i += newAttr.Size
+	}
+
+	oldData, newData := data, []float64{}
+
+	for i := 0; i < len(oldData); i += oldFormat.Size() {
+		j := 0
+		for _, attr := range newFormat {
+			if oldIndex, ok := mapping[j]; ok { // the attribute was found in the old format
+				newData = append(newData, oldData[i+oldIndex:i+oldIndex+attr.Size]...)
+			} else { // the attribute wasn't found in the old format, so fill with zeros
+				newData = append(newData, make([]float64, attr.Size)...)
+			}
+			j += attr.Size
+		}
+	}
+
+	return newData
+}
diff --git a/pixelgl/interface.go b/pixelgl/interface.go
index b82542f..cb140a4 100644
--- a/pixelgl/interface.go
+++ b/pixelgl/interface.go
@@ -1,34 +1,28 @@
 package pixelgl
 
-// BeginEnder is an interface for manipulating OpenGL state.
+// Doer is an interface for manipulating OpenGL state.
 //
-// OpenGL is a state machine and as such, it is natural to manipulate it in a begin-end manner.
-// This interface is intended for all OpenGL objects, that can begin being active and end being active
-// such as windows, vertex arrays, vertex buffers, textures, shaders, pretty much everything.
+// OpenGL is a state machine. Every object can 'enter' it's state and 'leave' it's state. For example,
+// you can bind a buffer and unbind a buffer, bind a texture and unbind it, use shader and unuse it, and so on.
 //
-// It might seem natural to use BeginEnders this way:
+// This interface provides a clever and flexible way to do it. A typical workflow of an OpenGL object is that
+// you enter (load, bind) that object's state, then do something with it, and then leave the state. That 'something'
+// in between, let's call it sub (as in subroutine).
 //
-//   window.Begin()
-//   shader.Begin()
-//   texture.Begin()
-//   vertexarray.Begin()
-//   vertexarray.Draw()
-//   vertexarray.End()
-//   texture.End()
-//   shader.End()
-//   window.End()
+// The recommended way to implement a Doer is to wrap another Doer (vertex array wrap texture and so on), let's call
+// it parent. Then the Do method will look like this:
 //
-// Don't do this! A better practice is to make a BeginEnder so that it wraps another BeginEnder like this:
+//   func (o *MyObject) Do(sub func()) {
+//       o.parent.Do(func() {
+//	     // enter the object's state
+//           sub()
+//           // leave the object's state
+//       })
+//   }
 //
-//   shader := NewShader(window)
-//   texture := NewTexture(shader)
-//   vertexarray := NewVertexArray(texture)
-//   // now, somewhere else in your code, instead of calling numerous Begin/Ends, you just call
-//   vertexarray.Draw()
-//
-// The final single call to draw a vertex array executes all of the Begins and Ends, because the objects are
-// wrapped around each other.
-type BeginEnder interface {
-	Begin()
-	End()
+// It might seem difficult to grasp this kind of recursion at first, but it's really simple. What it's basically saying
+// is: "Hey parent, enter your state, then let me enter mine, then I'll do whatever I'm supposed to do in the middle.
+// After that I'll leave my state and please leave your state too parent."
+type Doer interface {
+	Do(sub func())
 }
diff --git a/pixelgl/shader.go b/pixelgl/shader.go
index 1cdc766..e3c9bc1 100644
--- a/pixelgl/shader.go
+++ b/pixelgl/shader.go
@@ -9,95 +9,104 @@ import (
 
 // Shader is an OpenGL shader program.
 type Shader struct {
-	parent  BeginEnder
+	parent  Doer
 	program uint32
 }
 
 // NewShader creates a new shader program from the specified vertex shader and fragment shader sources.
 //
 // Note that vertexShader and fragmentShader parameters must contain the source code, they're not filenames.
-func NewShader(parent BeginEnder, vertexShader, fragmentShader string) (*Shader, error) {
-	parent.Begin()
-	defer parent.End()
-
+func NewShader(parent Doer, vertexShader, fragmentShader string) (*Shader, error) {
 	shader := &Shader{
 		parent: parent,
 	}
-	err, glerr := DoErrGLErr(func() error {
-		var vshader, fshader uint32
-
-		// vertex shader
-		{
-			vshader = gl.CreateShader(gl.VERTEX_SHADER)
-			src, free := gl.Strs(vertexShader)
-			defer free()
-			length := int32(len(vertexShader))
-			gl.ShaderSource(vshader, 1, src, &length)
-			gl.CompileShader(vshader)
-
-			var (
-				success int32
-				infoLog = make([]byte, 512)
-			)
-			gl.GetShaderiv(vshader, gl.COMPILE_STATUS, &success)
-			if success == 0 {
-				gl.GetShaderInfoLog(vshader, int32(len(infoLog)), nil, &infoLog[0])
-				return fmt.Errorf("error compiling vertex shader: %s", string(infoLog))
+
+	errChan := make(chan error, 1)
+	parent.Do(func() {
+		err, glerr := DoErrGLErr(func() error {
+			var vshader, fshader uint32
+
+			// vertex shader
+			{
+				vshader = gl.CreateShader(gl.VERTEX_SHADER)
+				src, free := gl.Strs(vertexShader)
+				defer free()
+				length := int32(len(vertexShader))
+				gl.ShaderSource(vshader, 1, src, &length)
+				gl.CompileShader(vshader)
+
+				var (
+					success int32
+					infoLog = make([]byte, 512)
+				)
+				gl.GetShaderiv(vshader, gl.COMPILE_STATUS, &success)
+				if success == 0 {
+					gl.GetShaderInfoLog(vshader, int32(len(infoLog)), nil, &infoLog[0])
+					return fmt.Errorf("error compiling vertex shader: %s", string(infoLog))
+				}
 			}
-		}
 
-		// fragment shader
-		{
-			fshader = gl.CreateShader(gl.FRAGMENT_SHADER)
-			src, free := gl.Strs(fragmentShader)
-			defer free()
-			length := int32(len(fragmentShader))
-			gl.ShaderSource(fshader, 1, src, &length)
-			gl.CompileShader(fshader)
-
-			var (
-				success int32
-				infoLog = make([]byte, 512)
-			)
-			gl.GetShaderiv(fshader, gl.COMPILE_STATUS, &success)
-			if success == 0 {
-				gl.GetShaderInfoLog(fshader, int32(len(infoLog)), nil, &infoLog[0])
-				return fmt.Errorf("error compiling fragment shader: %s", string(infoLog))
+			// fragment shader
+			{
+				fshader = gl.CreateShader(gl.FRAGMENT_SHADER)
+				src, free := gl.Strs(fragmentShader)
+				defer free()
+				length := int32(len(fragmentShader))
+				gl.ShaderSource(fshader, 1, src, &length)
+				gl.CompileShader(fshader)
+
+				var (
+					success int32
+					infoLog = make([]byte, 512)
+				)
+				gl.GetShaderiv(fshader, gl.COMPILE_STATUS, &success)
+				if success == 0 {
+					gl.GetShaderInfoLog(fshader, int32(len(infoLog)), nil, &infoLog[0])
+					return fmt.Errorf("error compiling fragment shader: %s", string(infoLog))
+				}
 			}
-		}
 
-		// shader program
-		{
-			shader.program = gl.CreateProgram()
-			gl.AttachShader(shader.program, vshader)
-			gl.AttachShader(shader.program, fshader)
-			gl.LinkProgram(shader.program)
-
-			var (
-				success int32
-				infoLog = make([]byte, 512)
-			)
-			gl.GetProgramiv(shader.program, gl.LINK_STATUS, &success)
-			if success == 0 {
-				gl.GetProgramInfoLog(shader.program, int32(len(infoLog)), nil, &infoLog[0])
-				return fmt.Errorf("error linking shader program: %s", string(infoLog))
+			// shader program
+			{
+				shader.program = gl.CreateProgram()
+				gl.AttachShader(shader.program, vshader)
+				gl.AttachShader(shader.program, fshader)
+				gl.LinkProgram(shader.program)
+
+				var (
+					success int32
+					infoLog = make([]byte, 512)
+				)
+				gl.GetProgramiv(shader.program, gl.LINK_STATUS, &success)
+				if success == 0 {
+					gl.GetProgramInfoLog(shader.program, int32(len(infoLog)), nil, &infoLog[0])
+					return fmt.Errorf("error linking shader program: %s", string(infoLog))
+				}
 			}
-		}
 
-		gl.DeleteShader(vshader)
-		gl.DeleteShader(fshader)
+			gl.DeleteShader(vshader)
+			gl.DeleteShader(fshader)
 
-		return nil
-	})
-	if err != nil {
+			return nil
+		})
+		if err != nil {
+			if glerr != nil {
+				err = errors.Wrap(glerr, err.Error())
+			}
+			errChan <- err
+			return
+		}
 		if glerr != nil {
-			err = errors.Wrap(glerr, err.Error())
+			errChan <- err
+			return
 		}
+		errChan <- nil
+	})
+	err := <-errChan
+	if err != nil {
 		return nil, err
 	}
-	if glerr != nil {
-		return nil, glerr
-	}
+
 	return shader, nil
 }
 
@@ -108,18 +117,15 @@ func (s *Shader) Delete() {
 	})
 }
 
-// Begin starts using a shader program.
-func (s *Shader) Begin() {
-	s.parent.Begin()
-	DoNoBlock(func() {
-		gl.UseProgram(s.program)
-	})
-}
-
-// End stops using a shader program.
-func (s *Shader) End() {
-	DoNoBlock(func() {
-		gl.UseProgram(0)
+// Do stars using a shader, executes sub, and stops using it.
+func (s *Shader) Do(sub func()) {
+	s.parent.Do(func() {
+		DoNoBlock(func() {
+			gl.UseProgram(s.program)
+		})
+		sub()
+		DoNoBlock(func() {
+			gl.UseProgram(0)
+		})
 	})
-	s.parent.End()
 }
diff --git a/pixelgl/texture.go b/pixelgl/texture.go
index f948ada..35ec841 100644
--- a/pixelgl/texture.go
+++ b/pixelgl/texture.go
@@ -7,37 +7,39 @@ import (
 
 // Texture is an OpenGL texture.
 type Texture struct {
-	parent BeginEnder
+	parent Doer
 	tex    uint32
 }
 
 // NewTexture creates a new texture with the specified width and height.
 // The pixels must be a sequence of RGBA values.
-func NewTexture(parent BeginEnder, width, height int, pixels []uint8) (*Texture, error) {
-	parent.Begin()
-	defer parent.End()
-
+func NewTexture(parent Doer, width, height int, pixels []uint8) (*Texture, error) {
 	texture := &Texture{parent: parent}
 
-	err := DoGLErr(func() {
-		gl.GenTextures(1, &texture.tex)
-		gl.BindTexture(gl.TEXTURE_2D, texture.tex)
+	errChan := make(chan error, 1)
+	parent.Do(func() {
+		errChan <- DoGLErr(func() {
+			gl.GenTextures(1, &texture.tex)
+			gl.BindTexture(gl.TEXTURE_2D, texture.tex)
+
+			gl.TexImage2D(
+				gl.TEXTURE_2D,
+				0,
+				gl.RGBA,
+				int32(width),
+				int32(height),
+				0,
+				gl.RGBA,
+				gl.UNSIGNED_BYTE,
+				gl.Ptr(pixels),
+			)
 
-		gl.TexImage2D(
-			gl.TEXTURE_2D,
-			0,
-			gl.RGBA,
-			int32(width),
-			int32(height),
-			0,
-			gl.RGBA,
-			gl.UNSIGNED_BYTE,
-			gl.Ptr(pixels),
-		)
-		gl.GenerateMipmap(gl.TEXTURE_2D)
+			gl.GenerateMipmap(gl.TEXTURE_2D)
 
-		gl.BindTexture(gl.TEXTURE_2D, 0)
+			gl.BindTexture(gl.TEXTURE_2D, 0)
+		})
 	})
+	err := <-errChan
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to create a texture")
 	}
@@ -52,18 +54,15 @@ func (t *Texture) Delete() {
 	})
 }
 
-// Begin binds a texture.
-func (t *Texture) Begin() {
-	t.parent.Begin()
-	DoNoBlock(func() {
-		gl.BindTexture(gl.TEXTURE_2D, t.tex)
-	})
-}
-
-// End unbinds a texture.
-func (t *Texture) End() {
-	DoNoBlock(func() {
-		gl.BindTexture(gl.TEXTURE_2D, 0)
+// Do bind a texture, executes sub, and unbinds the texture.
+func (t *Texture) Do(sub func()) {
+	t.parent.Do(func() {
+		DoNoBlock(func() {
+			gl.BindTexture(gl.TEXTURE_2D, t.tex)
+		})
+		sub()
+		DoNoBlock(func() {
+			gl.BindTexture(gl.TEXTURE_2D, 0)
+		})
 	})
-	t.parent.End()
 }
diff --git a/pixelgl/vertex.go b/pixelgl/vertex.go
index ec0d535..8dde67b 100644
--- a/pixelgl/vertex.go
+++ b/pixelgl/vertex.go
@@ -90,7 +90,7 @@ const (
 // VertexArray is an OpenGL vertex array object that also holds it's own vertex buffer object.
 // From the user's points of view, VertexArray is an array of vertices that can be drawn.
 type VertexArray struct {
-	parent BeginEnder
+	parent Doer
 	format VertexFormat
 	vao    uint32
 	vbo    uint32
@@ -99,44 +99,50 @@ type VertexArray struct {
 }
 
 // NewVertexArray creates a new vertex array and wraps another BeginEnder around it.
-func NewVertexArray(parent BeginEnder, format VertexFormat, mode VertexDrawMode, usage VertexUsage, data []float64) (*VertexArray, error) {
-	parent.Begin()
-	defer parent.End()
-
+func NewVertexArray(parent Doer, format VertexFormat, mode VertexDrawMode, usage VertexUsage, data []float64) (*VertexArray, error) {
 	va := &VertexArray{
 		parent: parent,
 		format: format,
 		mode:   mode,
 	}
 
-	err := DoGLErr(func() {
-		gl.GenVertexArrays(1, &va.vao)
-		gl.BindVertexArray(va.vao)
-
-		gl.GenBuffers(1, &va.vbo)
-		gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
-		gl.BufferData(gl.ARRAY_BUFFER, 8*len(data), gl.Ptr(data), uint32(usage))
-
-		stride := format.Size()
-		va.count = len(data) / stride
-
-		offset := 0
-		for i, attr := range format {
-			gl.VertexAttribPointer(
-				uint32(i),
-				int32(attr.Size),
-				gl.DOUBLE,
-				false,
-				int32(8*stride),
-				gl.PtrOffset(8*offset),
-			)
-			gl.EnableVertexAttribArray(uint32(i))
-			offset += attr.Size
+	errChan := make(chan error, 1)
+	parent.Do(func() {
+		err := DoGLErr(func() {
+			gl.GenVertexArrays(1, &va.vao)
+			gl.BindVertexArray(va.vao)
+
+			gl.GenBuffers(1, &va.vbo)
+			gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
+			gl.BufferData(gl.ARRAY_BUFFER, 8*len(data), gl.Ptr(data), uint32(usage))
+
+			stride := format.Size()
+			va.count = len(data) / stride
+
+			offset := 0
+			for i, attr := range format {
+				gl.VertexAttribPointer(
+					uint32(i),
+					int32(attr.Size),
+					gl.DOUBLE,
+					false,
+					int32(8*stride),
+					gl.PtrOffset(8*offset),
+				)
+				gl.EnableVertexAttribArray(uint32(i))
+				offset += attr.Size
+			}
+
+			gl.BindBuffer(gl.ARRAY_BUFFER, 0)
+			gl.BindVertexArray(0)
+		})
+		if err != nil {
+			errChan <- err
+			return
 		}
-
-		gl.BindBuffer(gl.ARRAY_BUFFER, 0)
-		gl.BindVertexArray(0)
+		errChan <- nil
 	})
+	err := <-errChan
 	if err != nil {
 		return nil, errors.Wrap(err, "failed to create a vertex array")
 	}
@@ -176,8 +182,7 @@ func (va *VertexArray) DrawMode() VertexDrawMode {
 
 // Draw draws a vertex array.
 func (va *VertexArray) Draw() {
-	va.Begin()
-	va.End()
+	va.Do(func() {})
 }
 
 // Data returns a copy of data inside a vertex array (actually it's vertex buffer).
@@ -207,21 +212,18 @@ func (va *VertexArray) UpdateData(offset int, data []float64) {
 	})
 }
 
-// Begin binds a vertex array and it's associated vertex buffer.
-func (va *VertexArray) Begin() {
-	va.parent.Begin()
-	DoNoBlock(func() {
-		gl.BindVertexArray(va.vao)
-		gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
-	})
-}
-
-// End draws a vertex array and unbinds it alongside with it's associated vertex buffer.
-func (va *VertexArray) End() {
-	DoNoBlock(func() {
-		gl.DrawArrays(uint32(va.mode), 0, int32(va.count))
-		gl.BindBuffer(gl.ARRAY_BUFFER, 0)
-		gl.BindVertexArray(0)
+// Do binds a vertex arrray and it's associated vertex buffer, executes sub, and unbinds the vertex array and it's vertex buffer.
+func (va *VertexArray) Do(sub func()) {
+	va.parent.Do(func() {
+		DoNoBlock(func() {
+			gl.BindVertexArray(va.vao)
+			gl.BindBuffer(gl.ARRAY_BUFFER, va.vbo)
+		})
+		sub()
+		DoNoBlock(func() {
+			gl.DrawArrays(uint32(va.mode), 0, int32(va.count))
+			gl.BindBuffer(gl.ARRAY_BUFFER, 0)
+			gl.BindVertexArray(0)
+		})
 	})
-	va.parent.End()
 }
diff --git a/window.go b/window.go
index 1699eb8..723c05d 100644
--- a/window.go
+++ b/window.go
@@ -104,79 +104,79 @@ func NewWindow(config WindowConfig) (*Window, error) {
 
 // Delete destroys a window. The window can't be used any further.
 func (w *Window) Delete() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.Destroy()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.Destroy()
+		})
 	})
 }
 
 // Clear clears the window with a color.
 func (w *Window) Clear(c color.Color) {
-	w.Begin()
-	defer w.End()
-	pixelgl.Clear(colorToRGBA(c))
+	w.Do(func() {
+		pixelgl.Clear(colorToRGBA(c))
+	})
 }
 
 // Update swaps buffers and polls events.
 func (w *Window) Update() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		if w.config.VSync {
-			glfw.SwapInterval(1)
-		}
-		w.window.SwapBuffers()
-		glfw.PollEvents()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			if w.config.VSync {
+				glfw.SwapInterval(1)
+			}
+			w.window.SwapBuffers()
+			glfw.PollEvents()
+		})
 	})
 }
 
 // SetTitle changes the title of a window.
 func (w *Window) SetTitle(title string) {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.SetTitle(title)
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.SetTitle(title)
+		})
 	})
 }
 
 // SetSize resizes a window to the specified size in pixels.
 // In case of a fullscreen window, it changes the resolution of that window.
 func (w *Window) SetSize(width, height float64) {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.SetSize(int(width), int(height))
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.SetSize(int(width), int(height))
+		})
 	})
 }
 
 // Size returns the size of the client area of a window (the part you can draw on).
 func (w *Window) Size() (width, height float64) {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		wi, hi := w.window.GetSize()
-		width = float64(wi)
-		height = float64(hi)
+	w.Do(func() {
+		pixelgl.Do(func() {
+			wi, hi := w.window.GetSize()
+			width = float64(wi)
+			height = float64(hi)
+		})
 	})
 	return width, height
 }
 
 // Show makes a window visible if it was hidden.
 func (w *Window) Show() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.Show()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.Show()
+		})
 	})
 }
 
 // Hide hides a window if it was visible.
 func (w *Window) Hide() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.Hide()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.Hide()
+		})
 	})
 }
 
@@ -187,37 +187,35 @@ func (w *Window) Hide() {
 func (w *Window) SetFullscreen(monitor *Monitor) {
 	if w.Monitor() != monitor {
 		if monitor == nil {
-			w.Begin()
-			defer w.End()
-
-			pixelgl.Do(func() {
-				w.window.SetMonitor(
-					nil,
-					w.restore.xpos,
-					w.restore.ypos,
-					w.restore.width,
-					w.restore.height,
-					0,
-				)
+			w.Do(func() {
+				pixelgl.Do(func() {
+					w.window.SetMonitor(
+						nil,
+						w.restore.xpos,
+						w.restore.ypos,
+						w.restore.width,
+						w.restore.height,
+						0,
+					)
+				})
 			})
 		} else {
-			w.Begin()
-			defer w.End()
-
-			pixelgl.Do(func() {
-				w.restore.xpos, w.restore.ypos = w.window.GetPos()
-				w.restore.width, w.restore.height = w.window.GetSize()
-
-				width, height := monitor.Size()
-				refreshRate := monitor.RefreshRate()
-				w.window.SetMonitor(
-					monitor.monitor,
-					0,
-					0,
-					int(width),
-					int(height),
-					int(refreshRate),
-				)
+			w.Do(func() {
+				pixelgl.Do(func() {
+					w.restore.xpos, w.restore.ypos = w.window.GetPos()
+					w.restore.width, w.restore.height = w.window.GetSize()
+
+					width, height := monitor.Size()
+					refreshRate := monitor.RefreshRate()
+					w.window.SetMonitor(
+						monitor.monitor,
+						0,
+						0,
+						int(width),
+						int(height),
+						int(refreshRate),
+					)
+				})
 			})
 		}
 	}
@@ -230,12 +228,12 @@ func (w *Window) IsFullscreen() bool {
 
 // Monitor returns a monitor a fullscreen window is on. If the window is not fullscreen, this function returns nil.
 func (w *Window) Monitor() *Monitor {
-	w.Begin()
-	defer w.End()
-
-	monitor := pixelgl.DoVal(func() interface{} {
-		return w.window.GetMonitor()
-	}).(*glfw.Monitor)
+	var monitor *glfw.Monitor
+	w.Do(func() {
+		monitor = pixelgl.DoVal(func() interface{} {
+			return w.window.GetMonitor()
+		}).(*glfw.Monitor)
+	})
 	if monitor == nil {
 		return nil
 	}
@@ -246,37 +244,39 @@ func (w *Window) Monitor() *Monitor {
 
 // Focus brings a window to the front and sets input focus.
 func (w *Window) Focus() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.Focus()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.Focus()
+		})
 	})
 }
 
 // Focused returns true if a window has input focus.
 func (w *Window) Focused() bool {
-	w.Begin()
-	defer w.End()
-	return pixelgl.DoVal(func() interface{} {
-		return w.window.GetAttrib(glfw.Focused) == glfw.True
-	}).(bool)
+	var focused bool
+	w.Do(func() {
+		focused = pixelgl.DoVal(func() interface{} {
+			return w.window.GetAttrib(glfw.Focused) == glfw.True
+		}).(bool)
+	})
+	return focused
 }
 
 // Maximize puts a windowed window to a maximized state.
 func (w *Window) Maximize() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.Maximize()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.Maximize()
+		})
 	})
 }
 
 // Restore restores a windowed window from a maximized state.
 func (w *Window) Restore() {
-	w.Begin()
-	defer w.End()
-	pixelgl.Do(func() {
-		w.window.Restore()
+	w.Do(func() {
+		pixelgl.Do(func() {
+			w.window.Restore()
+		})
 	})
 }
 
@@ -285,11 +285,11 @@ var currentWindow struct {
 	handler *Window
 }
 
-// Begin makes the context of this window current.
-//
-// Note that you only need to use this function if you're designing a low-level technical plugin (such as an effect).
-func (w *Window) Begin() {
+// Do makes the context of this window current, if it's not already, and executes sub.
+func (w *Window) Do(sub func()) {
 	currentWindow.Lock()
+	defer currentWindow.Unlock()
+
 	if currentWindow.handler != w {
 		pixelgl.Do(func() {
 			w.window.MakeContextCurrent()
@@ -297,11 +297,6 @@ func (w *Window) Begin() {
 		})
 		currentWindow.handler = w
 	}
-}
 
-// End makes it possible for other windows to make their context current.
-//
-// Note that you only need to use this function if you're designing a low-level technical plugin (such as an effect).
-func (w *Window) End() {
-	currentWindow.Unlock()
+	sub()
 }
-- 
GitLab