Menu Toolbar for Editor + Shell Prompts + Theme
* Added a "menu toolbar" to the top of the Edit Mode with useful buttons that work: New Level, New Doodad (same thing), Save, Save as, Open. * Added ability for the dev console to prompt the user for a question, which opens the console automatically. "Save", "Save as" and "Load" ask for their filenames this way. * Started groundwork for theming the app. The palette window is a light brown with an orange title bar, the Menu Toolbar has a black background, etc. * Added support for multiple fonts instead of just monospace. DejaVu Sans (normal and bold) are used now for most labels and window titles, respectively. The dev console uses DejaVu Sans Mono as before. * Update ui.Label to accept PadX and PadY separately instead of only having the Padding option which did both. * Improvements to Frame packing algorithm. * Set the SDL draw mode to BLEND so we can use alpha colors properly, so now the dev console is semi-translucent.
This commit is contained in:
parent
42caa20f6e
commit
5956863996
12
README.md
12
README.md
|
@ -201,3 +201,15 @@ Fedora dependencies:
|
||||||
```bash
|
```bash
|
||||||
$ sudo dnf install SDL2-devel SDL2_ttf-devel
|
$ sudo dnf install SDL2-devel SDL2_ttf-devel
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Fonts
|
||||||
|
|
||||||
|
The `fonts/` folder is git-ignored. The app currently uses font files here
|
||||||
|
named:
|
||||||
|
|
||||||
|
* `DejaVuSans.ttf` for sans-serif font.
|
||||||
|
* `DejaVuSans-Bold.ttf` for bold sans-serif font.
|
||||||
|
* `DejaVuSansMono.ttf` for monospace font.
|
||||||
|
|
||||||
|
These are the open source **DejaVu Sans [Mono]** fonts, so copy them in from
|
||||||
|
your `/usr/share/fonts/dejavu` folder or provide alternative fonts.
|
||||||
|
|
|
@ -7,8 +7,10 @@ import (
|
||||||
// Shell related variables.
|
// Shell related variables.
|
||||||
var (
|
var (
|
||||||
// TODO: why not renders transparent
|
// TODO: why not renders transparent
|
||||||
ShellBackgroundColor = render.RGBA(0, 10, 20, 128)
|
ShellFontFilename = "./fonts/DejaVuSansMono.ttf"
|
||||||
ShellForegroundColor = render.White
|
ShellBackgroundColor = render.RGBA(0, 20, 40, 200)
|
||||||
|
ShellForegroundColor = render.RGBA(0, 153, 255, 255)
|
||||||
|
ShellPromptColor = render.White
|
||||||
ShellPadding int32 = 8
|
ShellPadding int32 = 8
|
||||||
ShellFontSize = 16
|
ShellFontSize = 16
|
||||||
ShellCursorBlinkRate uint64 = 20
|
ShellCursorBlinkRate uint64 = 20
|
||||||
|
@ -17,10 +19,3 @@ var (
|
||||||
// Ticks that a flashed message persists for.
|
// Ticks that a flashed message persists for.
|
||||||
FlashTTL uint64 = 400
|
FlashTTL uint64 = 400
|
||||||
)
|
)
|
||||||
|
|
||||||
// StatusFont is the font for the status bar.
|
|
||||||
var StatusFont = render.Text{
|
|
||||||
Size: 12,
|
|
||||||
Padding: 4,
|
|
||||||
Color: render.Black,
|
|
||||||
}
|
|
||||||
|
|
39
balance/theme.go
Normal file
39
balance/theme.go
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
package balance
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Theme and appearance variables.
|
||||||
|
var (
|
||||||
|
// Window and panel styles.
|
||||||
|
TitleConfig = ui.Config{
|
||||||
|
Background: render.MustHexColor("#FF9900"),
|
||||||
|
OutlineSize: 1,
|
||||||
|
OutlineColor: render.Black,
|
||||||
|
}
|
||||||
|
TitleFont = render.Text{
|
||||||
|
FontFilename: "./fonts/DejaVuSans-Bold.ttf",
|
||||||
|
Size: 12,
|
||||||
|
Padding: 4,
|
||||||
|
Color: render.White,
|
||||||
|
Stroke: render.Red,
|
||||||
|
}
|
||||||
|
WindowBackground = render.MustHexColor("#cdb689")
|
||||||
|
WindowBorder = render.Grey
|
||||||
|
|
||||||
|
// Menu bar styles.
|
||||||
|
MenuBackground = render.Black
|
||||||
|
MenuFont = render.Text{
|
||||||
|
Size: 12,
|
||||||
|
PadX: 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
// StatusFont is the font for the status bar.
|
||||||
|
StatusFont = render.Text{
|
||||||
|
Size: 12,
|
||||||
|
Padding: 4,
|
||||||
|
Color: render.Black,
|
||||||
|
}
|
||||||
|
)
|
|
@ -96,7 +96,6 @@ func (d *Doodle) Run() error {
|
||||||
|
|
||||||
// Command line shell.
|
// Command line shell.
|
||||||
if d.shell.Open {
|
if d.shell.Open {
|
||||||
|
|
||||||
} else if ev.EnterKey.Read() {
|
} else if ev.EnterKey.Read() {
|
||||||
log.Debug("Shell: opening shell")
|
log.Debug("Shell: opening shell")
|
||||||
d.shell.Open = true
|
d.shell.Open = true
|
||||||
|
|
|
@ -43,6 +43,8 @@ func (s *EditorScene) Name() string {
|
||||||
|
|
||||||
// Setup the editor scene.
|
// Setup the editor scene.
|
||||||
func (s *EditorScene) Setup(d *Doodle) error {
|
func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
|
s.Palette = level.DefaultPalette()
|
||||||
|
|
||||||
// Were we given configuration data?
|
// Were we given configuration data?
|
||||||
if s.Filename != "" {
|
if s.Filename != "" {
|
||||||
log.Debug("EditorScene: Set filename to %s", s.Filename)
|
log.Debug("EditorScene: Set filename to %s", s.Filename)
|
||||||
|
@ -61,7 +63,7 @@ func (s *EditorScene) Setup(d *Doodle) error {
|
||||||
s.Canvas = nil
|
s.Canvas = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.Palette = level.DefaultPalette()
|
// Select the first swatch in the palette.
|
||||||
if len(s.Palette.Swatches) > 0 {
|
if len(s.Palette.Swatches) > 0 {
|
||||||
s.Swatch = s.Palette.Swatches[0]
|
s.Swatch = s.Palette.Swatches[0]
|
||||||
s.Palette.ActiveSwatch = s.Swatch.Name
|
s.Palette.ActiveSwatch = s.Swatch.Name
|
||||||
|
@ -103,9 +105,6 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear the canvas and fill it with white.
|
|
||||||
d.Engine.Clear(render.White)
|
|
||||||
|
|
||||||
// Clicking? Log all the pixels while doing so.
|
// Clicking? Log all the pixels while doing so.
|
||||||
if ev.Button1.Now {
|
if ev.Button1.Now {
|
||||||
// log.Warn("Button1: %+v", ev.Button1)
|
// log.Warn("Button1: %+v", ev.Button1)
|
||||||
|
@ -149,6 +148,9 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
|
|
||||||
// Draw the current frame.
|
// Draw the current frame.
|
||||||
func (s *EditorScene) Draw(d *Doodle) error {
|
func (s *EditorScene) Draw(d *Doodle) error {
|
||||||
|
// Clear the canvas and fill it with white.
|
||||||
|
d.Engine.Clear(render.White)
|
||||||
|
|
||||||
s.canvas.Draw(d.Engine)
|
s.canvas.Draw(d.Engine)
|
||||||
s.UI.Present(d.Engine)
|
s.UI.Present(d.Engine)
|
||||||
|
|
||||||
|
|
106
editor_ui.go
106
editor_ui.go
|
@ -21,6 +21,7 @@ type EditorUI struct {
|
||||||
|
|
||||||
// Widgets
|
// Widgets
|
||||||
Supervisor *ui.Supervisor
|
Supervisor *ui.Supervisor
|
||||||
|
MenuBar *ui.Frame
|
||||||
Palette *ui.Window
|
Palette *ui.Window
|
||||||
StatusBar *ui.Frame
|
StatusBar *ui.Frame
|
||||||
}
|
}
|
||||||
|
@ -35,6 +36,7 @@ func NewEditorUI(d *Doodle, s *EditorScene) *EditorUI {
|
||||||
StatusPaletteText: "Swatch: <none>",
|
StatusPaletteText: "Swatch: <none>",
|
||||||
StatusFilenameText: "Filename: <none>",
|
StatusFilenameText: "Filename: <none>",
|
||||||
}
|
}
|
||||||
|
u.MenuBar = u.SetupMenuBar(d)
|
||||||
u.StatusBar = u.SetupStatusBar(d)
|
u.StatusBar = u.SetupStatusBar(d)
|
||||||
u.Palette = u.SetupPalette(d)
|
u.Palette = u.SetupPalette(d)
|
||||||
return u
|
return u
|
||||||
|
@ -61,26 +63,124 @@ func (u *EditorUI) Loop(ev *events.State) {
|
||||||
filename,
|
filename,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
u.MenuBar.Compute(u.d.Engine)
|
||||||
u.StatusBar.Compute(u.d.Engine)
|
u.StatusBar.Compute(u.d.Engine)
|
||||||
u.Palette.Compute(u.d.Engine)
|
u.Palette.Compute(u.d.Engine)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present the UI to the screen.
|
// Present the UI to the screen.
|
||||||
func (u *EditorUI) Present(e render.Engine) {
|
func (u *EditorUI) Present(e render.Engine) {
|
||||||
|
// TODO: if I don't Compute() the palette window, then, whenever the dev console
|
||||||
|
// is open the window will blank out its contents leaving only the outermost Frame.
|
||||||
|
// The title bar and borders are gone. But other UI widgets don't do this.
|
||||||
|
// FIXME: Scene interface should have a separate ComputeUI() from Loop()?
|
||||||
|
u.Palette.Compute(u.d.Engine)
|
||||||
|
|
||||||
u.Palette.Present(e, u.Palette.Point())
|
u.Palette.Present(e, u.Palette.Point())
|
||||||
|
u.MenuBar.Present(e, u.MenuBar.Point())
|
||||||
u.StatusBar.Present(e, u.StatusBar.Point())
|
u.StatusBar.Present(e, u.StatusBar.Point())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetupMenuBar sets up the menu bar.
|
||||||
|
func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.Frame {
|
||||||
|
frame := ui.NewFrame("MenuBar")
|
||||||
|
frame.Configure(ui.Config{
|
||||||
|
Width: d.width,
|
||||||
|
Background: render.Black,
|
||||||
|
})
|
||||||
|
|
||||||
|
type menuButton struct {
|
||||||
|
Text string
|
||||||
|
Click func(render.Point)
|
||||||
|
}
|
||||||
|
buttons := []menuButton{
|
||||||
|
menuButton{
|
||||||
|
Text: "New Level",
|
||||||
|
Click: func(render.Point) {
|
||||||
|
d.NewMap()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
menuButton{
|
||||||
|
Text: "New Doodad",
|
||||||
|
Click: func(render.Point) {
|
||||||
|
d.NewMap()
|
||||||
|
},
|
||||||
|
},
|
||||||
|
menuButton{
|
||||||
|
Text: "Save",
|
||||||
|
Click: func(render.Point) {
|
||||||
|
if u.Scene.filename != "" {
|
||||||
|
u.Scene.SaveLevel(u.Scene.filename)
|
||||||
|
d.Flash("Saved: %s", u.Scene.filename)
|
||||||
|
} else {
|
||||||
|
d.Prompt("Save filename>", func(answer string) {
|
||||||
|
if answer != "" {
|
||||||
|
u.Scene.SaveLevel("./maps/" + answer) // TODO: maps path
|
||||||
|
d.Flash("Saved: %s", answer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
menuButton{
|
||||||
|
Text: "Save as...",
|
||||||
|
Click: func(render.Point) {
|
||||||
|
d.Prompt("Save as filename>", func(answer string) {
|
||||||
|
if answer != "" {
|
||||||
|
u.Scene.SaveLevel("./maps/" + answer) // TODO: maps path
|
||||||
|
d.Flash("Saved: %s", answer)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
menuButton{
|
||||||
|
Text: "Load",
|
||||||
|
Click: func(render.Point) {
|
||||||
|
d.Prompt("Open filename>", func(answer string) {
|
||||||
|
if answer != "" {
|
||||||
|
u.d.EditLevel("./maps/" + answer) // TODO: maps path
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, btn := range buttons {
|
||||||
|
w := ui.NewButton(btn.Text, ui.NewLabel(ui.Label{
|
||||||
|
Text: btn.Text,
|
||||||
|
Font: balance.MenuFont,
|
||||||
|
}))
|
||||||
|
w.Configure(ui.Config{
|
||||||
|
BorderSize: 1,
|
||||||
|
OutlineSize: 0,
|
||||||
|
})
|
||||||
|
w.Handle("MouseUp", btn.Click)
|
||||||
|
u.Supervisor.Add(w)
|
||||||
|
frame.Pack(w, ui.Pack{
|
||||||
|
Anchor: ui.W,
|
||||||
|
PadX: 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
frame.Compute(d.Engine)
|
||||||
|
return 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 {
|
||||||
|
log.Error("SetupPalette Window")
|
||||||
window := ui.NewWindow("Palette")
|
window := ui.NewWindow("Palette")
|
||||||
|
window.ConfigureTitle(balance.TitleConfig)
|
||||||
|
window.TitleBar().Font = balance.TitleFont
|
||||||
window.Configure(ui.Config{
|
window.Configure(ui.Config{
|
||||||
Width: 150,
|
Width: 150,
|
||||||
Height: u.d.height - u.StatusBar.Size().H,
|
Height: u.d.height - u.StatusBar.Size().H,
|
||||||
|
Background: balance.WindowBackground,
|
||||||
|
BorderColor: balance.WindowBorder,
|
||||||
})
|
})
|
||||||
window.MoveTo(render.NewPoint(
|
window.MoveTo(render.NewPoint(
|
||||||
u.d.width-window.BoxSize().W,
|
u.d.width-window.BoxSize().W,
|
||||||
0,
|
u.MenuBar.BoxSize().H,
|
||||||
))
|
))
|
||||||
|
|
||||||
// Handler function for the radio buttons being clicked.
|
// Handler function for the radio buttons being clicked.
|
||||||
|
@ -109,6 +209,7 @@ func (u *EditorUI) SetupPalette(d *Doodle) *ui.Window {
|
||||||
window.Pack(btn, ui.Pack{
|
window.Pack(btn, ui.Pack{
|
||||||
Anchor: ui.N,
|
Anchor: ui.N,
|
||||||
Fill: true,
|
Fill: true,
|
||||||
|
PadY: 4,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,6 +241,7 @@ func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
|
||||||
cursorLabel.Compute(d.Engine)
|
cursorLabel.Compute(d.Engine)
|
||||||
frame.Pack(cursorLabel, ui.Pack{
|
frame.Pack(cursorLabel, ui.Pack{
|
||||||
Anchor: ui.W,
|
Anchor: ui.W,
|
||||||
|
PadX: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
paletteLabel := ui.NewLabel(ui.Label{
|
paletteLabel := ui.NewLabel(ui.Label{
|
||||||
|
@ -150,6 +252,7 @@ func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
|
||||||
paletteLabel.Compute(d.Engine)
|
paletteLabel.Compute(d.Engine)
|
||||||
frame.Pack(paletteLabel, ui.Pack{
|
frame.Pack(paletteLabel, ui.Pack{
|
||||||
Anchor: ui.W,
|
Anchor: ui.W,
|
||||||
|
PadX: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
filenameLabel := ui.NewLabel(ui.Label{
|
filenameLabel := ui.NewLabel(ui.Label{
|
||||||
|
@ -160,6 +263,7 @@ func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame {
|
||||||
filenameLabel.Compute(d.Engine)
|
filenameLabel.Compute(d.Engine)
|
||||||
frame.Pack(filenameLabel, ui.Pack{
|
frame.Pack(filenameLabel, ui.Pack{
|
||||||
Anchor: ui.W,
|
Anchor: ui.W,
|
||||||
|
PadX: 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// TODO: right-aligned labels clip out of bounds
|
// TODO: right-aligned labels clip out of bounds
|
||||||
|
|
2
fps.go
2
fps.go
|
@ -49,7 +49,7 @@ func (d *Doodle) DrawDebugOverlay() {
|
||||||
},
|
},
|
||||||
render.Point{
|
render.Point{
|
||||||
X: DebugTextPadding,
|
X: DebugTextPadding,
|
||||||
Y: DebugTextPadding,
|
Y: DebugTextPadding + 32, // extra padding to not overlay menu bars
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -216,13 +216,14 @@ func (s *GUITestScene) Setup(d *Doodle) error {
|
||||||
log.Info("Button1 bg: %s", button1.Background())
|
log.Info("Button1 bg: %s", button1.Background())
|
||||||
|
|
||||||
button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
|
button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{
|
||||||
Text: "New Map",
|
Text: "Load Map",
|
||||||
Font: balance.StatusFont,
|
Font: balance.StatusFont,
|
||||||
}))
|
}))
|
||||||
button2.Handle("Click", func(p render.Point) {
|
button2.Handle("Click", func(p render.Point) {
|
||||||
d.Flash("Button2 clicked")
|
d.Prompt("Map name>", func(name string) {
|
||||||
|
d.EditLevel(name)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
button2.SetText("Load Map")
|
|
||||||
|
|
||||||
var align = ui.W
|
var align = ui.W
|
||||||
btnFrame.Pack(button1, ui.Pack{
|
btnFrame.Pack(button1, ui.Pack{
|
||||||
|
|
|
@ -32,6 +32,7 @@ func LoadJSON(filename string) (*Level, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inflate the private instance values.
|
// Inflate the private instance values.
|
||||||
|
m.Palette.Inflate()
|
||||||
for _, px := range m.Pixels {
|
for _, px := range m.Pixels {
|
||||||
if int(px.PaletteIndex) > len(m.Palette.Swatches) {
|
if int(px.PaletteIndex) > len(m.Palette.Swatches) {
|
||||||
return nil, fmt.Errorf(
|
return nil, fmt.Errorf(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package level
|
package level
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,9 +62,17 @@ func (s Swatch) String() string {
|
||||||
|
|
||||||
// Index returns the Swatch's position in the palette.
|
// Index returns the Swatch's position in the palette.
|
||||||
func (s Swatch) Index() int {
|
func (s Swatch) Index() int {
|
||||||
|
fmt.Printf("%+v index: %d", s, s.index)
|
||||||
return s.index
|
return s.index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inflate the palette swatch caches. Always call this method after you have
|
||||||
|
// initialized the palette (i.e. loaded it from JSON); this will update the
|
||||||
|
// "color by name" cache and assign the index numbers to each swatch.
|
||||||
|
func (p *Palette) Inflate() {
|
||||||
|
p.update()
|
||||||
|
}
|
||||||
|
|
||||||
// Get a swatch by name.
|
// Get a swatch by name.
|
||||||
func (p *Palette) Get(name string) (result *Swatch, exists bool) {
|
func (p *Palette) Get(name string) (result *Swatch, exists bool) {
|
||||||
p.update()
|
p.update()
|
||||||
|
|
|
@ -36,6 +36,15 @@ func RGBA(r, g, b, a uint8) Color {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MustHexColor parses a color from hex code or panics.
|
||||||
|
func MustHexColor(hex string) Color {
|
||||||
|
color, err := HexColor(hex)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
// HexColor parses a color from hexadecimal code.
|
// HexColor parses a color from hexadecimal code.
|
||||||
func HexColor(hex string) (Color, error) {
|
func HexColor(hex string) (Color, error) {
|
||||||
c := Black // default color
|
c := Black // default color
|
||||||
|
@ -80,8 +89,8 @@ func HexColor(hex string) (Color, error) {
|
||||||
|
|
||||||
func (c Color) String() string {
|
func (c Color) String() string {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
"Color<#%02x%02x%02x>",
|
"Color<#%02x%02x%02x+%02x>",
|
||||||
c.Red, c.Green, c.Blue,
|
c.Red, c.Green, c.Blue, c.Alpha,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,8 +99,11 @@ type Text struct {
|
||||||
Size int
|
Size int
|
||||||
Color Color
|
Color Color
|
||||||
Padding int32
|
Padding int32
|
||||||
|
PadX int32
|
||||||
|
PadY int32
|
||||||
Stroke Color // Stroke color (if not zero)
|
Stroke Color // Stroke color (if not zero)
|
||||||
Shadow Color // Drop shadow color (if not zero)
|
Shadow Color // Drop shadow color (if not zero)
|
||||||
|
FontFilename string // Path to *.ttf file on disk
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t Text) String() string {
|
func (t Text) String() string {
|
||||||
|
|
|
@ -82,6 +82,7 @@ func (r *Renderer) Setup() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
renderer.SetDrawBlendMode(sdl.BLENDMODE_BLEND)
|
||||||
r.renderer = renderer
|
r.renderer = renderer
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package sdl
|
package sdl
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
|
@ -9,19 +10,28 @@ import (
|
||||||
"github.com/veandco/go-sdl2/ttf"
|
"github.com/veandco/go-sdl2/ttf"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fonts map[int]*ttf.Font = map[int]*ttf.Font{}
|
// TODO: font filenames
|
||||||
|
var defaultFontFilename = "./fonts/DejaVuSans.ttf"
|
||||||
|
|
||||||
|
var fonts = map[string]*ttf.Font{}
|
||||||
|
|
||||||
// LoadFont loads and caches the font at a given size.
|
// LoadFont loads and caches the font at a given size.
|
||||||
func LoadFont(size int) (*ttf.Font, error) {
|
func LoadFont(filename string, size int) (*ttf.Font, error) {
|
||||||
if font, ok := fonts[size]; ok {
|
if filename == "" {
|
||||||
|
filename = defaultFontFilename
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cached font available?
|
||||||
|
keyName := fmt.Sprintf("%s@%d", filename, size)
|
||||||
|
if font, ok := fonts[keyName]; ok {
|
||||||
return font, nil
|
return font, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
font, err := ttf.OpenFont("./fonts/DejaVuSansMono.ttf", size)
|
font, err := ttf.OpenFont(filename, size)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
fonts[size] = font
|
fonts[keyName] = font
|
||||||
|
|
||||||
return font, nil
|
return font, nil
|
||||||
}
|
}
|
||||||
|
@ -51,7 +61,7 @@ func (r *Renderer) ComputeTextRect(text render.Text) (render.Rect, error) {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if font, err = LoadFont(text.Size); err != nil {
|
if font, err = LoadFont(text.FontFilename, text.Size); err != nil {
|
||||||
return rect, err
|
return rect, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,7 +84,7 @@ func (r *Renderer) DrawText(text render.Text, point render.Point) error {
|
||||||
err error
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
if font, err = LoadFont(text.Size); err != nil {
|
if font, err = LoadFont(text.FontFilename, text.Size); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
23
shell.go
23
shell.go
|
@ -16,12 +16,20 @@ func (d *Doodle) Flash(template string, v ...interface{}) {
|
||||||
d.shell.Write(fmt.Sprintf(template, v...))
|
d.shell.Write(fmt.Sprintf(template, v...))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prompt the user for a question in the dev console.
|
||||||
|
func (d *Doodle) Prompt(question string, callback func(string)) {
|
||||||
|
d.shell.Prompt = question
|
||||||
|
d.shell.callback = callback
|
||||||
|
d.shell.Open = true
|
||||||
|
}
|
||||||
|
|
||||||
// Shell implements the developer console in-game.
|
// Shell implements the developer console in-game.
|
||||||
type Shell struct {
|
type Shell struct {
|
||||||
parent *Doodle
|
parent *Doodle
|
||||||
|
|
||||||
Open bool
|
Open bool
|
||||||
Prompt string
|
Prompt string
|
||||||
|
callback func(string) // for prompt answers only
|
||||||
Text string
|
Text string
|
||||||
History []string
|
History []string
|
||||||
Output []string
|
Output []string
|
||||||
|
@ -82,6 +90,7 @@ func (s *Shell) Close() {
|
||||||
log.Debug("Shell: closing shell")
|
log.Debug("Shell: closing shell")
|
||||||
s.Open = false
|
s.Open = false
|
||||||
s.Prompt = ">"
|
s.Prompt = ">"
|
||||||
|
s.callback = nil
|
||||||
s.Text = ""
|
s.Text = ""
|
||||||
s.historyPaging = false
|
s.historyPaging = false
|
||||||
s.historyIndex = 0
|
s.historyIndex = 0
|
||||||
|
@ -95,6 +104,14 @@ func (s *Shell) Execute(input string) {
|
||||||
s.History = append(s.History, command.Raw)
|
s.History = append(s.History, command.Raw)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Are we answering a Prompt?
|
||||||
|
if s.callback != nil {
|
||||||
|
log.Info("Invoking prompt callback:")
|
||||||
|
s.callback(command.Raw)
|
||||||
|
s.Close()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if command.Command == "clear" {
|
if command.Command == "clear" {
|
||||||
s.Output = []string{}
|
s.Output = []string{}
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,9 +274,10 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
||||||
line := s.Output[len(s.Output)-1-i]
|
line := s.Output[len(s.Output)-1-i]
|
||||||
d.Engine.DrawText(
|
d.Engine.DrawText(
|
||||||
render.Text{
|
render.Text{
|
||||||
|
FontFilename: balance.ShellFontFilename,
|
||||||
Text: line,
|
Text: line,
|
||||||
Size: balance.ShellFontSize,
|
Size: balance.ShellFontSize,
|
||||||
Color: render.Grey,
|
Color: balance.ShellForegroundColor,
|
||||||
},
|
},
|
||||||
render.Point{
|
render.Point{
|
||||||
X: balance.ShellPadding,
|
X: balance.ShellPadding,
|
||||||
|
@ -273,9 +291,10 @@ func (s *Shell) Draw(d *Doodle, ev *events.State) error {
|
||||||
// Draw the command prompt.
|
// Draw the command prompt.
|
||||||
d.Engine.DrawText(
|
d.Engine.DrawText(
|
||||||
render.Text{
|
render.Text{
|
||||||
|
FontFilename: balance.ShellFontFilename,
|
||||||
Text: s.Prompt + s.Text + string(s.cursor),
|
Text: s.Prompt + s.Text + string(s.cursor),
|
||||||
Size: balance.ShellFontSize,
|
Size: balance.ShellFontSize,
|
||||||
Color: balance.ShellForegroundColor,
|
Color: balance.ShellPromptColor,
|
||||||
},
|
},
|
||||||
render.Point{
|
render.Point{
|
||||||
X: balance.ShellPadding,
|
X: balance.ShellPadding,
|
||||||
|
|
|
@ -106,8 +106,6 @@ func (w *Button) Present(e render.Engine, P render.Point) {
|
||||||
if S.Bigger(ChildSize) {
|
if S.Bigger(ChildSize) {
|
||||||
moveTo.X = P.X + (S.W / 2) - (ChildSize.W / 2)
|
moveTo.X = P.X + (S.W / 2) - (ChildSize.W / 2)
|
||||||
}
|
}
|
||||||
_ = S
|
|
||||||
_ = ChildSize
|
|
||||||
|
|
||||||
// Draw the text label inside.
|
// Draw the text label inside.
|
||||||
w.child.Present(e, moveTo)
|
w.child.Present(e, moveTo)
|
||||||
|
|
|
@ -22,9 +22,8 @@ func NewFrame(name string) *Frame {
|
||||||
widgets: []Widget{},
|
widgets: []Widget{},
|
||||||
}
|
}
|
||||||
w.IDFunc(func() string {
|
w.IDFunc(func() string {
|
||||||
return fmt.Sprintf("Frame<%s; %d widgets>",
|
return fmt.Sprintf("Frame<%s>",
|
||||||
name,
|
name,
|
||||||
len(w.widgets),
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
return w
|
return w
|
||||||
|
|
|
@ -41,12 +41,17 @@ func (w *Frame) computePacked(e render.Engine) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, packedWidget := range w.packs[anchor] {
|
for _, packedWidget := range w.packs[anchor] {
|
||||||
|
|
||||||
child := packedWidget.widget
|
child := packedWidget.widget
|
||||||
pack := packedWidget.pack
|
pack := packedWidget.pack
|
||||||
child.Compute(e)
|
child.Compute(e)
|
||||||
|
|
||||||
|
x += pack.PadX
|
||||||
|
y += pack.PadY
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// point = child.Point()
|
// point = child.Point()
|
||||||
size = child.BoxSize()
|
size = child.Size()
|
||||||
yStep = y * yDirection
|
yStep = y * yDirection
|
||||||
xStep = x * xDirection
|
xStep = x * xDirection
|
||||||
)
|
)
|
||||||
|
@ -59,19 +64,19 @@ func (w *Frame) computePacked(e render.Engine) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if anchor.IsSouth() {
|
if anchor.IsSouth() {
|
||||||
y -= size.H + (pack.PadY * 2)
|
y -= size.H + pack.PadY
|
||||||
}
|
}
|
||||||
if anchor.IsEast() {
|
if anchor.IsEast() {
|
||||||
x -= size.W + (pack.PadX * 2)
|
x -= size.W + pack.PadX
|
||||||
}
|
}
|
||||||
|
|
||||||
child.MoveTo(render.NewPoint(x, y))
|
child.MoveTo(render.NewPoint(x, y))
|
||||||
|
|
||||||
if anchor.IsNorth() {
|
if anchor.IsNorth() {
|
||||||
y += size.H + (pack.PadY * 2)
|
y += size.H + pack.PadY
|
||||||
}
|
}
|
||||||
if anchor == W {
|
if anchor == W {
|
||||||
x += size.W + (pack.PadX * 2)
|
x += size.W + pack.PadX
|
||||||
}
|
}
|
||||||
|
|
||||||
visited = append(visited, packedWidget)
|
visited = append(visited, packedWidget)
|
||||||
|
|
18
ui/label.go
18
ui/label.go
|
@ -66,10 +66,15 @@ func (w *Label) Compute(e render.Engine) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
padX = w.Font.Padding + w.Font.PadX
|
||||||
|
padY = w.Font.Padding + w.Font.PadY
|
||||||
|
)
|
||||||
|
|
||||||
if !w.FixedSize() {
|
if !w.FixedSize() {
|
||||||
w.resizeAuto(render.Rect{
|
w.resizeAuto(render.Rect{
|
||||||
W: rect.W + (w.Font.Padding * 2),
|
W: rect.W + (padX * 2),
|
||||||
H: rect.H + (w.Font.Padding * 2),
|
H: rect.H + (padY * 2),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,9 +88,14 @@ func (w *Label) Compute(e render.Engine) {
|
||||||
func (w *Label) Present(e render.Engine, P render.Point) {
|
func (w *Label) Present(e render.Engine, P render.Point) {
|
||||||
border := w.BoxThickness(1)
|
border := w.BoxThickness(1)
|
||||||
|
|
||||||
|
var (
|
||||||
|
padX = w.Font.Padding + w.Font.PadX
|
||||||
|
padY = w.Font.Padding + w.Font.PadY
|
||||||
|
)
|
||||||
|
|
||||||
w.DrawBox(e, P)
|
w.DrawBox(e, P)
|
||||||
e.DrawText(w.text(), render.Point{
|
e.DrawText(w.text(), render.Point{
|
||||||
X: P.X + border + w.Font.Padding,
|
X: P.X + border + padX,
|
||||||
Y: P.Y + border + w.Font.Padding,
|
Y: P.Y + border + padY,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,11 @@ func (w *Window) TitleBar() *Label {
|
||||||
func (w *Window) Configure(C Config) {
|
func (w *Window) Configure(C Config) {
|
||||||
w.BaseWidget.Configure(C)
|
w.BaseWidget.Configure(C)
|
||||||
w.body.Configure(C)
|
w.body.Configure(C)
|
||||||
|
|
||||||
|
// Don't pass dimensions down any further than the body.
|
||||||
|
C.Width = 0
|
||||||
|
C.Height = 0
|
||||||
|
w.content.Configure(C)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigureTitle configures the title bar widget.
|
// ConfigureTitle configures the title bar widget.
|
||||||
|
|
Loading…
Reference in New Issue
Block a user