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]
|
||||
Open a map in Play Mode.
|
||||
|
||||
echo <text>
|
||||
Flash a message to the console.
|
||||
|
||||
clear
|
||||
Clear the console output history.
|
||||
|
||||
exit
|
||||
quit
|
||||
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
|
||||
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.
|
||||
* [ ] 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
|
||||
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**
|
||||
* [ ] Wrap a Qt GUI around the SDL window to make the Edit Mode easier to
|
||||
work with, with toolbars to select brushes and doodads and junk.
|
||||
* [ ] Work on support for solid vs. transparent, fire, etc. geometry.
|
||||
* [x] Ability to move laterally along the ground.
|
||||
* [x] Ability to walk up reasonable size slopes but be stopped when
|
||||
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
|
||||
|
|
|
@ -114,7 +114,7 @@ func (d *Doodle) Run() error {
|
|||
}
|
||||
|
||||
// Draw the debug overlay over all scenes.
|
||||
d.DrawDebugOverlay()
|
||||
// d.DrawDebugOverlay()
|
||||
|
||||
// Render the pixels to the screen.
|
||||
err = d.Engine.Present()
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"git.kirsle.net/apps/doodle/events"
|
||||
"git.kirsle.net/apps/doodle/level"
|
||||
"git.kirsle.net/apps/doodle/render"
|
||||
"git.kirsle.net/apps/doodle/ui"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ type Engine interface {
|
|||
DrawRect(Color, Rect)
|
||||
DrawBox(Color, Rect)
|
||||
DrawText(Text, Point) error
|
||||
ComputeTextRect(Text) (Rect, error)
|
||||
|
||||
// Delay for a moment using the render engine's delay method,
|
||||
// implemented by sdl.Delay(uint32)
|
||||
|
@ -68,6 +69,14 @@ type Point struct {
|
|||
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 {
|
||||
return fmt.Sprintf("Point<%d,%d>", p.X, p.Y)
|
||||
}
|
||||
|
|
|
@ -40,6 +40,31 @@ func (r *Renderer) Keysym(ev *events.State) string {
|
|||
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.
|
||||
func (r *Renderer) DrawText(text render.Text, point render.Point) error {
|
||||
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