Compare commits

...

3 Commits

Author SHA1 Message Date
Noah e2a561fbd0 Add the Pager Widget 2020-07-09 19:31:46 -07:00
Noah 0e027a9fee CheckButton Styles, Supervisor fixes 2020-07-09 19:29:44 -07:00
Noah e675ead0ed WIP Themes Support 2020-06-17 18:04:18 -07:00
17 changed files with 939 additions and 33 deletions

View File

@ -133,6 +133,8 @@ 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] **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

View File

@ -5,13 +5,14 @@ import (
"fmt"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/theme"
"git.kirsle.net/go/ui/style"
)
// Button is a clickable button.
type Button struct {
BaseWidget
child Widget
style *style.Button
// Private options.
hovering bool
@ -22,27 +23,28 @@ type Button struct {
func NewButton(name string, child Widget) *Button {
w := &Button{
child: child,
style: &style.DefaultButton,
}
w.IDFunc(func() string {
return fmt.Sprintf("Button<%s>", name)
})
w.Configure(Config{
BorderSize: 2,
BorderStyle: BorderRaised,
OutlineSize: 1,
OutlineColor: theme.ButtonOutlineColor,
Background: theme.ButtonBackgroundColor,
})
w.SetStyle(Theme.Button)
w.Handle(MouseOver, func(e EventData) error {
w.hovering = true
w.SetBackground(theme.ButtonHoverColor)
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(theme.ButtonBackgroundColor)
w.SetBackground(w.style.Background)
if label, ok := w.child.(*Label); ok {
label.Font.Color = w.style.Foreground
}
return nil
})
@ -53,13 +55,34 @@ func NewButton(name string, child Widget) *Button {
})
w.Handle(MouseUp, func(e EventData) error {
w.clicked = false
w.SetBorderStyle(BorderRaised)
w.SetBorderStyle(BorderStyle(w.style.BorderStyle))
return nil
})
return w
}
// SetStyle sets the button style.
func (w *Button) SetStyle(v *style.Button) {
if v == nil {
v = &style.DefaultButton
}
w.style = v
w.Configure(Config{
BorderSize: w.style.BorderSize,
BorderStyle: BorderStyle(w.style.BorderStyle),
OutlineSize: w.style.OutlineSize,
OutlineColor: w.style.OutlineColor,
Background: w.style.Background,
})
// If the child is a Label, apply the foreground color.
if label, ok := w.child.(*Label); ok {
label.Font.Color = w.style.Foreground
}
}
// Children returns the button's child widget.
func (w *Button) Children() []Widget {
return []Widget{w.child}

View File

@ -5,7 +5,6 @@ import (
"strconv"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/theme"
)
// CheckButton implements a checkbox and radiobox widget. It's based on a
@ -28,6 +27,8 @@ func NewCheckButton(name string, boolVar *bool, child Widget) *CheckButton {
return fmt.Sprintf("CheckButton<%s %+v>", name, w.BoolVar)
})
w.SetStyle(Theme.Button)
w.setup()
return w
}
@ -42,6 +43,9 @@ func NewRadioButton(name string, stringVar *string, value string, child Widget)
w.IDFunc(func() string {
return fmt.Sprintf(`RadioButton<%s "%s" %s>`, name, w.Value, strconv.FormatBool(*w.StringVar == w.Value))
})
w.SetStyle(Theme.Button)
w.setup()
return w
}
@ -63,10 +67,14 @@ func (w *CheckButton) Compute(e render.Engine) {
// setup the common things between checkboxes and radioboxes.
func (w *CheckButton) setup() {
var borderStyle BorderStyle = BorderRaised
var (
borderStyle BorderStyle = BorderRaised
background = w.style.Background
)
if w.BoolVar != nil {
if *w.BoolVar == true {
borderStyle = BorderSunken
background = w.style.Background.Darken(40)
}
}
@ -74,18 +82,31 @@ func (w *CheckButton) setup() {
BorderSize: 2,
BorderStyle: borderStyle,
OutlineSize: 1,
OutlineColor: theme.ButtonOutlineColor,
Background: theme.ButtonBackgroundColor,
OutlineColor: w.style.OutlineColor,
Background: background,
})
w.Handle(MouseOver, func(ed EventData) error {
w.hovering = true
w.SetBackground(theme.ButtonHoverColor)
w.SetBackground(w.style.HoverBackground)
return nil
})
w.Handle(MouseOut, func(ed EventData) error {
w.hovering = false
w.SetBackground(theme.ButtonBackgroundColor)
var sunken bool
if w.BoolVar != nil {
sunken = *w.BoolVar == true
} else if w.StringVar != nil {
sunken = *w.StringVar == w.Value
}
if sunken {
w.SetBackground(w.style.Background.Darken(40))
} else {
w.SetBackground(w.style.Background)
}
return nil
})
@ -115,9 +136,12 @@ func (w *CheckButton) setup() {
if sunken {
w.SetBorderStyle(BorderSunken)
w.SetBackground(w.style.Background.Darken(40))
} else {
w.SetBorderStyle(BorderRaised)
w.SetBackground(w.style.Background)
}
return nil
})
}

