Brush Pattern Textures
Palette swatches gain a new property: Pattern. Patterns are grayscale textures that the swatch color will sample against when drawing pixels to the level, by taking the world coordinate modulo a value inside the texture. A few algorithms were tried (Screen, Overlay), this branch lands on one that tries to cast the color from grayscale which comes out rather dark; to get a patterned color to look black while still seeing the pattern, the color needs to be as bright as #777 to get the effect.
BIN
assets/pattern/bars.png
Normal file
After Width: | Height: | Size: 989 B |
BIN
assets/pattern/circles.png
Normal file
After Width: | Height: | Size: 652 B |
BIN
assets/pattern/dashed.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
assets/pattern/grid.png
Normal file
After Width: | Height: | Size: 594 B |
BIN
assets/pattern/ink.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/pattern/marker.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
assets/pattern/noise.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
|
@ -4,7 +4,7 @@ package branding
|
|||
const (
|
||||
AppName = "Sketchy Maze"
|
||||
Summary = "A drawing-based maze game"
|
||||
Version = "0.6.0-alpha"
|
||||
Version = "0.6.1-alpha"
|
||||
Website = "https://www.sketchymaze.com"
|
||||
Copyright = "2021 Noah Petherbridge"
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/modal"
|
||||
"git.kirsle.net/apps/doodle/pkg/native"
|
||||
"git.kirsle.net/apps/doodle/pkg/pattern"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
golog "git.kirsle.net/go/log"
|
||||
"git.kirsle.net/go/render"
|
||||
|
@ -95,6 +96,9 @@ func (d *Doodle) SetupEngine() error {
|
|||
// Initialize the UI modal manager.
|
||||
modal.Initialize(d.Engine)
|
||||
|
||||
// Preload the builtin brush patterns.
|
||||
pattern.LoadBuiltins(d.Engine)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@ type Stroke struct {
|
|||
ID int // Unique ID per each stroke
|
||||
Shape Shape
|
||||
Color render.Color
|
||||
Pattern string
|
||||
Thickness int // 0 = 1px; thickness creates a box N pixels away from each point
|
||||
ExtraData interface{} // arbitrary storage for extra data to attach
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
|
||||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/pattern"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/go/render"
|
||||
"github.com/google/uuid"
|
||||
|
@ -150,6 +151,7 @@ func (c *Chunk) ToBitmap(filename string, mask render.Color) (render.Texturer, e
|
|||
img := image.NewRGBA(imgSize)
|
||||
|
||||
// Blank out the pixels.
|
||||
// TODO PERF: may be slow?
|
||||
for x := 0; x < img.Bounds().Max.X; x++ {
|
||||
for y := 0; y < img.Bounds().Max.Y; y++ {
|
||||
img.Set(x, y, balance.DebugChunkBitmapBackground.ToColor())
|
||||
|
@ -166,6 +168,12 @@ func (c *Chunk) ToBitmap(filename string, mask render.Color) (render.Texturer, e
|
|||
// Blot all the pixels onto it.
|
||||
for px := range c.Iter() {
|
||||
var color = px.Swatch.Color
|
||||
|
||||
// If the swatch has a pattern, mesh it in.
|
||||
if px.Swatch.Pattern != "" {
|
||||
color = pattern.SampleColor(px.Swatch.Pattern, color, px.Point())
|
||||
}
|
||||
|
||||
if mask != render.Invisible {
|
||||
// A semi-transparent mask will overlay on top of the actual color.
|
||||
if mask.Alpha < 255 {
|
||||
|
|
|
@ -34,6 +34,7 @@ func DefaultPalette() *Palette {
|
|||
}
|
||||
|
||||
// NewBlueprintPalette returns the blueprint theme's color palette.
|
||||
// DEPRECATED in favor of DefaultPalettes.
|
||||
func NewBlueprintPalette() *Palette {
|
||||
return &Palette{
|
||||
Swatches: []*Swatch{
|
||||
|
|
|
@ -9,8 +9,9 @@ import (
|
|||
|
||||
// Swatch holds details about a single value in the palette.
|
||||
type Swatch struct {
|
||||
Name string `json:"name"`
|
||||
Color render.Color `json:"color"`
|
||||
Name string `json:"name"`
|
||||
Color render.Color `json:"color"`
|
||||
Pattern string `json:"pattern"` // like "noise.png"
|
||||
|
||||
// Optional attributes.
|
||||
Solid bool `json:"solid,omitempty"`
|
||||
|
|
230
pkg/pattern/pattern.go
Normal file
|
@ -0,0 +1,230 @@
|
|||
// Package pattern applies a kind of brush texture to a palette swatch.
|
||||
package pattern
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/sprites"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
)
|
||||
|
||||
// Pattern applies a texture to a color in level drawings.
|
||||
type Pattern struct {
|
||||
Name string
|
||||
Filename string
|
||||
Hidden bool // boolProp showHiddenDoodads true
|
||||
}
|
||||
|
||||
// Builtins are the list of the game's built-in patterns.
|
||||
var Builtins = []Pattern{
|
||||
{
|
||||
Name: "No pattern",
|
||||
Filename: "",
|
||||
},
|
||||
{
|
||||
Name: "Noise",
|
||||
Filename: "noise.png",
|
||||
},
|
||||
{
|
||||
Name: "Marker",
|
||||
Filename: "marker.png",
|
||||
},
|
||||
{
|
||||
Name: "Ink",
|
||||
Filename: "ink.png",
|
||||
},
|
||||
{
|
||||
Name: "Dashed Lines",
|
||||
Filename: "circles.png",
|
||||
},
|
||||
{
|
||||
Name: "Grid",
|
||||
Filename: "grid.png",
|
||||
},
|
||||
{
|
||||
Name: "Bars (debug)",
|
||||
Filename: "bars.png",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Images is a map of file names to ui.Image widgets,
|
||||
// after LoadBuiltins had been called.
|
||||
var images map[string]*ui.Image
|
||||
|
||||
// LoadBuiltins loads all of the PNG textures of built-in patterns
|
||||
// into ui.Image widgets.
|
||||
func LoadBuiltins(e render.Engine) {
|
||||
images = map[string]*ui.Image{}
|
||||
|
||||
for _, pat := range Builtins {
|
||||
if pat.Filename == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
img, err := sprites.LoadImage(e, "assets/pattern/"+pat.Filename)
|
||||
if err != nil {
|
||||
log.Error("Load pattern %s: %s", pat.Filename, err)
|
||||
}
|
||||
images[pat.Filename] = img
|
||||
}
|
||||
}
|
||||
|
||||
// GetImage returns the ui.Image for a builtin pattern.
|
||||
func GetImage(filename string) (*ui.Image, error) {
|
||||
if images == nil {
|
||||
return nil, errors.New("pattern.GetImage: LoadBuiltins() was not called")
|
||||
}
|
||||
|
||||
if im, ok := images[filename]; ok {
|
||||
return im, nil
|
||||
}
|
||||
return nil, fmt.Errorf("pattern.GetImage: filename %s not found", filename)
|
||||
}
|
||||
|
||||
// SampleColor samples a color with the pattern for a given coordinate in infinite space.
|
||||
func SampleColor(filename string, color render.Color, point render.Point) render.Color {
|
||||
if filename == "" {
|
||||
return color
|
||||
}
|
||||
|
||||
// Not loaded in memory?
|
||||
if _, ok := images[filename]; !ok {
|
||||
return color
|
||||
}
|
||||
|
||||
// Translate the world coord (point) into the bounds of the texture image.
|
||||
var (
|
||||
image = images[filename].Image // the Go image.Image
|
||||
bounds = image.Bounds()
|
||||
coord = render.Point{
|
||||
// The world coordinate bounded to the pattern image size.
|
||||
X: render.AbsInt(point.X % bounds.Max.X),
|
||||
Y: render.AbsInt(point.Y % bounds.Max.Y),
|
||||
}
|
||||
|
||||
// Sample the color from the pattern texture.
|
||||
colorAt = render.FromColor(image.At(coord.X, coord.Y))
|
||||
|
||||
// Average the RGBA color out to a grayscale brightness.
|
||||
// sourceAvgGray = (int(color.Red) + int(color.Blue) + int(color.Green)/3) % 255
|
||||
// patternAvgGray = (int(colorAt.Red) + int(colorAt.Blue) + int(colorAt.Green)/3) % 255
|
||||
)
|
||||
|
||||
// See if the gray average is brighter or lower than the color.
|
||||
// if sourceAvgGray < patternAvgGray {
|
||||
// delta := patternAvgGray - sourceAvgGray
|
||||
// color = color.Lighten(delta)
|
||||
// } else if sourceAvgGray > patternAvgGray {
|
||||
// color = color.Darken(sourceAvgGray - patternAvgGray)
|
||||
// }
|
||||
|
||||
// return OverlayFilter(color, colorAt)
|
||||
// return ScreenFilter(color, colorAt)
|
||||
return GrayToColor(color, colorAt)
|
||||
|
||||
// log.Info("color: %s at point: %s image point: %s", color, point, coord)
|
||||
|
||||
// return color
|
||||
}
|
||||
|
||||
// GrayToColor samples a colorful swatch with the grayscale pattern img.
|
||||
func GrayToColor(color, grayscale render.Color) render.Color {
|
||||
// The grayscale image ranges from 0 to 255.
|
||||
// The color might be #FF0000 (red)
|
||||
// 127 in grayscale should be FF0000 (perfectly red)
|
||||
// 0 (black) in grayscale should be black in output
|
||||
// 255 (white) in grayscale should be white in output
|
||||
var (
|
||||
AR = float64(color.Red)
|
||||
AG = float64(color.Green)
|
||||
AB = float64(color.Blue)
|
||||
BR = float64(grayscale.Red)
|
||||
BG = float64(grayscale.Green)
|
||||
BB = float64(grayscale.Blue)
|
||||
)
|
||||
|
||||
// If the pattern has a fully transparent pixel here, return transparent.
|
||||
if grayscale.Alpha == 0 {
|
||||
return render.RGBA(1, 0, 0, 1)
|
||||
}
|
||||
|
||||
convert := func(cc, gs float64) uint8 {
|
||||
var delta float64
|
||||
if gs < 127 {
|
||||
// return uint8(cc + cc/gs)
|
||||
delta = cc * (gs / 255)
|
||||
} else {
|
||||
delta = cc * (gs / 255)
|
||||
}
|
||||
return uint8(delta)
|
||||
}
|
||||
|
||||
return render.RGBA(
|
||||
convert(AR, BR),
|
||||
convert(AG, BG),
|
||||
convert(AB, BB),
|
||||
255,
|
||||
)
|
||||
}
|
||||
|
||||
// ScreenFilter applies a "screen" blend mode between the two colors (a > b).
|
||||
func ScreenFilter(a, b render.Color) render.Color {
|
||||
// The algorithm we're going for is:
|
||||
// 1 - (1 - a) * (1 - b)
|
||||
var (
|
||||
AR = a.Red
|
||||
AG = a.Green
|
||||
AB = a.Blue
|
||||
BR = b.Red
|
||||
BG = b.Green
|
||||
BB = b.Blue
|
||||
|
||||
deltaR = 255 - (255-AR)*(255-BR)
|
||||
deltaG = 255 - (255-AG)*(255-BG)
|
||||
deltaB = 255 - (255-AB)*(255-BB)
|
||||
)
|
||||
|
||||
// If the pattern has a fully transparent pixel here, return transparent.
|
||||
// if b.Alpha == 0 {
|
||||
// return render.RGBA(1, 0, 0, 1)
|
||||
// }
|
||||
|
||||
return render.RGBA(deltaR, deltaG, deltaB, a.Alpha)
|
||||
}
|
||||
|
||||
// OverlayFilter applies an "overlay" blend mode between the two colors.
|
||||
func OverlayFilter(a, b render.Color) render.Color {
|
||||
// The algorithm we're going for is:
|
||||
// If a < 0.5: 2ab
|
||||
// Otherwise: 1 - 2(1 - a)(1 - b)
|
||||
munch := func(a, b uint8) uint8 {
|
||||
if a < 127 {
|
||||
return 2 * a * b
|
||||
}
|
||||
return 255 - (2 * (255 - a) * (255 - b))
|
||||
}
|
||||
|
||||
// If the pattern has a fully transparent pixel here, return transparent.
|
||||
if b.Alpha == 0 {
|
||||
return render.RGBA(1, 0, 0, 0)
|
||||
}
|
||||
|
||||
var (
|
||||
AR = a.Red
|
||||
AG = a.Green
|
||||
AB = a.Blue
|
||||
BR = b.Red
|
||||
BG = b.Green
|
||||
BB = b.Blue
|
||||
|
||||
deltaR = munch(AR, BR)
|
||||
deltaG = munch(AG, BG)
|
||||
deltaB = munch(AB, BB)
|
||||
)
|
||||
|
||||
return render.RGBA(deltaR, deltaG, deltaB, a.Alpha)
|
||||
}
|
|
@ -146,6 +146,7 @@ func (w *Canvas) loopEditable(ev *event.State) error {
|
|||
// Initialize a new Stroke for this atomic drawing operation?
|
||||
if w.currentStroke == nil {
|
||||
w.currentStroke = drawtool.NewStroke(drawtool.Freehand, w.Palette.ActiveSwatch.Color)
|
||||
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
||||
w.currentStroke.Thickness = w.BrushSize
|
||||
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
||||
w.AddStroke(w.currentStroke)
|
||||
|
@ -198,6 +199,7 @@ func (w *Canvas) loopEditable(ev *event.State) error {
|
|||
// Initialize a new Stroke for this atomic drawing operation?
|
||||
if w.currentStroke == nil {
|
||||
w.currentStroke = drawtool.NewStroke(drawtool.Line, w.Palette.ActiveSwatch.Color)
|
||||
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
||||
w.currentStroke.Thickness = w.BrushSize
|
||||
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
||||
w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y)
|
||||
|
@ -219,6 +221,7 @@ func (w *Canvas) loopEditable(ev *event.State) error {
|
|||
// Initialize a new Stroke for this atomic drawing operation?
|
||||
if w.currentStroke == nil {
|
||||
w.currentStroke = drawtool.NewStroke(drawtool.Rectangle, w.Palette.ActiveSwatch.Color)
|
||||
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
||||
w.currentStroke.Thickness = w.BrushSize
|
||||
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
||||
w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y)
|
||||
|
@ -237,6 +240,7 @@ func (w *Canvas) loopEditable(ev *event.State) error {
|
|||
if ev.Button1 {
|
||||
if w.currentStroke == nil {
|
||||
w.currentStroke = drawtool.NewStroke(drawtool.Ellipse, w.Palette.ActiveSwatch.Color)
|
||||
w.currentStroke.Pattern = w.Palette.ActiveSwatch.Pattern
|
||||
w.currentStroke.Thickness = w.BrushSize
|
||||
w.currentStroke.ExtraData = w.Palette.ActiveSwatch
|
||||
w.currentStroke.PointA = render.NewPoint(cursor.X, cursor.Y)
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/drawtool"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/pattern"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
|
@ -237,6 +238,12 @@ func (w *Canvas) DrawStrokes(e render.Engine, strokes []*drawtool.Stroke) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Does the swatch have a pattern to sample?
|
||||
color := stroke.Color
|
||||
if stroke.Pattern != "" {
|
||||
color = pattern.SampleColor(stroke.Pattern, color, rect.Point())
|
||||
}
|
||||
|
||||
// Destination rectangle to draw to screen, taking into account
|
||||
// the position of the Canvas itself.
|
||||
dest := render.Rect{
|
||||
|
@ -264,7 +271,7 @@ func (w *Canvas) DrawStrokes(e render.Engine, strokes []*drawtool.Stroke) {
|
|||
if balance.DebugCanvasStrokeColor != render.Invisible {
|
||||
e.DrawBox(balance.DebugCanvasStrokeColor, dest)
|
||||
} else {
|
||||
e.DrawBox(stroke.Color, dest)
|
||||
e.DrawBox(color, dest)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -273,6 +280,12 @@ func (w *Canvas) DrawStrokes(e render.Engine, strokes []*drawtool.Stroke) {
|
|||
continue
|
||||
}
|
||||
|
||||
// Does the swatch have a pattern to sample?
|
||||
color := stroke.Color
|
||||
if stroke.Pattern != "" {
|
||||
color = pattern.SampleColor(stroke.Pattern, color, point)
|
||||
}
|
||||
|
||||
dest := render.Point{
|
||||
X: P.X + w.Scroll.X + w.BoxThickness(1) + point.X,
|
||||
Y: P.Y + w.Scroll.Y + w.BoxThickness(1) + point.Y,
|
||||
|
@ -281,7 +294,7 @@ func (w *Canvas) DrawStrokes(e render.Engine, strokes []*drawtool.Stroke) {
|
|||
if balance.DebugCanvasStrokeColor != render.Invisible {
|
||||
e.DrawPoint(balance.DebugCanvasStrokeColor, dest)
|
||||
} else {
|
||||
e.DrawPoint(stroke.Color, dest)
|
||||
e.DrawPoint(color, dest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/pkg/balance"
|
||||
"git.kirsle.net/apps/doodle/pkg/level"
|
||||
"git.kirsle.net/apps/doodle/pkg/log"
|
||||
"git.kirsle.net/apps/doodle/pkg/pattern"
|
||||
"git.kirsle.net/apps/doodle/pkg/shmem"
|
||||
"git.kirsle.net/go/render"
|
||||
"git.kirsle.net/go/ui"
|
||||
|
@ -44,8 +45,9 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
height = (buttonSize * balance.DoodadDropperRows) + 64 // account for button borders :(
|
||||
|
||||
// Column sizes of the palette table.
|
||||
col1 = 30 // ID no.
|
||||
col1 = 15 // ID no.
|
||||
col2 = 24 // Color
|
||||
col5 = 24 // Texture
|
||||
col3 = 130 // Name
|
||||
col4 = 140 // Attributes
|
||||
// col5 = 150 // Delete
|
||||
|
@ -80,6 +82,7 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
}{
|
||||
{"ID", col1},
|
||||
{"Col", col2},
|
||||
{"Tex", col5},
|
||||
{"Name", col3},
|
||||
{"Attributes", col4},
|
||||
// {"Delete", col5},
|
||||
|
@ -125,6 +128,7 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
row.Hide()
|
||||
}
|
||||
|
||||
//////////////
|
||||
// ID label.
|
||||
idLabel := ui.NewLabel(ui.Label{
|
||||
Text: idStr + ".",
|
||||
|
@ -135,6 +139,7 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
Height: 24,
|
||||
})
|
||||
|
||||
//////////////
|
||||
// Name button (click to rename the swatch)
|
||||
btnName := ui.NewButton("Name", ui.NewLabel(ui.Label{
|
||||
TextVariable: &swatch.Name,
|
||||
|
@ -157,6 +162,7 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
})
|
||||
config.Supervisor.Add(btnName)
|
||||
|
||||
//////////////
|
||||
// Color Choice button.
|
||||
btnColor := ui.NewButton("Color", ui.NewFrame("Color Frame"))
|
||||
btnColor.SetStyle(&style.Button{
|
||||
|
@ -209,6 +215,43 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
})
|
||||
config.Supervisor.Add(btnColor)
|
||||
|
||||
//////////////
|
||||
// Texture (pattern) option.
|
||||
selTexture := ui.NewSelectBox("Texture", ui.Label{
|
||||
Font: balance.MenuFont,
|
||||
})
|
||||
selTexture.Configure(ui.Config{
|
||||
Width: col5,
|
||||
Height: 24,
|
||||
})
|
||||
|
||||
for _, t := range pattern.Builtins {
|
||||
if t.Hidden && !balance.ShowHiddenDoodads {
|
||||
continue
|
||||
}
|
||||
|
||||
selTexture.AddItem(t.Name, t.Filename, func() {})
|
||||
}
|
||||
selTexture.SetValue(swatch.Pattern)
|
||||
setImageOnSelect(selTexture, swatch.Pattern)
|
||||
|
||||
selTexture.Handle(ui.Change, func(ed ui.EventData) error {
|
||||
if val, ok := selTexture.GetValue(); ok {
|
||||
filename, _ := val.Value.(string)
|
||||
setImageOnSelect(selTexture, filename)
|
||||
|
||||
swatch.Pattern = filename
|
||||
if config.OnChange != nil {
|
||||
config.OnChange()
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
selTexture.Supervise(config.Supervisor)
|
||||
config.Supervisor.Add(selTexture)
|
||||
|
||||
//////////////
|
||||
// Attribute flags.
|
||||
attrFrame := ui.NewFrame("Attributes")
|
||||
attrFrame.Configure(ui.Config{
|
||||
|
@ -254,6 +297,7 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
}
|
||||
}
|
||||
|
||||
//////////////
|
||||
// Pack all the widgets.
|
||||
row.Pack(idLabel, ui.Pack{
|
||||
Side: ui.W,
|
||||
|
@ -263,6 +307,10 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
Side: ui.W,
|
||||
PadX: 2,
|
||||
})
|
||||
row.Pack(selTexture, ui.Pack{
|
||||
Side: ui.W,
|
||||
PadX: 2,
|
||||
})
|
||||
row.Pack(btnName, ui.Pack{
|
||||
Side: ui.W,
|
||||
PadX: 2,
|
||||
|
@ -377,3 +425,14 @@ func NewPaletteEditor(config PaletteEditor) *ui.Window {
|
|||
window.Hide()
|
||||
return window
|
||||
}
|
||||
|
||||
// Helper function to get the Tex (pattern) select box to
|
||||
// show the image by its filename... for both onChange and
|
||||
// initial render needs.
|
||||
func setImageOnSelect(sel *ui.SelectBox, filename string) {
|
||||
if image, err := pattern.GetImage(filename); err == nil {
|
||||
sel.SetImage(image)
|
||||
} else {
|
||||
sel.SetImage(nil)
|
||||
}
|
||||
}
|
||||
|
|