Text Tool and Pan Tool
Two new tools added to the Level Editor: * Pan Tool: left-click to scroll the level around safely. * Text Tool: write text onto your level. Features of the Text Tool: * Can choose from the game's built-in fonts, size and enter the message you want to write. * The mouse cursor previews the text when hovered over the level. * Click to "stamp" the text onto your level. The currently selected color swatch will be used to color the text in. * Adds two new fonts: Azulian.ttf and Rive.ttf that can be selected in the Text Tool. Some implementation notes: * Added package native/engine_sdl.go that handles the lower-level SDL2_TTF logic to rasterize the text into a black&white image. * WASM not supported yet (if the game even still built for WASM); native/engine_wasm.go stubs out the TextToImage() call with a "not supported" error just in case. Other changes: * New Toolbar icons: they are 24x24 instead of 32x32 to make more room for more tools. * The toolbar now shows two buttons per row for a more densely packed layout. For very narrow screen widths (< 600px) the default Vertical Toolbar layout will use one-button-per-row to not eat too much screen real estate. * In the Horizontal Toolbars layout there are 2 buttons per column.
Before Width: | Height: | Size: 687 B After Width: | Height: | Size: 661 B |
Before Width: | Height: | Size: 717 B After Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 709 B After Width: | Height: | Size: 668 B |
Before Width: | Height: | Size: 626 B After Width: | Height: | Size: 619 B |
Before Width: | Height: | Size: 679 B After Width: | Height: | Size: 665 B |
BIN
assets/sprites/pan-tool.png
Normal file
After Width: | Height: | Size: 662 B |
Before Width: | Height: | Size: 752 B After Width: | Height: | Size: 689 B |
Before Width: | Height: | Size: 648 B After Width: | Height: | Size: 637 B |
BIN
assets/sprites/text-tool.png
Normal file
After Width: | Height: | Size: 639 B |
|
@ -59,6 +59,10 @@ var (
|
|||
DefaultEraserBrushSize = 8
|
||||
MaxEraserBrushSize = 32 // the bigger, the slower
|
||||
|
||||
// Default font filename selected for Text Tool in the editor.
|
||||
// TODO: better centralize font filenames, here and in theme.go
|
||||
TextToolDefaultFont = "DejaVuSans.ttf"
|
||||
|
||||
// Interval for auto-save in the editor
|
||||
AutoSaveInterval = 5 * time.Minute
|
||||
|
||||
|
|
56
pkg/drawtool/text_tool.go
Normal file
|
@ -0,0 +1,56 @@
|
|||
package drawtool
|
||||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// TextSettings holds currently selected Text Tool settings.
|
||||
type TextSettings struct {
|
||||
Font string // like 'DejaVuSans.ttf'
|
||||
Size int
|
||||
Message string
|
||||
Label *ui.Label // cached label texture
|
||||
}
|
||||
|
||||
// Currently active settings (global variable)
|
||||
var TT TextSettings
|
||||
|
||||
// IsZero checks if the TextSettings are populated.
|
||||
func (tt TextSettings) IsZero() bool {
|
||||
return tt.Font == "" && tt.Size == 0 && tt.Message == ""
|
||||
}
|
||||
|
||||
// ToStroke converts a TextSettings configuration into a Freehand
|
||||
// Stroke, coloring in all of the pixels.
|
||||
func (tt TextSettings) ToStroke(e render.Engine, color render.Color, at render.Point) (*Stroke, error) {
|
||||
stroke := NewStroke(Freehand, color)
|
||||
|
||||
// Render the text to a Go image so we can get the colors from
|
||||
// it uniformly.
|
||||
img, err := native.TextToImage(e, tt.Label.Font)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pull all its pixels.
|
||||
var (
|
||||
max = img.Bounds().Max
|
||||
x = 0
|
||||
y = 0
|
||||
)
|
||||
for x = 0; x < max.X; x++ {
|
||||
for y = 0; y < max.Y; y++ {
|
||||
hue := img.At(x, y)
|
||||
r, g, b, _ := hue.RGBA()
|
||||
if r == 65535 && g == r && b == r {
|
||||
continue
|
||||
}
|
||||
|
||||
stroke.Points = append(stroke.Points, render.NewPoint(x+at.X, y+at.Y))
|
||||
}
|
||||
}
|
||||
|
||||
return stroke, nil
|
||||
}
|
|
@ -12,6 +12,8 @@ const (
|
|||
ActorTool // drag and move actors
|
||||
LinkTool
|
||||
EraserTool
|
||||
PanTool
|
||||
TextTool
|
||||
)
|
||||
|
||||
var toolNames = []string{
|
||||
|
@ -22,6 +24,8 @@ var toolNames = []string{
|
|||
"Doodad", // readable name for ActorTool
|
||||
"Link",
|
||||
"Eraser",
|
||||
"PanTool",
|
||||
"TextTool",
|
||||
}
|
||||
|
||||
func (t Tool) String() string {
|
||||
|
|
|
@ -51,6 +51,7 @@ type EditorUI struct {
|
|||
doodadWindow *ui.Window
|
||||
paletteEditor *ui.Window
|
||||
layersWindow *ui.Window
|
||||
textToolWindow *ui.Window
|
||||
publishWindow *ui.Window
|
||||
filesystemWindow *ui.Window
|
||||
licenseWindow *ui.Window
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/doodads"
|
||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/license"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
|
@ -49,6 +50,12 @@ func (u *EditorUI) OpenDoodadDropper() {
|
|||
u.Supervisor.FocusWindow(u.doodadWindow)
|
||||
}
|
||||
|
||||
// OpenTextTool opens the Text Tool window.
|
||||
func (u *EditorUI) OpenTextTool() {
|
||||
u.textToolWindow.Show()
|
||||
u.Supervisor.FocusWindow(u.textToolWindow)
|
||||
}
|
||||
|
||||
// OpenPublishWindow opens the Publisher window.
|
||||
func (u *EditorUI) OpenPublishWindow() {
|
||||
scene, _ := u.d.Scene.(*EditorScene)
|
||||
|
@ -148,6 +155,23 @@ func (u *EditorUI) SetupPopups(d *Doodle) {
|
|||
u.ConfigureWindow(d, u.doodadWindow)
|
||||
}
|
||||
|
||||
// Text Tool window.
|
||||
if u.textToolWindow == nil {
|
||||
u.textToolWindow = windows.NewTextToolWindow(windows.TextTool{
|
||||
Supervisor: u.Supervisor,
|
||||
Engine: d.Engine,
|
||||
OnChangeSettings: func(font string, size int, message string) {
|
||||
log.Info("Updated Text Tool settings: %s (%d): %s", font, size, message)
|
||||
drawtool.TT = drawtool.TextSettings{
|
||||
Font: font,
|
||||
Size: size,
|
||||
Message: message,
|
||||
}
|
||||
},
|
||||
})
|
||||
u.ConfigureWindow(d, u.textToolWindow)
|
||||
}
|
||||
|
||||
// Page Settings
|
||||
if u.levelSettingsWindow == nil {
|
||||
scene, _ := d.Scene.(*EditorScene)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package doodle
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/enum"
|
||||
|
@ -11,33 +13,54 @@ import (
|
|||
"git.kirsle.net/go/ui/style"
|
||||
)
|
||||
|
||||
// Width of the toolbar frame.
|
||||
var toolbarWidth = 44 // 38px button (32px sprite + borders) + padding
|
||||
var toolbarSpriteSize = 32 // 32x32 sprites.
|
||||
// Global toolbarWidth, TODO: editor_ui.go wants it
|
||||
var toolbarWidth int
|
||||
|
||||
// SetupToolbar configures the UI for the Tools panel.
|
||||
func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
||||
// Horizontal toolbar instead of vertical?
|
||||
var (
|
||||
isHoz = usercfg.Current.HorizontalToolbars
|
||||
packAlign = ui.N
|
||||
frameSize = render.NewRect(toolbarWidth, 100)
|
||||
tooltipEdge = ui.Right
|
||||
btnPack = ui.Pack{
|
||||
toolbarSpriteSize = 24 // size of sprite images
|
||||
frameSize render.Rect
|
||||
isHoz = usercfg.Current.HorizontalToolbars
|
||||
buttonsPerRow = 2
|
||||
packAlign = ui.N
|
||||
tooltipEdge = ui.Right
|
||||
btnRowPack = ui.Pack{
|
||||
Side: packAlign,
|
||||
PadY: 2,
|
||||
PadY: 1,
|
||||
Fill: true,
|
||||
}
|
||||
btnPack = ui.Pack{
|
||||
Side: ui.W,
|
||||
PadX: 1,
|
||||
}
|
||||
)
|
||||
if isHoz {
|
||||
packAlign = ui.W
|
||||
frameSize = render.NewRect(100, toolbarWidth)
|
||||
tooltipEdge = ui.Bottom
|
||||
btnPack = ui.Pack{
|
||||
btnRowPack = ui.Pack{
|
||||
Side: packAlign,
|
||||
PadX: 2,
|
||||
}
|
||||
btnPack = ui.Pack{
|
||||
Side: ui.N,
|
||||
PadY: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// Button Layout Controls:
|
||||
// We can draw 2 buttons per row, but for very small screens
|
||||
// e.g. mobile in portrait orientation, draw 1 button per row.
|
||||
buttonsPerRow = 1
|
||||
if isHoz || d.width >= enum.ScreenWidthSmall {
|
||||
buttonsPerRow = 2
|
||||
}
|
||||
|
||||
// Compute toolbar size to accommodate all buttons (+10 for borders/padding)
|
||||
toolbarWidth = buttonsPerRow * (toolbarSpriteSize + 10)
|
||||
frameSize = render.NewRect(toolbarWidth, 100)
|
||||
|
||||
frame := ui.NewFrame("Tool Bar")
|
||||
frame.Resize(frameSize)
|
||||
frame.Configure(ui.Config{
|
||||
|
@ -62,6 +85,16 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
|||
// Optional fields.
|
||||
NoDoodad bool // tool not available for Doodad editing (Levels only)
|
||||
}{
|
||||
{
|
||||
Value: drawtool.PanTool.String(),
|
||||
Icon: "assets/sprites/pan-tool.png",
|
||||
Tooltip: "Pan Tool",
|
||||
Click: func() {
|
||||
u.Canvas.Tool = drawtool.PanTool
|
||||
d.Flash("Pan Tool selected.")
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Value: drawtool.PencilTool.String(),
|
||||
Icon: "assets/sprites/pencil-tool.png",
|
||||
|
@ -102,6 +135,17 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
|||
},
|
||||
},
|
||||
|
||||
{
|
||||
Value: drawtool.TextTool.String(),
|
||||
Icon: "assets/sprites/text-tool.png",
|
||||
Tooltip: "Text Tool",
|
||||
Click: func() {
|
||||
u.Canvas.Tool = drawtool.TextTool
|
||||
u.OpenTextTool()
|
||||
d.Flash("Text Tool selected.")
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
Value: drawtool.ActorTool.String(),
|
||||
Icon: "assets/sprites/actor-tool.png",
|
||||
|
@ -146,12 +190,20 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
|||
},
|
||||
},
|
||||
}
|
||||
for _, button := range buttons {
|
||||
|
||||
// Arrange the buttons 2x2.
|
||||
var btnRow *ui.Frame
|
||||
for i, button := range buttons {
|
||||
button := button
|
||||
if button.NoDoodad && u.Scene.DrawingType == enum.DoodadDrawing {
|
||||
continue
|
||||
}
|
||||
|
||||
if buttonsPerRow == 1 || i%buttonsPerRow == 0 {
|
||||
btnRow = ui.NewFrame(fmt.Sprintf("Button Row %d", i))
|
||||
btnFrame.Pack(btnRow, btnRowPack)
|
||||
}
|
||||
|
||||
image, err := sprites.LoadImage(d.Engine, button.Icon)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -168,6 +220,7 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
|||
}
|
||||
|
||||
var btnSize = btn.BoxThickness(2) + toolbarSpriteSize
|
||||
btn.SetBorderSize(1)
|
||||
btn.Resize(render.NewRect(btnSize, btnSize))
|
||||
|
||||
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
|
@ -181,7 +234,7 @@ func (u *EditorUI) SetupToolbar(d *Doodle) *ui.Frame {
|
|||
Edge: tooltipEdge,
|
||||
})
|
||||
|
||||
btnFrame.Pack(btn, btnPack)
|
||||
btnRow.Pack(btn, btnPack)
|
||||
}
|
||||
|
||||
// Doodad Editor: show the Layers button.
|
||||
|
|
84
pkg/native/engine_sdl.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
// +build !js
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/sdl"
|
||||
sdl2 "github.com/veandco/go-sdl2/sdl"
|
||||
"github.com/veandco/go-sdl2/ttf"
|
||||
)
|
||||
|
||||
// Native render engine functions (SDL2 edition),
|
||||
// not for JavaScript/WASM yet.
|
||||
|
||||
/*
|
||||
TextToImage takes an SDL2_TTF texture and makes it into a Go image.
|
||||
|
||||
Notes:
|
||||
- The text is made Black & White with a white background on the image.
|
||||
- Drop shadow, stroke, etc. probably not supported.
|
||||
- Returns a non-antialiased image.
|
||||
*/
|
||||
func TextToImage(e render.Engine, text render.Text) (image.Image, error) {
|
||||
// engine, _ := e.(*sdl.Renderer)
|
||||
|
||||
// Make the text black & white for ease of identifying pixels.
|
||||
text.Color = render.Black
|
||||
|
||||
var (
|
||||
// renderer = engine.GetSDL2Renderer()
|
||||
font *ttf.Font
|
||||
surface *sdl2.Surface
|
||||
pixFmt *sdl2.PixelFormat
|
||||
surface2 *sdl2.Surface
|
||||
err error
|
||||
)
|
||||
|
||||
if font, err = sdl.LoadFont(text.FontFilename, text.Size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if surface, err = font.RenderUTF8Solid(text.Text, sdl.ColorToSDL(text.Color)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer surface.Free()
|
||||
log.Error("surf fmt: %+v", surface.Format)
|
||||
|
||||
// Convert the Surface into a pixelformat that supports the .At(x,y)
|
||||
// function properly, as the one we got above is "Not implemented"
|
||||
if pixFmt, err = sdl2.AllocFormat(sdl2.PIXELFORMAT_RGB888); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if surface2, err = surface.Convert(pixFmt, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer surface2.Free()
|
||||
|
||||
// Read back the pixels.
|
||||
var (
|
||||
x int
|
||||
y int
|
||||
w = int(surface2.W)
|
||||
h = int(surface2.H)
|
||||
img = image.NewRGBA(image.Rect(x, y, w, h))
|
||||
)
|
||||
for x = 0; x < w; x++ {
|
||||
for y = 0; y < h; y++ {
|
||||
hue := surface2.At(x, y)
|
||||
img.Set(x, y, hue)
|
||||
// log.Warn("hue: %s", hue)
|
||||
// r, g, b, _ := hue.RGBA()
|
||||
// if r == 0 && g == 0 && b == 0 {
|
||||
// img.Set(x, y, hue)
|
||||
// } else {
|
||||
// img.Set(x, y, color.Transparent)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
return img, nil
|
||||
}
|
13
pkg/native/engine_wasm.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
// +build js,wasm
|
||||
|
||||
package native
|
||||
|
||||
import (
|
||||
"image"
|
||||
|
||||
"git.kirsle.net/go/render"
|
||||
)
|
||||
|
||||
func TextToImage(e render.Engine, text render.Text) (image.Image, error) {
|
||||
return nil, errors.New("not supported on WASM")
|
||||
}
|
|
@ -2,7 +2,9 @@ package uix
|
|||
|
||||
import (
|
||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/keybind"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/render/event"
|
||||
"git.kirsle.net/go/ui"
|
||||
|
@ -137,6 +139,30 @@ func (w *Canvas) loopEditable(ev *event.State) error {
|
|||
}
|
||||
|
||||
switch w.Tool {
|
||||
case drawtool.PanTool:
|
||||
// Pan tool = click to pan the level.
|
||||
if ev.Button1 || keybind.MiddleClick(ev) {
|
||||
if !w.scrollDragging {
|
||||
w.scrollDragging = true
|
||||
w.scrollStartAt = shmem.Cursor
|
||||
w.scrollWasAt = w.Scroll
|
||||
} else {
|
||||
delta := shmem.Cursor.Compare(w.scrollStartAt)
|
||||
w.Scroll = w.scrollWasAt
|
||||
w.Scroll.Subtract(delta)
|
||||
|
||||
// TODO: if I don't call this, the user is able to (temporarily!)
|
||||
// pan outside the level boundaries before it snaps-back when they
|
||||
// release. But the normal middle-click to pan code doesn't let
|
||||
// them do this.. investigate why later.
|
||||
w.loopConstrainScroll()
|
||||
}
|
||||
} else {
|
||||
if w.scrollDragging {
|
||||
w.scrollDragging = false
|
||||
}
|
||||
}
|
||||
|
||||
case drawtool.PencilTool:
|
||||
// If no swatch is active, do nothing with mouse clicks.
|
||||
if w.Palette.ActiveSwatch == nil {
|
||||
|
@ -253,6 +279,47 @@ func (w *Canvas) loopEditable(ev *event.State) error {
|
|||
} else {
|
||||
w.commitStroke(w.Tool, true)
|
||||
}
|
||||
case drawtool.TextTool:
|
||||
// The Text Tool popup should initialize this for us, if somehow not
|
||||
// initialized skip this tool processing.
|
||||
if w.Palette.ActiveSwatch == nil || drawtool.TT.IsZero() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Do we need to create the Label?
|
||||
if drawtool.TT.Label == nil {
|
||||
drawtool.TT.Label = ui.NewLabel(ui.Label{
|
||||
Text: drawtool.TT.Message,
|
||||
Font: render.Text{
|
||||
FontFilename: drawtool.TT.Font,
|
||||
Size: drawtool.TT.Size,
|
||||
Color: w.Palette.ActiveSwatch.Color,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Do we need to update the color of the label?
|
||||
if drawtool.TT.Label.Font.Color != w.Palette.ActiveSwatch.Color {
|
||||
drawtool.TT.Label.Font.Color = w.Palette.ActiveSwatch.Color
|
||||
}
|
||||
|
||||
// NOTE: Canvas.presentStrokes() will handle drawing the font preview
|
||||
// at the cursor location while the TextTool is active.
|
||||
|
||||
// On mouse click, commit the text to the drawing.
|
||||
if ev.Button1 {
|
||||
if stroke, err := drawtool.TT.ToStroke(shmem.CurrentRenderEngine, w.Palette.ActiveSwatch.Color, cursor); err != nil {
|
||||
shmem.FlashError("Text Tool error: %s", err)
|
||||
return nil
|
||||
} else {
|
||||
w.currentStroke = stroke
|
||||
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
||||
w.commitStroke(drawtool.PencilTool, true)
|
||||
}
|
||||
|
||||
ev.Button1 = false
|
||||
}
|
||||
|
||||
case drawtool.EraserTool:
|
||||
// Clicking? Log all the pixels while doing so.
|
||||
if ev.Button1 {
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/keybind"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
|
@ -89,19 +90,22 @@ func (w *Canvas) loopEditorScroll(ev *event.State) error {
|
|||
}
|
||||
|
||||
// Middle click of the mouse to pan the level.
|
||||
if keybind.MiddleClick(ev) {
|
||||
if !w.scrollDragging {
|
||||
w.scrollDragging = true
|
||||
w.scrollStartAt = shmem.Cursor
|
||||
w.scrollWasAt = w.Scroll
|
||||
// NOTE: PanTool intercepts both Left and MiddleClick.
|
||||
if w.Tool != drawtool.PanTool {
|
||||
if keybind.MiddleClick(ev) {
|
||||
if !w.scrollDragging {
|
||||
w.scrollDragging = true
|
||||
w.scrollStartAt = shmem.Cursor
|
||||
w.scrollWasAt = w.Scroll
|
||||
} else {
|
||||
delta := shmem.Cursor.Compare(w.scrollStartAt)
|
||||
w.Scroll = w.scrollWasAt
|
||||
w.Scroll.Subtract(delta)
|
||||
}
|
||||
} else {
|
||||
delta := shmem.Cursor.Compare(w.scrollStartAt)
|
||||
w.Scroll = w.scrollWasAt
|
||||
w.Scroll.Subtract(delta)
|
||||
}
|
||||
} else {
|
||||
if w.scrollDragging {
|
||||
w.scrollDragging = false
|
||||
if w.scrollDragging {
|
||||
w.scrollDragging = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -143,6 +143,11 @@ func (w *Canvas) presentStrokes(e render.Engine) {
|
|||
if w.Tool == drawtool.ActorTool || w.Tool == drawtool.LinkTool {
|
||||
w.presentActorLinks(e)
|
||||
}
|
||||
|
||||
// Text Tool preview.
|
||||
if w.Tool == drawtool.TextTool && drawtool.TT.Label != nil {
|
||||
drawtool.TT.Label.Present(e, shmem.Cursor)
|
||||
}
|
||||
}
|
||||
|
||||
// presentActorLinks draws strokes connecting actors together by their links.
|
||||
|
|
|
@ -31,6 +31,8 @@ type Form struct {
|
|||
// For vertical forms.
|
||||
Vertical bool
|
||||
LabelWidth int // size of left frame for labels.
|
||||
PadY int // spacer between (vertical) forms
|
||||
PadX int
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -61,6 +63,7 @@ type Field struct {
|
|||
// Variable bindings, the type may infer to be:
|
||||
BoolVariable *bool // Checkbox
|
||||
TextVariable *string // Textbox
|
||||
IntVariable *int // Textbox
|
||||
Options []Option // Selectbox
|
||||
SelectValue interface{} // Selectbox default choice
|
||||
|
||||
|
@ -100,6 +103,7 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
|
|||
into.Pack(frame, ui.Pack{
|
||||
Side: ui.N,
|
||||
FillX: true,
|
||||
PadY: form.PadY,
|
||||
})
|
||||
|
||||
// Pager row?
|
||||
|
@ -177,6 +181,35 @@ func (form Form) Create(into *ui.Frame, fields []Field) {
|
|||
})
|
||||
}
|
||||
|
||||
// Buttons and Text fields (for now).
|
||||
if row.Type == Button || row.Type == Textbox {
|
||||
btn := ui.NewButton("Button", ui.NewLabel(ui.Label{
|
||||
Text: row.Label,
|
||||
Font: row.Font,
|
||||
TextVariable: row.TextVariable,
|
||||
IntVariable: row.IntVariable,
|
||||
}))
|
||||
form.Supervisor.Add(btn)
|
||||
frame.Pack(btn, ui.Pack{
|
||||
Side: ui.W,
|
||||
FillX: true,
|
||||
Expand: true,
|
||||
})
|
||||
|
||||
// Tooltip? TODO - make nicer.
|
||||
if row.Tooltip.Text != "" || row.Tooltip.TextVariable != nil {
|
||||
ui.NewTooltip(btn, row.Tooltip)
|
||||
}
|
||||
|
||||
// Handlers
|
||||
btn.Handle(ui.Click, func(ed ui.EventData) error {
|
||||
if row.OnClick != nil {
|
||||
row.OnClick()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Checkbox?
|
||||
if row.Type == Checkbox {
|
||||
cb := ui.NewCheckbox("Checkbox", row.BoolVariable, ui.NewLabel(ui.Label{
|
||||
|
@ -266,7 +299,7 @@ func (field Field) Infer() Type {
|
|||
return Selectbox
|
||||
}
|
||||
|
||||
if field.TextVariable != nil {
|
||||
if field.TextVariable != nil || field.IntVariable != nil {
|
||||
return Textbox
|
||||
}
|
||||
|
||||
|
|
124
pkg/windows/text_tool.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package windows
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"git.kirsle.net/apps/doodle/assets"
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/branding"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
magicform "git.kirsle.net/apps/doodle/pkg/uix/magic-form"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// TextTool window.
|
||||
type TextTool struct {
|
||||
// Settings passed in by doodle
|
||||
Supervisor *ui.Supervisor
|
||||
Engine render.Engine
|
||||
|
||||
// Callback when font settings are changed.
|
||||
OnChangeSettings func(font string, size int, message string)
|
||||
}
|
||||
|
||||
// NewTextToolWindow initializes the window.
|
||||
func NewTextToolWindow(cfg TextTool) *ui.Window {
|
||||
window := ui.NewWindow("Text Tool")
|
||||
window.SetButtons(ui.CloseButton)
|
||||
window.Configure(ui.Config{
|
||||
Width: 330,
|
||||
Height: 170,
|
||||
Background: render.Grey,
|
||||
})
|
||||
|
||||
// Text variables
|
||||
var (
|
||||
currentText = branding.AppName
|
||||
fontName = balance.TextToolDefaultFont
|
||||
fontSize = 16
|
||||
)
|
||||
|
||||
// Get a listing of the available fonts.
|
||||
fonts, _ := assets.AssetDir("assets/fonts")
|
||||
var fontOption = []magicform.Option{}
|
||||
for _, font := range fonts {
|
||||
// Select the first font by default.
|
||||
if fontName == "" {
|
||||
fontName = font
|
||||
}
|
||||
|
||||
fontOption = append(fontOption, magicform.Option{
|
||||
Label: font,
|
||||
Value: font,
|
||||
})
|
||||
}
|
||||
|
||||
// Send the default config out.
|
||||
if cfg.OnChangeSettings != nil {
|
||||
cfg.OnChangeSettings(fontName, fontSize, currentText)
|
||||
}
|
||||
|
||||
form := magicform.Form{
|
||||
Supervisor: cfg.Supervisor,
|
||||
Engine: cfg.Engine,
|
||||
Vertical: true,
|
||||
LabelWidth: 100,
|
||||
PadY: 2,
|
||||
}
|
||||
form.Create(window.ContentFrame(), []magicform.Field{
|
||||
{
|
||||
Label: "Font Face:",
|
||||
Font: balance.LabelFont,
|
||||
Options: fontOption,
|
||||
SelectValue: fontName,
|
||||
OnSelect: func(v interface{}) {
|
||||
fontName = v.(string)
|
||||
if cfg.OnChangeSettings != nil {
|
||||
cfg.OnChangeSettings(fontName, fontSize, currentText)
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Font Size:",
|
||||
Font: balance.LabelFont,
|
||||
IntVariable: &fontSize,
|
||||
OnClick: func() {
|
||||
shmem.Prompt("Enter new font size: ", func(answer string) {
|
||||
if answer != "" {
|
||||
if i, err := strconv.Atoi(answer); err == nil {
|
||||
fontSize = i
|
||||
if cfg.OnChangeSettings != nil {
|
||||
cfg.OnChangeSettings(fontName, fontSize, currentText)
|
||||
}
|
||||
} else {
|
||||
shmem.FlashError("Not a valid font size: %s", answer)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Message:",
|
||||
Font: balance.LabelFont,
|
||||
TextVariable: ¤tText,
|
||||
OnClick: func() {
|
||||
shmem.Prompt("Enter new message: ", func(answer string) {
|
||||
if answer != "" {
|
||||
currentText = answer
|
||||
if cfg.OnChangeSettings != nil {
|
||||
cfg.OnChangeSettings(fontName, fontSize, currentText)
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
Label: "Be sure the Text Tool is selected, and click onto your\n" +
|
||||
"drawing to place this text onto it.",
|
||||
Font: balance.UIFont,
|
||||
},
|
||||
})
|
||||
|
||||
return window
|
||||
}
|