Add README, LICENSE and Examples
This commit is contained in:
parent
e391e703bf
commit
2f5b498ca1
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2020 Noah Petherbridge
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
236
README.md
Normal file
236
README.md
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
# ui: User Interface Toolkit for Go
|
||||||
|
|
||||||
|
Package ui is a user interface toolkit for Go that targets desktop
|
||||||
|
applications (SDL2, for Linux, MacOS and Windows) as well as web browsers
|
||||||
|
(WebAssembly rendering to an HTML Canvas).
|
||||||
|
|
||||||
|
![Screenshot](docs/guitest.png)
|
||||||
|
|
||||||
|
> _(Screenshot is from Project: Doodle's GUITest debug screen showing a_
|
||||||
|
> _Window, several Frames, Labels, Buttons and a Checkbox widget.)_
|
||||||
|
|
||||||
|
It is very much a **work in progress** and it's a bit buggy. See the
|
||||||
|
[Known Issues](#known-issues) at the bottom of this document.
|
||||||
|
|
||||||
|
This library is being developed in conjunction with my drawing-based maze
|
||||||
|
game, [Project: Doodle](https://www.kirsle.net/doodle). The rendering engine
|
||||||
|
library is at [go/render](https://git.kirsle.net/go/render) which provides
|
||||||
|
the SDL2 and Canvas back-ends.
|
||||||
|
(GitHub mirror: [kirsle/render](https://github.com/kirsle/render))
|
||||||
|
|
||||||
|
# Example
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
"git.kirsle.net/go/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mw, err := ui.NewMainWindow("Hello World")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mw.SetBackground(render.White)
|
||||||
|
|
||||||
|
// Draw a label.
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Hello, world!",
|
||||||
|
Font: render.Text{
|
||||||
|
FontFilename: "../DejaVuSans.ttf",
|
||||||
|
Size: 32,
|
||||||
|
Color: render.SkyBlue,
|
||||||
|
Shadow: render.SkyBlue.Darken(40),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mw.Pack(label, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
PadY: 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Draw a button.
|
||||||
|
button := ui.NewButton("My Button", ui.NewLabel(ui.Label{
|
||||||
|
Text: "Click me!",
|
||||||
|
Font: render.Text{
|
||||||
|
FontFilename: "../DejaVuSans.ttf",
|
||||||
|
Size: 12,
|
||||||
|
Color: render.Red,
|
||||||
|
Padding: 4,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
button.Handle(ui.Click, func(p render.Point) {
|
||||||
|
fmt.Println("I've been clicked!")
|
||||||
|
})
|
||||||
|
mw.Pack(button, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the button to the MainWindow's Supervisor so it can be
|
||||||
|
// clicked on and interacted with.
|
||||||
|
mw.Add(button)
|
||||||
|
|
||||||
|
mw.MainLoop()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# Widgets and Features
|
||||||
|
|
||||||
|
The following widgets have been implemented or are planned for the future.
|
||||||
|
|
||||||
|
Widgets are designed to be composable, making use of pre-existing widgets to
|
||||||
|
create more complex ones. The widgets here are ordered from simplest to
|
||||||
|
most complex.
|
||||||
|
|
||||||
|
**Fully implemented widgets:**
|
||||||
|
|
||||||
|
* [x] **BaseWidget**: the base class of all Widgets.
|
||||||
|
* The `Widget` interface describes the functions common to all Widgets,
|
||||||
|
such as SetBackground, Configure, MoveTo, Resize, and so on.
|
||||||
|
* BaseWidget provides sane default implementations for all the methods
|
||||||
|
required by the Widget interface. Most Widgets inherit from
|
||||||
|
the BaseWidget.
|
||||||
|
* [x] **Frame**: a layout wrapper for other widgets.
|
||||||
|
* Pack() lets you add child widgets to the Frame, aligned against one side
|
||||||
|
or another, and ability to expand widgets to take up remaining space in
|
||||||
|
their part of the Frame.
|
||||||
|
* [x] **Label**: Textual labels for your UI.
|
||||||
|
* Supports TrueType fonts, color, stroke, drop shadow, font size, etc.
|
||||||
|
* Variable binding support: TextVariable or IntVariable can point to a
|
||||||
|
string or int reference, respectively, to provide the text of the label
|
||||||
|
dynamically.
|
||||||
|
* [x] **Image**: show a PNG or Bitmap image on your UI.
|
||||||
|
* [x] **Button**: clickable buttons.
|
||||||
|
* They can wrap _any_ widget. Labels are most common but can also wrap a
|
||||||
|
Frame so you can have labels + icon images inside the button, etc.
|
||||||
|
* Mouse hover and click event handlers.
|
||||||
|
* [x] **CheckButton** and **RadioButton**
|
||||||
|
* Variants on the Button which bind to a variable and toggle its state
|
||||||
|
when clicked. Boolean variable pointers are used with CheckButton and
|
||||||
|
string pointers for RadioButton.
|
||||||
|
* CheckButtons stay pressed in when clicked (true) and pop back out when
|
||||||
|
clicked again (false).
|
||||||
|
* RadioButtons stay pressed in when the string variable matches their
|
||||||
|
value, and pop out when the string variable changes.
|
||||||
|
* [x] **Checkbox** and **Radiobox**: a Frame widget that wraps a
|
||||||
|
CheckButton and a Label to provide a more traditional UI element.
|
||||||
|
* Works the same as CheckButton and RadioButton but draws a separate
|
||||||
|
label next to a small check button. Clicking the label will toggle the
|
||||||
|
state of the checkbox.
|
||||||
|
* [x] **Window**: a Frame with a title bar Frame on top.
|
||||||
|
* Note: Window is not yet draggable or closeable.
|
||||||
|
|
||||||
|
**Work in progress widgets:**
|
||||||
|
|
||||||
|
* [x] **Menu**: a frame with clickable menu items.
|
||||||
|
* To be a base widget behind right-click context menus, pull-down menus
|
||||||
|
from a MenuBar, options from a SelectBox and so on.
|
||||||
|
* Powered by Frame and Button but with a nice API for composing menu
|
||||||
|
actions.
|
||||||
|
* Partially implemented so far.
|
||||||
|
* [ ] **MenuButton**: a Button that opens a Menu when clicked.
|
||||||
|
* [ ] **MenuBar**: a Frame that houses many MenuButtons, intended for the
|
||||||
|
main menu at the top of a UI window (File, Edit, Help, etc.).
|
||||||
|
* [ ] **Scrollbar**: a Frame including a trough, scroll buttons and a
|
||||||
|
draggable slider.
|
||||||
|
|
||||||
|
**Wish list for the longer-term future:**
|
||||||
|
|
||||||
|
* [ ] **SelectBox:** a kind of MenuButton that lets the user choose a value
|
||||||
|
from a list of possible values, bound to a string variable.
|
||||||
|
* [ ] **WindowManager**: manages Window widgets and focus support for all
|
||||||
|
interactable widgets.
|
||||||
|
* Would enable Windows to be dragged around by their title bar, overlap
|
||||||
|
other Windows, and rise on top of other Windows when clicked.
|
||||||
|
* Would enable "focus" support for Buttons, Text Boxes and other
|
||||||
|
interactable widgets.
|
||||||
|
* [ ] **TextBox:** an editable text field that the user can focus and type
|
||||||
|
a value into.
|
||||||
|
* Would depend on the WindowManager to manage focus for the widgets.
|
||||||
|
|
||||||
|
## Supervisor for Interaction
|
||||||
|
|
||||||
|
Some widgets that support user interaction (such as Button, CheckButton and
|
||||||
|
Checkbox) need to be added to the Supervisor which watches over them and
|
||||||
|
communicates events that they're interested in.
|
||||||
|
|
||||||
|
```go
|
||||||
|
func SupervisorSDL2Example() {
|
||||||
|
// NOTE: using the render/sdl engine.
|
||||||
|
window := sdl.New("Hello World", 800, 600)
|
||||||
|
window.Setup()
|
||||||
|
|
||||||
|
// One Supervisor is needed per UI.
|
||||||
|
supervisor := ui.NewSupervisor()
|
||||||
|
|
||||||
|
// A button for our UI.
|
||||||
|
btn := ui.NewButton("Button1", ui.NewLabel(ui.Label{
|
||||||
|
Text: "Click me!",
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Add it to the Supervisor.
|
||||||
|
supervisor.Add(btn)
|
||||||
|
|
||||||
|
// Main loop
|
||||||
|
for {
|
||||||
|
// Check for keyboard/mouse events
|
||||||
|
ev, _ := window.Poll()
|
||||||
|
|
||||||
|
// Ping the Supervisor Loop function with the event state, so
|
||||||
|
// it can trigger events on the widgets under its care.
|
||||||
|
supervisor.Loop(ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You only need one Supervisor instance per UI. Add() each interactive widget
|
||||||
|
to it, and call its Loop() method in your main loop so it can update the
|
||||||
|
state of the widgets under its care.
|
||||||
|
|
||||||
|
The MainWindow includes its own Supervisor, see below.
|
||||||
|
|
||||||
|
## MainWindow for Simple Applications
|
||||||
|
|
||||||
|
The MainWindow widget may be used for "simple" UI applications where all you
|
||||||
|
want is a GUI and you don't want to manage your own SDL2 (or Canvas) engine.
|
||||||
|
|
||||||
|
MainWindow is only to be used **one time** per application, and it sets up
|
||||||
|
its own SDL2 render context and creates the main window. It also contains a
|
||||||
|
Frame widget for the window contents and you may Pack() widgets into the
|
||||||
|
window the same as you would a Frame.
|
||||||
|
|
||||||
|
MainWindow includes its own Supervisor: just call the `.Add(Widget)`
|
||||||
|
method to add interactive widgets to the supervisor. The MainLoop() of the
|
||||||
|
window calls Supervisor.Loop() automatically.
|
||||||
|
|
||||||
|
# Known Issues
|
||||||
|
|
||||||
|
The frame packing algorithm (frame_pack.go) is currently very buggy and in
|
||||||
|
need of a re-write. Some examples of issues with it:
|
||||||
|
|
||||||
|
* Currently, when the Frame is iterating over packed widgets to decide their
|
||||||
|
location and size, it explicitly calls MoveTo() and Resize() giving them
|
||||||
|
their pixel-coordinates, relative to the Frame's own position.
|
||||||
|
* When Frames nest other Frames this becomes more of an issue.
|
||||||
|
* The Supervisor sometimes can't determine the correct position of a
|
||||||
|
button packed inside of nested frames. It currently checks the
|
||||||
|
Point() of the button (set by its parent Frame) and this doesn't
|
||||||
|
account for the grandparent frame's position. Using the
|
||||||
|
AbsolutePosition() helper function (which recursively crawls up a
|
||||||
|
widget tree) also yields incorrect results, as the position of each
|
||||||
|
Frame is _added_ to the position of the Button which throws it off even
|
||||||
|
further.
|
||||||
|
|
||||||
|
It's on my to-do list to rewrite the algorithm from scratch and make it
|
||||||
|
more resilient. One thing I also want to do is rename the `Anchor` field
|
||||||
|
and call it `Side` to be more in line with the Tk GUI toolkit's naming
|
||||||
|
convention. ("Side: N" or "Side: SE", and let the "Anchor" name be used for
|
||||||
|
how to center the widget inside of its space ("Top", "Center", "Left", etc.)
|
||||||
|
|
||||||
|
# License
|
||||||
|
|
||||||
|
MIT.
|
BIN
docs/guitest.png
Normal file
BIN
docs/guitest.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 31 KiB |
BIN
eg/DejaVuSans.ttf
Normal file
BIN
eg/DejaVuSans.ttf
Normal file
Binary file not shown.
55
eg/hello-world/main.go
Normal file
55
eg/hello-world/main.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kirsle.net/go/render"
|
||||||
|
"git.kirsle.net/go/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
mw, err := ui.NewMainWindow("Hello World")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mw.SetBackground(render.White)
|
||||||
|
|
||||||
|
// Draw a label.
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Hello, world!",
|
||||||
|
Font: render.Text{
|
||||||
|
FontFilename: "../DejaVuSans.ttf",
|
||||||
|
Size: 32,
|
||||||
|
Color: render.SkyBlue,
|
||||||
|
Shadow: render.SkyBlue.Darken(40),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
mw.Pack(label, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
PadY: 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Draw a button.
|
||||||
|
button := ui.NewButton("My Button", ui.NewLabel(ui.Label{
|
||||||
|
Text: "Click me!",
|
||||||
|
Font: render.Text{
|
||||||
|
FontFilename: "../DejaVuSans.ttf",
|
||||||
|
Size: 12,
|
||||||
|
Color: render.Red,
|
||||||
|
Padding: 4,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
button.Handle(ui.Click, func(p render.Point) {
|
||||||
|
fmt.Println("I've been clicked!")
|
||||||
|
})
|
||||||
|
mw.Pack(button, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the button to the MainWindow's Supervisor so it can be
|
||||||
|
// clicked on and interacted with.
|
||||||
|
mw.Add(button)
|
||||||
|
|
||||||
|
mw.MainLoop()
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/lib/ui/eg/layout"
|
"git.kirsle.net/go/ui/eg/layout"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
102
eg/main.go
102
eg/main.go
|
@ -1,8 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.kirsle.net/go/render"
|
"git.kirsle.net/go/render"
|
||||||
"git.kirsle.net/apps/doodle/lib/ui"
|
"git.kirsle.net/go/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -13,7 +15,7 @@ func main() {
|
||||||
|
|
||||||
leftFrame := ui.NewFrame("Left Frame")
|
leftFrame := ui.NewFrame("Left Frame")
|
||||||
leftFrame.Configure(ui.Config{
|
leftFrame.Configure(ui.Config{
|
||||||
Width: 200,
|
Width: 160,
|
||||||
BorderSize: 1,
|
BorderSize: 1,
|
||||||
BorderStyle: ui.BorderRaised,
|
BorderStyle: ui.BorderRaised,
|
||||||
Background: render.Grey,
|
Background: render.Grey,
|
||||||
|
@ -37,7 +39,37 @@ func main() {
|
||||||
Text: "Hello world",
|
Text: "Hello world",
|
||||||
})
|
})
|
||||||
leftFrame.Pack(label, ui.Pack{
|
leftFrame.Pack(label, ui.Pack{
|
||||||
Anchor: ui.SE,
|
Anchor: ui.N,
|
||||||
|
PadY: 12,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Draw some buttons in the left frame.
|
||||||
|
for i := 1; i <= 12; i++ {
|
||||||
|
i := i
|
||||||
|
|
||||||
|
btn := ui.NewButton(fmt.Sprintf("Button-%d", i), ui.NewLabel(ui.Label{
|
||||||
|
Text: fmt.Sprintf("Button #%d", i),
|
||||||
|
}))
|
||||||
|
btn.Handle(ui.Click, func(p render.Point) {
|
||||||
|
fmt.Printf("Button %d was clicked\n", i)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Add the button to the MainWindow's event supervisor, so it may be
|
||||||
|
// clicked and interacted with.
|
||||||
|
mw.Add(btn)
|
||||||
|
|
||||||
|
leftFrame.Pack(btn, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
PadY: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Frame to show off check buttons.
|
||||||
|
mainFrame.Pack(radioButtonFrame(mw), ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
PadY: 8,
|
||||||
})
|
})
|
||||||
|
|
||||||
err = mw.MainLoop()
|
err = mw.MainLoop()
|
||||||
|
@ -45,3 +77,67 @@ func main() {
|
||||||
panic("MainLoop:" + err.Error())
|
panic("MainLoop:" + err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Frame that shows off radio buttons.
|
||||||
|
func radioButtonFrame(mw *ui.MainWindow) *ui.Frame {
|
||||||
|
// The string variable that will be bound to the radio buttons.
|
||||||
|
// This could also be a global variable at the package level.
|
||||||
|
radioValue := "Red"
|
||||||
|
|
||||||
|
// Main frame.
|
||||||
|
frame := ui.NewFrame("radio button demo")
|
||||||
|
frame.Configure(ui.Config{
|
||||||
|
Background: render.RGBA(153, 255, 153, 255),
|
||||||
|
BorderSize: 1,
|
||||||
|
BorderStyle: ui.BorderRaised,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Top row to show the label and current radiobutton bound value.
|
||||||
|
topFrame := ui.NewFrame("radio button label frame")
|
||||||
|
frame.Pack(topFrame, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Draw the labels.
|
||||||
|
{
|
||||||
|
label := ui.NewLabel(ui.Label{
|
||||||
|
Text: "Radio buttons. Value:",
|
||||||
|
})
|
||||||
|
topFrame.Pack(label, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
})
|
||||||
|
|
||||||
|
valueLabel := ui.NewLabel(ui.Label{
|
||||||
|
TextVariable: &radioValue,
|
||||||
|
})
|
||||||
|
topFrame.Pack(valueLabel, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
PadX: 4,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// The radio buttons themselves.
|
||||||
|
btnFrame := ui.NewFrame("radio button frame")
|
||||||
|
frame.Pack(btnFrame, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
FillX: true,
|
||||||
|
})
|
||||||
|
{
|
||||||
|
colors := []string{"Red", "Green", "Blue", "Yellow"}
|
||||||
|
for _, color := range colors {
|
||||||
|
color := color
|
||||||
|
|
||||||
|
btn := ui.NewRadioButton("color:"+color, &radioValue, color, ui.NewLabel(ui.Label{
|
||||||
|
Text: color,
|
||||||
|
}))
|
||||||
|
mw.Add(btn)
|
||||||
|
btnFrame.Pack(btn, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
PadX: 2,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return frame
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ var (
|
||||||
|
|
||||||
// MainWindow is the parent window of a UI application.
|
// MainWindow is the parent window of a UI application.
|
||||||
type MainWindow struct {
|
type MainWindow struct {
|
||||||
engine render.Engine
|
Engine render.Engine
|
||||||
supervisor *Supervisor
|
supervisor *Supervisor
|
||||||
frame *Frame
|
frame *Frame
|
||||||
w int
|
w int
|
||||||
|
@ -33,12 +33,12 @@ func NewMainWindow(title string) (*MainWindow, error) {
|
||||||
supervisor: NewSupervisor(),
|
supervisor: NewSupervisor(),
|
||||||
}
|
}
|
||||||
|
|
||||||
mw.engine = sdl.New(
|
mw.Engine = sdl.New(
|
||||||
title,
|
title,
|
||||||
mw.w,
|
mw.w,
|
||||||
mw.h,
|
mw.h,
|
||||||
)
|
)
|
||||||
if err := mw.engine.Setup(); err != nil {
|
if err := mw.Engine.Setup(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,9 +72,14 @@ func (mw *MainWindow) resized() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBackground changes the window's frame's background color.
|
||||||
|
func (mw *MainWindow) SetBackground(color render.Color) {
|
||||||
|
mw.frame.SetBackground(color)
|
||||||
|
}
|
||||||
|
|
||||||
// Present the window.
|
// Present the window.
|
||||||
func (mw *MainWindow) Present() {
|
func (mw *MainWindow) Present() {
|
||||||
mw.supervisor.Present(mw.engine)
|
mw.supervisor.Present(mw.Engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MainLoop starts the main event loop and blocks until there's an error.
|
// MainLoop starts the main event loop and blocks until there's an error.
|
||||||
|
@ -89,19 +94,19 @@ func (mw *MainWindow) MainLoop() error {
|
||||||
|
|
||||||
// Loop does one loop of the UI.
|
// Loop does one loop of the UI.
|
||||||
func (mw *MainWindow) Loop() error {
|
func (mw *MainWindow) Loop() error {
|
||||||
mw.engine.Clear(render.White)
|
mw.Engine.Clear(render.White)
|
||||||
|
|
||||||
// Record how long this loop took.
|
// Record how long this loop took.
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
// Poll for events.
|
// Poll for events.
|
||||||
ev, err := mw.engine.Poll()
|
ev, err := mw.Engine.Poll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("event poll error: %s", err)
|
return fmt.Errorf("event poll error: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ev.WindowResized {
|
if ev.WindowResized {
|
||||||
w, h := mw.engine.WindowSize()
|
w, h := mw.Engine.WindowSize()
|
||||||
if w != mw.w || h != mw.h {
|
if w != mw.w || h != mw.h {
|
||||||
mw.w = w
|
mw.w = w
|
||||||
mw.h = h
|
mw.h = h
|
||||||
|
@ -109,11 +114,12 @@ func (mw *MainWindow) Loop() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mw.frame.Compute(mw.engine)
|
mw.frame.Compute(mw.Engine)
|
||||||
|
|
||||||
// Render the child widgets.
|
// Render the child widgets.
|
||||||
mw.supervisor.Present(mw.engine)
|
mw.supervisor.Loop(ev)
|
||||||
mw.engine.Present()
|
mw.supervisor.Present(mw.Engine)
|
||||||
|
mw.Engine.Present()
|
||||||
|
|
||||||
// Delay to maintain target frames per second.
|
// Delay to maintain target frames per second.
|
||||||
var delay uint32
|
var delay uint32
|
||||||
|
@ -122,7 +128,7 @@ func (mw *MainWindow) Loop() error {
|
||||||
if targetFPS-int(elapsed) > 0 {
|
if targetFPS-int(elapsed) > 0 {
|
||||||
delay = uint32(targetFPS - int(elapsed))
|
delay = uint32(targetFPS - int(elapsed))
|
||||||
}
|
}
|
||||||
mw.engine.Delay(delay)
|
mw.Engine.Delay(delay)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user