11
eg/themes/Makefile Normal file
View File

@ -0,0 +1,11 @@
.PHONY: run
run:
go run main.go
.PHONY: wasm
wasm:
GOOS=js GOARCH=wasm go build -v -o windows.wasm main_wasm.go
.PHONY: wasm-serve
wasm-serve: wasm
../wasm-common/serve.sh

107
eg/themes/main.go Normal file
View File

@ -0,0 +1,107 @@
package main
import (
"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
)
func init() {
sdl.DefaultFontFilename = "../DejaVuSans.ttf"
}
func main() {
mw, err := ui.NewMainWindow("Theme Demo", Width, Height)
if err != nil {
panic(err)
}
// Menu bar.
menu := ui.NewMenuBar("Main Menu")
file := menu.AddMenu("Select Theme")
file.AddItem("Default", func() {
addWindow(mw, theme.Default)
})
file.AddItem("DefaultFlat", func() {
addWindow(mw, theme.DefaultFlat)
})
file.AddItem("DefaultDark", func() {
addWindow(mw, theme.DefaultDark)
})
menu.Supervise(mw.Supervisor())
menu.Compute(mw.Engine)
mw.Pack(menu, menu.PackTop())
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, theme theme.Theme) {
ui.Theme = theme
win1 := ui.NewWindow(theme.Name)
win1.SetButtons(ui.CloseButton)
win1.Configure(ui.Config{
Width: 320,
Height: 240,
})
win1.Compute(mw.Engine)
win1.Supervise(mw.Supervisor())
// Draw a label.
label := ui.NewLabel(ui.Label{
Text: theme.Name,
})
win1.Place(label, ui.Place{
Top: 10,
Left: 10,
})
// Add a button with tooltip.
btn2 := ui.NewButton(theme.Name+":Button2", ui.NewLabel(ui.Label{
Text: "Button",
}))
btn2.Handle(ui.Click, func(ed ui.EventData) error {
return nil
})
mw.Add(btn2)
win1.Place(btn2, ui.Place{
Top: 10,
Right: 10,
})
ui.NewTooltip(btn2, ui.Tooltip{
Text: "Hello world!",
Edge: ui.Bottom,
})
// Add a checkbox.
var b bool
cb := ui.NewCheckbox("Checkbox", &b, ui.NewLabel(ui.Label{
Text: "Check me!",
}))
mw.Add(cb)
win1.Place(cb, ui.Place{
Top: 30,
Left: 10,
})
}

169
eg/themes/main_wasm.go Normal file
View File

