Add initial User Interface Toolkit
With Labels and Buttons so far. * Labels are pretty much complete, they wrap a render.Text and have a Compute() method that returns their Width and Height when rendered onto an SDL Surface. * Buttons wrap a Label widget and Compute() its size and takes that into consideration when rendering itself. Buttons render themselves from scratch in a "Windows 95" themed way, with configurable colors, border widths and outline.
This commit is contained in:
parent
0efb2ab24f
commit
94c1df050b
41
README.md
41
README.md
|
@ -78,6 +78,12 @@ edit [filename.json]
|
||||||
play [filename.json]
|
play [filename.json]
|
||||||
Open a map in Play Mode.
|
Open a map in Play Mode.
|
||||||
|
|
||||||
|
echo <text>
|
||||||
|
Flash a message to the console.
|
||||||
|
|
||||||
|
clear
|
||||||
|
Clear the console output history.
|
||||||
|
|
||||||
exit
|
exit
|
||||||
quit
|
quit
|
||||||
Close the developer console.
|
Close the developer console.
|
||||||
|
@ -111,18 +117,39 @@ As a rough idea of the milestones needed for this game to work:
|
||||||
* [ ] Add support for the shell to pop itself open and ask the user for
|
* [ ] Add support for the shell to pop itself open and ask the user for
|
||||||
input prompts.
|
input prompts.
|
||||||
|
|
||||||
## Platformer
|
## Alpha Platformer
|
||||||
|
|
||||||
* [ ] Inflate the pixel history from the map file into a full lookup grid
|
* [x] Inflate the pixel history from the map file into a full lookup grid
|
||||||
of `(X,Y)` coordinates. This will be useful for collision detection.
|
of `(X,Y)` coordinates. This will be useful for collision detection.
|
||||||
* [ ] Create a dummy player character sprite, probably just a
|
* [x] Create a dummy player character sprite, probably just a
|
||||||
`render.Circle()`. In **Play Mode** run collision checks and gravity on
|
`render.Circle()`. In **Play Mode** run collision checks and gravity on
|
||||||
the player sprite.
|
the player sprite.
|
||||||
* [ ] Get basic movement and collision working. With a cleanup this can
|
* [x] Create the concept of the Doodad and make the player character
|
||||||
|
implement one.
|
||||||
|
* [x] Get basic movement and collision working. With a cleanup this can
|
||||||
make a workable **ALPHA RELEASE**
|
make a workable **ALPHA RELEASE**
|
||||||
* [ ] Wrap a Qt GUI around the SDL window to make the Edit Mode easier to
|
* [x] Ability to move laterally along the ground.
|
||||||
work with, with toolbars to select brushes and doodads and junk.
|
* [x] Ability to walk up reasonable size slopes but be stopped when
|
||||||
* [ ] Work on support for solid vs. transparent, fire, etc. geometry.
|
running against a steeper wall.
|
||||||
|
* [x] Basic gravity
|
||||||
|
|
||||||
|
## UI Overhaul
|
||||||
|
|
||||||
|
* [x] Create a user interface toolkit which will be TREMENDOUSLY helpful
|
||||||
|
for the rest of this program.
|
||||||
|
* [x] Labels
|
||||||
|
* [ ] Buttons (text only is OK)
|
||||||
|
* [x] Buttons wrap their Label and dynamically compute their size based
|
||||||
|
on how wide the label will render, plus padding and border.
|
||||||
|
* [x] Border colors and widths and paddings are all configurable.
|
||||||
|
* [ ] Buttons should interact with the cursor and be hoverable and
|
||||||
|
clickable.
|
||||||
|
* [ ] UI Manager that will keep track of buttons to know when the mouse
|
||||||
|
is interacting with them.
|
||||||
|
* [ ] Frames
|
||||||
|
* [ ] Windows (fixed, non-draggable is OK)
|
||||||
|
* [ ] Expand the Palette support in levels for solid vs. transparent, fire,
|
||||||
|
etc. with UI toolbar to choose palettes.
|
||||||
* [ ] ???
|
* [ ] ???
|
||||||
|
|
||||||
# Building
|
# Building
|
||||||
|
|
|
@ -114,7 +114,7 @@ func (d *Doodle) Run() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw the debug overlay over all scenes.
|
// Draw the debug overlay over all scenes.
|
||||||
d.DrawDebugOverlay()
|
// d.DrawDebugOverlay()
|
||||||
|
|
||||||
// Render the pixels to the screen.
|
// Render the pixels to the screen.
|
||||||
err = d.Engine.Present()
|
err = d.Engine.Present()
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"git.kirsle.net/apps/doodle/events"
|
"git.kirsle.net/apps/doodle/events"
|
||||||
"git.kirsle.net/apps/doodle/level"
|
"git.kirsle.net/apps/doodle/level"
|
||||||
"git.kirsle.net/apps/doodle/render"
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EditorScene manages the "Edit Level" game mode.
|
// EditorScene manages the "Edit Level" game mode.
|
||||||
|
@ -132,6 +133,59 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error {
|
||||||
func (s *EditorScene) Draw(d *Doodle) error {
|
func (s *EditorScene) Draw(d *Doodle) error {
|
||||||
s.canvas.Draw(d.Engine)
|
s.canvas.Draw(d.Engine)
|
||||||
|
|
||||||
|
label := ui.NewLabel(render.Text{
|
||||||
|
Text: "Hello UI toolkit!",
|
||||||
|
Size: 26,
|
||||||
|
Color: render.Pink,
|
||||||
|
Stroke: render.SkyBlue,
|
||||||
|
Shadow: render.Black,
|
||||||
|
})
|
||||||
|
label.SetPoint(render.NewPoint(128, 128))
|
||||||
|
label.Compute(d.Engine)
|
||||||
|
log.Info("Label rect: %+v", label.Size())
|
||||||
|
log.Info("Label at: %s", label.Point())
|
||||||
|
label.Present(d.Engine)
|
||||||
|
|
||||||
|
button := ui.NewButton(*ui.NewLabel(render.Text{
|
||||||
|
Text: "Hello",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.Black,
|
||||||
|
}))
|
||||||
|
button.SetPoint(render.NewPoint(200, 200))
|
||||||
|
button.Present(d.Engine)
|
||||||
|
|
||||||
|
// Point and size of that button
|
||||||
|
point := button.Point()
|
||||||
|
size := button.Size()
|
||||||
|
|
||||||
|
button2 := ui.NewButton(*ui.NewLabel(render.Text{
|
||||||
|
Text: "World!",
|
||||||
|
Size: 14,
|
||||||
|
Color: render.Blue,
|
||||||
|
}))
|
||||||
|
button2.SetPoint(render.Point{
|
||||||
|
X: point.X + size.W,
|
||||||
|
Y: point.Y,
|
||||||
|
})
|
||||||
|
button2.Present(d.Engine)
|
||||||
|
|
||||||
|
button.SetText("Buttons that don't click yet")
|
||||||
|
button.SetPoint(render.NewPoint(250, 300))
|
||||||
|
button.Label.Text.Size = 24
|
||||||
|
button.Border = 8
|
||||||
|
button.Outline = 4
|
||||||
|
button.Present(d.Engine)
|
||||||
|
|
||||||
|
button2.SetText("Multiple colors, too")
|
||||||
|
button2.Label.Text.Color = render.White
|
||||||
|
button2.Background = render.RGBA(0, 153, 255, 255)
|
||||||
|
button2.HighlightColor = render.RGBA(100, 200, 255, 255)
|
||||||
|
button2.ShadowColor = render.RGBA(0, 100, 153, 255)
|
||||||
|
button2.SetPoint(render.NewPoint(10, 300))
|
||||||
|
button2.Present(d.Engine)
|
||||||
|
|
||||||
|
_ = label
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ type Engine interface {
|
||||||
DrawRect(Color, Rect)
|
DrawRect(Color, Rect)
|
||||||
DrawBox(Color, Rect)
|
DrawBox(Color, Rect)
|
||||||
DrawText(Text, Point) error
|
DrawText(Text, Point) error
|
||||||
|
ComputeTextRect(Text) (Rect, error)
|
||||||
|
|
||||||
// Delay for a moment using the render engine's delay method,
|
// Delay for a moment using the render engine's delay method,
|
||||||
// implemented by sdl.Delay(uint32)
|
// implemented by sdl.Delay(uint32)
|
||||||
|
@ -68,6 +69,14 @@ type Point struct {
|
||||||
Y int32
|
Y int32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewPoint makes a new Point at an X,Y coordinate.
|
||||||
|
func NewPoint(x, y int32) Point {
|
||||||
|
return Point{
|
||||||
|
X: x,
|
||||||
|
Y: y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (p Point) String() string {
|
func (p Point) String() string {
|
||||||
return fmt.Sprintf("Point<%d,%d>", p.X, p.Y)
|
return fmt.Sprintf("Point<%d,%d>", p.X, p.Y)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,31 @@ func (r *Renderer) Keysym(ev *events.State) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ComputeTextRect computes and returns a Rect for how large the text would
|
||||||
|
// appear if rendered.
|
||||||
|
func (r *Renderer) ComputeTextRect(text render.Text) (render.Rect, error) {
|
||||||
|
var (
|
||||||
|
rect render.Rect
|
||||||
|
font *ttf.Font
|
||||||
|
surface *sdl.Surface
|
||||||
|
color = ColorToSDL(text.Color)
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if font, err = LoadFont(text.Size); err != nil {
|
||||||
|
return rect, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if surface, err = font.RenderUTF8Blended(text.Text, color); err != nil {
|
||||||
|
return rect, err
|
||||||
|
}
|
||||||
|
defer surface.Free()
|
||||||
|
|
||||||
|
rect.W = surface.W
|
||||||
|
rect.H = surface.H
|
||||||
|
return rect, err
|
||||||
|
}
|
||||||
|
|
||||||
// DrawText draws text on the canvas.
|
// DrawText draws text on the canvas.
|
||||||
func (r *Renderer) DrawText(text render.Text, point render.Point) error {
|
func (r *Renderer) DrawText(text render.Text, point render.Point) error {
|
||||||
var (
|
var (
|
||||||
|
|
98
ui/button.go
Normal file
98
ui/button.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"git.kirsle.net/apps/doodle/render"
|
||||||
|
"git.kirsle.net/apps/doodle/ui/theme"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Button is a clickable button.
|
||||||
|
type Button struct {
|
||||||
|
BaseWidget
|
||||||
|
Label Label
|
||||||
|
Padding int32
|
||||||
|
Border int32
|
||||||
|
Outline int32
|
||||||
|
|
||||||
|
// Color options.
|
||||||
|
Background render.Color
|
||||||
|
HighlightColor render.Color
|
||||||
|
ShadowColor render.Color
|
||||||
|
OutlineColor render.Color
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewButton creates a new Button.
|
||||||
|
func NewButton(label Label) *Button {
|
||||||
|
return &Button{
|
||||||
|
Label: label,
|
||||||
|
Padding: 4, // TODO magic number
|
||||||
|
Border: 2,
|
||||||
|
Outline: 1,
|
||||||
|
|
||||||
|
// Default theme colors.
|
||||||
|
Background: theme.ButtonBackgroundColor,
|
||||||
|
HighlightColor: theme.ButtonHighlightColor,
|
||||||
|
ShadowColor: theme.ButtonShadowColor,
|
||||||
|
OutlineColor: theme.ButtonOutlineColor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetText quickly changes the text of the label.
|
||||||
|
func (w *Button) SetText(text string) {
|
||||||
|
w.Label.Text.Text = text
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the size of the button.
|
||||||
|
func (w *Button) Compute(e render.Engine) {
|
||||||
|
// Compute the size of the inner widget first.
|
||||||
|
w.Label.Compute(e)
|
||||||
|
size := w.Label.Size()
|
||||||
|
w.Resize(render.Rect{
|
||||||
|
W: size.W + (w.Padding * 2) + (w.Border * 2) + (w.Outline * 2),
|
||||||
|
H: size.H + (w.Padding * 2) + (w.Border * 2) + (w.Outline * 2),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the button.
|
||||||
|
func (w *Button) Present(e render.Engine) {
|
||||||
|
w.Compute(e)
|
||||||
|
P := w.Point()
|
||||||
|
S := w.Size()
|
||||||
|
|
||||||
|
box := render.Rect{
|
||||||
|
X: P.X,
|
||||||
|
Y: P.Y,
|
||||||
|
W: S.W,
|
||||||
|
H: S.H,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw the outline layer as the full size of the widget.
|
||||||
|
e.DrawBox(w.OutlineColor, render.Rect{
|
||||||
|
X: P.X - w.Outline,
|
||||||
|
Y: P.Y - w.Outline,
|
||||||
|
W: S.W + (w.Outline * 2),
|
||||||
|
H: S.H + (w.Outline * 2),
|
||||||
|
})
|
||||||
|
|
||||||
|
// Highlight on the top left edge.
|
||||||
|
e.DrawBox(w.HighlightColor, box)
|
||||||
|
box.W = S.W
|
||||||
|
|
||||||
|
// Shadow on the bottom right edge.
|
||||||
|
box.X += w.Border
|
||||||
|
box.Y += w.Border
|
||||||
|
box.W -= w.Border
|
||||||
|
box.H -= w.Border
|
||||||
|
e.DrawBox(w.ShadowColor, box)
|
||||||
|
|
||||||
|
// Background color of the button.
|
||||||
|
box.W -= w.Border
|
||||||
|
box.H -= w.Border
|
||||||
|
e.DrawBox(w.Background, box)
|
||||||
|
|
||||||
|
// Draw the text label inside.
|
||||||
|
w.Label.SetPoint(render.Point{
|
||||||
|
X: P.X + w.Padding + w.Border + w.Outline,
|
||||||
|
Y: P.Y + w.Padding + w.Border + w.Outline,
|
||||||
|
})
|
||||||
|
w.Label.Present(e)
|
||||||
|
}
|
31
ui/label.go
Normal file
31
ui/label.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// Label is a simple text label widget.
|
||||||
|
type Label struct {
|
||||||
|
BaseWidget
|
||||||
|
width int32
|
||||||
|
height int32
|
||||||
|
Text render.Text
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLabel creates a new label.
|
||||||
|
func NewLabel(t render.Text) *Label {
|
||||||
|
return &Label{
|
||||||
|
Text: t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute the size of the label widget.
|
||||||
|
func (w *Label) Compute(e render.Engine) {
|
||||||
|
rect, err := e.ComputeTextRect(w.Text)
|
||||||
|
w.Resize(rect)
|
||||||
|
_ = rect
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Present the label widget.
|
||||||
|
func (w *Label) Present(e render.Engine) {
|
||||||
|
e.DrawText(w.Text, w.Point())
|
||||||
|
}
|
11
ui/theme/theme.go
Normal file
11
ui/theme/theme.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package theme
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// Color schemes.
|
||||||
|
var (
|
||||||
|
ButtonBackgroundColor = render.RGBA(250, 250, 250, 255)
|
||||||
|
ButtonHighlightColor = render.RGBA(128, 128, 128, 255)
|
||||||
|
ButtonShadowColor = render.RGBA(20, 20, 20, 255)
|
||||||
|
ButtonOutlineColor = render.Black
|
||||||
|
)
|
56
ui/widget.go
Normal file
56
ui/widget.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package ui
|
||||||
|
|
||||||
|
import "git.kirsle.net/apps/doodle/render"
|
||||||
|
|
||||||
|
// Widget is a user interface element.
|
||||||
|
type Widget interface {
|
||||||
|
Width() int32 // Get width
|
||||||
|
Height() int32 // Get height
|
||||||
|
SetWidth(int32) // Set
|
||||||
|
SetHeight(int32) // Set
|
||||||
|
Point() render.Point
|
||||||
|
SetPoint(render.Point)
|
||||||
|
Size() render.Rect // Return the Width and Height of the widget.
|
||||||
|
Resize(render.Rect)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
Compute(render.Engine)
|
||||||
|
|
||||||
|
// Render the final widget onto the drawing engine.
|
||||||
|
Present(render.Engine)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BaseWidget holds common functionality for all widgets, such as managing
|
||||||
|
// their widths and heights.
|
||||||
|
type BaseWidget struct {
|
||||||
|
width int32
|
||||||
|
height int32
|
||||||
|
point render.Point
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point returns the X,Y position of the widget on the window.
|
||||||
|
func (w *BaseWidget) Point() render.Point {
|
||||||
|
return w.point
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPoint updates the X,Y position of the widget relative to the window.
|
||||||
|
func (w *BaseWidget) SetPoint(v render.Point) {
|
||||||
|
w.point = v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size returns the box with W and H attributes containing the size of the
|
||||||
|
// widget. The X,Y attributes of the box are ignored and zero.
|
||||||
|
func (w *BaseWidget) Size() render.Rect {
|
||||||
|
return render.Rect{
|
||||||
|
W: w.width,
|
||||||
|
H: w.height,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resize sets the size of the widget to the .W and .H attributes of a rect.
|
||||||
|
func (w *BaseWidget) Resize(v render.Rect) {
|
||||||
|
w.width = v.W
|
||||||
|
w.height = v.H
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user