Add Initial "Doodad Palette" UX
* Add a tab bar to the top of the Palette window that has two radiobuttons for "Palette" and "Doodads" * UI: add the concept of a Hidden() widget and the corresponding Hide() and Show() methods. Hidden widgets are skipped over when evaluating Frame packing, rendering, and event supervision. * The Palette Window in editor mode now displays one of two tabs: * Palette: the old color swatch palette now lives here. * Doodads: the new Doodad palette. * The Doodad Palette shows a grid of buttons (2 per row) showing the available Doodad drawings in the user's config folder. * The Doodad buttons act as radiobuttons for now and have no other effect. TODO will be making them react to drag-drop events. * UI: added a `Children()` method as the inverse of `Parent()` for container widgets (like Frame, Window and Button) to expose their children. The BaseWidget just returns an empty []Widget. * Console: added a `repl` command that keeps the dev console open and prefixes every command with `$` filled out -- for rapid JavaScript console evaluation.
This commit is contained in:
parent
f18dcf9c2c
commit
b67c4b67b2
|
@ -52,6 +52,9 @@ func (c Command) Run(d *Doodle) error {
|
||||||
out, err := d.shell.js.Run(c.ArgsLiteral)
|
out, err := d.shell.js.Run(c.ArgsLiteral)
|
||||||
d.Flash("%+v", out)
|
d.Flash("%+v", out)
|
||||||
return err
|
return err
|
||||||
|
case "repl":
|
||||||
|
d.shell.Repl = true
|
||||||
|
d.shell.Text = "$ "
|
||||||
case "boolProp":
|
case "boolProp":
|
||||||
return c.BoolProp(d)
|
return c.BoolProp(d)
|
||||||
default:
|
default:
|
||||||
|
|
20
config.go
20
config.go
|
@ -2,6 +2,7 @@ package doodle
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
@ -69,6 +70,25 @@ func DoodadPath(filename string) string {
|
||||||
return resolvePath(DoodadDirectory, filename, extDoodad)
|
return resolvePath(DoodadDirectory, filename, extDoodad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListDoodads returns a listing of all available doodads.
|
||||||
|
func ListDoodads() ([]string, error) {
|
||||||
|
var names []string
|
||||||
|
|
||||||
|
files, err := ioutil.ReadDir(DoodadDirectory)
|
||||||
|
if err != nil {
|
||||||
|
return names, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
name := file.Name()
|
||||||
|
if strings.HasSuffix(strings.ToLower(name), extDoodad) {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
// resolvePath is the inner logic for LevelPath and DoodadPath.
|
// resolvePath is the inner logic for LevelPath and DoodadPath.
|
||||||
func resolvePath(directory, filename, extension string) string {
|
func resolvePath(directory, filename, extension string) string {
|
||||||
if strings.Contains(filename, "/") {
|
if strings.Contains(filename, "/") {
|
||||||
|
|
123
editor_ui.go
123
editor_ui.go
|
@ -5,6 +5,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
|
"git.kirsle.net/apps/doodle/doodads"
|
||||||
"git.kirsle.net/apps/doodle/enum"
|
"git.kirsle.net/apps/doodle/enum"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
|
@ -23,14 +24,22 @@ type EditorUI struct {
|
||||||
StatusPaletteText string
|
StatusPaletteText string
|
||||||
StatusFilenameText string
|
StatusFilenameText string
|
||||||
selectedSwatch string // name of selected swatch in palette
|
selectedSwatch string // name of selected swatch in palette
|
||||||
|
selectedDoodad string
|
||||||
|
|
||||||
// Widgets
|
// Widgets
|
||||||
Supervisor *ui.Supervisor
|
Supervisor *ui.Supervisor
|
||||||
Canvas *uix.Canvas
|
Canvas *uix.Canvas
|
||||||
Workspace *ui.Frame
|
Workspace *ui.Frame
|
||||||
MenuBar *ui.Frame
|
MenuBar *ui.Frame
|
||||||
Palette *ui.Window
|
|
||||||
StatusBar *ui.Frame
|
StatusBar *ui.Frame
|
||||||
|
|
||||||
|
// Palette window.
|
||||||
|
Palette *ui.Window
|
||||||
|
PaletteTab *ui.Frame
|
||||||
|
DoodadTab *ui.Frame
|
||||||
|
|
||||||
|
// Palette variables.
|
||||||
|
paletteTab string // selected tab, Palette or Doodads
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEditorUI initializes the Editor UI.
|
// NewEditorUI initializes the Editor UI.
|
||||||
|
@ -268,11 +277,13 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame {
|
||||||
|
|
||||||
// SetupPalette sets up the palette panel.
|
// SetupPalette sets up the palette panel.
|
||||||
func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
||||||
|
var paletteWidth int32 = 150
|
||||||
|
|
||||||
window := ui.NewWindow("Palette")
|
window := ui.NewWindow("Palette")
|
||||||
window.ConfigureTitle(balance.TitleConfig)
|
window.ConfigureTitle(balance.TitleConfig)
|
||||||
window.TitleBar().Font = balance.TitleFont
|
window.TitleBar().Font = balance.TitleFont
|
||||||
window.Configure(ui.Config{
|
window.Configure(ui.Config{
|
||||||
Width: 150,
|
Width: paletteWidth,
|
||||||
Height: u.d.height - u.StatusBar.Size().H,
|
Height: u.d.height - u.StatusBar.Size().H,
|
||||||
Background: balance.WindowBackground,
|
Background: balance.WindowBackground,
|
||||||
BorderColor: balance.WindowBorder,
|
BorderColor: balance.WindowBorder,
|
||||||
|
@ -282,6 +293,111 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
||||||
u.MenuBar.BoxSize().H,
|
u.MenuBar.BoxSize().H,
|
||||||
))
|
))
|
||||||
|
|
||||||
|
// Frame that holds the tab buttons in Level Edit mode.
|
||||||
|
tabFrame := ui.NewFrame("Palette Tabs")
|
||||||
|
if u.Scene.DrawingType != enum.LevelDrawing {
|
||||||
|
// Don't show the tab bar except in Level Edit mode.
|
||||||
|
tabFrame.Hide()
|
||||||
|
}
|
||||||
|
for _, name := range []string{"Palette", "Doodads"} {
|
||||||
|
if u.paletteTab == "" {
|
||||||
|
u.paletteTab = name
|
||||||
|
}
|
||||||
|
|
||||||
|
tab := ui.NewRadioButton("Palette Tab", &u.paletteTab, name, ui.NewLabel(ui.Label{
|
||||||
|
Text: name,
|
||||||
|
}))
|
||||||
|
tab.Handle(ui.Click, func(p render.Point) {
|
||||||
|
if u.paletteTab == "Palette" {
|
||||||
|
u.PaletteTab.Show()
|
||||||
|
u.DoodadTab.Hide()
|
||||||
|
} else {
|
||||||
|
u.PaletteTab.Hide()
|
||||||
|
u.DoodadTab.Show()
|
||||||
|
}
|
||||||
|
window.Compute(d.Engine)
|
||||||
|
})
|
||||||
|
u.Supervisor.Add(tab)
|
||||||
|
tabFrame.Pack(tab, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
Fill: true,
|
||||||
|
Expand: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
window.Pack(tabFrame, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
Fill: true,
|
||||||
|
PadY: 4,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Doodad frame.
|
||||||
|
{
|
||||||
|
u.DoodadTab = ui.NewFrame("Doodad Tab")
|
||||||
|
u.DoodadTab.Hide()
|
||||||
|
window.Pack(u.DoodadTab, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
doodadsAvailable, err := ListDoodads()
|
||||||
|
if err != nil {
|
||||||
|
d.Flash("ListDoodads: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buttonSize = (paletteWidth - window.BoxThickness(2)) / 2
|
||||||
|
|
||||||
|
// Draw the doodad buttons in a grid 2 wide.
|
||||||
|
var row *ui.Frame
|
||||||
|
for i, filename := range doodadsAvailable {
|
||||||
|
si := fmt.Sprintf("%d", i)
|
||||||
|
if row == nil || i%2 == 0 {
|
||||||
|
row = ui.NewFrame("Doodad Row " + si)
|
||||||
|
row.SetBackground(balance.WindowBackground)
|
||||||
|
u.DoodadTab.Pack(row, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
Fill: true,
|
||||||
|
// Expand: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
doodad, err := doodads.LoadJSON(DoodadPath(filename))
|
||||||
|
if err != nil {
|
||||||
|
log.Error(err.Error())
|
||||||
|
doodad = doodads.New(balance.DoodadSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
can := uix.NewCanvas(int(buttonSize), true)
|
||||||
|
can.LoadDoodad(doodad)
|
||||||
|
btn := ui.NewRadioButton(filename, &u.selectedDoodad, si, can)
|
||||||
|
btn.Resize(render.NewRect(
|
||||||
|
buttonSize-2, // TODO: without the -2 the button border
|
||||||
|
buttonSize-2, // rests on top of the window border.
|
||||||
|
))
|
||||||
|
u.Supervisor.Add(btn)
|
||||||
|
row.Pack(btn, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Resize the canvas to fill the button interior.
|
||||||
|
btnSize := btn.Size()
|
||||||
|
can.Resize(render.NewRect(
|
||||||
|
btnSize.W-btn.BoxThickness(2),
|
||||||
|
btnSize.H-btn.BoxThickness(2),
|
||||||
|
))
|
||||||
|
|
||||||
|
btn.Compute(d.Engine)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Color Palette Frame.
|
||||||
|
{
|
||||||
|
u.PaletteTab = ui.NewFrame("Palette Tab")
|
||||||
|
u.PaletteTab.SetBackground(balance.WindowBackground)
|
||||||
|
window.Pack(u.PaletteTab, ui.Pack{
|
||||||
|
Anchor: ui.N,
|
||||||
|
Fill: true,
|
||||||
|
})
|
||||||
|
|
||||||
// Handler function for the radio buttons being clicked.
|
// Handler function for the radio buttons being clicked.
|
||||||
onClick := func(p render.Point) {
|
onClick := func(p render.Point) {
|
||||||
name := u.selectedSwatch
|
name := u.selectedSwatch
|
||||||
|
@ -307,13 +423,14 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
||||||
btn.Handle(ui.Click, onClick)
|
btn.Handle(ui.Click, onClick)
|
||||||
u.Supervisor.Add(btn)
|
u.Supervisor.Add(btn)
|
||||||
|
|
||||||
window.Pack(btn, ui.Pack{
|
u.PaletteTab.Pack(btn, ui.Pack{
|
||||||
Anchor: ui.N,
|
Anchor: ui.N,
|
||||||
Fill: true,
|
Fill: true,
|
||||||
PadY: 4,
|
PadY: 4,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return window
|
return window
|
||||||
}
|
}
|
||||||
|
|
19
shell.go
19
shell.go
|
@ -8,6 +8,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,6 +31,7 @@ type Shell struct {
|
||||||
|
|
||||||
Open bool
|
Open bool
|
||||||
Prompt string
|
Prompt string
|
||||||
|
Repl bool
|
||||||
callback func(string) // for prompt answers only
|
callback func(string) // for prompt answers only
|
||||||
Text string
|
Text string
|
||||||
History []string
|
History []string
|
||||||
|
@ -75,6 +77,12 @@ func NewShell(d *Doodle) Shell {
|
||||||
"RGBA": render.RGBA,
|
"RGBA": render.RGBA,
|
||||||
"Point": render.NewPoint,
|
"Point": render.NewPoint,
|
||||||
"Rect": render.NewRect,
|
"Rect": render.NewRect,
|
||||||
|
"Tree": func(w ui.Widget) string {
|
||||||
|
for _, row := range ui.WidgetTree(w) {
|
||||||
|
d.Flash(row)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for name, v := range bindings {
|
for name, v := range bindings {
|
||||||
err := s.js.Set(name, v)
|
err := s.js.Set(name, v)
|
||||||
|
@ -90,6 +98,7 @@ func NewShell(d *Doodle) Shell {
|
||||||
func (s *Shell) Close() {
|
func (s *Shell) Close() {
|
||||||
log.Debug("Shell: closing shell")
|
log.Debug("Shell: closing shell")
|
||||||
s.Open = false
|
s.Open = false
|
||||||
|
s.Repl = false
|
||||||
s.Prompt = ">"
|
s.Prompt = ">"
|
||||||
s.callback = nil
|
s.callback = nil
|
||||||
s.Text = ""
|
s.Text = ""
|
||||||
|
@ -100,6 +109,7 @@ func (s *Shell) Close() {
|
||||||
// Execute a command in the shell.
|
// Execute a command in the shell.
|
||||||
func (s *Shell) Execute(input string) {
|
func (s *Shell) Execute(input string) {
|
||||||
command := s.Parse(input)
|
command := s.Parse(input)
|
||||||
|
|
||||||
if command.Raw != "" {
|
if command.Raw != "" {
|
||||||
s.Output = append(s.Output, s.Prompt+command.Raw)
|
s.Output = append(s.Output, s.Prompt+command.Raw)
|
||||||
s.History = append(s.History, command.Raw)
|
s.History = append(s.History, command.Raw)
|
||||||
|
@ -123,7 +133,11 @@ func (s *Shell) Execute(input string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the text buffer in the shell.
|
// Reset the text buffer in the shell.
|
||||||
|
if s.Repl {
|
||||||
|
s.Text = "$ "
|
||||||
|
} else {
|
||||||
s.Text = ""
|
s.Text = ""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a line of output text to the console.
|
// Write a line of output text to the console.
|
||||||
|
@ -197,7 +211,12 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
||||||
return nil
|
return nil
|
||||||
} else if ev.EnterKey.Read() || ev.EscapeKey.Read() {
|
} else if ev.EnterKey.Read() || ev.EscapeKey.Read() {
|
||||||
s.Execute(s.Text)
|
s.Execute(s.Text)
|
||||||
|
|
||||||
|
// Auto-close the console unless in REPL mode.
|
||||||
|
if !s.Repl {
|
||||||
s.Close()
|
s.Close()
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
} else if (ev.Up.Now || ev.Down.Now) && len(s.History) > 0 {
|
} else if (ev.Up.Now || ev.Down.Now) && len(s.History) > 0 {
|
||||||
// Paging through history.
|
// Paging through history.
|
||||||
|
|
|
@ -56,6 +56,11 @@ func NewButton(name string, child Widget) *Button {
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Children returns the button's child widget.
|
||||||
|
func (w *Button) Children() []Widget {
|
||||||
|
return []Widget{w.child}
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the size of the button.
|
// Compute the size of the button.
|
||||||
func (w *Button) Compute(e render.Engine) {
|
func (w *Button) Compute(e render.Engine) {
|
||||||
// Compute the size of the inner widget first.
|
// Compute the size of the inner widget first.
|
||||||
|
@ -81,6 +86,10 @@ func (w *Button) SetText(text string) error {
|
||||||
|
|
||||||
// Present the button.
|
// Present the button.
|
||||||
func (w *Button) Present(e render.Engine, P render.Point) {
|
func (w *Button) Present(e render.Engine, P render.Point) {
|
||||||
|
if w.Hidden() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
w.Compute(e)
|
w.Compute(e)
|
||||||
w.MoveTo(P)
|
w.MoveTo(P)
|
||||||
var (
|
var (
|
||||||
|
|
23
ui/debug.go
Normal file
23
ui/debug.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
// WidgetTree returns a string representing the tree of widgets starting
|
||||||
|
// at a given widget.
|
||||||
|
func WidgetTree(root Widget) []string {
|
||||||
|
var crawl func(int, Widget) []string
|
||||||
|
crawl = func(depth int, node Widget) []string {
|
||||||
|
var (
|
||||||
|
prefix = strings.Repeat(" ", depth)
|
||||||
|
lines = []string{prefix + node.ID()}
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, child := range node.Children() {
|
||||||
|
lines = append(lines, crawl(depth+1, child)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines
|
||||||
|
}
|
||||||
|
|
||||||
|
return crawl(0, root)
|
||||||
|
}
|
|
@ -39,6 +39,11 @@ func (w *Frame) Setup() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Children returns all of the child widgets.
|
||||||
|
func (w *Frame) Children() []Widget {
|
||||||
|
return w.widgets
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the size of the Frame.
|
// Compute the size of the Frame.
|
||||||
func (w *Frame) Compute(e render.Engine) {
|
func (w *Frame) Compute(e render.Engine) {
|
||||||
w.computePacked(e)
|
w.computePacked(e)
|
||||||
|
@ -46,6 +51,10 @@ func (w *Frame) Compute(e render.Engine) {
|
||||||
|
|
||||||
// Present the Frame.
|
// Present the Frame.
|
||||||
func (w *Frame) Present(e render.Engine, P render.Point) {
|
func (w *Frame) Present(e render.Engine, P render.Point) {
|
||||||
|
if w.Hidden() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
S = w.Size()
|
S = w.Size()
|
||||||
)
|
)
|
||||||
|
|
108
ui/frame_pack.go
108
ui/frame_pack.go
|
@ -2,6 +2,58 @@ package ui
|
||||||
|
|
||||||
import "git.kirsle.net/apps/doodle/render"
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// Pack provides configuration fields for Frame.Pack().
|
||||||
|
type Pack struct {
|
||||||
|
// Side of the parent to anchor the position to, like N, SE, W. Default
|
||||||
|
// is Center.
|
||||||
|
Anchor Anchor
|
||||||
|
|
||||||
|
// If the widget is smaller than its allocated space, grow the widget
|
||||||
|
// to fill its space in the Frame.
|
||||||
|
Fill bool
|
||||||
|
FillX bool
|
||||||
|
FillY bool
|
||||||
|
|
||||||
|
Padding int32 // Equal padding on X and Y.
|
||||||
|
PadX int32
|
||||||
|
PadY int32
|
||||||
|
Expand bool // Widget should grow its allocated space to better fill the parent.
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pack a widget along a side of the frame.
|
||||||
|
func (w *Frame) Pack(child Widget, config ...Pack) {
|
||||||
|
var C Pack
|
||||||
|
if len(config) > 0 {
|
||||||
|
C = config[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the pack list for this anchor?
|
||||||
|
if _, ok := w.packs[C.Anchor]; !ok {
|
||||||
|
w.packs[C.Anchor] = []packedWidget{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Padding: if the user only provided Padding add it to both
|
||||||
|
// the X and Y value. If the user additionally provided the X
|
||||||
|
// and Y value, it will add to the base padding as you'd expect.
|
||||||
|
C.PadX += C.Padding
|
||||||
|
C.PadY += C.Padding
|
||||||
|
|
||||||
|
// Fill: true implies both directions.
|
||||||
|
if C.Fill {
|
||||||
|
C.FillX = true
|
||||||
|
C.FillY = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adopt the child widget so it can access the Frame.
|
||||||
|
child.Adopt(w)
|
||||||
|
|
||||||
|
w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{
|
||||||
|
widget: child,
|
||||||
|
pack: C,
|
||||||
|
})
|
||||||
|
w.widgets = append(w.widgets, child)
|
||||||
|
}
|
||||||
|
|
||||||
// computePacked processes all the Pack layout widgets in the Frame.
|
// computePacked processes all the Pack layout widgets in the Frame.
|
||||||
func (w *Frame) computePacked(e render.Engine) {
|
func (w *Frame) computePacked(e render.Engine) {
|
||||||
var (
|
var (
|
||||||
|
@ -46,6 +98,10 @@ func (w *Frame) computePacked(e render.Engine) {
|
||||||
pack := packedWidget.pack
|
pack := packedWidget.pack
|
||||||
child.Compute(e)
|
child.Compute(e)
|
||||||
|
|
||||||
|
if child.Hidden() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
x += pack.PadX
|
x += pack.PadX
|
||||||
y += pack.PadY
|
y += pack.PadY
|
||||||
|
|
||||||
|
@ -187,24 +243,6 @@ func (w *Frame) computePacked(e render.Engine) {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack provides configuration fields for Frame.Pack().
|
|
||||||
type Pack struct {
|
|
||||||
// Side of the parent to anchor the position to, like N, SE, W. Default
|
|
||||||
// is Center.
|
|
||||||
Anchor Anchor
|
|
||||||
|
|
||||||
// If the widget is smaller than its allocated space, grow the widget
|
|
||||||
// to fill its space in the Frame.
|
|
||||||
Fill bool
|
|
||||||
FillX bool
|
|
||||||
FillY bool
|
|
||||||
|
|
||||||
Padding int32 // Equal padding on X and Y.
|
|
||||||
PadX int32
|
|
||||||
PadY int32
|
|
||||||
Expand bool // Widget should grow its allocated space to better fill the parent.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Anchor is a cardinal direction.
|
// Anchor is a cardinal direction.
|
||||||
type Anchor uint8
|
type Anchor uint8
|
||||||
|
|
||||||
|
@ -259,40 +297,6 @@ func (a Anchor) IsMiddle() bool {
|
||||||
return a == Center || a == W || a == E
|
return a == Center || a == W || a == E
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pack a widget along a side of the frame.
|
|
||||||
func (w *Frame) Pack(child Widget, config ...Pack) {
|
|
||||||
var C Pack
|
|
||||||
if len(config) > 0 {
|
|
||||||
C = config[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize the pack list for this anchor?
|
|
||||||
if _, ok := w.packs[C.Anchor]; !ok {
|
|
||||||
w.packs[C.Anchor] = []packedWidget{}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Padding: if the user only provided Padding add it to both
|
|
||||||
// the X and Y value. If the user additionally provided the X
|
|
||||||
// and Y value, it will add to the base padding as you'd expect.
|
|
||||||
C.PadX += C.Padding
|
|
||||||
C.PadY += C.Padding
|
|
||||||
|
|
||||||
// Fill: true implies both directions.
|
|
||||||
if C.Fill {
|
|
||||||
C.FillX = true
|
|
||||||
C.FillY = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adopt the child widget so it can access the Frame.
|
|
||||||
child.Adopt(w)
|
|
||||||
|
|
||||||
w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{
|
|
||||||
widget: child,
|
|
||||||
pack: C,
|
|
||||||
})
|
|
||||||
w.widgets = append(w.widgets, child)
|
|
||||||
}
|
|
||||||
|
|
||||||
type packLayout struct {
|
type packLayout struct {
|
||||||
widgets []packedWidget
|
widgets []packedWidget
|
||||||
}
|
}
|
||||||
|
|
|
@ -86,6 +86,10 @@ func (w *Label) Compute(e render.Engine) {
|
||||||
|
|
||||||
// Present the label widget.
|
// Present the label widget.
|
||||||
func (w *Label) Present(e render.Engine, P render.Point) {
|
func (w *Label) Present(e render.Engine, P render.Point) {
|
||||||
|
if w.Hidden() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
border := w.BoxThickness(1)
|
border := w.BoxThickness(1)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -53,6 +53,12 @@ func (s *Supervisor) Loop(ev *events.State) {
|
||||||
|
|
||||||
// See if we are hovering over any widgets.
|
// See if we are hovering over any widgets.
|
||||||
for id, w := range s.widgets {
|
for id, w := range s.widgets {
|
||||||
|
if w.Hidden() {
|
||||||
|
// TODO: somehow the Supervisor wasn't triggering hidden widgets
|
||||||
|
// anyway, but I don't know why. Adding this check for safety.
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
P = w.Point()
|
P = w.Point()
|
||||||
S = w.Size()
|
S = w.Size()
|
||||||
|
|
38
ui/widget.go
38
ui/widget.go
|
@ -56,10 +56,16 @@ type Widget interface {
|
||||||
OutlineSize() int32 // Outline size (default 0)
|
OutlineSize() int32 // Outline size (default 0)
|
||||||
SetOutlineSize(int32) //
|
SetOutlineSize(int32) //
|
||||||
|
|
||||||
|
// Visibility
|
||||||
|
Hide()
|
||||||
|
Show()
|
||||||
|
Hidden() bool
|
||||||
|
|
||||||
// Container widgets like Frames can wire up associations between the
|
// Container widgets like Frames can wire up associations between the
|
||||||
// child widgets and the parent.
|
// child widgets and the parent.
|
||||||
Parent() (parent Widget, ok bool)
|
Parent() (parent Widget, ok bool)
|
||||||
Adopt(parent Widget) // for the container to assign itself the parent
|
Adopt(parent Widget) // for the container to assign itself the parent
|
||||||
|
Children() []Widget // for containers to return their children
|
||||||
|
|
||||||
// Run any render computations; by the end the widget must know its
|
// Run any render computations; by the end the widget must know its
|
||||||
// Width and Height. For example the Label widget will render itself onto
|
// Width and Height. For example the Label widget will render itself onto
|
||||||
|
@ -98,6 +104,7 @@ type BaseWidget struct {
|
||||||
id string
|
id string
|
||||||
idFunc func() string
|
idFunc func() string
|
||||||
fixedSize bool
|
fixedSize bool
|
||||||
|
hidden bool
|
||||||
width int32
|
width int32
|
||||||
height int32
|
height int32
|
||||||
point render.Point
|
point render.Point
|
||||||
|
@ -276,6 +283,37 @@ func (w *BaseWidget) Adopt(parent Widget) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Children returns the widget's children, to be implemented by containers.
|
||||||
|
// The default implementation returns an empty slice.
|
||||||
|
func (w *BaseWidget) Children() []Widget {
|
||||||
|
return []Widget{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the widget from being rendered.
|
||||||
|
func (w *BaseWidget) Hide() {
|
||||||
|
w.hidden = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the widget.
|
||||||
|
func (w *BaseWidget) Show() {
|
||||||
|
w.hidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hidden returns whether the widget is hidden. If this widget is not hidden,
|
||||||
|
// but it has a parent, this will recursively crawl the parents to see if any
|
||||||
|
// of them are hidden.
|
||||||
|
func (w *BaseWidget) Hidden() bool {
|
||||||
|
if w.hidden {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if parent, ok := w.Parent(); ok {
|
||||||
|
return parent.Hidden()
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// DrawBox draws the border and outline.
|
// DrawBox draws the border and outline.
|
||||||
func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) {
|
func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) {
|
||||||
var (
|
var (
|
||||||
|
|
|
@ -69,6 +69,13 @@ func NewWindow(title string) *Window {
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Children returns the window's child widgets.
|
||||||
|
func (w *Window) Children() []Widget {
|
||||||
|
return []Widget{
|
||||||
|
w.body,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TitleBar returns the title bar widget.
|
// TitleBar returns the title bar widget.
|
||||||
func (w *Window) TitleBar() *Label {
|
func (w *Window) TitleBar() *Label {
|
||||||
return w.titleBar
|
return w.titleBar
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package uix
|
package uix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/balance"
|
"git.kirsle.net/apps/doodle/balance"
|
||||||
"git.kirsle.net/apps/doodle/doodads"
|
"git.kirsle.net/apps/doodle/doodads"
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
@ -39,7 +42,19 @@ func NewCanvas(size int, editable bool) *Canvas {
|
||||||
}
|
}
|
||||||
w.setup()
|
w.setup()
|
||||||
w.IDFunc(func() string {
|
w.IDFunc(func() string {
|
||||||
return "Canvas"
|
var attrs []string
|
||||||
|
|
||||||
|
if w.Editable {
|
||||||
|
attrs = append(attrs, "editable")
|
||||||
|
} else {
|
||||||
|
attrs = append(attrs, "read-only")
|
||||||
|
}
|
||||||
|
|
||||||
|
if w.Scrollable {
|
||||||
|
attrs = append(attrs, "scrollable")
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("Canvas<%d; %s>", size, strings.Join(attrs, "; "))
|
||||||
})
|
})
|
||||||
return w
|
return w
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user