@ -0,0 +1,169 @@
// +build js,wasm
// WebAssembly version of the window manager demo.
// To build: make wasm
// To test: make wasm-serve
package main
import (
"fmt"
"time"
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/canvas"
"git.kirsle.net/go/ui"
)
// Program globals.
var (
ThrottleFPS = 1000 / 60
// 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
)
func main() {
mw, err := canvas.New("canvas")
if err != nil {
panic(err)
}
// Bind DOM event handlers.
mw.AddEventListeners()
supervisor := ui.NewSupervisor()
frame := ui.NewFrame("Main Frame")
frame.Resize(render.NewRect(mw.WindowSize()))
frame.Compute(mw)
_, height := mw.WindowSize()
lbl := ui.NewLabel(ui.Label{
Text: "Window Manager Demo",
Font: render.Text{
FontFilename: "DejaVuSans.ttf",
Size: 32,
Color: render.SkyBlue,
Shadow: render.SkyBlue.Darken(60),
},
})
lbl.Compute(mw)
lbl.MoveTo(render.NewPoint(
20,
height-lbl.Size().H-20,
))
// Menu bar.
menu := ui.NewMenuBar("Main Menu")
file := menu.AddMenu("Options")
file.AddItem("New window", func() {
addWindow(mw, frame, supervisor)
})
file.AddItem("Close all windows", func() {
OpenWindows -= supervisor.CloseAllWindows()
})
menu.Supervise(supervisor)
menu.Compute(mw)
frame.Pack(menu, menu.PackTop())
// Add some windows to play with.
addWindow(mw, frame, supervisor)
addWindow(mw, frame, supervisor)
for {
mw.Clear(render.RGBA(255, 255, 200, 255))
start := time.Now()
ev, err := mw.Poll()
if err != nil {
panic(err)
}
frame.Present(mw, frame.Point())
lbl.Present(mw, lbl.Point())
supervisor.Loop(ev)
supervisor.Present(mw)
var delay uint32
elapsed := time.Now().Sub(start)
tmp := elapsed / time.Millisecond
if ThrottleFPS-int(tmp) > 0 {
delay = uint32(ThrottleFPS - int(tmp))
}
mw.Delay(delay)
}
}
// Add a new child window.
func addWindow(engine render.Engine, parent *ui.Frame, sup *ui.Supervisor) {
var (
color = WindowColors[WindowID%len(WindowColors)]
title = fmt.Sprintf("Window %d", WindowID+1)
)
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(engine)
win1.Supervise(sup)
// 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(engine, parent, sup)
}
return nil
})
// Default placement via cascade.
win1.MoveTo(Cascade)
Cascade.Add(CascadeStep)
if Cascade.Y > Height-240-64 {
CascadeLoops++
Cascade.Y = 24
Cascade.X = 24 * CascadeLoops
}
// 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(engine, parent, sup)
return nil
})
sup.Add(btn2)
win1.Place(btn2, ui.Place{
Top: 10,
Right: 10,
})
}

4
go.mod
View File

@ -4,4 +4,6 @@ go 1.13
replace git.kirsle.net/go/render => /home/kirsle/go/src/git.kirsle.net/go/render
require git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d
require (
git.kirsle.net/go/render v0.0.0-20200102014411-4d008b5c468d
)

153
go.sum
View File

@ -1,7 +1,160 @@
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=
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=
github.com/veandco/go-sdl2 v0.4.1/go.mod h1:FB+kTpX9YTE+urhYiClnRzpOXbiWgaU3+5F2AB78DPg=
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=
golang.org/x/image v0.0.0-20200119044424-58c23975cae1/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/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=

View File

