Browse Source

WIP Themes Support

themes
Noah Petherbridge 10 months ago
parent
commit
e675ead0ed
11 changed files with 534 additions and 22 deletions
  1. +34
    -11
      button.go
  2. +11
    -0
      eg/themes/Makefile
  3. +107
    -0
      eg/themes/main.go
  4. +169
    -0
      eg/themes/main_wasm.go
  5. +14
    -0
      label.go
  6. +71
    -0
      style/button.go
  7. +13
    -0
      style/style.go
  8. +6
    -0
      theme.go
  9. +65
    -1
      theme/theme.go
  10. +15
    -0
      tooltip.go
  11. +29
    -10
      window.go

+ 34
- 11
button.go 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}


+ 11
- 0
eg/themes/Makefile 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
- 0
eg/themes/main.go 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
- 0
eg/themes/main_wasm.go 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,
})
}

+ 14
- 0
label.go 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 {


+ 71
- 0
style/button.go 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
- 0
style/style.go 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"
)

+ 6
- 0
theme.go 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

+ 65
- 1
theme/theme.go 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,
},
}

+ 15
- 0
tooltip.go 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 {


+ 29
- 10
window.go 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


Loading…
Cancel
Save