diff --git a/menu/hints.go b/menu/hints.go index fc67d825..8c83540f 100644 --- a/menu/hints.go +++ b/menu/hints.go @@ -5,14 +5,38 @@ import ( "github.com/libretro/ludo/input" ) -// Used to easily compose different hint bars based on the context. -func stackHint(stack *float32, icon uint32, label string, h int) { - vid.Font.SetColor(darkGrey) - *stack += 30 * menu.ratio - vid.DrawImage(icon, *stack, float32(h)-70*menu.ratio, 70*menu.ratio, 70*menu.ratio, 1.0, darkGrey) - *stack += 70 * menu.ratio - vid.Font.Printf(*stack, float32(h)-23*menu.ratio, 0.4*menu.ratio, label) - *stack += vid.Font.Width(0.4*menu.ratio, label) +// HintBar is the bar showing at the bottom of the screen +func HintBar(props *Props, children ...func()) func() { + w, h := vid.Window.GetFramebufferSize() + return HBox(&Props{ + Y: float32(h) - 70*menu.ratio, + Width: float32(w), + Height: 70 * menu.ratio, + Color: lightGrey, + Hidden: props.Hidden, + }, + children..., + ) +} + +// Hint is a widget combining an icon and a label +func Hint(props *Props, icon uint32, title string) func() { + darkGrey := darkGrey + return HBox(props, + Box(&Props{Width: 15}), + Image(&Props{ + Width: 70 * menu.ratio, + Height: 70 * menu.ratio, + Scale: 1, + Color: darkGrey, + }, icon), + Label(&Props{ + Height: 70 * menu.ratio, + Scale: 0.4 * menu.ratio, + Color: darkGrey, + }, title), + Box(&Props{Width: 15}), + ) } func hintIcons() (arrows, upDown, leftRight, a, b, x, y, start, slct, guide uint32) { diff --git a/menu/notifications.go b/menu/notifications.go index 8bf19f4c..26b2b305 100644 --- a/menu/notifications.go +++ b/menu/notifications.go @@ -23,32 +23,41 @@ var severityBgColor = map[ntf.Severity]video.Color{ func (m *Menu) RenderNotifications() { fbw, fbh := vid.Window.GetFramebufferSize() vid.Font.UpdateResolution(fbw, fbh) - var h float32 = 75 - stack := h + stack := float32(0) for _, n := range ntf.List() { - fading := n.Duration * 4 - if fading > 1 { - fading = 1 - } - offset := fading*h - h - lw := vid.Font.Width(0.5*m.ratio, n.Message) - fg := severityFgColor[n.Severity] - bg := severityBgColor[n.Severity] - vid.DrawRect( - 25*m.ratio, - (stack+offset-46)*m.ratio, - lw+40*m.ratio, - 70*m.ratio, - 0.25, - bg.Alpha(fading), - ) - vid.Font.SetColor(fg.Alpha(fading)) - vid.Font.Printf( - 45*m.ratio, - (stack+offset)*m.ratio, - 0.5*m.ratio, - n.Message, - ) - stack += h + offset + offset := minf32(n.Duration*4, 1) * 80 + lw := vid.Font.Width(0.4*menu.ratio, n.Message) + + Toast(&Props{ + X: 25 * menu.ratio, + Y: (stack + offset - 80 + 25) * menu.ratio, + Width: lw + 70*menu.ratio + 20*menu.ratio, + Height: 70 * menu.ratio, + BorderRadius: 0.25, + }, n)() + + stack += offset } } + +// Toast can render a notification +func Toast(props *Props, n *ntf.Notification) func() { + alpha := minf32(n.Duration*4, 1) + fg := severityFgColor[n.Severity].Alpha(alpha) + bg := severityBgColor[n.Severity].Alpha(alpha) + props.Color = bg + + return HBox(props, + Image(&Props{ + Width: props.Height, + Height: props.Height, + Scale: 1, + Color: fg, + }, menu.icons["core-infos"]), + Label(&Props{ + Height: props.Height, + Scale: 0.4 * menu.ratio, + Color: fg, + }, n.Message), + ) +} diff --git a/menu/scene.go b/menu/scene.go index 0548c613..b3b230fb 100644 --- a/menu/scene.go +++ b/menu/scene.go @@ -174,6 +174,7 @@ func thumbnailDrawCursor(list *entry) { // It also display values of settings if we are displaying a settings scene func genericRender(list *entry) { w, h := vid.Window.GetFramebufferSize() + fontOffset := 64 * 0.7 * menu.ratio * 0.3 genericDrawCursor(list) @@ -184,8 +185,6 @@ func genericRender(list *entry) { continue } - fontOffset := 64 * 0.7 * menu.ratio * 0.3 - vid.DrawImage(menu.icons[e.icon], 610*menu.ratio-64*0.5*menu.ratio, float32(h)*e.yp-14*menu.ratio-64*0.5*menu.ratio+fontOffset, @@ -215,16 +214,11 @@ func genericRender(list *entry) { } func genericDrawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - _, upDown, _, a, b, _, _, _, _, guide := hintIcons() - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - stackHint(&stack, a, "OK", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{}, a, "OK"), + )() } diff --git a/menu/scene_core_options.go b/menu/scene_core_options.go index 7c11ae7f..89fdf43a 100644 --- a/menu/scene_core_options.go +++ b/menu/scene_core_options.go @@ -80,16 +80,11 @@ func (s *sceneCoreOptions) render() { } func (s *sceneCoreOptions) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - _, upDown, leftRight, _, b, _, _, _, _, guide := hintIcons() - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - stackHint(&stack, leftRight, "SET", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{}, leftRight, "SET"), + )() } diff --git a/menu/scene_dialog.go b/menu/scene_dialog.go index d90969ae..d8a8bce0 100644 --- a/menu/scene_dialog.go +++ b/menu/scene_dialog.go @@ -50,59 +50,52 @@ func (s *sceneDialog) render() { w, h := vid.Window.GetFramebufferSize() fw := float32(w) fh := float32(h) - vid.DrawRect(0, 0, fw, fh, 0, black.Alpha(0.85)) - var width float32 = 1000 - var height float32 = 400 - - vid.DrawRect( - fw/2-width/2*menu.ratio, - fh/2-height/2*menu.ratio, - width*menu.ratio, - height*menu.ratio, - 0.05, - white, - ) - - vid.Font.SetColor(orange) - msg1 := "A game is currently running." - lw1 := vid.Font.Width(0.7*menu.ratio, msg1) - vid.Font.Printf(fw/2-lw1/2, fh/2-120*menu.ratio+20*menu.ratio, 0.7*menu.ratio, msg1) - vid.Font.SetColor(black) - msg2 := "If you have not saved yet, your progress will be lost." - lw2 := vid.Font.Width(0.5*menu.ratio, msg2) - vid.Font.Printf(fw/2-lw2/2, fh/2-30*menu.ratio+20*menu.ratio, 0.5*menu.ratio, msg2) - msg3 := "Do you want to exit Ludo anyway?" - lw3 := vid.Font.Width(0.5*menu.ratio, msg3) - vid.Font.Printf(fw/2-lw3/2, fh/2+30*menu.ratio+20*menu.ratio, 0.5*menu.ratio, msg3) - - vid.Font.SetColor(darkGrey) - - var margin float32 = 15 + width := 1000 * menu.ratio + height := 400 * menu.ratio _, _, _, a, b, _, _, _, _, _ := hintIcons() - vid.DrawImage( - b, - fw/2-width/2*menu.ratio+margin*menu.ratio, - fh/2+height/2*menu.ratio-70*menu.ratio-margin*menu.ratio, - 70*menu.ratio, 70*menu.ratio, 1.0, darkGrey) - vid.Font.Printf( - fw/2-width/2*menu.ratio+margin*menu.ratio+70*menu.ratio, - fh/2+height/2*menu.ratio-23*menu.ratio-margin*menu.ratio, - 0.4*menu.ratio, - "NO") - - vid.DrawImage( - a, - fw/2+width/2*menu.ratio-150*menu.ratio-margin*menu.ratio, - fh/2+height/2*menu.ratio-70*menu.ratio-margin*menu.ratio, - 70*menu.ratio, 70*menu.ratio, 1.0, darkGrey) - vid.Font.Printf( - fw/2+width/2*menu.ratio-150*menu.ratio-margin*menu.ratio+70*menu.ratio, - fh/2+height/2*menu.ratio-23*menu.ratio-margin*menu.ratio, - 0.4*menu.ratio, - "YES") + // Background + Box(&Props{Width: fw, Height: fh, Color: black.Alpha(0.85)}, + // Dialog + VBox(&Props{ + X: fw/2 - width/2, + Y: fh/2 - height/2, + Width: width, + Height: height, + BorderRadius: 0.05, + Color: white, + }, + // Title + Label(&Props{ + TextAlign: "center", + Scale: 0.7 * menu.ratio, + Color: orange, + Height: 150 * menu.ratio, + }, "A game is currently running"), + // Messages + Label(&Props{ + TextAlign: "center", + Scale: 0.5 * menu.ratio, + Color: black, + Height: 60 * menu.ratio, + }, "If you have not saved yet, your progress will be lost."), + Label(&Props{ + TextAlign: "center", + Scale: 0.5 * menu.ratio, + Color: black, + Height: 60 * menu.ratio, + }, "Do you want to exit Ludo anyway?"), + Box(&Props{Height: 40 * menu.ratio}), + Box(&Props{}, + // The NO Hint + Hint(&Props{}, b, "NO"), + // The YES Hint + Hint(&Props{X: width - 175*menu.ratio}, a, "YES"), + ), + ), + )() } func (s *sceneDialog) drawHintBar() { diff --git a/menu/scene_history.go b/menu/scene_history.go index 5447cc27..e119b43d 100644 --- a/menu/scene_history.go +++ b/menu/scene_history.go @@ -189,16 +189,11 @@ func (s *sceneHistory) render() { } func (s *sceneHistory) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - _, upDown, _, a, b, _, _, _, _, guide := hintIcons() - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - stackHint(&stack, a, "RUN", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{}, a, "RUN"), + )() } diff --git a/menu/scene_keyboard.go b/menu/scene_keyboard.go index c5068a76..eecc73a6 100644 --- a/menu/scene_keyboard.go +++ b/menu/scene_keyboard.go @@ -204,16 +204,13 @@ func (s *sceneKeyboard) render() { } func (s *sceneKeyboard) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - arrows, _, _, a, b, x, y, start, _, _ := hintIcons() - - var stack float32 - stackHint(&stack, arrows, "SELECT", h) - stackHint(&stack, b, "BACK", h) - stackHint(&stack, x, "SHIFT", h) - stackHint(&stack, y, "DELETE", h) - stackHint(&stack, a, "INSERT", h) - stackHint(&stack, start, "DONE", h) + HintBar(&Props{}, + Hint(&Props{}, arrows, "SELECT"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{}, x, "SHIFT"), + Hint(&Props{}, y, "DELETE"), + Hint(&Props{}, a, "INSERT"), + Hint(&Props{}, start, "DONE"), + )() } diff --git a/menu/scene_playlist.go b/menu/scene_playlist.go index 6dbf0728..cf4d7598 100644 --- a/menu/scene_playlist.go +++ b/menu/scene_playlist.go @@ -210,16 +210,11 @@ func (s *scenePlaylist) render() { } func (s *scenePlaylist) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - _, upDown, _, a, b, _, _, _, _, guide := hintIcons() - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - stackHint(&stack, a, "RUN", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{}, a, "RUN"), + )() } diff --git a/menu/scene_savestates.go b/menu/scene_savestates.go index 82fe791e..20f46c3a 100644 --- a/menu/scene_savestates.go +++ b/menu/scene_savestates.go @@ -135,22 +135,13 @@ func (s *sceneSavestates) render() { } func (s *sceneSavestates) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - ptr := menu.stack[len(menu.stack)-1].Entry().ptr - _, upDown, _, a, b, _, _, _, _, guide := hintIcons() - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - if ptr == 0 { - stackHint(&stack, a, "SAVE", h) - } else { - stackHint(&stack, a, "LOAD", h) - } + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{Hidden: ptr != 0}, a, "SAVE"), + Hint(&Props{Hidden: ptr == 0}, a, "LOAD"), + )() } diff --git a/menu/scene_settings.go b/menu/scene_settings.go index b2882ecf..1e03847f 100644 --- a/menu/scene_settings.go +++ b/menu/scene_settings.go @@ -260,21 +260,13 @@ func (s *sceneSettings) render() { } func (s *sceneSettings) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - + cb := s.children[s.ptr].callbackOK _, upDown, leftRight, a, b, _, _, _, _, guide := hintIcons() - - var stack float32 - list := menu.stack[len(menu.stack)-1].Entry() - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - if list.children[list.ptr].callbackOK != nil { - stackHint(&stack, a, "SET", h) - } else { - stackHint(&stack, leftRight, "SET", h) - } + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{Hidden: cb == nil}, a, "SET"), + Hint(&Props{Hidden: cb != nil}, leftRight, "SET"), + )() } diff --git a/menu/scene_tabs.go b/menu/scene_tabs.go index 49371081..5655ee69 100644 --- a/menu/scene_tabs.go +++ b/menu/scene_tabs.go @@ -256,47 +256,64 @@ func (tabs *sceneTabs) update(dt float32) { } } +// Tab is a widget that draws the homepage hexagon plus title +func Tab(props *Props, i int, e entry) func() { + c := colorful.Hcl(float64(i)*20, 0.5, 0.5) + return Box(props, + Box(&Props{Width: e.width * menu.ratio}, + Image(&Props{ + X: e.width/2*menu.ratio - 220*e.scale*menu.ratio, + Y: -220 * e.scale * menu.ratio, + Width: 440 * menu.ratio, + Height: 440 * menu.ratio, + Scale: e.scale, + Color: video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.iconAlpha}, + }, menu.icons["hexagon"]), + Image(&Props{ + X: e.width/2*menu.ratio - 128*e.scale*menu.ratio, + Y: -128 * e.scale * menu.ratio, + Width: 256 * menu.ratio, + Height: 256 * menu.ratio, + Scale: e.scale, + Color: video.Color{R: 1, G: 1, B: 1, A: e.iconAlpha}, + }, menu.icons[e.icon]), + Label(&Props{ + Y: 250 * menu.ratio, + TextAlign: "center", + Scale: 0.6 * menu.ratio, + Color: video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.labelAlpha}, + }, e.label), + Label(&Props{ + Y: 330 * menu.ratio, + TextAlign: "center", + Scale: 0.4 * menu.ratio, + Color: video.Color{R: float32(c.R), G: float32(c.B), B: float32(c.G), A: e.labelAlpha}, + }, e.subLabel), + ), + ) +} + func (tabs sceneTabs) render() { _, h := vid.Window.GetFramebufferSize() - stackWidth := 710 * menu.ratio + var children []func() for i, e := range tabs.children { - - cf := colorful.Hcl(float64(i)*20, 0.5, 0.5) - c := video.Color{R: float32(cf.R), G: float32(cf.B), B: float32(cf.G), A: e.iconAlpha} - - x := -menu.scroll*menu.ratio + stackWidth + e.width/2*menu.ratio - - stackWidth += e.width*menu.ratio + e.margin*menu.ratio - - if e.labelAlpha > 0 { - vid.Font.SetColor(c.Alpha(e.labelAlpha)) - lw := vid.Font.Width(0.5*menu.ratio, e.label) - vid.Font.Printf(x-lw/2, float32(int(float32(h)/2+250*menu.ratio)), 0.5*menu.ratio, e.label) - lw = vid.Font.Width(0.4*menu.ratio, e.subLabel) - vid.Font.Printf(x-lw/2, float32(int(float32(h)/2+330*menu.ratio)), 0.4*menu.ratio, e.subLabel) - } - - vid.DrawImage(menu.icons["hexagon"], - x-220*e.scale*menu.ratio, float32(h)/2-220*e.scale*menu.ratio, - 440*menu.ratio, 440*menu.ratio, e.scale, c) - - vid.DrawImage(menu.icons[e.icon], - x-128*e.scale*menu.ratio, float32(h)/2-128*e.scale*menu.ratio, - 256*menu.ratio, 256*menu.ratio, e.scale, white.Alpha(e.iconAlpha)) + children = append(children, Tab(&Props{ + Y: float32(h) / 2, + Width: e.width*menu.ratio + e.margin*menu.ratio, + }, i, e)) } + + HBox(&Props{ + X: 710*menu.ratio - menu.scroll*menu.ratio, + }, children...)() } func (tabs sceneTabs) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - _, _, leftRight, a, _, _, _, _, _, guide := hintIcons() - - var stack float32 - if state.Global.CoreRunning { - stackHint(&stack, guide, "RESUME", h) - } - stackHint(&stack, leftRight, "NAVIGATE", h) - stackHint(&stack, a, "OPEN", h) + HintBar(&Props{}, + Hint(&Props{Hidden: !state.Global.CoreRunning}, guide, "RESUME"), + Hint(&Props{}, leftRight, "NAVIGATE"), + Hint(&Props{}, a, "OPEN"), + )() } diff --git a/menu/scene_wifi.go b/menu/scene_wifi.go index ec78295b..507056e8 100644 --- a/menu/scene_wifi.go +++ b/menu/scene_wifi.go @@ -85,15 +85,10 @@ func (s *sceneWiFi) render() { } func (s *sceneWiFi) drawHintBar() { - w, h := vid.Window.GetFramebufferSize() - vid.DrawRect(0, float32(h)-70*menu.ratio, float32(w), 70*menu.ratio, 0, lightGrey) - _, upDown, _, a, b, _, _, _, _, _ := hintIcons() - - var stack float32 - stackHint(&stack, upDown, "NAVIGATE", h) - stackHint(&stack, b, "BACK", h) - if s.children[0].callbackOK != nil { - stackHint(&stack, a, "CONNECT", h) - } + HintBar(&Props{}, + Hint(&Props{}, upDown, "NAVIGATE"), + Hint(&Props{}, b, "BACK"), + Hint(&Props{Hidden: s.children[0].callbackOK == nil}, a, "CONNECT"), + )() } diff --git a/menu/widgets.go b/menu/widgets.go new file mode 100644 index 00000000..b19f323e --- /dev/null +++ b/menu/widgets.go @@ -0,0 +1,191 @@ +package menu + +import ( + "github.com/libretro/ludo/video" +) + +// Direction of the children widgets in a container +type Direction uint8 + +const ( + // Vertical means that the children will stack on top of each others + Vertical Direction = iota + // Horizontal means that the children will stack from left to right + Horizontal +) + +// Props are the properties of a widget +type Props struct { + X, Y float32 + Width, Height float32 + BorderRadius float32 + Scale float32 + TextAlign string + ContentWidth float32 + ContentHeight float32 + Direction Direction + Color video.Color + Hidden bool +} + +type propsStack []*Props + +func (s *propsStack) Push(v *Props) *propsStack { + *s = append(*s, v) + return s +} + +func (s *propsStack) Pop() { + l := len(*s) + *s = (*s)[:l-1] +} + +func (s *propsStack) Last() *Props { + if len(*s) == 0 { + return nil + } + return (*s)[len(*s)-1] +} + +func (s *propsStack) Sum() *Props { + sum := Props{} + for _, t := range *s { + sum.X += t.X + sum.Y += t.Y + } + return &sum +} + +var wStack = &propsStack{} +var tStack = &propsStack{} + +// Box is a basic widget container +func Box(props *Props, children ...func()) func() { + return func() { + if props.Hidden { + return + } + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.DrawRect(sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.BorderRadius, props.Color) + for _, child := range children { + child() + } + updateParent(parent, maxf32(props.Width, self.ContentWidth), maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// HBox is an horizontal widget container +func HBox(props *Props, children ...func()) func() { + return func() { + if props.Hidden { + return + } + props.Direction = Horizontal + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.DrawRect(sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.BorderRadius, props.Color) + for _, child := range children { + tStack.Push(&Props{X: wStack.Last().ContentWidth}) + child() + tStack.Pop() + } + updateParent(parent, maxf32(props.Width, self.ContentWidth), maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// VBox is a vertical widget container +func VBox(props *Props, children ...func()) func() { + return func() { + if props.Hidden { + return + } + props.Direction = Vertical + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.DrawRect(sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.BorderRadius, props.Color) + for _, child := range children { + tStack.Push(&Props{Y: wStack.Last().ContentHeight}) + child() + tStack.Pop() + } + updateParent(parent, maxf32(props.Width, self.ContentWidth), maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// Label a widget is used to draw some text +func Label(props *Props, msg string) func() { + return func() { + if props.Hidden || props.Color.A == 0 { + return + } + parent := wStack.Last() + self := wStack.Push(props).Last() + tsum := tStack.Push(&Props{}).Sum() + sum := wStack.Sum() + vid.Font.SetColor(props.Color) + textWidth := vid.Font.Width(props.Scale, msg) + switch props.TextAlign { + case "center": + vid.Font.Printf(sum.X+tsum.X+parent.Width/2-textWidth/2, sum.Y+tsum.Y+self.Height*0.67, props.Scale, msg) + default: + vid.Font.Printf(sum.X+tsum.X, sum.Y+tsum.Y+self.Height*0.67, props.Scale, msg) + } + updateParent(parent, textWidth, maxf32(props.Height, self.ContentHeight)) + tStack.Pop() + wStack.Pop() + } +} + +// Image is a widget used to display an image +func Image(props *Props, image uint32) func() { + return func() { + if props.Hidden { + return + } + parent := wStack.Last() + sum := wStack.Push(props).Sum() + tsum := tStack.Push(&Props{}).Sum() + vid.DrawImage(image, sum.X+tsum.X, sum.Y+tsum.Y, props.Width, props.Height, props.Scale, props.Color) + updateParent(parent, props.Width, props.Height) + tStack.Pop() + wStack.Pop() + } +} + +func updateParent(parent *Props, w, h float32) { + if parent != nil { + switch parent.Direction { + case Horizontal: + parent.ContentWidth += w + case Vertical: + parent.ContentHeight += h + } + } +} + +func maxf32(a, b float32) float32 { + if b > a { + return b + } + return a +} + +func minf32(a, b float32) float32 { + if b < a { + return b + } + return a +}