@ -5,6 +5,7 @@ import (
"strings"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/style"
)
// DefaultFont is the default font settings used for a Label.
@ -23,6 +24,7 @@ type Label struct {
IntVariable *int
Font render.Text
style *style.Label
width int
height int
lineHeight int
@ -36,6 +38,7 @@ func NewLabel(c Label) *Label {
IntVariable: c.IntVariable,
Font: DefaultFont,
}
w.SetStyle(Theme.Label)
if !c.Font.IsZero() {
w.Font = c.Font
}
@ -45,6 +48,17 @@ func NewLabel(c Label) *Label {
return w
}
// SetStyle sets the label's default style.
func (w *Label) SetStyle(v *style.Label) {
if v == nil {
v = &style.DefaultLabel
}
w.style = v
w.SetBackground(w.style.Background)
w.Font.Color = w.style.Foreground
}
// text returns the label's displayed text, coming from the TextVariable if
// available or else the Text attribute instead.
func (w *Label) text() render.Text {

213
pager.go Normal file
View File

@ -0,0 +1,213 @@
package ui
import (
"fmt"
"strconv"
"git.kirsle.net/go/render"
)
// Pager is a frame with Pagers for paginated UI.
type Pager struct {
BaseWidget
// Config settings.
Page int // default 1
Pages int
PerPage int // default 20
Font render.Text
OnChange func(page, perPage int)
supervisor *Supervisor
child Widget
buttons []Widget
page string // radio button value of Page
// Private options.
hovering bool
clicked bool
}
// NewPager creates a new Pager.
func NewPager(config Pager) *Pager {
w := &Pager{
Page: config.Page,
Pages: config.Pages,
PerPage: config.PerPage,
Font: config.Font,
OnChange: config.OnChange,
buttons: []Widget{},
}
// default settings
if w.Page == 0 {
w.Page = 1
}
if w.PerPage == 0 {
w.PerPage = 20
}
w.IDFunc(func() string {
return fmt.Sprintf("Pager<%d of %d>", w.Page, w.PerPage)
})
w.child = w.setup()
return w
}
// Supervise the pager to make its buttons work.
func (w *Pager) Supervise(s *Supervisor) {
w.supervisor = s
for _, btn := range w.buttons {
w.supervisor.Add(btn)
}
}
// setup the frame
func (w *Pager) setup() *Frame {
frame := NewFrame("Pager Frame")
frame.SetParent(w)
if w.Pages == 0 {
return frame
}
w.buttons = []Widget{}
w.page = fmt.Sprintf("%d", w.Page)
// Previous Page Button
prev := NewButton("Previous", NewLabel(Label{
Text: "<",
Font: w.Font,
}))
w.buttons = append(w.buttons, prev)
prev.Handle(Click, func(ed EventData) error {
return w.next(-1)
})
frame.Pack(prev, Pack{
Side: W,
})
// Draw the numbered buttons.
for i := 1; i <= w.Pages; i++ {
page := fmt.Sprintf("%d", i)
btn := NewRadioButton(
"Page "+page,
&w.page,
page,
NewLabel(Label{
Text: page,
Font: w.Font,
}))
w.buttons = append(w.buttons, btn)
btn.Handle(Click, func(ed EventData) error {
if w.OnChange != nil {
page, _ := strconv.Atoi(w.page)
w.OnChange(page, w.PerPage)
}
return nil
})
if w.supervisor != nil {
w.supervisor.Add(btn)
}
frame.Pack(btn, Pack{
Side: W,
})
}
// Next Page Button
next := NewButton("Next", NewLabel(Label{
Text: ">",
Font: w.Font,
}))
w.buttons = append(w.buttons, next)
next.Handle(Click, func(ed EventData) error {
return w.next(1)
})
frame.Pack(next, Pack{
Side: W,
})
return frame
}
// next (1) or previous (-1) button
func (w *Pager) next(value int) error {
fmt.Printf("next(%d)\n", value)
intvalue, _ := strconv.Atoi(w.page)
intvalue += value
if intvalue < 1 {
intvalue = 1
} else if intvalue > w.Pages {
intvalue = w.Pages
}
w.page = fmt.Sprintf("%d", intvalue)
if w.OnChange != nil {
w.OnChange(intvalue, w.PerPage)
}
return nil
}
// Compute the size of the Pager.
func (w *Pager) Compute(e render.Engine) {
// Compute the size of the inner widget first.
w.child.Compute(e)
// Auto-resize only if we haven't been given a fixed size.
if !w.FixedSize() {
size := w.child.Size()
w.Resize(render.Rect{
W: size.W + w.BoxThickness(2),
H: size.H + w.BoxThickness(2),
})
}
w.BaseWidget.Compute(e)
}
// Present the Pager.
func (w *Pager) Present(e render.Engine, P render.Point) {
if w.Hidden() {
return
}
w.Compute(e)
var (
S = w.Size()
ChildSize = w.child.Size()
)
// Draw the widget's border and everything.
w.DrawBox(e, P)
// Offset further if we are currently sunken.
var clickOffset int
if w.clicked {
clickOffset++
}
// Where to place the child widget.
moveTo := render.Point{
X: P.X + w.BoxThickness(1) + clickOffset,
Y: P.Y + w.BoxThickness(1) + clickOffset,
}
// If we're bigger than we need to be, center the child widget.
if S.Bigger(ChildSize) {
moveTo.X = P.X + (S.W / 2) - (ChildSize.W / 2)
}
// Draw the text label inside.
w.child.Present(e, moveTo)
w.BaseWidget.Present(e, P)
}

71
style/button.go Normal file
View File

@ -0,0 +1,71 @@
// Package style provides style definitions for UI components.
package style
import "git.kirsle.net/go/render"
// Default styles for widgets without a theme.
var (
DefaultWindow = Window{
ActiveTitleBackground: render.Blue,
ActiveTitleForeground: render.White,
InactiveTitleBackground: render.DarkGrey,
InactiveTitleForeground: render.Grey,
ActiveBackground: render.Grey,
InactiveBackground: render.Grey,
}
DefaultLabel = Label{
Background: render.Invisible,
Foreground: render.Black,
}
DefaultButton = Button{
Background: render.RGBA(200, 200, 200, 255),
Foreground: render.Black,
OutlineColor: render.Black,
OutlineSize: 1,
HoverBackground: render.RGBA(200, 255, 255, 255),
HoverForeground: render.Black,
BorderStyle: BorderRaised,
BorderSize: 2,
}
DefaultTooltip = Tooltip{
Background: render.RGBA(0, 0, 0, 230),
Foreground: render.White,
}
)
// Window style configuration.
type Window struct {
ActiveTitleBackground render.Color
ActiveTitleForeground render.Color
ActiveBackground render.Color
InactiveTitleBackground render.Color
InactiveTitleForeground render.Color
InactiveBackground render.Color
}
// Label style configuration.
type Label struct {
Background render.Color
Foreground render.Color
}
// Button style configuration.
type Button struct {
Background render.Color
Foreground render.Color // Labels only
OutlineColor render.Color
OutlineSize int
HoverBackground render.Color
HoverForeground render.Color
BorderStyle BorderStyle
BorderSize int
}
// Tooltip style configuration.
type Tooltip struct {
Background render.Color
Foreground render.Color
}

13
style/style.go Normal file
View File

@ -0,0 +1,13 @@
// Package style provides style definitions for UI components.
package style
// BorderStyle options for widget.SetBorderStyle()
type BorderStyle string
// Styles for a widget border.
const (
BorderNone BorderStyle = ""
BorderSolid BorderStyle = "solid"
BorderRaised = "raised"
BorderSunken = "sunken"
)

View File

@ -265,7 +265,7 @@ func (s *Supervisor) runWidgetEvents(XY render.Point, ev *event.State,
// the bounding box of the active focused window. Prevents clicking "thru"
// the window and activating widgets/other windows behind it.
var cursorInsideFocusedWindow bool
if !toFocusedWindow && s.winFocus != nil {
if !toFocusedWindow && s.winFocus != nil && !s.winFocus.window.Hidden() {
// Get the bounding box of the focused window.
if XY.Inside(AbsoluteRect(s.winFocus.window)) {
cursorInsideFocusedWindow = true

6
theme.go Normal file
View File

@ -0,0 +1,6 @@
package ui
import "git.kirsle.net/go/ui/theme"
// Theme sets the default theme used when creating new widgets.
var Theme = theme.Default

View File

@ -1,6 +1,9 @@
package theme
import "git.kirsle.net/go/render"
import (
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/style"
)
// Color schemes.
var (
@ -10,3 +13,64 @@ var (
BorderColorOffset = 40
)
// 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
}
// Default theme.
var Default = Theme{
Name: "Default",
Label: &style.DefaultLabel,
Button: &style.DefaultButton,
Tooltip: &style.DefaultTooltip,
}
// DefaultFlat is a flat version of the default theme.
var DefaultFlat = Theme{
Name: "DefaultFlat",
Button: &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.
var DefaultDark = Theme{
Name: "DefaultDark",
Label: &style.Label{
Foreground: render.Grey,
},
Window: &style.Window{
ActiveTitleBackground: render.Red,
ActiveTitleForeground: render.White,
InactiveTitleBackground: render.DarkGrey,
InactiveTitleForeground: render.Grey,
ActiveBackground: render.Black,
InactiveBackground: render.Black,
},
Button: &style.Button{
Background: render.Black,
Foreground: render.Grey,
OutlineColor: render.DarkGrey,
OutlineSize: 1,
HoverBackground: render.Grey,
BorderStyle: style.BorderRaised,
BorderSize: 2,
},
Tooltip: &style.Tooltip{
Background: render.RGBA(60, 60, 60, 230),
Foreground: render.Cyan,
},
}

View File

@ -5,6 +5,7 @@ import (
"strings"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/style"
)
func init() {
@ -20,6 +21,7 @@ type Tooltip struct {
TextVariable *string // String pointer instead of text.
Edge Edge // side to display tooltip on
style *style.Tooltip
target Widget
lineHeight int
font render.Text
@ -74,9 +76,22 @@ func NewTooltip(target Widget, tt Tooltip) *Tooltip {
return fmt.Sprintf(`Tooltip<"%s">`, w.Value())
})
w.SetStyle(Theme.Tooltip)
return w
}
// SetStyle sets the tooltip's default style.
func (w *Tooltip) SetStyle(v *style.Tooltip) {
if v == nil {
v = &style.DefaultTooltip
}
w.style = v
w.SetBackground(w.style.Background)
w.font.Color = w.style.Foreground
}
// Value returns the current text displayed in the tooltop, whether from the
// configured Text or the TextVariable pointer.
func (w *Tooltip) Value() string {

View File

@ -4,6 +4,7 @@ import (
"fmt"
"git.kirsle.net/go/render"
"git.kirsle.net/go/ui/style"
)
// Window is a frame with a title bar.
@ -19,6 +20,7 @@ type Window struct {
InactiveTitleForeground render.Color
// Private widgets.
style *style.Window
body *Frame
titleBar *Frame
titleLabel *Label
@ -58,12 +60,6 @@ func NewWindow(title string) *Window {
)
})
w.body.Configure(Config{
Background: render.Grey,
BorderSize: 2,
BorderStyle: BorderRaised,
})
// Title bar widget.
titleBar, titleLabel := w.setupTitleBar()
w.body.Pack(titleBar, Pack{
@ -87,9 +83,32 @@ func NewWindow(title string) *Window {
// Set up parent/child relationships
w.body.SetParent(w)
w.SetStyle(Theme.Window)
return w
}
// SetStyle sets the window style.
func (w *Window) SetStyle(v *style.Window) {
if v == nil {
v = &style.DefaultWindow
}
w.style = v
w.body.Configure(Config{
Background: w.style.ActiveBackground,
BorderSize: 2,
BorderStyle: BorderRaised,
})
if w.focused {
w.titleBar.SetBackground(w.style.ActiveTitleBackground)
w.titleLabel.Font.Color = w.style.ActiveTitleForeground
} else {
w.titleBar.SetBackground(w.style.InactiveTitleBackground)
w.titleLabel.Font.Color = w.style.InactiveTitleForeground
}
}
// setupTitlebar creates the title bar frame of the window.
func (w *Window) setupTitleBar() (*Frame, *Label) {
frame := NewFrame("Titlebar for Window: " + w.Title)
@ -260,12 +279,12 @@ func (w *Window) SetFocus(v bool) {
// Update the title bar colors.
var (
bg = w.ActiveTitleBackground
fg = w.ActiveTitleForeground
bg = w.style.ActiveTitleBackground
fg = w.style.ActiveTitleForeground
)
if !w.focused {
bg = w.InactiveTitleBackground
fg = w.InactiveTitleForeground
bg = w.style.InactiveTitleBackground
fg = w.style.InactiveTitleForeground
}
w.titleBar.SetBackground(bg)
w.titleLabel.Font.Color = fg
@ -319,9 +338,9 @@ func (w *Window) Place(child Widget, config Place) {
w.content.Place(child, config)
}
// TitleBar returns the title bar widget.
func (w *Window) TitleBar() *Frame {
return w.titleBar
// TitleBar returns the title bar widgets.
func (w *Window) TitleBar() (*Frame, *Label) {
return w.titleBar, w.titleLabel
}
// Configure the widget. Color and style changes are passed down to the inner