Move Editor Canvas Into UI + UI Improvements
* Increase the default window size from 800x600 to 1024x768. * Move the drawing canvas in EditorMode to inside the EditorUI where it can be better managed with the other widgets it shares the screen with. * Slightly fix Frame packing bug (with East orientation) that was causing right-aligned statusbar items to be partially cropped off-screen. Moved a couple statusbar labels in EditorMode to the right. * Add `Parent()` and `Adopt()` methods to widgets for when they're managed by containers like the Frame. * Add utility functions to UI toolkit for computing a widget's Absolute Position and Absolute Rect, by crawling all parent widgets and summing them up. * Add `lib/debugging` package with useful stack tracing utilities. * Add `make guitest` to launch the program into the GUI Test. The command line flag is: `doodle -guitest` * Console: add a `close` command which returns to the MainScene. * Initialize the font cache directory (~/.cache/doodle/fonts) but don't extract the fonts there yet.
This commit is contained in:
parent
cfe26cb964
commit
f18dcf9c2c
5
Makefile
5
Makefile
|
@ -23,6 +23,11 @@ build:
|
|||
run:
|
||||
go run cmd/doodle/main.go -debug
|
||||
|
||||
# `make guitest` to run it in guitest mode.
|
||||
.PHONY: guitest
|
||||
guitest:
|
||||
go run cmd/doodle/main.go -debug -guitest
|
||||
|
||||
# `make test` to run unit tests.
|
||||
.PHONY: test
|
||||
test:
|
||||
|
|
|
@ -2,6 +2,10 @@ package balance
|
|||
|
||||
// Numbers.
|
||||
var (
|
||||
// Window dimensions.
|
||||
Width = 1024
|
||||
Height = 768
|
||||
|
||||
// Speed to scroll a canvas with arrow keys in Edit Mode.
|
||||
CanvasScrollSpeed int32 = 8
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"runtime"
|
||||
|
||||
"git.kirsle.net/apps/doodle"
|
||||
"git.kirsle.net/apps/doodle/balance"
|
||||
"git.kirsle.net/apps/doodle/render/sdl"
|
||||
)
|
||||
|
||||
|
@ -15,11 +16,13 @@ var Build string
|
|||
var (
|
||||
debug bool
|
||||
edit bool
|
||||
guitest bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.BoolVar(&debug, "debug", false, "Debug mode")
|
||||
flag.BoolVar(&edit, "edit", false, "Edit the map given on the command line. Default is to play the map.")
|
||||
flag.BoolVar(&guitest, "guitest", false, "Enter the GUI Test scene.")
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
@ -35,13 +38,15 @@ func main() {
|
|||
// SDL engine.
|
||||
engine := sdl.New(
|
||||
"Doodle v"+doodle.Version,
|
||||
800,
|
||||
600,
|
||||
balance.Width,
|
||||
balance.Height,
|
||||
)
|
||||
|
||||
app := doodle.New(debug, engine)
|
||||
app.SetupEngine()
|
||||
if filename != "" {
|
||||
if guitest {
|
||||
app.Goto(&doodle.GUITestScene{})
|
||||
} else if filename != "" {
|
||||
if edit {
|
||||
app.EditFile(filename)
|
||||
} else {
|
||||
|
|
|
@ -34,6 +34,8 @@ func (c Command) Run(d *Doodle) error {
|
|||
return c.Edit(d)
|
||||
case "play":
|
||||
return c.Play(d)
|
||||
case "close":
|
||||
return c.Close(d)
|
||||
case "exit":
|
||||
case "quit":
|
||||
return c.Quit()
|
||||
|
@ -65,6 +67,13 @@ func (c Command) New(d *Doodle) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Close returns to the Main Scene.
|
||||
func (c Command) Close(d *Doodle) error {
|
||||
main := &MainScene{}
|
||||
d.Goto(main)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Help prints the help info.
|
||||
func (c Command) Help(d *Doodle) error {
|
||||
if len(c.Args) == 0 {
|
||||
|
|
15
config.go
15
config.go
|
@ -23,10 +23,14 @@ var (
|
|||
// Profile Directory settings.
|
||||
var (
|
||||
ConfigDirectoryName = "doodle"
|
||||
|
||||
ProfileDirectory string
|
||||
LevelDirectory string
|
||||
DoodadDirectory string
|
||||
|
||||
CacheDirectory string
|
||||
FontDirectory string
|
||||
|
||||
// Regexp to match simple filenames for maps and doodads.
|
||||
reSimpleFilename = regexp.MustCompile(`^([A-Za-z0-9-_.,+ '"\[\](){}]+)$`)
|
||||
)
|
||||
|
@ -38,10 +42,19 @@ const (
|
|||
)
|
||||
|
||||
func init() {
|
||||
// Profile directory contains the user's levels and doodads.
|
||||
ProfileDirectory = configdir.LocalConfig(ConfigDirectoryName)
|
||||
LevelDirectory = configdir.LocalConfig(ConfigDirectoryName, "levels")
|
||||
DoodadDirectory = configdir.LocalConfig(ConfigDirectoryName, "doodads")
|
||||
configdir.MakePath(LevelDirectory, DoodadDirectory)
|
||||
|
||||
// Cache directory to extract font files to.
|
||||
CacheDirectory = configdir.LocalCache(ConfigDirectoryName)
|
||||
FontDirectory = configdir.LocalCache(ConfigDirectoryName, "fonts")
|
||||
|
||||
// Ensure all the directories exist.
|
||||
configdir.MakePath(LevelDirectory)
|
||||
configdir.MakePath(DoodadDirectory)
|
||||
configdir.MakePath(FontDirectory)
|
||||
}
|
||||
|
||||
// LevelPath will turn a "simple" filename into an absolute path in the user's
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"git.kirsle.net/apps/doodle/balance"
|
||||
"git.kirsle.net/apps/doodle/enum"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
"github.com/kirsle/golog"
|
||||
|
@ -46,8 +47,8 @@ func New(debug bool, engine render.Engine) *Doodle {
|
|||
Engine: engine,
|
||||
startTime: time.Now(),
|
||||
running: true,
|
||||
width: 800,
|
||||
height: 600,
|
||||
width: int32(balance.Width),
|
||||
height: int32(balance.Height),
|
||||
}
|
||||
d.shell = NewShell(d)
|
||||
|
||||
|
|
|
@ -7,13 +7,11 @@ import (
|
|||
"os"
|
||||
"strings"
|
||||
|
||||
"git.kirsle.net/apps/doodle/balance"
|
||||
"git.kirsle.net/apps/doodle/doodads"
|
||||
"git.kirsle.net/apps/doodle/enum"
|
||||
"git.kirsle.net/apps/doodle/events"
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
"git.kirsle.net/apps/doodle/uix"
|
||||
)
|
||||
|
||||
// EditorScene manages the "Edit Level" game mode.
|
||||
|
@ -31,10 +29,6 @@ type EditorScene struct {
|
|||
Level *level.Level
|
||||
Doodad *doodads.Doodad
|
||||
|
||||
// The canvas widget that contains the map we're working on.
|
||||
// XXX: in dev builds this is available at $ d.Scene.GetDrawing()
|
||||
drawing *uix.Canvas
|
||||
|
||||
// Last saved filename by the user.
|
||||
filename string
|
||||
}
|
||||
|
@ -46,15 +40,9 @@ func (s *EditorScene) Name() string {
|
|||
|
||||
// Setup the editor scene.
|
||||
func (s *EditorScene) Setup(d *Doodle) error {
|
||||
s.drawing = uix.NewCanvas(balance.ChunkSize, true)
|
||||
if len(s.drawing.Palette.Swatches) > 0 {
|
||||
s.drawing.SetSwatch(s.drawing.Palette.Swatches[0])
|
||||
}
|
||||
|
||||
// TODO: move inside the UI. Just an approximate position for now.
|
||||
s.drawing.MoveTo(render.NewPoint(0, 19))
|
||||
s.drawing.Resize(render.NewRect(d.width-150, d.height-44))
|
||||
s.drawing.Compute(d.Engine)
|
||||
// Initialize the user interface. It references the palette and such so it
|
||||
// must be initialized after those things.
|
||||
s.UI = NewEditorUI(d, s)
|
||||
|
||||
// Were we given configuration data?
|
||||
if s.Filename != "" {
|
||||
|
@ -68,7 +56,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
case enum.LevelDrawing:
|
||||
if s.Level != nil {
|
||||
log.Debug("EditorScene.Setup: received level from scene caller")
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.UI.Canvas.LoadLevel(s.Level)
|
||||
} else if s.filename != "" && s.OpenFile {
|
||||
log.Debug("EditorScene.Setup: Loading map from filename at %s", s.filename)
|
||||
if err := s.LoadLevel(s.filename); err != nil {
|
||||
|
@ -81,9 +69,9 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
log.Debug("EditorScene.Setup: initializing a new Level")
|
||||
s.Level = level.New()
|
||||
s.Level.Palette = level.DefaultPalette()
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.drawing.ScrollTo(render.Origin)
|
||||
s.drawing.Scrollable = true
|
||||
s.UI.Canvas.LoadLevel(s.Level)
|
||||
s.UI.Canvas.ScrollTo(render.Origin)
|
||||
s.UI.Canvas.Scrollable = true
|
||||
}
|
||||
case enum.DoodadDrawing:
|
||||
// No Doodad?
|
||||
|
@ -98,20 +86,16 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
if s.Doodad == nil {
|
||||
log.Debug("EditorScene.Setup: initializing a new Doodad")
|
||||
s.Doodad = doodads.New(s.DoodadSize)
|
||||
s.drawing.LoadDoodad(s.Doodad)
|
||||
s.UI.Canvas.LoadDoodad(s.Doodad)
|
||||
}
|
||||
|
||||
// TODO: move inside the UI. Just an approximate position for now.
|
||||
s.drawing.MoveTo(render.NewPoint(200, 200))
|
||||
s.drawing.Resize(render.NewRect(int32(s.DoodadSize), int32(s.DoodadSize)))
|
||||
s.drawing.ScrollTo(render.Origin)
|
||||
s.drawing.Scrollable = false
|
||||
s.drawing.Compute(d.Engine)
|
||||
s.UI.Canvas.Resize(render.NewRect(int32(s.DoodadSize), int32(s.DoodadSize)))
|
||||
s.UI.Canvas.ScrollTo(render.Origin)
|
||||
s.UI.Canvas.Scrollable = false
|
||||
s.UI.Workspace.Compute(d.Engine)
|
||||
}
|
||||
|
||||
// Initialize the user interface. It references the palette and such so it
|
||||
// must be initialized after those things.
|
||||
s.UI = NewEditorUI(d, s)
|
||||
d.Flash("Editor Mode. Press 'P' to play this map.")
|
||||
|
||||
return nil
|
||||
|
@ -120,7 +104,6 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
|||
// Loop the editor scene.
|
||||
func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||
s.UI.Loop(ev)
|
||||
s.drawing.Loop(ev)
|
||||
|
||||
// Switching to Play Mode?
|
||||
if ev.KeyName.Read() == "p" {
|
||||
|
@ -141,7 +124,6 @@ func (s *EditorScene) Draw(d *Doodle) error {
|
|||
d.Engine.Clear(render.Magenta)
|
||||
|
||||
s.UI.Present(d.Engine)
|
||||
s.drawing.Present(d.Engine, s.drawing.Point())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -157,7 +139,7 @@ func (s *EditorScene) LoadLevel(filename string) error {
|
|||
|
||||
s.DrawingType = enum.LevelDrawing
|
||||
s.Level = level
|
||||
s.drawing.LoadLevel(s.Level)
|
||||
s.UI.Canvas.LoadLevel(s.Level)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -182,8 +164,8 @@ func (s *EditorScene) SaveLevel(filename string) error {
|
|||
m.Author = os.Getenv("USER")
|
||||
}
|
||||
|
||||
m.Palette = s.drawing.Palette
|
||||
m.Chunker = s.drawing.Chunker()
|
||||
m.Palette = s.UI.Canvas.Palette
|
||||
m.Chunker = s.UI.Canvas.Chunker()
|
||||
|
||||
json, err := m.ToJSON()
|
||||
if err != nil {
|
||||
|
@ -213,7 +195,7 @@ func (s *EditorScene) LoadDoodad(filename string) error {
|
|||
s.DrawingType = enum.DoodadDrawing
|
||||
s.Doodad = doodad
|
||||
s.DoodadSize = doodad.Layers[0].Chunker.Size
|
||||
s.drawing.LoadDoodad(s.Doodad)
|
||||
s.UI.Canvas.LoadDoodad(s.Doodad)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -237,8 +219,8 @@ func (s *EditorScene) SaveDoodad(filename string) error {
|
|||
}
|
||||
|
||||
// TODO: is this copying necessary?
|
||||
d.Palette = s.drawing.Palette
|
||||
d.Layers[0].Chunker = s.drawing.Chunker()
|
||||
d.Palette = s.UI.Canvas.Palette
|
||||
d.Layers[0].Chunker = s.UI.Canvas.Chunker()
|
||||
|
||||
// Save it to their profile directory.
|
||||
filename = DoodadPath(filename)
|
||||
|
|
|
@ -7,5 +7,5 @@ import "git.kirsle.net/apps/doodle/uix"
|
|||
|
||||
// GetDrawing returns the uix.Canvas
|
||||
func (w *EditorScene) GetDrawing() *uix.Canvas {
|
||||
return w.drawing
|
||||
return w.UI.Canvas
|
||||
}
|
||||
|
|
105
editor_ui.go
105
editor_ui.go
|
@ -7,8 +7,10 @@ import (
|
|||
"git.kirsle.net/apps/doodle/balance"
|
||||
"git.kirsle.net/apps/doodle/enum"
|
||||
"git.kirsle.net/apps/doodle/events"
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
"git.kirsle.net/apps/doodle/ui"
|
||||
"git.kirsle.net/apps/doodle/uix"
|
||||
)
|
||||
|
||||
// EditorUI manages the user interface for the Editor Scene.
|
||||
|
@ -24,6 +26,8 @@ type EditorUI struct {
|
|||
|
||||
// Widgets
|
||||
Supervisor *ui.Supervisor
|
||||
Canvas *uix.Canvas
|
||||
Workspace *ui.Frame
|
||||
MenuBar *ui.Frame
|
||||
Palette *ui.Window
|
||||
StatusBar *ui.Frame
|
||||
|
@ -40,14 +44,23 @@ func NewEditorUI(d *Doodle, s *EditorScene) *EditorUI {
|
|||
StatusFilenameText: "Filename: <none>",
|
||||
}
|
||||
|
||||
// Select the first swatch of the palette.
|
||||
if u.Scene.drawing.Palette.ActiveSwatch != nil {
|
||||
u.selectedSwatch = u.Scene.drawing.Palette.ActiveSwatch.Name
|
||||
}
|
||||
|
||||
u.Canvas = u.SetupCanvas(d)
|
||||
u.MenuBar = u.SetupMenuBar(d)
|
||||
u.StatusBar = u.SetupStatusBar(d)
|
||||
u.Palette = u.SetupPalette(d)
|
||||
u.Workspace = u.SetupWorkspace(d) // important that this is last!
|
||||
|
||||
// Position the Canvas inside the frame.
|
||||
u.Workspace.Pack(u.Canvas, ui.Pack{
|
||||
Anchor: ui.N,
|
||||
})
|
||||
u.Workspace.Compute(d.Engine)
|
||||
u.ExpandCanvas(d.Engine)
|
||||
|
||||
// Select the first swatch of the palette.
|
||||
if u.Canvas.Palette != nil && u.Canvas.Palette.ActiveSwatch != nil {
|
||||
u.selectedSwatch = u.Canvas.Palette.ActiveSwatch.Name
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
|
@ -60,7 +73,7 @@ func (u *EditorUI) Loop(ev *events.State) {
|
|||
ev.CursorY.Now,
|
||||
)
|
||||
u.StatusPaletteText = fmt.Sprintf("Swatch: %s",
|
||||
u.Scene.drawing.Palette.ActiveSwatch,
|
||||
u.Canvas.Palette.ActiveSwatch,
|
||||
)
|
||||
|
||||
// Statusbar filename label.
|
||||
|
@ -80,6 +93,7 @@ func (u *EditorUI) Loop(ev *events.State) {
|
|||
u.MenuBar.Compute(u.d.Engine)
|
||||
u.StatusBar.Compute(u.d.Engine)
|
||||
u.Palette.Compute(u.d.Engine)
|
||||
u.Canvas.Loop(ev)
|
||||
}
|
||||
|
||||
// Present the UI to the screen.
|
||||
|
@ -93,6 +107,46 @@ func (u *EditorUI) Present(e render.Engine) {
|
|||
u.Palette.Present(e, u.Palette.Point())
|
||||
u.MenuBar.Present(e, u.MenuBar.Point())
|
||||
u.StatusBar.Present(e, u.StatusBar.Point())
|
||||
u.Workspace.Present(e, u.Workspace.Point())
|
||||
}
|
||||
|
||||
// SetupWorkspace configures the main Workspace frame that takes up the full
|
||||
// window apart from toolbars. The Workspace has a single child element, the
|
||||
// Canvas, so it can easily full-screen it or center it for Doodad editing.
|
||||
func (u *EditorUI) SetupWorkspace(d *Doodle) *ui.Frame {
|
||||
frame := ui.NewFrame("Workspace")
|
||||
|
||||
// Position and size the frame around the other main widgets.
|
||||
frame.MoveTo(render.NewPoint(
|
||||
0,
|
||||
u.MenuBar.Size().H,
|
||||
))
|
||||
frame.Resize(render.NewRect(
|
||||
d.width-u.Palette.Size().W,
|
||||
d.height-u.MenuBar.Size().H-u.StatusBar.Size().H,
|
||||
))
|
||||
frame.Compute(d.Engine)
|
||||
|
||||
return frame
|
||||
}
|
||||
|
||||
// SetupCanvas configures the main drawing canvas in the editor.
|
||||
func (u *EditorUI) SetupCanvas(d *Doodle) *uix.Canvas {
|
||||
drawing := uix.NewCanvas(balance.ChunkSize, true)
|
||||
drawing.Palette = level.DefaultPalette()
|
||||
if len(drawing.Palette.Swatches) > 0 {
|
||||
drawing.SetSwatch(drawing.Palette.Swatches[0])
|
||||
}
|
||||
return drawing
|
||||
}
|
||||
|
||||
// ExpandCanvas manually expands the Canvas to fill the frame, to work around
|
||||
// UI packing bugs. Ideally I would use `Expand: true` when packing the Canvas
|
||||
// in its frame, but that would artificially expand the Canvas also when it
|
||||
// _wanted_ to be smaller, as in Doodad Editing Mode.
|
||||
func (u *EditorUI) ExpandCanvas(e render.Engine) {
|
||||
u.Canvas.Resize(u.Workspace.Size())
|
||||
u.Workspace.Compute(e)
|
||||
}
|
||||
|
||||
// SetupMenuBar sets up the menu bar.
|
||||
|
@ -214,7 +268,6 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame {
|
|||
|
||||
// SetupPalette sets up the palette panel.
|
||||
func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
||||
log.Error("SetupPalette Window")
|
||||
window := ui.NewWindow("Palette")
|
||||
window.ConfigureTitle(balance.TitleConfig)
|
||||
window.TitleBar().Font = balance.TitleFont
|
||||
|
@ -232,17 +285,18 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
|||
// Handler function for the radio buttons being clicked.
|
||||
onClick := func(p render.Point) {
|
||||
name := u.selectedSwatch
|
||||
swatch, ok := u.Scene.drawing.Palette.Get(name)
|
||||
swatch, ok := u.Canvas.Palette.Get(name)
|
||||
if !ok {
|
||||
log.Error("Palette onClick: couldn't get swatch named '%s' from palette", name)
|
||||
return
|
||||
}
|
||||
log.Info("Set swatch: %s", swatch)
|
||||
u.Scene.drawing.SetSwatch(swatch)
|
||||
u.Canvas.SetSwatch(swatch)
|
||||
}
|
||||
|
||||
// Draw the radio buttons for the palette.
|
||||
for _, swatch := range u.Scene.drawing.Palette.Swatches {
|
||||
if u.Canvas != nil && u.Canvas.Palette != nil {
|
||||
for _, swatch := range u.Canvas.Palette.Swatches {
|
||||
label := ui.NewLabel(ui.Label{
|
||||
Text: swatch.Name,
|
||||
Font: balance.StatusFont,
|
||||
|
@ -259,6 +313,7 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
|||
PadY: 4,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return window
|
||||
}
|
||||
|
@ -309,25 +364,25 @@ func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
|
|||
filenameLabel.Configure(style)
|
||||
filenameLabel.Compute(d.Engine)
|
||||
frame.Pack(filenameLabel, ui.Pack{
|
||||
Anchor: ui.W,
|
||||
Anchor: ui.E,
|
||||
PadX: 1,
|
||||
})
|
||||
|
||||
// TODO: right-aligned labels clip out of bounds
|
||||
// extraLabel := ui.NewLabel(ui.Label{
|
||||
// Text: "blah",
|
||||
// Font: balance.StatusFont,
|
||||
// })
|
||||
// extraLabel.Configure(ui.Config{
|
||||
// Background: render.Grey,
|
||||
// BorderStyle: ui.BorderSunken,
|
||||
// BorderColor: render.Grey,
|
||||
// BorderSize: 1,
|
||||
// })
|
||||
// extraLabel.Compute(d.Engine)
|
||||
// frame.Pack(extraLabel, ui.Pack{
|
||||
// Anchor: ui.E,
|
||||
// })
|
||||
extraLabel := ui.NewLabel(ui.Label{
|
||||
Text: "blah",
|
||||
Font: balance.StatusFont,
|
||||
})
|
||||
extraLabel.Configure(ui.Config{
|
||||
Background: render.Grey,
|
||||
BorderStyle: ui.BorderSunken,
|
||||
BorderColor: render.Grey,
|
||||
BorderSize: 1,
|
||||
})
|
||||
extraLabel.Compute(d.Engine)
|
||||
frame.Pack(extraLabel, ui.Pack{
|
||||
Anchor: ui.E,
|
||||
})
|
||||
|
||||
frame.Resize(render.Rect{
|
||||
W: d.width,
|
||||
|
|
104
lib/debugging/debugging.go
Normal file
104
lib/debugging/debugging.go
Normal file
|
@ -0,0 +1,104 @@
|
|||
// Package debugging contains useful methods for debugging the app, safely
|
||||
// isolated from the rest of the app's packages.
|
||||
package debugging
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Configurable variables for the stack tracer functions.
|
||||
var (
|
||||
// StackDepth is the depth that Callers() will crawl up the call stack. This
|
||||
// variable is configurable.
|
||||
StackDepth = 20
|
||||
|
||||
// StopAt is the function name to stop the tracebacks at. Set to a blank
|
||||
// string to not stop and trace all the way up to `runtime.goexit` or
|
||||
// wherever.
|
||||
StopAt = "main.main"
|
||||
)
|
||||
|
||||
// Minimum depth given to runtime.Caller() so that the call stacks will exclude
|
||||
// the call to debugging.Caller() itself -- so this debug module won't debug its
|
||||
// own function calls in the tracebacks.
|
||||
const minDepth = 2
|
||||
|
||||
// Caller returns the filename and line number that called the calling
|
||||
// function.
|
||||
func Caller() string {
|
||||
if pc, file, no, ok := runtime.Caller(minDepth); ok {
|
||||
frames := runtime.CallersFrames([]uintptr{pc})
|
||||
frame, _ := frames.Next()
|
||||
if frame.Function != "" {
|
||||
return fmt.Sprintf("%s#%d: %s()",
|
||||
frame.File,
|
||||
frame.Line,
|
||||
frame.Function,
|
||||
)
|
||||
}
|
||||
return fmt.Sprintf("%s#%d",
|
||||
file,
|
||||
no,
|
||||
)
|
||||
}
|
||||
return "[no caller information]"
|
||||
}
|
||||
|
||||
// Callers returns an array of all the callers of the current function.
|
||||
func Callers() []string {
|
||||
var (
|
||||
callers []string
|
||||
pc = make([]uintptr, StackDepth)
|
||||
count = runtime.Callers(minDepth, pc)
|
||||
)
|
||||
pc = pc[:count] // only pass valid program counters to CallersFrames
|
||||
var frames = runtime.CallersFrames(pc)
|
||||
_ = frames
|
||||
|
||||
// Loop to get frames of the call stack.
|
||||
for {
|
||||
frame, more := frames.Next()
|
||||
|
||||
callers = append(callers, fmt.Sprintf("%s#%d: %s()",
|
||||
frame.File,
|
||||
frame.Line,
|
||||
frame.Function,
|
||||
))
|
||||
|
||||
if StopAt != "" && frame.Function == StopAt {
|
||||
break
|
||||
}
|
||||
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return callers
|
||||
}
|
||||
|
||||
// StringifyCallers pretty-prints the Callers as a single string with newlines.
|
||||
func StringifyCallers() string {
|
||||
callers := Callers()
|
||||
var result []string
|
||||
for i, caller := range callers {
|
||||
if i == 0 {
|
||||
continue // StringifyCallers() would be the first row, skip it.
|
||||
}
|
||||
result = append(result, fmt.Sprintf("%d: %s", i, caller))
|
||||
}
|
||||
return strings.Join(result, "\n")
|
||||
}
|
||||
|
||||
// PrintCallers prints the stringified callers directly to STDOUT.
|
||||
func PrintCallers() {
|
||||
fmt.Println("Call stack (most recent/current function first):")
|
||||
for i, caller := range Callers() {
|
||||
if i == 0 {
|
||||
continue // PrintCallers() would be the first row, skip it.
|
||||
}
|
||||
fmt.Printf("%d: %s\n", i, caller)
|
||||
}
|
||||
}
|
|
@ -31,12 +31,12 @@ type Renderer struct {
|
|||
}
|
||||
|
||||
// New creates the SDL renderer.
|
||||
func New(title string, width, height int32) *Renderer {
|
||||
func New(title string, width, height int) *Renderer {
|
||||
return &Renderer{
|
||||
events: events.New(),
|
||||
title: title,
|
||||
width: width,
|
||||
height: height,
|
||||
width: int32(width),
|
||||
height: int32(height),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ func (w *Button) SetText(text string) error {
|
|||
// Present the button.
|
||||
func (w *Button) Present(e render.Engine, P render.Point) {
|
||||
w.Compute(e)
|
||||
w.MoveTo(P)
|
||||
var (
|
||||
S = w.Size()
|
||||
ChildSize = w.child.Size()
|
||||
|
|
|
@ -69,7 +69,14 @@ func (w *Frame) Present(e render.Engine, P render.Point) {
|
|||
P.X+p.X+w.BoxThickness(1),
|
||||
P.Y+p.Y+w.BoxThickness(1),
|
||||
)
|
||||
child.MoveTo(moveTo)
|
||||
// if child.ID() == "Canvas" {
|
||||
// log.Debug("Frame X=%d Child X=%d Box=%d Point=%s", P.X, p.X, w.BoxThickness(1), p)
|
||||
// log.Debug("Frame Y=%d Child Y=%d Box=%d MoveTo=%s", P.Y, p.Y, w.BoxThickness(1), moveTo)
|
||||
// }
|
||||
// child.MoveTo(moveTo) // TODO: if uncommented the child will creep down the parent each tick
|
||||
// if child.ID() == "Canvas" {
|
||||
// log.Debug("New Point: %s", child.Point())
|
||||
// }
|
||||
child.Present(e, moveTo)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,12 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
xDirection int32 = 1
|
||||
)
|
||||
|
||||
if anchor.IsSouth() {
|
||||
y = frameSize.H
|
||||
yDirection = -1 - w.BoxThickness(2) // parent + child BoxThickness(1) = 2
|
||||
if anchor.IsSouth() { // TODO: these need tuning
|
||||
y = frameSize.H - w.BoxThickness(4)
|
||||
yDirection = -1 * w.BoxThickness(4) // parent + child BoxThickness(1) = 2
|
||||
} else if anchor == E {
|
||||
x = frameSize.W
|
||||
xDirection = -1 // - w.BoxThickness(2)
|
||||
x = frameSize.W - w.BoxThickness(4)
|
||||
xDirection = -1 - w.BoxThickness(4) // - w.BoxThickness(2)
|
||||
}
|
||||
|
||||
for _, packedWidget := range w.packs[anchor] {
|
||||
|
@ -64,10 +64,10 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
}
|
||||
|
||||
if anchor.IsSouth() {
|
||||
y -= size.H + pack.PadY
|
||||
y -= size.H - pack.PadY
|
||||
}
|
||||
if anchor.IsEast() {
|
||||
x -= size.W + pack.PadX
|
||||
x -= size.W - pack.PadX
|
||||
}
|
||||
|
||||
child.MoveTo(render.NewPoint(x, y))
|
||||
|
@ -80,7 +80,7 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
}
|
||||
|
||||
visited = append(visited, packedWidget)
|
||||
if pack.Expand {
|
||||
if pack.Expand { // TODO: don't fuck with children of fixed size
|
||||
expanded = append(expanded, packedWidget)
|
||||
}
|
||||
}
|
||||
|
@ -131,10 +131,6 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
moved bool
|
||||
)
|
||||
|
||||
if w.String() == "Frame<Row0; 3 widgets>" {
|
||||
log.Debug("%s>%s: pack.FillX=%d resize=%s innerFrameSize=%s", w, child, pack.FillX, resize, innerFrameSize)
|
||||
}
|
||||
|
||||
if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() {
|
||||
if pack.FillX && resize.W < innerFrameSize.W {
|
||||
resize.W = innerFrameSize.W - w.BoxThickness(2)
|
||||
|
@ -175,7 +171,6 @@ func (w *Frame) computePacked(e render.Engine) {
|
|||
}
|
||||
|
||||
if resized && size != resize {
|
||||
// log.Debug("%s/%s: resize to: %s", w, child, resize)
|
||||
child.Resize(resize)
|
||||
child.Compute(e)
|
||||
}
|
||||
|
@ -288,6 +283,9 @@ func (w *Frame) Pack(child Widget, config ...Pack) {
|
|||
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,
|
||||
|
|
38
ui/functions.go
Normal file
38
ui/functions.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package ui
|
||||
|
||||
import "git.kirsle.net/apps/doodle/render"
|
||||
|
||||
// AbsolutePosition computes a widget's absolute X,Y position on the
|
||||
// window on screen by crawling its parent widget tree.
|
||||
func AbsolutePosition(w Widget) render.Point {
|
||||
abs := w.Point()
|
||||
|
||||
var (
|
||||
node = w
|
||||
ok bool
|
||||
)
|
||||
|
||||
for {
|
||||
node, ok = node.Parent()
|
||||
if !ok { // reached the top of the tree
|
||||
return abs
|
||||
}
|
||||
|
||||
abs.Add(node.Point())
|
||||
}
|
||||
}
|
||||
|
||||
// AbsoluteRect returns a Rect() offset with the absolute position.
|
||||
func AbsoluteRect(w Widget) render.Rect {
|
||||
var (
|
||||
P = AbsolutePosition(w)
|
||||
R = w.Rect()
|
||||
)
|
||||
return render.Rect{
|
||||
X: P.X,
|
||||
Y: P.Y,
|
||||
W: R.W + P.X,
|
||||
H: R.H, // TODO: the Canvas in EditMode lets you draw pixels
|
||||
// below the status bar if we do `+ R.Y` here.
|
||||
}
|
||||
}
|
26
ui/widget.go
26
ui/widget.go
|
@ -56,6 +56,11 @@ type Widget interface {
|
|||
OutlineSize() int32 // Outline size (default 0)
|
||||
SetOutlineSize(int32) //
|
||||
|
||||
// Container widgets like Frames can wire up associations between the
|
||||
// child widgets and the parent.
|
||||
Parent() (parent Widget, ok bool)
|
||||
Adopt(parent Widget) // for the container to assign itself the parent
|
||||
|
||||
// Run any render computations; by the end the widget must know its
|
||||
// Width and Height. For example the Label widget will render itself onto
|
||||
// an SDL Surface and then it will know its bounding box, but not before.
|
||||
|
@ -105,6 +110,8 @@ type BaseWidget struct {
|
|||
outlineColor render.Color
|
||||
outlineSize int32
|
||||
handlers map[Event][]func(render.Point)
|
||||
hasParent bool
|
||||
parent Widget
|
||||
}
|
||||
|
||||
// SetID sets a string name for your widget, helpful for debugging purposes.
|
||||
|
@ -250,6 +257,25 @@ func (w *BaseWidget) BoxThickness(m int32) int32 {
|
|||
return (w.Margin() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m)
|
||||
}
|
||||
|
||||
// Parent returns the parent widget, like a Frame, and a boolean indicating
|
||||
// whether the widget had a parent.
|
||||
func (w *BaseWidget) Parent() (Widget, bool) {
|
||||
return w.parent, w.hasParent
|
||||
}
|
||||
|
||||
// Adopt sets the widget's parent. This function is called by container
|
||||
// widgets like Frame when they add a child widget to their care.
|
||||
// Pass a nil parent to unset the parent.
|
||||
func (w *BaseWidget) Adopt(parent Widget) {
|
||||
if parent == nil {
|
||||
w.hasParent = false
|
||||
w.parent = nil
|
||||
} else {
|
||||
w.hasParent = true
|
||||
w.parent = parent
|
||||
}
|
||||
}
|
||||
|
||||
// DrawBox draws the border and outline.
|
||||
func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) {
|
||||
var (
|
||||
|
|
|
@ -38,6 +38,9 @@ func NewCanvas(size int, editable bool) *Canvas {
|
|||
chunks: level.NewChunker(size),
|
||||
}
|
||||
w.setup()
|
||||
w.IDFunc(func() string {
|
||||
return "Canvas"
|
||||
})
|
||||
return w
|
||||
}
|
||||
|
||||
|
@ -81,10 +84,9 @@ func (w *Canvas) setup() {
|
|||
// Loop is called on the scene's event loop to handle mouse interaction with
|
||||
// the canvas, i.e. to edit it.
|
||||
func (w *Canvas) Loop(ev *events.State) error {
|
||||
var (
|
||||
P = w.Point()
|
||||
_ = P
|
||||
)
|
||||
// Get the absolute position of the canvas on screen to accurately match
|
||||
// it up to mouse clicks.
|
||||
var P = ui.AbsolutePosition(w)
|
||||
|
||||
if w.Scrollable {
|
||||
// Arrow keys to scroll the view.
|
||||
|
@ -106,7 +108,7 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
|
||||
// Only care if the cursor is over our space.
|
||||
cursor := render.NewPoint(ev.CursorX.Now, ev.CursorY.Now)
|
||||
if !cursor.Inside(w.Rect()) {
|
||||
if !cursor.Inside(ui.AbsoluteRect(w)) {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -117,7 +119,6 @@ func (w *Canvas) Loop(ev *events.State) error {
|
|||
|
||||
// Clicking? Log all the pixels while doing so.
|
||||
if ev.Button1.Now {
|
||||
// log.Warn("Button1: %+v", ev.Button1)
|
||||
lastPixel := w.lastPixel
|
||||
cursor := render.Point{
|
||||
X: ev.CursorX.Now - P.X + w.Scroll.X,
|
||||
|
@ -193,7 +194,7 @@ func (w *Canvas) Present(e render.Engine, p render.Point) {
|
|||
S = w.Size()
|
||||
Viewport = w.Viewport()
|
||||
)
|
||||
w.MoveTo(p)
|
||||
// w.MoveTo(p) // TODO: when uncommented the canvas will creep down the Workspace frame in EditorMode
|
||||
w.DrawBox(e, p)
|
||||
e.DrawBox(w.Background(), render.Rect{
|
||||
X: p.X + w.BoxThickness(1),
|
||||
|
|
14
uix/log.go
Normal file
14
uix/log.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package uix
|
||||
|
||||
import "github.com/kirsle/golog"
|
||||
|
||||
var log *golog.Logger
|
||||
|
||||
func init() {
|
||||
log = golog.GetLogger("uix")
|
||||
log.Configure(&golog.Config{
|
||||
Level: golog.DebugLevel,
|
||||
Theme: golog.DarkTheme,
|
||||
Colors: golog.ExtendedColor,
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue
Block a user