Initial commit
This commit is contained in:
commit
8e194e278c
|
@ -0,0 +1,12 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
menu "github.com/localhots/themenu"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
menu.Main()
|
||||||
|
log.Println("Shut down")
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type output struct {
|
||||||
|
code int
|
||||||
|
body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func execCommand(command string) (string, error) {
|
||||||
|
if command == "todo" {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Command", command)
|
||||||
|
cmd := exec.Command("bash", "-c", command)
|
||||||
|
out, err := cmd.CombinedOutput()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(string(out)), nil
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
package fonts
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find looks up font file location by the name.
|
||||||
|
func Find(name string) (string, error) {
|
||||||
|
// Normalize font file name
|
||||||
|
fileName := strings.Replace(name, " ", "-", -1) + ".ttf"
|
||||||
|
|
||||||
|
for _, dir := range fontDirs() {
|
||||||
|
dir, err := expandHome(dir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
path := filepath.Join(dir, fileName)
|
||||||
|
if fileExist(path) {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expandHome(path string) (string, error) {
|
||||||
|
if !strings.HasPrefix(path, "~/") {
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(u.HomeDir, path[2:]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fileExist(path string) bool {
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
// +build darwin
|
||||||
|
|
||||||
|
package fonts
|
||||||
|
|
||||||
|
// Docs: https://support.apple.com/en-us/HT201722
|
||||||
|
func fontDirs() []string {
|
||||||
|
return []string{
|
||||||
|
".", // Current directory
|
||||||
|
"~/Library/Fonts", // User
|
||||||
|
"/Library/Fonts", // Local
|
||||||
|
"/System/Library/Fonts", // System
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build !linux,!darwin
|
||||||
|
|
||||||
|
package fonts
|
||||||
|
|
||||||
|
func fontDirs() []string {
|
||||||
|
return []string{
|
||||||
|
".", // Current directory
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package fonts
|
||||||
|
|
||||||
|
func fontDirs() []string {
|
||||||
|
return []string{
|
||||||
|
".", // Current directory
|
||||||
|
"~/.fonts", // User
|
||||||
|
"~/.fonts/truetype", // User
|
||||||
|
"~/.local/share/fonts", // User
|
||||||
|
"~/.local/share/fonts/truetype", // User
|
||||||
|
"/usr/share/fonts", // System
|
||||||
|
"/usr/share/fonts/truetype", // System
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,227 @@
|
||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/juju/errors"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type listItem struct {
|
||||||
|
Label string `json:"label"`
|
||||||
|
ActionKey string `json:"key"`
|
||||||
|
Keep bool `json:"keep"`
|
||||||
|
Command string `json:"command"`
|
||||||
|
Condition *ifCondition `json:"if"`
|
||||||
|
Items []listItem `json:"items"`
|
||||||
|
|
||||||
|
active bool
|
||||||
|
|
||||||
|
subLabel string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifCondition struct {
|
||||||
|
Command string `json:"command"`
|
||||||
|
Output map[string]listItem `json:"output"`
|
||||||
|
|
||||||
|
cachedOutput *string
|
||||||
|
busy bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *listItem) render(r *sdl.Renderer, offsetY int32) error {
|
||||||
|
if err := drawItemBackground(r, offsetY, li.active); err != nil {
|
||||||
|
return errors.Annotate(err, "draw item background")
|
||||||
|
}
|
||||||
|
if err := drawItemLabel(r, offsetY, *li); err != nil {
|
||||||
|
return errors.Annotate(err, "draw item label")
|
||||||
|
}
|
||||||
|
if err := drawActionKeyLabel(r, offsetY, *li); err != nil {
|
||||||
|
return errors.Annotate(err, "draw item label")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *listItem) call() error {
|
||||||
|
defer func() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Printf("Action failed: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case li.Condition != nil:
|
||||||
|
if li.Condition.cachedOutput == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
out := *li.Condition.cachedOutput
|
||||||
|
if match, ok := li.Condition.Output[out]; ok {
|
||||||
|
_, err := execCommand(match.Command)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("no match", out)
|
||||||
|
return nil
|
||||||
|
case li.Command != "":
|
||||||
|
_, err := execCommand(li.Command)
|
||||||
|
return err
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (li *listItem) label() string {
|
||||||
|
if li.subLabel != "" {
|
||||||
|
return li.Label + " " + li.subLabel
|
||||||
|
}
|
||||||
|
return li.Label
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawItemBackground(r *sdl.Renderer, offsetY int32, active bool) error {
|
||||||
|
if err := setDrawColor(r, theme.ItemBackground); err != nil {
|
||||||
|
return errors.Annotate(err, "set item background color")
|
||||||
|
}
|
||||||
|
|
||||||
|
actionKeyFrame := &sdl.Rect{
|
||||||
|
X: int32(paddingX + borderWidth),
|
||||||
|
Y: offsetY,
|
||||||
|
W: int32(itemHeight()),
|
||||||
|
H: int32(itemHeight()),
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
if active {
|
||||||
|
err = r.DrawRect(actionKeyFrame)
|
||||||
|
} else {
|
||||||
|
err = r.FillRect(actionKeyFrame)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Annotate(err, "draw item")
|
||||||
|
}
|
||||||
|
|
||||||
|
itemBackground := &sdl.Rect{
|
||||||
|
X: int32(borderWidth + paddingX + itemHeight() + paddingX),
|
||||||
|
Y: offsetY,
|
||||||
|
W: int32(windowWidth - 3*paddingX - itemHeight() - 2*borderWidth),
|
||||||
|
H: int32(itemHeight()),
|
||||||
|
}
|
||||||
|
|
||||||
|
if active {
|
||||||
|
err = r.DrawRect(itemBackground)
|
||||||
|
} else {
|
||||||
|
err = r.FillRect(itemBackground)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Annotate(err, "draw item")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawItemLabel(r *sdl.Renderer, offsetY int32, li listItem) error {
|
||||||
|
label := li.Label
|
||||||
|
if li.Condition != nil {
|
||||||
|
if li.Condition.cachedOutput != nil {
|
||||||
|
out := *li.Condition.cachedOutput
|
||||||
|
if match, ok := li.Condition.Output[out]; ok {
|
||||||
|
label = match.Label
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if li.Condition.busy {
|
||||||
|
label += " [busy]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color := theme.ItemText
|
||||||
|
if li.Condition != nil && li.Condition.busy {
|
||||||
|
color = theme.TextBusy
|
||||||
|
}
|
||||||
|
|
||||||
|
labelTexture, err := renderText(r, label, color)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Annotate(err, "draw item label")
|
||||||
|
}
|
||||||
|
defer labelTexture.Destroy()
|
||||||
|
lw, lh, err := font.SizeUTF8(label)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
const magicNumber = 3
|
||||||
|
itemLabel := &sdl.Rect{
|
||||||
|
X: int32(paddingX*2+itemHeight()+(itemHeight()-lh)/2) + magicNumber,
|
||||||
|
Y: offsetY + int32(itemHeight()-lh)/2,
|
||||||
|
W: int32(lw),
|
||||||
|
H: int32(lh),
|
||||||
|
}
|
||||||
|
if err := r.Copy(labelTexture, nil, itemLabel); err != nil {
|
||||||
|
return errors.Annotate(err, "render item label")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawActionKeyLabel(r *sdl.Renderer, offsetY int32, li listItem) error {
|
||||||
|
label := strings.ToUpper(li.ActionKey)
|
||||||
|
color := theme.ItemText
|
||||||
|
if li.Condition != nil && li.Condition.busy {
|
||||||
|
color = theme.TextBusy
|
||||||
|
}
|
||||||
|
|
||||||
|
labelTexture, err := renderText(r, label, color)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Annotate(err, "draw item label")
|
||||||
|
}
|
||||||
|
defer labelTexture.Destroy()
|
||||||
|
lw, lh, err := font.SizeUTF8(label)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
itemLabel := &sdl.Rect{
|
||||||
|
X: int32(paddingX + (itemHeight()-lw)/2),
|
||||||
|
Y: offsetY + int32(itemHeight()-lh)/2,
|
||||||
|
W: int32(lw),
|
||||||
|
H: int32(lh),
|
||||||
|
}
|
||||||
|
if err := r.Copy(labelTexture, nil, itemLabel); err != nil {
|
||||||
|
return errors.Annotate(err, "render item label")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderText(r *sdl.Renderer, text string, c sdl.Color) (t *sdl.Texture, err error) {
|
||||||
|
fs, err := font.RenderUTF8Blended(text, c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fs.Free()
|
||||||
|
|
||||||
|
t, err = r.CreateTextureFromSurface(fs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ifCondition) evaluate() {
|
||||||
|
fn := func() {
|
||||||
|
c.busy = true
|
||||||
|
out, err := execCommand(c.Command)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Error evaluating condition: %v", err)
|
||||||
|
} else {
|
||||||
|
out = strings.TrimSpace(out)
|
||||||
|
c.cachedOutput = &out
|
||||||
|
}
|
||||||
|
c.busy = false
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
|
||||||
|
t := time.NewTicker(2 * time.Second)
|
||||||
|
defer t.Stop()
|
||||||
|
for range t.C {
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,147 @@
|
||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
)
|
||||||
|
|
||||||
|
var frameLimit = 60
|
||||||
|
|
||||||
|
// Main is the main function.
|
||||||
|
func Main() {
|
||||||
|
conf := flag.String("config", "config.json", "Path to config file")
|
||||||
|
flag.IntVar(&windowWidth, "width", windowWidth, "Window width")
|
||||||
|
flag.IntVar(&windowHeight, "height", windowHeight, "Window height")
|
||||||
|
flag.IntVar(&borderWidth, "border", borderWidth, "Border width")
|
||||||
|
flag.IntVar(&frameLimit, "fps", frameLimit, "FPS limit")
|
||||||
|
flag.StringVar(&fontName, "fontname", fontName, "Font name (must be TrueType)")
|
||||||
|
flag.IntVar(&fontSize, "fontsize", fontSize, "Font size")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)
|
||||||
|
log.Println("Loading config")
|
||||||
|
items, err := getItems(*conf)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read config file: %v", err)
|
||||||
|
}
|
||||||
|
keepConditionsEvaluated(items)
|
||||||
|
|
||||||
|
log.Println("Loading font")
|
||||||
|
if err := ttf.Init(); err != nil {
|
||||||
|
log.Fatalf("Failed to initialize TrueType package: %v", err)
|
||||||
|
}
|
||||||
|
defer ttf.Quit()
|
||||||
|
if err := useFont(fontName); err != nil {
|
||||||
|
log.Fatalf("Failed to open font: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println("Initializing SDL")
|
||||||
|
if err := sdl.Init(sdl.INIT_EVENTS); err != nil {
|
||||||
|
log.Fatalf("Failed to initialize sdl: %v", err)
|
||||||
|
}
|
||||||
|
defer sdl.Quit()
|
||||||
|
sdl.SetHint(sdl.HINT_RENDER_SCALE_QUALITY, "1")
|
||||||
|
|
||||||
|
log.Println("Creating window")
|
||||||
|
sdlWindow, err := sdl.CreateWindow("menu",
|
||||||
|
sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED,
|
||||||
|
int32(windowWidth), int32(windowHeight),
|
||||||
|
sdl.WINDOW_SHOWN|sdl.WINDOW_BORDERLESS|sdl.WINDOW_ALLOW_HIGHDPI|sdl.WINDOW_OPENGL,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create window: %v", err)
|
||||||
|
}
|
||||||
|
defer sdlWindow.Destroy()
|
||||||
|
|
||||||
|
log.Println("Creating renderer")
|
||||||
|
renderer, err := sdl.CreateRenderer(sdlWindow, -1, sdl.RENDERER_ACCELERATED|sdl.RENDERER_PRESENTVSYNC)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to create renderer: %v", err)
|
||||||
|
}
|
||||||
|
defer renderer.Destroy()
|
||||||
|
|
||||||
|
w := window{
|
||||||
|
window: sdlWindow,
|
||||||
|
allItems: items,
|
||||||
|
curItems: items,
|
||||||
|
}
|
||||||
|
w.eventLoop(renderer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) eventLoop(r *sdl.Renderer) {
|
||||||
|
// Throttle rendering
|
||||||
|
ticker := time.NewTicker(time.Second / time.Duration(frameLimit))
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for range ticker.C {
|
||||||
|
event := sdl.PollEvent()
|
||||||
|
switch tevt := event.(type) {
|
||||||
|
case *sdl.QuitEvent:
|
||||||
|
log.Println("Shutting down")
|
||||||
|
return
|
||||||
|
case *sdl.KeyboardEvent:
|
||||||
|
if err := w.handleKeyEvent(tevt); err != nil {
|
||||||
|
log.Printf("Failed to process key event: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.render(r); err != nil {
|
||||||
|
log.Printf("Failed to render window: %v", err)
|
||||||
|
}
|
||||||
|
// Ta-dah!
|
||||||
|
r.Present()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *window) handleKeyEvent(e *sdl.KeyboardEvent) error {
|
||||||
|
if e.Keysym.Sym == sdl.K_ESCAPE {
|
||||||
|
w.curItems = w.allItems
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try and transform a key code into a string containing a letter
|
||||||
|
key := string(rune(e.Keysym.Sym))
|
||||||
|
for i, itm := range w.curItems {
|
||||||
|
if itm.ActionKey == key {
|
||||||
|
if len(itm.Items) > 0 {
|
||||||
|
w.curItems = itm.Items
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
w.curItems[i].active = (e.State == sdl.PRESSED)
|
||||||
|
if e.State == sdl.RELEASED {
|
||||||
|
if itm.Condition == nil || !itm.Condition.busy {
|
||||||
|
go w.curItems[i].call()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getItems(path string) ([]listItem, error) {
|
||||||
|
b, err := ioutil.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var items []listItem
|
||||||
|
err = json.Unmarshal(b, &items)
|
||||||
|
return items, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func keepConditionsEvaluated(items []listItem) {
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Condition != nil {
|
||||||
|
go item.Condition.evaluate()
|
||||||
|
}
|
||||||
|
keepConditionsEvaluated(item.Items)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
)
|
||||||
|
|
||||||
|
type colorScheme struct {
|
||||||
|
Background sdl.Color
|
||||||
|
ItemBackground sdl.Color
|
||||||
|
ItemText sdl.Color
|
||||||
|
TextBusy sdl.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
var theme = themeDracula
|
||||||
|
|
||||||
|
var themeGrayScale = colorScheme{
|
||||||
|
Background: sdl.Color{R: 20, G: 20, B: 20, A: 255},
|
||||||
|
ItemBackground: sdl.Color{R: 60, G: 60, B: 60, A: 255},
|
||||||
|
ItemText: sdl.Color{R: 240, G: 240, B: 240, A: 255},
|
||||||
|
}
|
||||||
|
|
||||||
|
var themeBlueOnBlack = colorScheme{
|
||||||
|
Background: sdl.Color{R: 20, G: 20, B: 20, A: 255},
|
||||||
|
ItemBackground: sdl.Color{R: 0, G: 100, B: 200, A: 255},
|
||||||
|
ItemText: sdl.Color{R: 255, G: 255, B: 255, A: 255},
|
||||||
|
}
|
||||||
|
|
||||||
|
var themeDracula = colorScheme{
|
||||||
|
Background: rgb(40, 42, 54),
|
||||||
|
ItemBackground: rgb(68, 71, 90),
|
||||||
|
ItemText: rgb(248, 248, 242),
|
||||||
|
TextBusy: rgb(255, 184, 108),
|
||||||
|
}
|
||||||
|
|
||||||
|
func rgb(r, g, b uint8) sdl.Color {
|
||||||
|
return sdl.Color{R: r, G: g, B: b, A: 255}
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
package menu
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/juju/errors"
|
||||||
|
"github.com/localhots/themenu/fonts"
|
||||||
|
"github.com/veandco/go-sdl2/sdl"
|
||||||
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
paddingX = 8
|
||||||
|
paddingY = 8
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
windowWidth = 600
|
||||||
|
windowHeight = 800
|
||||||
|
borderWidth = 1
|
||||||
|
|
||||||
|
fontName = "InconsolataGo Regular"
|
||||||
|
fontSize = 24
|
||||||
|
font *ttf.Font
|
||||||
|
)
|
||||||
|
|
||||||
|
type window struct {
|
||||||
|
window *sdl.Window
|
||||||
|
allItems, curItems []listItem
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w window) render(r *sdl.Renderer) error {
|
||||||
|
// Resize window
|
||||||
|
windowHeight = borderWidth*2 + // Border
|
||||||
|
len(w.curItems)*itemHeight() + // Backgrounds
|
||||||
|
(len(w.curItems)+1)*paddingY // Paddings
|
||||||
|
w.window.SetSize(int32(windowWidth), int32(windowHeight))
|
||||||
|
|
||||||
|
// Reset background
|
||||||
|
if err := drawWindowBackground(r); err != nil {
|
||||||
|
return errors.Annotate(err, "draw window background")
|
||||||
|
}
|
||||||
|
if err := drawWindowBorder(r, int32(borderWidth)); err != nil {
|
||||||
|
return errors.Annotate(err, "draw window border")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw items
|
||||||
|
for i, itm := range w.curItems {
|
||||||
|
offsetY := int32(i*(itemHeight()+paddingY) + paddingY + borderWidth)
|
||||||
|
if err := itm.render(r, offsetY); err != nil {
|
||||||
|
return errors.Annotate(err, "render item")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawWindowBackground(r *sdl.Renderer) error {
|
||||||
|
if err := setDrawColor(r, theme.Background); err != nil {
|
||||||
|
return errors.Annotate(err, "set background color")
|
||||||
|
}
|
||||||
|
backgroundFrame := &sdl.Rect{
|
||||||
|
X: 0,
|
||||||
|
Y: 0,
|
||||||
|
W: int32(windowWidth),
|
||||||
|
H: int32(windowHeight),
|
||||||
|
}
|
||||||
|
if err := r.FillRect(backgroundFrame); err != nil {
|
||||||
|
return errors.Annotate(err, "draw background")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func drawWindowBorder(r *sdl.Renderer, width int32) error {
|
||||||
|
if err := setDrawColor(r, theme.ItemBackground); err != nil {
|
||||||
|
return errors.Annotate(err, "set border color")
|
||||||
|
}
|
||||||
|
for i := int32(1); i <= width; i++ {
|
||||||
|
borderFrame := &sdl.Rect{
|
||||||
|
X: i,
|
||||||
|
Y: i,
|
||||||
|
W: int32(windowWidth) - i*2,
|
||||||
|
H: int32(windowHeight) - i*2,
|
||||||
|
}
|
||||||
|
if err := r.DrawRect(borderFrame); err != nil {
|
||||||
|
return errors.Annotate(err, "draw border")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func useFont(name string) error {
|
||||||
|
fontPath, err := fonts.Find(name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if fontPath == "" {
|
||||||
|
return errors.New("font not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
font, err = ttf.OpenFont(fontPath, fontSize)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Annotatef(err, "load font %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func itemHeight() int {
|
||||||
|
return fontSize + 2*paddingY
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDrawColor(r *sdl.Renderer, c sdl.Color) error {
|
||||||
|
return r.SetDrawColor(c.R, c.G, c.B, c.A)
|
||||||
|
}
|
Loading…
Reference in New Issue