Code

To get started with our first nk application, there's a certain amount of setup code we need to write. Nuklear is focused on delivering a graphical toolkit API and not the operating system-specific code such as managing windows and user input. To avoid having to write all of that code ourselves, we'll use the glfw Go bindings to create and show a window for our application. The following code will set up an application window and show it (without any content). We also need to call runtime.LockOSThread() as this setup code must all execute on the main thread:

package main

import "runtime"
import "github.com/go-gl/glfw/v3.2/glfw"
import "github.com/go-gl/gl/v3.2-core/gl"

func
init() {
runtime.LockOSThread()
}

func main() {
glfw.Init()
win, _ := glfw.CreateWindow(120, 80, "Hello World", nil, nil)
win.MakeContextCurrent()
gl.Init()

...
}

After initializing glfw, we need to create a window, which glfw.CreateWindow() handles for us. We specify the window size and title in the first three parameters. The fourth parameter is used for fullscreen windows; by passing a *glfw.Monitor reference, we request a window that fills the specified monitor in its default video mode. The final parameter is related to context sharing, passing an existing *glfw.Window reference requests that this new window shares the same graphical context to reuse textures and other resources. We then make the new window current so that its context is used in the following code. Note that the window may not exactly match the requested parameters (exact window size or monitor modes may not be supported), so it's important to check these values after creation rather than assume the result.

The other setup we must do is to create an OpenGL context that the Nuklear code can utilize. For this task, we'll import the go-gl library (by the same authors as the glfw Go bindings). We initialize the OpenGL library ready to use the context from the window that was created by glfw.

Additionally, the nk package needs to be initialized and we need to set up a default font. Thankfully, Nuklear has a standard font packaged but we need to run some code to set it as the default (or load a custom one for our application):

import "github.com/golang-ui/nuklear/nk"

func main() {
...

ctx := nk.NkPlatformInit(win, nk.PlatformInstallCallbacks)
atlas := nk.NewFontAtlas()
nk.NkFontStashBegin(&atlas)
font := nk.NkFontAtlasAddDefault(atlas, 14, nil)
nk.NkFontStashEnd()
nk.NkStyleSetFont(ctx, font.Handle())

...
}

With all of the setup done, the window still looks the same as we haven't yet rendered any content. To actually run a Nuklear application, we need to add a run-loop that handles event management and GUI refreshing. The following code isn't the simplest possible event loop (it would be possible to use for !win.ShouldClose() { ... }, but that would consume a whole CPU!), but it's reasonably efficient for the brevity. It sets up a loop that will check for any events and then refresh the user interface 30 times a second. The following code block completes our basic nk main() function:

import "time"

func main() {
...

quit := make(chan struct{}, 1)
ticker := time.NewTicker(time.Second / 30)
for {
select {
case < -quit:
nk.NkPlatformShutdown()
glfw.Terminate()
ticker.Stop()
return
case<-ticker.C:
if win.ShouldClose() {
close(quit)
continue
}
glfw.PollEvents()
draw(win, ctx)
}
}
}

The preceding code will run our application, but we haven't defined the user interface. The call to a draw() function in the preceding code is the secret, so we should implement that now. Let's look at the method in two parts: first, the GUI layout and second, the actual rendering. To set up our interface, we create a new frame (imagine a single snapshot of a video) that will be drawn on the next refresh of the user interface. After calling nk.NkPlatformNewFrame(), we can set up our interface; any code between nk.NkBegin() and nk.NkEnd() will be part of our UI update for the frame we just started. We can find out whether re-drawing is needed by checking the returned update variable; if it's 0, then no changes have occurred and we can skip the UI code.

Inside the if update > 0 { ... } block, we lay out the application interface, two rows each containing a single cell. In the first row (created with nk.NkLayoutRowStatic()), we add an nk.NkLabel containing the text Hello World!. In the second, we create a Quit button using nk.NkButtonLabel(). As this is an immediate mode user interface, we don't retain a reference to the button to check its state, nor do we pass an on-click handler; we simply check the return value from the widget draw function. The value that's returned will be greater than 0 if the button has been clicked; and so we can place code inline that will tell the window to close and thereby close the application:

const pad = 8

func draw(win *glfw.Window, ctx *nk.Context) {
// Define GUI
nk.NkPlatformNewFrame()
width, height := win.GetSize()
bounds := nk.NkRect(0, 0, float32(width), float32(height))
update := nk.NkBegin(ctx, "", bounds, nk.WindowNoScrollbar)

if update > 0 {
cellWidth := int32(width-pad*2)
cellHeight := float32(height-pad*2) / 2.0
nk.NkLayoutRowStatic(ctx, cellHeight, cellWidth, 1)
{
nk.NkLabel(ctx, "Hello World!", nk.TextCentered)
}
nk.NkLayoutRowStatic(ctx, cellHeight, cellWidth, 1)
{
if nk.NkButtonLabel(ctx, "Quit") > 0 {
win.SetShouldClose(true)
}
}
}
nk.NkEnd(ctx)

...
}

Lastly, at the end of the draw() function, we need to ask our OpenGL viewport to render the created user interface. To do this, we set up the OpenGL viewport using gl.Viewport()—as you can see, we use the width and height parameters from the actual window size rather than assuming the size we requested at the beginning of this code is correct. Once the viewport is set up, we clear it and set a background color (using gl.ClearColor()). The main render work is handled by nk.NkPlatformRender(), which takes the frame that we defined previously and draws it into the current graphical context. This function requires that we specify buffer sizes for the vertex and element buffers. We pass numbers that will be large enough for our demonstration purposes.

Finally, we cause the content to be shown by calling win.SwapBuffers(). As glfw.Window is double buffered, we've been drawing to a back buffer that's currently off-screen. By calling swap, we're moving the back buffer to the screen and setting the previously shown front buffer to be hidden, ready for the next frame to be drawn:

func draw(win *glfw.Window, ctx *nk.Context) {
...

// Draw to viewport
gl.Viewport(0, 0, int32(width), int32(height))
gl.Clear(gl.COLOR_BUFFER_BIT)
gl.ClearColor(0x10, 0x10, 0x10, 0xff)
nk.NkPlatformRender(nk.AntiAliasingOn, 4096, 1024)
win.SwapBuffers()
}

That should complete the code for our hello world application. There was a lot of setup but the UI definition code was relatively succinct, so building more complex interfaces won't be much more work.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.219.63.95