New Widget: TabFrame
* Added the TabFrame widget with an example program and screenshot * Button: FixedColor=true to set a consistent background color and not worry about it with mouseover/down events.
This commit is contained in:
parent
e7e8b4b2c1
commit
5d16f5d50c
|
@ -113,6 +113,7 @@ most complex.
|
|||
* Place() lets you place child widgets relative to the parent. You can place
|
||||
it at an exact Point, or against the Top, Left, Bottom or Right sides, or
|
||||
aligned to the Center (horizontal) or Middle (vertical) of the parent.
|
||||
[Example](eg/frame-place)
|
||||
* [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
|
||||
|
@ -136,18 +137,20 @@ most complex.
|
|||
* 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] **TabFrame:** a collection of Frames navigated between using a row
|
||||
of tab buttons along their top edge. [Example](eg/tabframe).
|
||||
* [x] **Pager**: a series of numbered buttons to use with a paginated UI.
|
||||
Includes "Forward" and "Next" buttons and buttons for each page number.
|
||||
* [x] **Window**: a Frame with a title bar Frame on top.
|
||||
* Can be managed by Supervisor to give Window Manager controls to it
|
||||
(drag it by its title bar, Close button, window focus, multiple overlapping
|
||||
windows, and so on).
|
||||
* [x] **Tooltip**: a mouse hover label attached to a widget.
|
||||
windows, and so on). [Example](eg/windows)
|
||||
* [x] **Tooltip**: a mouse hover label attached to a widget. [Example](eg/tooltip)
|
||||
* [x] **MenuButton**: a button that opens a modal pop-up menu on click.
|
||||
* [x] **MenuBar**: a specialized Frame that groups a bunch of MenuButtons and
|
||||
provides a simple API to add menus and items to it.
|
||||
* [x] **Menu**: a frame full of clickable links and separators. Usually used as
|
||||
a modal pop-up by the MenuButton and MenuBar.
|
||||
a modal pop-up by the MenuButton and MenuBar. [Example](eg/menus)
|
||||
* [x] **SelectBox**: a kind of MenuButton that lets the user choose a
|
||||
value from a list of possible values.
|
||||
|
||||
|
|
29
button.go
29
button.go
|
@ -11,9 +11,14 @@ import (
|
|||
// Button is a clickable button.
|
||||
type Button struct {
|
||||
BaseWidget
|
||||
Name string
|
||||
child Widget
|
||||
style *style.Button
|
||||
|
||||
// Set this true to hard-set a color for this button;
|
||||
// it will not adjust on mouse-over or press.
|
||||
FixedColor bool
|
||||
|
||||
// Private options.
|
||||
hovering bool
|
||||
clicked bool
|
||||
|
@ -22,28 +27,33 @@ type Button struct {
|
|||
// NewButton creates a new Button.
|
||||
func NewButton(name string, child Widget) *Button {
|
||||
w := &Button{
|
||||
Name: name,
|
||||
child: child,
|
||||
style: &style.DefaultButton,
|
||||
}
|
||||
w.IDFunc(func() string {
|
||||
return fmt.Sprintf("Button<%s>", name)
|
||||
return fmt.Sprintf("Button<%s>", w.Name)
|
||||
})
|
||||
|
||||
w.SetStyle(Theme.Button)
|
||||
|
||||
w.Handle(MouseOver, func(e EventData) error {
|
||||
w.hovering = true
|
||||
w.SetBackground(w.style.HoverBackground)
|
||||
if label, ok := w.child.(*Label); ok {
|
||||
label.Font.Color = w.style.HoverForeground
|
||||
if !w.FixedColor {
|
||||
w.SetBackground(w.style.HoverBackground)
|
||||
if label, ok := w.child.(*Label); ok {
|
||||
label.Font.Color = w.style.HoverForeground
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
w.Handle(MouseOut, func(e EventData) error {
|
||||
w.hovering = false
|
||||
w.SetBackground(w.style.Background)
|
||||
if label, ok := w.child.(*Label); ok {
|
||||
label.Font.Color = w.style.Foreground
|
||||
if !w.FixedColor {
|
||||
w.SetBackground(w.style.Background)
|
||||
if label, ok := w.child.(*Label); ok {
|
||||
label.Font.Color = w.style.Foreground
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
@ -83,6 +93,11 @@ func (w *Button) SetStyle(v *style.Button) {
|
|||
}
|
||||
}
|
||||
|
||||
// GetStyle gets the button style.
|
||||
func (w *Button) GetStyle() *style.Button {
|
||||
return w.style
|
||||
}
|
||||
|
||||
// Children returns the button's child widget.
|
||||
func (w *Button) Children() []Widget {
|
||||
return []Widget{w.child}
|
||||
|
|
|
@ -14,3 +14,5 @@ screenshot and description:
|
|||
* [Menus](menus/): demonstrates various Menu Buttons and a Menu Bar.
|
||||
* [Themes](themes/): a UI demo that shows off the Default, Flat, and Dark UI
|
||||
themes as part of experimental theming support.
|
||||
* [TabFrame](tabframe/): demo for the TabFrame widget showing multiple Windows
|
||||
with tabbed interfaces.
|
11
eg/tabframe/README.md
Normal file
11
eg/tabframe/README.md
Normal file
|
@ -0,0 +1,11 @@
|
|||
# TabFrame Demo
|
||||
|
||||
![Screenshot](screenshot.png)
|
||||
|
||||
This demo shows off the TabFrame widget, in multiple copies of a
|
||||
pop-up Window widget.
|
||||
|
||||
## Running It
|
||||
|
||||
From your terminal, just type `go run main.go` from this
|
||||
example's directory.
|
267
eg/tabframe/main.go
Normal file
267
eg/tabframe/main.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package main
|
||||
|
||||
// See the MakeTabFrame() function just below for the meat of this example.
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/event"
|
||||
"git.kirsle.net/go/render/sdl"
|
||||
"git.kirsle.net/go/ui"
|
||||
"git.kirsle.net/go/ui/theme"
|
||||
)
|
||||
|
||||
// Program globals.
|
||||
var (
|
||||
// Size of the MainWindow.
|
||||
Width = 1024
|
||||
Height = 768
|
||||
|
||||
// Cascade offset for creating multiple windows.
|
||||
Cascade = render.NewPoint(10, 32)
|
||||
CascadeStep = render.NewPoint(24, 24)
|
||||
CascadeLoops = 1
|
||||
|
||||
// Colors for each window created.
|
||||
WindowColors = []render.Color{
|
||||
render.Blue,
|
||||
render.Red,
|
||||
render.DarkYellow,
|
||||
render.DarkGreen,
|
||||
render.DarkCyan,
|
||||
render.DarkBlue,
|
||||
render.DarkRed,
|
||||
}
|
||||
WindowID int
|
||||
OpenWindows int
|
||||
|
||||
TabFont = render.Text{
|
||||
Size: 10,
|
||||
Color: render.Black,
|
||||
Padding: 4,
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
sdl.DefaultFontFilename = "../DejaVuSans.ttf"
|
||||
}
|
||||
|
||||
// MakeTabFrame is the example use of the TabFrame widget.
|
||||
// The rest of this file is basically a copy of the eg/windows
|
||||
// demo, except each window embeds the TabFrame.
|
||||
func MakeTabFrame(mw *ui.MainWindow) *ui.TabFrame {
|
||||
notebook := ui.NewTabFrame("Example")
|
||||
|
||||
// AddTab gives you the Frame to populate for that tab.
|
||||
|
||||
// First Tab contents.
|
||||
tab1 := notebook.AddTab("Tab 1", ui.NewLabel(ui.Label{
|
||||
Text: "First Tab",
|
||||
Font: TabFont,
|
||||
}))
|
||||
{
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "Hello world",
|
||||
Font: render.Text{
|
||||
Size: 24,
|
||||
Color: render.SkyBlue,
|
||||
},
|
||||
})
|
||||
tab1.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
})
|
||||
|
||||
label2 := ui.NewLabel(ui.Label{
|
||||
Text: "This is the text content of the first\n" +
|
||||
"of the three tab frames.",
|
||||
Font: render.Text{
|
||||
Size: 10,
|
||||
Color: render.SkyBlue.Darken(40),
|
||||
},
|
||||
})
|
||||
tab1.Pack(label2, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 8,
|
||||
})
|
||||
}
|
||||
|
||||
// Second Tab.
|
||||
tab2 := notebook.AddTab("Tab 2", ui.NewLabel(ui.Label{
|
||||
Text: "Second",
|
||||
Font: TabFont,
|
||||
}))
|
||||
{
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "Goodbye Mars",
|
||||
Font: render.Text{
|
||||
Size: 24,
|
||||
Color: render.Orange,
|
||||
},
|
||||
})
|
||||
tab2.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
})
|
||||
|
||||
label2 := ui.NewLabel(ui.Label{
|
||||
Text: "This is the text content of the second\n" +
|
||||
"of the three tab frames.\n\nIt has longer text\nin it!",
|
||||
Font: render.Text{
|
||||
Size: 10,
|
||||
Color: render.Orange.Darken(20),
|
||||
},
|
||||
})
|
||||
tab2.Pack(label2, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 8,
|
||||
})
|
||||
}
|
||||
|
||||
// Third Tab.
|
||||
tab3 := notebook.AddTab("Tab 3", ui.NewLabel(ui.Label{
|
||||
Text: "Third",
|
||||
Font: TabFont,
|
||||
}))
|
||||
{
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: "The Third Tab",
|
||||
Font: render.Text{
|
||||
Size: 24,
|
||||
Color: render.Pink,
|
||||
},
|
||||
})
|
||||
tab3.Pack(label, ui.Pack{
|
||||
Side: ui.N,
|
||||
})
|
||||
|
||||
label2 := ui.NewLabel(ui.Label{
|
||||
Text: "This is the text content of the third\n" +
|
||||
"of the tab frames.",
|
||||
Font: render.Text{
|
||||
Size: 10,
|
||||
Color: render.Pink.Darken(40),
|
||||
},
|
||||
})
|
||||
tab3.Pack(label2, ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 8,
|
||||
})
|
||||
}
|
||||
|
||||
notebook.Supervise(mw.Supervisor())
|
||||
|
||||
// notebook.SetBackground(render.DarkGrey)
|
||||
return notebook
|
||||
}
|
||||
|
||||
func main() {
|
||||
mw, err := ui.NewMainWindow("TabFrame Example", Width, Height)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Dark theme.
|
||||
// ui.Theme = theme.DefaultDark
|
||||
|
||||
// Menu bar.
|
||||
menu := ui.NewMenuBar("Main Menu")
|
||||
file := menu.AddMenu("UI Theme")
|
||||
file.AddItem("Default", func() {
|
||||
ui.Theme = theme.Default
|
||||
addWindow(mw)
|
||||
})
|
||||
file.AddItem("DefaultFlat", func() {
|
||||
ui.Theme = theme.DefaultFlat
|
||||
addWindow(mw)
|
||||
})
|
||||
file.AddItem("DefaultDark", func() {
|
||||
ui.Theme = theme.DefaultDark
|
||||
addWindow(mw)
|
||||
})
|
||||
file.AddSeparator()
|
||||
file.AddItem("Close all windows", func() {
|
||||
OpenWindows -= mw.Supervisor().CloseAllWindows()
|
||||
})
|
||||
|
||||
menu.Supervise(mw.Supervisor())
|
||||
menu.Compute(mw.Engine)
|
||||
mw.Pack(menu, menu.PackTop())
|
||||
|
||||
// Add some windows to play with.
|
||||
addWindow(mw)
|
||||
addWindow(mw)
|
||||
|
||||
mw.SetBackground(render.White)
|
||||
|
||||
mw.OnLoop(func(e *event.State) {
|
||||
if e.Escape {
|
||||
os.Exit(0)
|
||||
}
|
||||
})
|
||||
|
||||
mw.MainLoop()
|
||||
}
|
||||
|
||||
// Add a new child window.
|
||||
func addWindow(mw *ui.MainWindow) {
|
||||
var (
|
||||
color = WindowColors[WindowID%len(WindowColors)]
|
||||
title = fmt.Sprintf("Window %d (%s)", WindowID+1, ui.Theme.Name)
|
||||
)
|
||||
WindowID++
|
||||
|
||||
win1 := ui.NewWindow(title)
|
||||
win1.SetButtons(ui.CloseButton)
|
||||
win1.ActiveTitleBackground = color
|
||||
win1.InactiveTitleBackground = color.Darken(60)
|
||||
win1.InactiveTitleForeground = render.Grey
|
||||
win1.Configure(ui.Config{
|
||||
Width: 320,
|
||||
Height: 240,
|
||||
})
|
||||
win1.Compute(mw.Engine)
|
||||
win1.Supervise(mw.Supervisor())
|
||||
|
||||
// Re-open a window when the last one is closed.
|
||||
OpenWindows++
|
||||
win1.Handle(ui.CloseWindow, func(ed ui.EventData) error {
|
||||
OpenWindows--
|
||||
if OpenWindows <= 0 {
|
||||
addWindow(mw)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
// Default placement via cascade.
|
||||
win1.MoveTo(Cascade)
|
||||
Cascade.Add(CascadeStep)
|
||||
if Cascade.Y > Height-240-64 {
|
||||
CascadeLoops++
|
||||
Cascade.Y = 32
|
||||
Cascade.X = 24 * CascadeLoops
|
||||
}
|
||||
|
||||
// Add the TabFrame.
|
||||
tabframe := MakeTabFrame(mw)
|
||||
win1.Pack(tabframe, ui.Pack{
|
||||
Side: ui.W,
|
||||
Expand: true,
|
||||
})
|
||||
|
||||
// Add a window duplicator button.
|
||||
btn2 := ui.NewButton(title+":Button2", ui.NewLabel(ui.Label{
|
||||
Text: "New Window",
|
||||
}))
|
||||
btn2.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
addWindow(mw)
|
||||
return nil
|
||||
})
|
||||
btn2.Compute(mw.Engine)
|
||||
mw.Add(btn2)
|
||||
win1.Compute(mw.Engine)
|
||||
win1.Place(btn2, ui.Place{
|
||||
Bottom: 12,
|
||||
Right: 12,
|
||||
})
|
||||
}
|
BIN
eg/tabframe/screenshot.png
Normal file
BIN
eg/tabframe/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 42 KiB |
|
@ -54,6 +54,8 @@ func (w *Frame) Place(child Widget, config Place) {
|
|||
func (w *Frame) computePlaced(e render.Engine) {
|
||||
var (
|
||||
frameSize = w.BoxSize()
|
||||
// maxWidth int
|
||||
// maxHeight int
|
||||
)
|
||||
|
||||
for _, row := range w.placed {
|
||||
|
|
8
go.mod
8
go.mod
|
@ -1,9 +1,9 @@
|
|||
module git.kirsle.net/go/ui
|
||||
|
||||
go 1.13
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
git.kirsle.net/go/render v0.0.0-20210104010442-b4a1979a8ba1
|
||||
github.com/veandco/go-sdl2 v0.4.7 // indirect
|
||||
golang.org/x/image v0.0.0-20210504121937-7319ad40d33e // indirect
|
||||
git.kirsle.net/go/render v0.0.0-20210614025954-d77f5056b782
|
||||
github.com/veandco/go-sdl2 v0.4.8 // indirect
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d
|
||||
)
|
||||
|
|
165
go.sum
165
go.sum
|
@ -1,166 +1,17 @@
|
|||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw=
|
||||
git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d h1:vErak6oVRT2dosyQzcwkjXyWQ2NRIVL8q9R8NOUTtsg=
|
||||
git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d/go.mod h1:ywZtC+zE2SpeObfkw0OvG01pWHQadsVQ4WDKOYzaejc=
|
||||
git.kirsle.net/go/render v0.0.0-20210104010442-b4a1979a8ba1 h1:wGQLjBnWvqx7rU43yFG8ow4rnYqUMUwqorEkxdPaJ6Q=
|
||||
git.kirsle.net/go/render v0.0.0-20210104010442-b4a1979a8ba1/go.mod h1:ss7pvZbGWrMaDuZwyUTjV9+T0AJwAkxZZHwMFsvHrkk=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo=
|
||||
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM=
|
||||
github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ=
|
||||
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/edwvee/exiffix v0.0.0-20180602190213-b57537c92a6b/go.mod h1:KoE3Ti1qbQXCb3s/XGj0yApHnbnNnn1bXTtB5Auq/Vc=
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
|
||||
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
|
||||
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
|
||||
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
|
||||
github.com/gorilla/feeds v1.1.1/go.mod h1:Nk0jZrvPFZX1OBe5NPiddPw7CfwF6Q9eqzaBbaightA=
|
||||
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w=
|
||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/jinzhu/gorm v1.9.9/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2BBJY=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/kirsle/blog v0.0.0-20191022175051-d78814b9c99b/go.mod h1:D6I6jquqhLewuhrVt9Q3p97r2YCC7CA4DgNv14+WS8k=
|
||||
github.com/kirsle/golog v0.0.0-20180411020913-51290b4f9292/go.mod h1:0KaOvOX8s5YINMREeyTILsuU0wkmnKQQTy99e/2oDGc=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
|
||||
github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk=
|
||||
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||
github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0=
|
||||
github.com/shurcooL/highlight_diff v0.0.0-20181222201841-111da2e7d480/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU=
|
||||
github.com/shurcooL/highlight_go v0.0.0-20181215221002-9d8641ddf2e1/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag=
|
||||
github.com/shurcooL/octicon v0.0.0-20181222203144-9ff1a4cf27f4/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE=
|
||||
github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA=
|
||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10=
|
||||
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e/go.mod h1:fSIW/szJHsRts/4U8wlMPhs+YqJC+7NYR+Qqb1uJVpA=
|
||||
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
|
||||
github.com/veandco/go-sdl2 v0.4.1 h1:HmSBvVmKWI8LAOeCfTTM8R33rMyPcs6U3o8n325c9Qg=
|
||||
git.kirsle.net/go/render v0.0.0-20210614025954-d77f5056b782 h1:Ko+NvZxmJbW+M1dA2jCSnV6qSpkLoZASramE6ltlf/s=
|
||||
git.kirsle.net/go/render v0.0.0-20210614025954-d77f5056b782/go.mod h1:ss7pvZbGWrMaDuZwyUTjV9+T0AJwAkxZZHwMFsvHrkk=
|
||||
github.com/veandco/go-sdl2 v0.4.1/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
|
||||
github.com/veandco/go-sdl2 v0.4.7 h1:VfpCM+LfEGDbHdByglCo2bcBsevjFvzl8W0f6VLNitg=
|
||||
github.com/veandco/go-sdl2 v0.4.7/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443 h1:IcSOAf4PyMp3U3XbIEj1/xJ2BjNN2jWv7JoyOsMxXUU=
|
||||
golang.org/x/crypto v0.0.0-20190618222545-ea8f1a30c443/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1 h1:5h3ngYt7+vXCDZCup/HkCQgW5XwmSvR/nA2JmJ0RErg=
|
||||
github.com/veandco/go-sdl2 v0.4.8 h1:A26KeX6R1CGt/BQGEov6oxYmVGMMEWDVqTvK1tXvahE=
|
||||
github.com/veandco/go-sdl2 v0.4.8/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY=
|
||||
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||
golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d h1:RNPAfi2nHY7C2srAV8A49jpsYr0ADedCk1wq6fTMTvs=
|
||||
golang.org/x/image v0.0.0-20210628002857-a66eb6448b8d/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
|
||||
gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
|
|
282
tabframe.go
Normal file
282
tabframe.go
Normal file
|
@ -0,0 +1,282 @@
|
|||
package ui
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui/style"
|
||||
"git.kirsle.net/go/ui/theme"
|
||||
)
|
||||
|
||||
// TabFrame is a tabbed notebook of multiple frames showing
|
||||
// tab names along the top and clicking them reveals each
|
||||
// named tab.
|
||||
type TabFrame struct {
|
||||
Name string
|
||||
Frame
|
||||
|
||||
supervisor *Supervisor
|
||||
style *style.Button
|
||||
|
||||
// Child widgets.
|
||||
header *Frame
|
||||
content *Frame
|
||||
tabButtons []*Button
|
||||
tabFrames []*Frame
|
||||
currentTabKey string
|
||||
}
|
||||
|
||||
// NewTabFrame creates a new Frame.
|
||||
func NewTabFrame(name string) *TabFrame {
|
||||
w := &TabFrame{
|
||||
Name: name,
|
||||
style: Theme.TabFrame,
|
||||
header: NewFrame(name + " Header"),
|
||||
content: NewFrame(name + " Content"),
|
||||
tabButtons: []*Button{},
|
||||
tabFrames: []*Frame{},
|
||||
}
|
||||
|
||||
// Initialize the root frame of this widget.
|
||||
w.Frame.Setup()
|
||||
|
||||
// Pack the high-level layout into the root frame.
|
||||
// Only root needs to Present for this widget.
|
||||
w.Frame.Pack(w.header, Pack{
|
||||
Side: N,
|
||||
FillX: true,
|
||||
})
|
||||
w.Frame.Pack(w.content, Pack{
|
||||
Side: N,
|
||||
Fill: true,
|
||||
Expand: true,
|
||||
})
|
||||
|
||||
w.SetBackground(render.RGBA(1, 0, 0, 0)) // invisible default BG
|
||||
w.IDFunc(func() string {
|
||||
return fmt.Sprintf("TabFrame<%s>",
|
||||
name,
|
||||
)
|
||||
})
|
||||
|
||||
return w
|
||||
}
|
||||
|
||||
// AddTab creates a new content tab. The key is a unique identifier
|
||||
// for the tab and is how the TabFrame knows which tab is selected.
|
||||
//
|
||||
// The child widget would probably be a Label or Image but could be
|
||||
// any other kind of widget.
|
||||
//
|
||||
// The first tab added becomes the selected tab by default.
|
||||
func (w *TabFrame) AddTab(key string, child Widget) *Frame {
|
||||
// Create the tab button for this tab.
|
||||
button := NewButton(key, child)
|
||||
button.SetStyle(w.style)
|
||||
button.FixedColor = true
|
||||
button.SetOutlineSize(0)
|
||||
button.SetBorderSize(1)
|
||||
w.setButtonStyle(button, len(w.tabButtons) == 0)
|
||||
w.header.Pack(button, Pack{
|
||||
Side: W,
|
||||
})
|
||||
|
||||
button.Handle(MouseDown, func(ed EventData) error {
|
||||
w.SetTab(key)
|
||||
return nil
|
||||
})
|
||||
|
||||
// Create the frame of the tab's body.
|
||||
frame := NewFrame(key)
|
||||
frame.Configure(Config{
|
||||
BorderSize: w.style.BorderSize,
|
||||
Background: w.style.Background,
|
||||
BorderStyle: BorderRaised,
|
||||
})
|
||||
if len(w.tabFrames) > 0 {
|
||||
frame.Hide()
|
||||
}
|
||||
|
||||
// Pack this frame into the content part of the widget.
|
||||
w.content.Pack(frame, Pack{
|
||||
Side: N,
|
||||
FillX: true,
|
||||
})
|
||||
|
||||
w.tabButtons = append(w.tabButtons, button)
|
||||
w.tabFrames = append(w.tabFrames, frame)
|
||||
return frame
|
||||
}
|
||||
|
||||
// set the tab style between active and inactive
|
||||
func (w *TabFrame) setButtonStyle(button *Button, active bool) {
|
||||
var style = button.GetStyle()
|
||||
if active {
|
||||
button.SetBackground(style.Background)
|
||||
button.SetBorderStyle(BorderRaised)
|
||||
if label, ok := button.child.(*Label); ok {
|
||||
label.Font.Color = style.Foreground
|
||||
}
|
||||
} else {
|
||||
button.SetBackground(style.Background.Darken(theme.BorderColorOffset))
|
||||
button.SetBorderStyle(BorderSolid)
|
||||
if label, ok := button.child.(*Label); ok {
|
||||
label.Font.Color = style.Foreground
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SetTab changes the selected tab to the new value. If the
|
||||
// tab doesn't exist, the first tab is selected.
|
||||
func (w *TabFrame) SetTab(key string) bool {
|
||||
var found bool
|
||||
for i, frame := range w.tabFrames {
|
||||
button := w.tabButtons[i]
|
||||
if frame.Name == key {
|
||||
frame.Show()
|
||||
w.setButtonStyle(button, true)
|
||||
w.currentTabKey = key
|
||||
found = true
|
||||
} else {
|
||||
frame.Hide()
|
||||
w.setButtonStyle(button, false)
|
||||
}
|
||||
}
|
||||
|
||||
if !found && len(w.tabFrames) > 0 {
|
||||
w.tabFrames[0].Show()
|
||||
w.currentTabKey = w.tabFrames[0].Name
|
||||
}
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
// Supervise activates the tab frame using your supervisor. If you
|
||||
// don't call this, the tab buttons won't be clickable!
|
||||
//
|
||||
// Call this AFTER adding all tabs. This function calls Supervisor.Add
|
||||
// on all tab buttons.
|
||||
func (w *TabFrame) Supervise(supervisor *Supervisor) {
|
||||
for _, button := range w.tabButtons {
|
||||
supervisor.Add(button)
|
||||
}
|
||||
}
|
||||
|
||||
// SetStyle controls the visual styling of the tab button bar.
|
||||
func (w *TabFrame) SetStyle(style *style.Button) {
|
||||
w.style = style
|
||||
for _, button := range w.tabButtons {
|
||||
button.SetStyle(style)
|
||||
w.setButtonStyle(button, !button.Hidden())
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the size of the Frame.
|
||||
func (w *TabFrame) Compute(e render.Engine) {
|
||||
// Compute all the child frames.
|
||||
w.Frame.Compute(e)
|
||||
|
||||
// Call the BaseWidget Compute in case we have subscribers.
|
||||
w.BaseWidget.Compute(e)
|
||||
}
|
||||
|
||||
// Present the Frame.
|
||||
func (w *TabFrame) Present(e render.Engine, P render.Point) {
|
||||
if w.Hidden() {
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
S = w.Size()
|
||||
)
|
||||
|
||||
// Draw the widget's border and everything.
|
||||
w.DrawBox(e, P)
|
||||
|
||||
// Draw the background color.
|
||||
e.DrawBox(w.Background(), render.Rect{
|
||||
X: P.X + w.BoxThickness(1),
|
||||
Y: P.Y + w.BoxThickness(1),
|
||||
W: S.W - w.BoxThickness(2),
|
||||
H: S.H - w.BoxThickness(2),
|
||||
})
|
||||
|
||||
// Present the root frame.
|
||||
w.Frame.Present(e, P)
|
||||
|
||||
// Draw the borders over the tabs.
|
||||
w.presentBorders(e, P)
|
||||
|
||||
// Call the BaseWidget Present in case we have subscribers.
|
||||
w.BaseWidget.Present(e, P)
|
||||
}
|
||||
|
||||
/*
|
||||
presentBorders handles drawing the borders around tab buttons.
|
||||
|
||||
The tabs are simple Button widgets but drawn with no borders. Instead,
|
||||
borders are painted on post-hoc in the Present function.
|
||||
*/
|
||||
func (w *TabFrame) presentBorders(e render.Engine, P render.Point) {
|
||||
if len(w.tabButtons) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Prep some variables.
|
||||
var (
|
||||
// The 1st and last tab button widgets.
|
||||
first = w.tabButtons[0]
|
||||
last = w.tabButtons[len(w.tabButtons)-1]
|
||||
topLeft = AbsolutePosition(first)
|
||||
bottomRight = AbsolutePosition(last)
|
||||
|
||||
// The absolute bounding box of the tabs part of the UI,
|
||||
// from the top-left corner of Tab #1 to the bottom-right
|
||||
// corner of the final tab.
|
||||
bounding = render.Rect{
|
||||
X: P.X, //topLeft.X + first.BoxThickness(4),
|
||||
Y: P.Y, //topLeft.Y + first.BoxThickness(4),
|
||||
W: bottomRight.X + last.Size().W - topLeft.X,
|
||||
H: bottomRight.Y + last.Size().H - topLeft.Y,
|
||||
}
|
||||
|
||||
// The very bottom edge of the whole tab bar,
|
||||
// to overlap the BorderSize=1 along their buttons.
|
||||
bottomLine = []render.Point{
|
||||
render.NewPoint(P.X+1, bounding.Y+bounding.H-1),
|
||||
render.NewPoint(bounding.X+bounding.W-1, bounding.Y+bounding.H-1),
|
||||
}
|
||||
)
|
||||
|
||||
// Draw a shadow border on all the inactive tabs' right edges,
|
||||
// so they don't all blend together in solid grey.
|
||||
// Note: the active button has a BorderSize=1 and others are 0.
|
||||
for i, button := range w.tabButtons {
|
||||
if button.Name != w.currentTabKey {
|
||||
// If it immediately precedes the current tab, do not draw the line,
|
||||
// it would cover the highlight color of the current tab's button.
|
||||
if i+1 < len(w.tabButtons) && w.tabButtons[i+1].Name == w.currentTabKey {
|
||||
continue
|
||||
}
|
||||
|
||||
var (
|
||||
abs = AbsolutePosition(button)
|
||||
size = button.BoxSize()
|
||||
points = []render.Point{
|
||||
render.NewPoint(abs.X+size.W-1, abs.Y+2),
|
||||
render.NewPoint(abs.X+size.W-1, abs.Y+size.H-2),
|
||||
}
|
||||
)
|
||||
e.DrawLine(button.Background().Darken(theme.BorderColorOffset), points[0], points[1])
|
||||
}
|
||||
}
|
||||
|
||||
// Erase the button edge from all tabs.
|
||||
e.DrawLine(w.style.Background, bottomLine[0], bottomLine[1])
|
||||
e.DrawBox(w.style.Background, render.Rect{
|
||||
X: bottomLine[0].X + 1,
|
||||
Y: bottomLine[0].Y,
|
||||
W: bounding.W - 2,
|
||||
H: 3,
|
||||
})
|
||||
}
|
|
@ -17,19 +17,21 @@ var (
|
|||
|
||||
// Theme is a collection of styles for various built-in widgets.
|
||||
type Theme struct {
|
||||
Name string
|
||||
Window *style.Window
|
||||
Label *style.Label
|
||||
Button *style.Button
|
||||
Tooltip *style.Tooltip
|
||||
Name string
|
||||
Window *style.Window
|
||||
Label *style.Label
|
||||
Button *style.Button
|
||||
Tooltip *style.Tooltip
|
||||
TabFrame *style.Button
|
||||
}
|
||||
|
||||
// Default theme.
|
||||
var Default = Theme{
|
||||
Name: "Default",
|
||||
Label: &style.DefaultLabel,
|
||||
Button: &style.DefaultButton,
|
||||
Tooltip: &style.DefaultTooltip,
|
||||
Name: "Default",
|
||||
Label: &style.DefaultLabel,
|
||||
Button: &style.DefaultButton,
|
||||
Tooltip: &style.DefaultTooltip,
|
||||
TabFrame: &style.DefaultButton,
|
||||
}
|
||||
|
||||
// DefaultFlat is a flat version of the default theme.
|
||||
|
@ -45,6 +47,16 @@ var DefaultFlat = Theme{
|
|||
BorderStyle: style.BorderSolid,
|
||||
BorderSize: 2,
|
||||
},
|
||||
TabFrame: &style.Button{
|
||||
Background: style.DefaultButton.Background,
|
||||
Foreground: style.DefaultButton.Foreground,
|
||||
OutlineColor: style.DefaultButton.OutlineColor,
|
||||
OutlineSize: 1,
|
||||
HoverBackground: style.DefaultButton.HoverBackground,
|
||||
HoverForeground: style.DefaultButton.HoverForeground,
|
||||
BorderStyle: style.BorderSolid,
|
||||
BorderSize: 2,
|
||||
},
|
||||
}
|
||||
|
||||
// DefaultDark is a dark version of the default theme.
|
||||
|
@ -74,4 +86,13 @@ var DefaultDark = Theme{
|
|||
Background: render.RGBA(60, 60, 60, 230),
|
||||
Foreground: render.Cyan,
|
||||
},
|
||||
TabFrame: &style.Button{
|
||||
Background: render.DarkGrey,
|
||||
Foreground: render.Grey,
|
||||
OutlineColor: render.DarkGrey,
|
||||
OutlineSize: 1,
|
||||
HoverBackground: render.Grey,
|
||||
BorderStyle: style.BorderRaised,
|
||||
BorderSize: 2,
|
||||
},
|
||||
}
|
||||
|
|
15
window.go
15
window.go
|
@ -341,7 +341,7 @@ func (w *Window) Pack(child Widget, config ...Pack) {
|
|||
|
||||
// Place a child widget into the window's main frame.
|
||||
func (w *Window) Place(child Widget, config Place) {
|
||||
w.content.Place(child, config)
|
||||
w.body.Place(child, config)
|
||||
}
|
||||
|
||||
// TitleBar returns the title bar widgets.
|
||||
|
@ -356,6 +356,14 @@ func (w *Window) Configure(C Config) {
|
|||
w.body.Configure(C)
|
||||
|
||||
// Don't pass dimensions down any further than the body.
|
||||
// TODO: this causes the content frame to compute its size
|
||||
// dynamically based on Packed widgets, but if using Place on
|
||||
// your window, the content frame doesn't know a size by which
|
||||
// to place the child relative to (Frame has size 0x0).
|
||||
// Commenting out these two lines causes windows to render very
|
||||
// incorrectly (child frame content flying off the window bottom).
|
||||
// In the meantime, Window.Place intercepts it and draws it onto
|
||||
// the parent window directly so it works how you expect.
|
||||
C.Width = 0
|
||||
C.Height = 0
|
||||
w.content.Configure(C)
|
||||
|
@ -366,6 +374,11 @@ func (w *Window) ConfigureTitle(C Config) {
|
|||
w.titleBar.Configure(C)
|
||||
}
|
||||
|
||||
// ContentFrame returns the main content Frame of this window.
|
||||
func (w *Window) ContentFrame() *Frame {
|
||||
return w.content
|
||||
}
|
||||
|
||||
// Compute the window.
|
||||
func (w *Window) Compute(e render.Engine) {
|
||||
w.engine = e // hang onto it in case of maximize
|
||||
|
|
Loading…
Reference in New Issue
Block a user