From 8624a28ea9f97fceb4cb4126b6375fa65bfcfd42 Mon Sep 17 00:00:00 2001 From: Noah Petherbridge Date: Sun, 5 Aug 2018 12:54:57 -0700 Subject: [PATCH] Add StatusBar to Editor Mode, Iterate on UI Toolkit * Added `BoxSize()` to Widget that reports the full box size including borders and margin. * The Frame uses the `BoxSize()` of widgets to position them. Reintroduces some padding issues (boxes on the GUI Test stick out of bounds a bit) but is on the right track. * Renamed `Padding` to `Margin` on the Widget object, since the Margin is taken into consideration along with Outline and Border in computing the widget's BoxSize. * Restructured the Label widget to take a Text or TextVariable property and the font settings (render.Text) are in a new `Font` property. --- balance/shell.go | 13 ++++- doodle.go | 3 +- editor_scene.go | 7 +++ editor_ui.go | 116 +++++++++++++++++++++++++++++++++++++ guitest_scene.go | 135 +++++++++++++++++++++++++------------------- main_scene.go | 65 ++++++++------------- render/interface.go | 11 ++-- ui/button.go | 11 ++-- ui/check_button.go | 1 - ui/frame.go | 13 +++-- ui/frame_pack.go | 61 ++++++++++++-------- ui/label.go | 51 ++++++++++------- ui/supervisor.go | 4 +- ui/widget.go | 60 ++++++++++---------- 14 files changed, 355 insertions(+), 196 deletions(-) create mode 100644 editor_ui.go diff --git a/balance/shell.go b/balance/shell.go index c0d6494..2c9ce50 100644 --- a/balance/shell.go +++ b/balance/shell.go @@ -1,11 +1,13 @@ package balance -import "git.kirsle.net/apps/doodle/render" +import ( + "git.kirsle.net/apps/doodle/render" +) // Shell related variables. var ( // TODO: why not renders transparent - ShellBackgroundColor = render.Color{0, 10, 20, 128} + ShellBackgroundColor = render.RGBA(0, 10, 20, 128) ShellForegroundColor = render.White ShellPadding int32 = 8 ShellFontSize = 16 @@ -15,3 +17,10 @@ var ( // Ticks that a flashed message persists for. FlashTTL uint64 = 400 ) + +// StatusFont is the font for the status bar. +var StatusFont = render.Text{ + Size: 12, + Padding: 4, + Color: render.Black, +} diff --git a/doodle.go b/doodle.go index e6dd44b..7f35d03 100644 --- a/doodle.go +++ b/doodle.go @@ -63,7 +63,8 @@ func (d *Doodle) Run() error { // Set up the default scene. if d.Scene == nil { - d.Goto(&GUITestScene{}) + // d.Goto(&GUITestScene{}) + d.NewMap() // d.Goto(&MainScene{}) } diff --git a/editor_scene.go b/editor_scene.go index 6491415..7bd088c 100644 --- a/editor_scene.go +++ b/editor_scene.go @@ -20,6 +20,8 @@ type EditorScene struct { Filename string Canvas render.Grid + UI *EditorUI + // History of all the pixels placed by the user. pixelHistory []level.Pixel lastPixel *level.Pixel // last pixel placed while mouse down and dragging @@ -56,6 +58,8 @@ func (s *EditorScene) Setup(d *Doodle) error { s.Canvas = nil } + s.UI = NewEditorUI(d) + d.Flash("Editor Mode. Press 'P' to play this map.") if s.pixelHistory == nil { @@ -72,6 +76,8 @@ func (s *EditorScene) Setup(d *Doodle) error { // Loop the editor scene. func (s *EditorScene) Loop(d *Doodle, ev *events.State) error { + s.UI.Loop(ev) + // Taking a screenshot? if ev.ScreenshotKey.Pressed() { log.Info("Taking a screenshot") @@ -131,6 +137,7 @@ func (s *EditorScene) Loop(d *Doodle, ev *events.State) error { // Draw the current frame. func (s *EditorScene) Draw(d *Doodle) error { s.canvas.Draw(d.Engine) + s.UI.Present(d.Engine) return nil } diff --git a/editor_ui.go b/editor_ui.go new file mode 100644 index 0000000..6202ffa --- /dev/null +++ b/editor_ui.go @@ -0,0 +1,116 @@ +package doodle + +import ( + "fmt" + + "git.kirsle.net/apps/doodle/balance" + "git.kirsle.net/apps/doodle/events" + "git.kirsle.net/apps/doodle/render" + "git.kirsle.net/apps/doodle/ui" +) + +// EditorUI manages the user interface for the Editor Scene. +type EditorUI struct { + d *Doodle + + // Variables + StatusMouseText string + + // Widgets + Supervisor *ui.Supervisor + StatusBar *ui.Frame +} + +// NewEditorUI initializes the Editor UI. +func NewEditorUI(d *Doodle) *EditorUI { + u := &EditorUI{ + d: d, + Supervisor: ui.NewSupervisor(), + StatusMouseText: ".", + } + u.StatusBar = u.SetupStatusBar(d) + return u +} + +// Loop to process events and update the UI. +func (u *EditorUI) Loop(ev *events.State) { + u.StatusMouseText = fmt.Sprintf("Mouse: (%d,%d)", + ev.CursorX.Now, + ev.CursorY.Now, + ) + u.StatusBar.Compute(u.d.Engine) + u.Supervisor.Loop(ev) +} + +// Present the UI to the screen. +func (u *EditorUI) Present(e render.Engine) { + u.StatusBar.Present(e, u.StatusBar.Point()) +} + +// SetupStatusBar sets up the status bar widget along the bottom of the window. +func (u *EditorUI) SetupStatusBar(d *Doodle) *ui.Frame { + frame := ui.NewFrame("Status Bar") + frame.Configure(ui.Config{ + BorderStyle: ui.BorderRaised, + Background: render.Grey, + BorderSize: 2, + Width: d.width, + }) + + cursorLabel := ui.NewLabel(ui.Label{ + TextVariable: &u.StatusMouseText, + Font: balance.StatusFont, + }) + cursorLabel.Configure(ui.Config{ + Background: render.Grey, + BorderStyle: ui.BorderSunken, + BorderColor: render.Grey, + BorderSize: 1, + }) + cursorLabel.Compute(d.Engine) + frame.Pack(cursorLabel, ui.Pack{ + Anchor: ui.W, + }) + + filenameLabel := ui.NewLabel(ui.Label{ + Text: "Filename: untitled.map", + Font: balance.StatusFont, + }) + filenameLabel.Configure(ui.Config{ + Background: render.Grey, + BorderStyle: ui.BorderSunken, + BorderColor: render.Grey, + BorderSize: 1, + }) + filenameLabel.Compute(d.Engine) + frame.Pack(filenameLabel, ui.Pack{ + Anchor: ui.W, + }) + + extraLabel := ui.NewLabel(ui.Label{ + Text: "blah", + Font: balance.StatusFont, + }) + extraLabel.Configure(ui.Config{ + Background: render.Grey, + BorderStyle: ui.BorderSunken, + BorderColor: render.Grey, + BorderSize: 1, + }) + extraLabel.Compute(d.Engine) + frame.Pack(extraLabel, ui.Pack{ + Anchor: ui.E, + }) + + frame.Resize(render.Rect{ + W: d.width, + H: cursorLabel.BoxSize().H + frame.BoxThickness(1), + }) + frame.Compute(d.Engine) + frame.MoveTo(render.Point{ + X: 0, + Y: d.height - frame.Size().H, + }) + + return frame +} diff --git a/guitest_scene.go b/guitest_scene.go index bfa627e..da736cc 100644 --- a/guitest_scene.go +++ b/guitest_scene.go @@ -3,6 +3,7 @@ package doodle import ( "fmt" + "git.kirsle.net/apps/doodle/balance" "git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/ui" @@ -37,11 +38,13 @@ func (s *GUITestScene) Setup(d *Doodle) error { }) // Title Bar - titleBar := ui.NewLabel(render.Text{ - Text: "Widget Toolkit", - Size: 12, - Color: render.White, - Stroke: render.DarkBlue, + titleBar := ui.NewLabel(ui.Label{ + Text: "Widget Toolkit", + Font: render.Text{ + Size: 12, + Color: render.White, + Stroke: render.DarkBlue, + }, }) titleBar.Configure(ui.Config{ Background: render.Blue, @@ -76,15 +79,17 @@ func (s *GUITestScene) Setup(d *Doodle) error { // Some left frame buttons. for _, label := range []string{"New", "Edit", "Play", "Help"} { - btn := ui.NewButton("dummy "+label, ui.NewLabel(render.Text{ - Text: label, - Size: 12, - Color: render.Black, + btn := ui.NewButton("dummy "+label, ui.NewLabel(ui.Label{ + Text: label, + Font: balance.StatusFont, })) + btn.Handle("Click", func(p render.Point) { + d.Flash("%s clicked", btn) + }) s.Supervisor.Add(btn) leftFrame.Pack(btn, ui.Pack{ Anchor: ui.N, - Fill: true, + FillX: true, PadY: 2, }) } @@ -98,6 +103,7 @@ func (s *GUITestScene) Setup(d *Doodle) error { body.Pack(frame, ui.Pack{ Anchor: ui.W, Expand: true, + Fill: true, }) // Right Frame @@ -116,19 +122,28 @@ func (s *GUITestScene) Setup(d *Doodle) error { // A grid of buttons. for row := 0; row < 3; row++ { rowFrame := ui.NewFrame(fmt.Sprintf("Row%d", row)) + rowFrame.Configure(ui.Config{ + Background: render.RGBA(0, uint8((row*20)+120), 0, 255), + }) for col := 0; col < 3; col++ { - btn := ui.NewButton("X", - ui.NewFrame(fmt.Sprintf("Col%d", col)), - ) - btn.Configure(ui.Config{ - Height: 20, - BorderStyle: ui.BorderRaised, - }) - rowFrame.Pack(btn, ui.Pack{ - Anchor: ui.W, - Expand: true, - }) - s.Supervisor.Add(btn) + (func(row, col int, frame *ui.Frame) { + btn := ui.NewButton(fmt.Sprintf("Grid Button %d:%d", col, row), + ui.NewFrame(fmt.Sprintf("Col%d", col)), + ) + btn.Configure(ui.Config{ + Height: 20, + BorderStyle: ui.BorderRaised, + }) + btn.Handle("Click", func(p render.Point) { + d.Flash("%s clicked", btn) + }) + rowFrame.Pack(btn, ui.Pack{ + Anchor: ui.W, + Expand: true, + FillX: true, + }) + s.Supervisor.Add(btn) + })(row, col, rowFrame) } rightFrame.Pack(rowFrame, ui.Pack{ Anchor: ui.N, @@ -136,10 +151,12 @@ func (s *GUITestScene) Setup(d *Doodle) error { }) } - frame.Pack(ui.NewLabel(render.Text{ - Text: "Hello World!", - Size: 14, - Color: render.Black, + frame.Pack(ui.NewLabel(ui.Label{ + Text: "Hello World!", + Font: render.Text{ + Size: 14, + Color: render.Black, + }, }), ui.Pack{ Anchor: ui.NW, Padding: 2, @@ -147,10 +164,9 @@ func (s *GUITestScene) Setup(d *Doodle) error { cb := ui.NewCheckbox("Overlay", &DebugOverlay, - ui.NewLabel(render.Text{ - Text: "Toggle Debug Overlay", - Size: 14, - Color: render.Black, + ui.NewLabel(ui.Label{ + Text: "Toggle Debug Overlay", + Font: balance.StatusFont, }), ) frame.Pack(cb, ui.Pack{ @@ -158,18 +174,22 @@ func (s *GUITestScene) Setup(d *Doodle) error { Padding: 4, }) cb.Supervise(s.Supervisor) - frame.Pack(ui.NewLabel(render.Text{ - Text: "Like Tk!", - Size: 16, - Color: render.Red, + frame.Pack(ui.NewLabel(ui.Label{ + Text: "Like Tk!", + Font: render.Text{ + Size: 16, + Color: render.Red, + }, }), ui.Pack{ Anchor: ui.SE, Padding: 8, }) - frame.Pack(ui.NewLabel(render.Text{ - Text: "Frame widget for pack layouts", - Size: 14, - Color: render.Blue, + frame.Pack(ui.NewLabel(ui.Label{ + Text: "Frame widget for pack layouts", + Font: render.Text{ + Size: 14, + Color: render.Blue, + }, }), ui.Pack{ Anchor: ui.SE, Padding: 8, @@ -184,10 +204,9 @@ func (s *GUITestScene) Setup(d *Doodle) error { Anchor: ui.N, }) - button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{ - Text: "New Map", - Size: 14, - Color: render.Black, + button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{ + Text: "New Map", + Font: balance.StatusFont, })) button1.SetBackground(render.Blue) button1.Handle("Click", func(p render.Point) { @@ -196,11 +215,13 @@ func (s *GUITestScene) Setup(d *Doodle) error { log.Info("Button1 bg: %s", button1.Background()) - button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{ - Text: "New Map", - Size: 14, - Color: render.Black, + button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{ + Text: "New Map", + Font: balance.StatusFont, })) + button2.Handle("Click", func(p render.Point) { + d.Flash("Button2 clicked") + }) button2.SetText("Load Map") var align = ui.W @@ -230,30 +251,28 @@ func (s *GUITestScene) Draw(d *Doodle) error { // Clear the canvas and fill it with white. d.Engine.Clear(render.White) - label := ui.NewLabel(render.Text{ - Text: "GUITest Doodle v" + Version, - Size: 26, - Color: render.Pink, - Stroke: render.SkyBlue, - Shadow: render.Black, + label := ui.NewLabel(ui.Label{ + Text: "GUITest Doodle v" + Version, + Font: render.Text{ + Size: 26, + Color: render.Pink, + Stroke: render.SkyBlue, + Shadow: render.Black, + }, }) label.Compute(d.Engine) label.MoveTo(render.Point{ X: (d.width / 2) - (label.Size().W / 2), Y: 40, }) - label.Present(d.Engine) + label.Present(d.Engine, label.Point()) s.Window.Compute(d.Engine) s.Window.MoveTo(render.Point{ X: (d.width / 2) - (s.Window.Size().W / 2), Y: 100, }) - s.Window.Present(d.Engine) - - s.Supervisor.Present(d.Engine) - - // os.Exit(1) + s.Window.Present(d.Engine, s.Window.Point()) return nil } diff --git a/main_scene.go b/main_scene.go index 5927270..ea23338 100644 --- a/main_scene.go +++ b/main_scene.go @@ -1,6 +1,7 @@ package doodle import ( + "git.kirsle.net/apps/doodle/balance" "git.kirsle.net/apps/doodle/events" "git.kirsle.net/apps/doodle/render" "git.kirsle.net/apps/doodle/ui" @@ -23,51 +24,29 @@ func (s *MainScene) Setup(d *Doodle) error { frame := ui.NewFrame("frame") s.frame = frame - s.frame.Configure(ui.Config{ - // Width: 400, - // Height: 200, - Background: render.Purple, - BorderStyle: ui.BorderSolid, - BorderSize: 1, - BorderColor: render.Blue, - }) - button1 := ui.NewButton("Button1", ui.NewLabel(render.Text{ - Text: "New Map", - Size: 14, - Color: render.Black, + button1 := ui.NewButton("Button1", ui.NewLabel(ui.Label{ + Text: "New Map", + Font: balance.StatusFont, })) - // button1.Compute(d.Engine) - // button1.MoveTo(render.Point{ - // X: (d.width / 2) - (button1.Size().W / 2), - // Y: 200, - // }) button1.Handle("Click", func(p render.Point) { d.NewMap() }) - button2 := ui.NewButton("Button2", ui.NewLabel(render.Text{ - Text: "New Map", - Size: 14, - Color: render.Black, + button2 := ui.NewButton("Button2", ui.NewLabel(ui.Label{ + Text: "New Map", + Font: balance.StatusFont, })) button2.SetText("Load Map") - // button2.Compute(d.Engine) - // button2.MoveTo(render.Point{ - // X: (d.width / 2) - (button2.Size().W / 2), - // Y: 260, - // }) - var align = ui.E frame.Pack(button1, ui.Pack{ - Anchor: align, - Padding: 12, - Fill: true, + Anchor: ui.N, + Fill: true, }) frame.Pack(button2, ui.Pack{ - Anchor: align, - Padding: 12, - Fill: true, + Anchor: ui.N, + PadY: 12, + Fill: true, }) s.Supervisor.Add(button1) @@ -87,28 +66,28 @@ func (s *MainScene) Draw(d *Doodle) error { // Clear the canvas and fill it with white. d.Engine.Clear(render.White) - label := ui.NewLabel(render.Text{ - Text: "Doodle v" + Version, - Size: 26, - Color: render.Pink, - Stroke: render.SkyBlue, - Shadow: render.Black, + label := ui.NewLabel(ui.Label{ + Text: "Doodle v" + Version, + Font: render.Text{ + Size: 26, + Color: render.Pink, + Stroke: render.SkyBlue, + Shadow: render.Black, + }, }) label.Compute(d.Engine) label.MoveTo(render.Point{ X: (d.width / 2) - (label.Size().W / 2), Y: 120, }) - label.Present(d.Engine) + label.Present(d.Engine, label.Point()) s.frame.Compute(d.Engine) s.frame.MoveTo(render.Point{ X: (d.width / 2) - (s.frame.Size().W / 2), Y: 200, }) - s.frame.Present(d.Engine) - - s.Supervisor.Present(d.Engine) + s.frame.Present(d.Engine, s.frame.Point()) return nil } diff --git a/render/interface.go b/render/interface.go index b0aea4a..aa6824a 100644 --- a/render/interface.go +++ b/render/interface.go @@ -156,11 +156,12 @@ func (r Rect) IsZero() bool { // Text holds information for drawing text. type Text struct { - Text string - Size int - Color Color - Stroke Color // Stroke color (if not zero) - Shadow Color // Drop shadow color (if not zero) + Text string + Size int + Color Color + Padding int32 + Stroke Color // Stroke color (if not zero) + Shadow Color // Drop shadow color (if not zero) } func (t Text) String() string { diff --git a/ui/button.go b/ui/button.go index d69718c..11e07e5 100644 --- a/ui/button.go +++ b/ui/button.go @@ -28,7 +28,6 @@ func NewButton(name string, child Widget) *Button { }) w.Configure(Config{ - Padding: 4, BorderSize: 2, BorderStyle: BorderRaised, OutlineSize: 1, @@ -75,22 +74,21 @@ func (w *Button) Compute(e render.Engine) { // SetText conveniently sets the button text, for Label children only. func (w *Button) SetText(text string) error { if label, ok := w.child.(*Label); ok { - label.Text.Text = text + label.Text = text } return errors.New("child is not a Label widget") } // Present the button. -func (w *Button) Present(e render.Engine) { +func (w *Button) Present(e render.Engine, P render.Point) { w.Compute(e) var ( - P = w.Point() S = w.Size() ChildSize = w.child.Size() ) // Draw the widget's border and everything. - w.DrawBox(e) + w.DrawBox(e, P) // Offset further if we are currently sunken. var clickOffset int32 @@ -112,6 +110,5 @@ func (w *Button) Present(e render.Engine) { _ = ChildSize // Draw the text label inside. - w.child.MoveTo(moveTo) - w.child.Present(e) + w.child.Present(e, moveTo) } diff --git a/ui/check_button.go b/ui/check_button.go index e49062b..ba4501d 100644 --- a/ui/check_button.go +++ b/ui/check_button.go @@ -32,7 +32,6 @@ func NewCheckButton(name string, boolVar *bool, child Widget) *CheckButton { } w.Configure(Config{ - Padding: 4, BorderSize: 2, BorderStyle: borderStyle, OutlineSize: 1, diff --git a/ui/frame.go b/ui/frame.go index 41b43c8..f1e08a3 100644 --- a/ui/frame.go +++ b/ui/frame.go @@ -46,14 +46,13 @@ func (w *Frame) Compute(e render.Engine) { } // Present the Frame. -func (w *Frame) Present(e render.Engine) { +func (w *Frame) Present(e render.Engine, P render.Point) { var ( - P = w.Point() S = w.Size() ) // Draw the widget's border and everything. - w.DrawBox(e) + w.DrawBox(e, P) // Draw the background color. e.DrawBox(w.Background(), render.Rect{ @@ -65,11 +64,13 @@ func (w *Frame) Present(e render.Engine) { // Draw the widgets. for _, child := range w.widgets { + // child.Compute(e) p := child.Point() - child.MoveTo(render.NewPoint( + moveTo := render.NewPoint( P.X+p.X+w.BoxThickness(1), P.Y+p.Y+w.BoxThickness(1), - )) - child.Present(e) + ) + child.MoveTo(moveTo) + child.Present(e, moveTo) } } diff --git a/ui/frame_pack.go b/ui/frame_pack.go index 3e56b0b..4c2b09b 100644 --- a/ui/frame_pack.go +++ b/ui/frame_pack.go @@ -5,7 +5,7 @@ import "git.kirsle.net/apps/doodle/render" // computePacked processes all the Pack layout widgets in the Frame. func (w *Frame) computePacked(e render.Engine) { var ( - frameSize = w.Size() + frameSize = w.BoxSize() // maxWidth and maxHeight are always the computed minimum dimensions // that the Frame must be to contain all of its children. If the Frame @@ -37,7 +37,7 @@ func (w *Frame) computePacked(e render.Engine) { yDirection = -1 - w.BoxThickness(2) // parent + child BoxThickness(1) = 2 } else if anchor == E { x = frameSize.W - xDirection = -1 - w.BoxThickness(2) + xDirection = -1 // - w.BoxThickness(2) } for _, packedWidget := range w.packs[anchor] { @@ -46,7 +46,7 @@ func (w *Frame) computePacked(e render.Engine) { child.Compute(e) var ( // point = child.Point() - size = child.Size() + size = child.BoxSize() yStep = y * yDirection xStep = x * xDirection ) @@ -65,10 +65,7 @@ func (w *Frame) computePacked(e render.Engine) { x -= size.W + (pack.PadX * 2) } - child.MoveTo(render.Point{ - X: x + pack.PadX, - Y: y + pack.PadY, - }) + child.MoveTo(render.NewPoint(x, y)) if anchor.IsNorth() { y += size.H + (pack.PadY * 2) @@ -90,8 +87,8 @@ func (w *Frame) computePacked(e render.Engine) { if len(expanded) > 0 && !frameSize.IsZero() && frameSize.Bigger(computedSize) { // Divy up the size available. growBy := render.Rect{ - W: ((frameSize.W - computedSize.W) / int32(len(expanded))) - w.BoxThickness(2), - H: ((frameSize.H - computedSize.H) / int32(len(expanded))) - w.BoxThickness(2), + W: ((frameSize.W - computedSize.W) / int32(len(expanded))), // - w.BoxThickness(2), + H: ((frameSize.H - computedSize.H) / int32(len(expanded))), // - w.BoxThickness(2), } for _, pw := range expanded { pw.widget.ResizeBy(growBy) @@ -102,10 +99,22 @@ func (w *Frame) computePacked(e render.Engine) { // If we're not using a fixed Frame size, use the dynamically computed one. if !w.FixedSize() { frameSize = render.NewRect(maxWidth, maxHeight) + } else { + // If either of the sizes were left zero, use the dynamically computed one. + if frameSize.W == 0 { + frameSize.W = maxWidth + } + if frameSize.H == 0 { + frameSize.H = maxHeight + } } // Rescan all the widgets in this anchor to re-center them // in their space. + innerFrameSize := render.NewRect( + frameSize.W-w.BoxThickness(2), + frameSize.H-w.BoxThickness(2), + ) for _, pw := range visited { var ( child = pw.widget @@ -117,38 +126,42 @@ func (w *Frame) computePacked(e render.Engine) { moved bool ) + if w.String() == "Frame" { + log.Debug("%s>%s: pack.FillX=%d resize=%s innerFrameSize=%s", w, child, pack.FillX, resize, innerFrameSize) + } + if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() { - if pack.FillX && resize.W < frameSize.W { - resize.W = frameSize.W - w.BoxThickness(2) + if pack.FillX && resize.W < innerFrameSize.W { + resize.W = innerFrameSize.W - w.BoxThickness(2) resized = true } - if resize.W < frameSize.W-w.BoxThickness(4) { + if resize.W < innerFrameSize.W-w.BoxThickness(4) { if pack.Anchor.IsCenter() { - point.X = (frameSize.W / 2) - (resize.W / 2) + point.X = (innerFrameSize.W / 2) - (resize.W / 2) } else if pack.Anchor.IsWest() { point.X = pack.PadX } else if pack.Anchor.IsEast() { - point.X = frameSize.W - resize.W - pack.PadX + point.X = innerFrameSize.W - resize.W - pack.PadX } moved = true } } else if pack.Anchor.IsWest() || pack.Anchor.IsEast() { - if pack.FillY && resize.H < frameSize.H { - resize.H = frameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child + if pack.FillY && resize.H < innerFrameSize.H { + resize.H = innerFrameSize.H - w.BoxThickness(2) // BoxThickness(2) for parent + child // point.Y -= (w.BoxThickness(4) + child.BoxThickness(2)) moved = true resized = true } // Vertically align the widgets. - if resize.H < frameSize.H { + if resize.H < innerFrameSize.H { if pack.Anchor.IsMiddle() { - point.Y = (frameSize.H / 2) - (resize.H / 2) + point.Y = (innerFrameSize.H / 2) - (resize.H / 2) - w.BoxThickness(1) } else if pack.Anchor.IsNorth() { point.Y = pack.PadY - w.BoxThickness(4) } else if pack.Anchor.IsSouth() { - point.Y = frameSize.H - resize.H - pack.PadY + point.Y = innerFrameSize.H - resize.H - pack.PadY } moved = true } @@ -157,6 +170,7 @@ func (w *Frame) computePacked(e render.Engine) { } if resized && size != resize { + // log.Debug("%s/%s: resize to: %s", w, child, resize) child.Resize(resize) child.Compute(e) } @@ -165,9 +179,12 @@ func (w *Frame) computePacked(e render.Engine) { } } - if !w.FixedSize() { - w.Resize(frameSize) - } + // if !w.FixedSize() { + w.Resize(render.NewRect( + frameSize.W-w.BoxThickness(2), + frameSize.H-w.BoxThickness(2), + )) + // } } // Pack provides configuration fields for Frame.Pack(). diff --git a/ui/label.go b/ui/label.go index 2452c88..57e049e 100644 --- a/ui/label.go +++ b/ui/label.go @@ -9,33 +9,48 @@ import ( // Label is a simple text label widget. type Label struct { BaseWidget + + // Configurable fields for the constructor. + Text string + TextVariable *string + Font render.Text + width int32 height int32 - Text render.Text } // NewLabel creates a new label. -func NewLabel(t render.Text) *Label { +func NewLabel(c Label) *Label { w := &Label{ - Text: t, + Text: c.Text, + TextVariable: c.TextVariable, + Font: c.Font, } - w.Configure(Config{ - Padding: 4, - }) w.IDFunc(func() string { - return fmt.Sprintf("Label<%s>", w.Text.Text) + return fmt.Sprintf("Label<%s>", w.text().Text) }) return w } +// text returns the label's displayed text, coming from the TextVariable if +// available or else the Text attribute instead. +func (w *Label) text() render.Text { + if w.TextVariable != nil { + w.Font.Text = *w.TextVariable + return w.Font + } + w.Font.Text = w.Text + return w.Font +} + // Compute the size of the label widget. func (w *Label) Compute(e render.Engine) { - rect, _ := e.ComputeTextRect(w.Text) + rect, _ := e.ComputeTextRect(w.text()) if !w.FixedSize() { w.resizeAuto(render.Rect{ - W: rect.W + w.Padding(), - H: rect.H + w.Padding(), + W: rect.W + (w.Font.Padding * 2), + H: rect.H + (w.Font.Padding * 2), }) } @@ -46,14 +61,12 @@ func (w *Label) Compute(e render.Engine) { } // Present the label widget. -func (w *Label) Present(e render.Engine) { - var ( - P = w.Point() - border = w.BoxThickness(1) - ) - w.DrawBox(e) - e.DrawText(w.Text, render.Point{ - X: P.X + border, - Y: P.Y + border, +func (w *Label) Present(e render.Engine, P render.Point) { + border := w.BoxThickness(1) + + w.DrawBox(e, P) + e.DrawText(w.text(), render.Point{ + X: P.X + border + w.Font.Padding, + Y: P.Y + border + w.Font.Padding, }) } diff --git a/ui/supervisor.go b/ui/supervisor.go index 4c6e295..dfa028e 100644 --- a/ui/supervisor.go +++ b/ui/supervisor.go @@ -20,6 +20,7 @@ type Supervisor struct { // NewSupervisor creates a supervisor. func NewSupervisor() *Supervisor { return &Supervisor{ + widgets: []Widget{}, hovering: map[int]interface{}{}, clicked: map[int]interface{}{}, } @@ -84,8 +85,7 @@ func (s *Supervisor) Present(e render.Engine) { defer s.lock.RUnlock() for _, w := range s.widgets { - // w.Present(e) - _ = w + w.Present(e, w.Point()) } } diff --git a/ui/widget.go b/ui/widget.go index e031228..1db6afb 100644 --- a/ui/widget.go +++ b/ui/widget.go @@ -24,8 +24,9 @@ type Widget interface { Point() render.Point MoveTo(render.Point) MoveBy(render.Point) - Size() render.Rect // Return the Width and Height of the widget. - FixedSize() bool // Return whether the size is fixed (true) or automatic (false) + Size() render.Rect // Return the Width and Height of the widget. + FixedSize() bool // Return whether the size is fixed (true) or automatic (false) + BoxSize() render.Rect // Return the full size including the border and outline. Resize(render.Rect) ResizeBy(render.Rect) @@ -34,11 +35,11 @@ type Widget interface { // Thickness of the padding + border + outline. BoxThickness(multiplier int32) int32 - DrawBox(render.Engine) + DrawBox(render.Engine, render.Point) // Widget configuration getters. - Padding() int32 // Padding - SetPadding(int32) // + Margin() int32 // Margin away from other widgets + SetMargin(int32) // Background() render.Color // Background color SetBackground(render.Color) // Foreground() render.Color // Foreground color @@ -60,7 +61,7 @@ type Widget interface { Compute(render.Engine) // Render the final widget onto the drawing engine. - Present(render.Engine) + Present(render.Engine, render.Point) } // Config holds common base widget configs for quick configuration. @@ -73,9 +74,9 @@ type Config struct { AutoResize bool Width int32 Height int32 - Padding int32 - PadX int32 - PadY int32 + Margin int32 + MarginX int32 + MarginY int32 Background render.Color Foreground render.Color BorderSize int32 @@ -94,7 +95,7 @@ type BaseWidget struct { width int32 height int32 point render.Point - padding int32 + margin int32 background render.Color foreground render.Color borderStyle BorderStyle @@ -142,8 +143,8 @@ func (w *BaseWidget) Configure(c Config) { } } - if c.Padding != 0 { - w.padding = c.Padding + if c.Margin != 0 { + w.margin = c.Margin } if c.Background != render.Invisible { w.background = c.Background @@ -194,6 +195,15 @@ func (w *BaseWidget) Size() render.Rect { } } +// BoxSize returns the full rendered size of the widget including its box +// thickness (border, padding and outline). +func (w *BaseWidget) BoxSize() render.Rect { + return render.Rect{ + W: w.width + w.BoxThickness(2), + H: w.height + w.BoxThickness(2), + } +} + // FixedSize returns whether the widget's size has been hard-coded by the user // (true) or if it automatically resizes based on its contents (false). func (w *BaseWidget) FixedSize() bool { @@ -226,13 +236,12 @@ func (w *BaseWidget) BoxThickness(m int32) int32 { if m == 0 { m = 1 } - return (w.Padding() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m) + return (w.Margin() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m) } // DrawBox draws the border and outline. -func (w *BaseWidget) DrawBox(e render.Engine) { +func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) { var ( - P = w.Point() S = w.Size() outline = w.OutlineSize() border = w.BorderSize() @@ -300,25 +309,16 @@ func (w *BaseWidget) DrawBox(e render.Engine) { if w.Background() != render.Invisible { e.DrawBox(w.Background(), box) } - - // log.Info("Widget %s background color: %s", w, w.Background()) - - // XXX: color effective area - // box.X += w.Padding() - // box.Y += w.Padding() - // box.W -= w.Padding() * 2 - // box.H -= w.Padding() * 2 - // e.DrawBox(render.RGBA(0, 255, 255, 153), box) } -// Padding returns the padding width. -func (w *BaseWidget) Padding() int32 { - return w.padding +// Margin returns the margin width. +func (w *BaseWidget) Margin() int32 { + return w.margin } -// SetPadding sets the padding width. -func (w *BaseWidget) SetPadding(v int32) { - w.padding = v +// SetMargin sets the margin width. +func (w *BaseWidget) SetMargin(v int32) { + w.margin = v } // Background returns the background color.