diff --git a/lib/ui/button.go b/lib/ui/button.go deleted file mode 100644 index 511227c..0000000 --- a/lib/ui/button.go +++ /dev/null @@ -1,122 +0,0 @@ -package ui - -import ( - "errors" - "fmt" - - "git.kirsle.net/go/render" - "git.kirsle.net/apps/doodle/lib/ui/theme" -) - -// Button is a clickable button. -type Button struct { - BaseWidget - child Widget - - // Private options. - hovering bool - clicked bool -} - -// NewButton creates a new Button. -func NewButton(name string, child Widget) *Button { - w := &Button{ - child: child, - } - w.IDFunc(func() string { - return fmt.Sprintf("Button<%s>", name) - }) - - w.Configure(Config{ - BorderSize: 2, - BorderStyle: BorderRaised, - OutlineSize: 1, - OutlineColor: theme.ButtonOutlineColor, - Background: theme.ButtonBackgroundColor, - }) - - w.Handle(MouseOver, func(p render.Point) { - w.hovering = true - w.SetBackground(theme.ButtonHoverColor) - }) - w.Handle(MouseOut, func(p render.Point) { - w.hovering = false - w.SetBackground(theme.ButtonBackgroundColor) - }) - - w.Handle(MouseDown, func(p render.Point) { - w.clicked = true - w.SetBorderStyle(BorderSunken) - }) - w.Handle(MouseUp, func(p render.Point) { - w.clicked = false - w.SetBorderStyle(BorderRaised) - }) - - return w -} - -// Children returns the button's child widget. -func (w *Button) Children() []Widget { - return []Widget{w.child} -} - -// Compute the size of the button. -func (w *Button) Compute(e render.Engine) { - // Compute the size of the inner widget first. - w.child.Compute(e) - - // Auto-resize only if we haven't been given a fixed size. - if !w.FixedSize() { - size := w.child.Size() - w.Resize(render.Rect{ - W: size.W + w.BoxThickness(2), - H: size.H + w.BoxThickness(2), - }) - } -} - -// 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 - } - return errors.New("child is not a Label widget") -} - -// Present the button. -func (w *Button) Present(e render.Engine, P render.Point) { - if w.Hidden() { - return - } - - w.Compute(e) - w.MoveTo(P) - var ( - S = w.Size() - ChildSize = w.child.Size() - ) - - // Draw the widget's border and everything. - w.DrawBox(e, P) - - // Offset further if we are currently sunken. - var clickOffset int32 - if w.clicked { - clickOffset++ - } - - // Where to place the child widget. - moveTo := render.Point{ - X: P.X + w.BoxThickness(1) + clickOffset, - Y: P.Y + w.BoxThickness(1) + clickOffset, - } - - // If we're bigger than we need to be, center the child widget. - if S.Bigger(ChildSize) { - moveTo.X = P.X + (S.W / 2) - (ChildSize.W / 2) - } - - // Draw the text label inside. - w.child.Present(e, moveTo) -} diff --git a/lib/ui/check_button.go b/lib/ui/check_button.go deleted file mode 100644 index 5074c94..0000000 --- a/lib/ui/check_button.go +++ /dev/null @@ -1,118 +0,0 @@ -package ui - -import ( - "fmt" - "strconv" - - "git.kirsle.net/go/render" - "git.kirsle.net/apps/doodle/lib/ui/theme" -) - -// CheckButton implements a checkbox and radiobox widget. It's based on a -// Button and holds a boolean or string pointer (boolean for checkbox, -// string for radio). -type CheckButton struct { - Button - BoolVar *bool - StringVar *string - Value string -} - -// NewCheckButton creates a new CheckButton. -func NewCheckButton(name string, boolVar *bool, child Widget) *CheckButton { - w := &CheckButton{ - BoolVar: boolVar, - } - w.Button.child = child - w.IDFunc(func() string { - return fmt.Sprintf("CheckButton<%s %+v>", name, w.BoolVar) - }) - - w.setup() - return w -} - -// NewRadioButton creates a CheckButton bound to a string variable. -func NewRadioButton(name string, stringVar *string, value string, child Widget) *CheckButton { - w := &CheckButton{ - StringVar: stringVar, - Value: value, - } - w.Button.child = child - w.IDFunc(func() string { - return fmt.Sprintf(`RadioButton<%s "%s" %s>`, name, w.Value, strconv.FormatBool(*w.StringVar == w.Value)) - }) - w.setup() - return w -} - -// Compute to re-evaluate the button state (in the case of radio buttons where -// a different button will affect the state of this one when clicked). -func (w *CheckButton) Compute(e render.Engine) { - if w.StringVar != nil { - // Radio button, always re-assign the border style in case a sister - // radio button has changed the value. - if *w.StringVar == w.Value { - w.SetBorderStyle(BorderSunken) - } else { - w.SetBorderStyle(BorderRaised) - } - } - w.Button.Compute(e) -} - -// setup the common things between checkboxes and radioboxes. -func (w *CheckButton) setup() { - var borderStyle BorderStyle = BorderRaised - if w.BoolVar != nil { - if *w.BoolVar == true { - borderStyle = BorderSunken - } - } - - w.Configure(Config{ - BorderSize: 2, - BorderStyle: borderStyle, - OutlineSize: 1, - OutlineColor: theme.ButtonOutlineColor, - Background: theme.ButtonBackgroundColor, - }) - - w.Handle(MouseOver, func(p render.Point) { - w.hovering = true - w.SetBackground(theme.ButtonHoverColor) - }) - w.Handle(MouseOut, func(p render.Point) { - w.hovering = false - w.SetBackground(theme.ButtonBackgroundColor) - }) - - w.Handle(MouseDown, func(p render.Point) { - w.clicked = true - w.SetBorderStyle(BorderSunken) - }) - w.Handle(MouseUp, func(p render.Point) { - w.clicked = false - }) - - w.Handle(Click, func(p render.Point) { - var sunken bool - if w.BoolVar != nil { - if *w.BoolVar { - *w.BoolVar = false - } else { - *w.BoolVar = true - sunken = true - } - } else if w.StringVar != nil { - *w.StringVar = w.Value - sunken = true - } - - if sunken { - w.SetBorderStyle(BorderSunken) - } else { - w.SetBorderStyle(BorderRaised) - } - }) -} diff --git a/lib/ui/checkbox.go b/lib/ui/checkbox.go deleted file mode 100644 index d47835f..0000000 --- a/lib/ui/checkbox.go +++ /dev/null @@ -1,65 +0,0 @@ -package ui - -import "git.kirsle.net/go/render" - -// Checkbox combines a CheckButton with a widget like a Label. -type Checkbox struct { - Frame - button *CheckButton - child Widget -} - -// NewCheckbox creates a new Checkbox. -func NewCheckbox(name string, boolVar *bool, child Widget) *Checkbox { - return makeCheckbox(name, boolVar, nil, "", child) -} - -// NewRadiobox creates a new Checkbox in radio mode. -func NewRadiobox(name string, stringVar *string, value string, child Widget) *Checkbox { - return makeCheckbox(name, nil, stringVar, value, child) -} - -// makeCheckbox constructs an appropriate type of checkbox. -func makeCheckbox(name string, boolVar *bool, stringVar *string, value string, child Widget) *Checkbox { - // Our custom checkbutton widget. - mark := NewFrame(name + "_mark") - - w := &Checkbox{ - child: child, - } - if boolVar != nil { - w.button = NewCheckButton(name+"_button", boolVar, mark) - } else if stringVar != nil { - w.button = NewRadioButton(name+"_button", stringVar, value, mark) - } - w.Frame.Setup() - - // Forward clicks on the child widget to the CheckButton. - for _, e := range []Event{MouseOver, MouseOut, MouseUp, MouseDown} { - func(e Event) { - w.child.Handle(e, func(p render.Point) { - w.button.Event(e, p) - }) - }(e) - } - - w.Pack(w.button, Pack{ - Anchor: W, - }) - w.Pack(w.child, Pack{ - Anchor: W, - }) - - return w -} - -// Child returns the child widget. -func (w *Checkbox) Child() Widget { - return w.child -} - -// Supervise the checkbutton inside the widget. -func (w *Checkbox) Supervise(s *Supervisor) { - s.Add(w.button) - s.Add(w.child) -} diff --git a/lib/ui/debug.go b/lib/ui/debug.go deleted file mode 100644 index 2111668..0000000 --- a/lib/ui/debug.go +++ /dev/null @@ -1,23 +0,0 @@ -package ui - -import "strings" - -// WidgetTree returns a string representing the tree of widgets starting -// at a given widget. -func WidgetTree(root Widget) []string { - var crawl func(int, Widget) []string - crawl = func(depth int, node Widget) []string { - var ( - prefix = strings.Repeat(" ", depth) - lines = []string{prefix + node.ID()} - ) - - for _, child := range node.Children() { - lines = append(lines, crawl(depth+1, child)...) - } - - return lines - } - - return crawl(0, root) -} diff --git a/lib/ui/dragdrop.go b/lib/ui/dragdrop.go deleted file mode 100644 index ac1fea0..0000000 --- a/lib/ui/dragdrop.go +++ /dev/null @@ -1,28 +0,0 @@ -package ui - -// DragDrop is a state machine to manage draggable UI components. -type DragDrop struct { - isDragging bool -} - -// NewDragDrop initializes the DragDrop struct. Normally your Supervisor -// will manage the drag/drop object, but you can use your own if you don't -// use a Supervisor. -func NewDragDrop() *DragDrop { - return &DragDrop{} -} - -// IsDragging returns whether the drag state is active. -func (dd *DragDrop) IsDragging() bool { - return dd.isDragging -} - -// Start the drag state. -func (dd *DragDrop) Start() { - dd.isDragging = true -} - -// Stop dragging. -func (dd *DragDrop) Stop() { - dd.isDragging = false -} diff --git a/lib/ui/eg/layout/layout.go b/lib/ui/eg/layout/layout.go deleted file mode 100644 index 511c885..0000000 --- a/lib/ui/eg/layout/layout.go +++ /dev/null @@ -1,7 +0,0 @@ -package layout - -import "fmt" - -func main() { - fmt.Println("Hello world") -} diff --git a/lib/ui/eg/layout/main.go b/lib/ui/eg/layout/main.go deleted file mode 100644 index 5bb48d7..0000000 --- a/lib/ui/eg/layout/main.go +++ /dev/null @@ -1,12 +0,0 @@ -package main - -import ( - "fmt" - - "git.kirsle.net/apps/doodle/lib/ui/eg/layout" -) - -func main() { - fmt.Println("Hello world") - layout.main() -} diff --git a/lib/ui/eg/main.go b/lib/ui/eg/main.go deleted file mode 100644 index 276a502..0000000 --- a/lib/ui/eg/main.go +++ /dev/null @@ -1,47 +0,0 @@ -package main - -import ( - "git.kirsle.net/go/render" - "git.kirsle.net/apps/doodle/lib/ui" -) - -func main() { - mw, err := ui.NewMainWindow("UI Toolkit Demo") - if err != nil { - panic(err) - } - - leftFrame := ui.NewFrame("Left Frame") - leftFrame.Configure(ui.Config{ - Width: 200, - BorderSize: 1, - BorderStyle: ui.BorderRaised, - Background: render.Grey, - }) - mw.Pack(leftFrame, ui.Pack{ - Anchor: ui.W, - FillY: true, - }) - - mainFrame := ui.NewFrame("Main Frame") - mainFrame.Configure(ui.Config{ - Background: render.RGBA(255, 255, 255, 180), - }) - mw.Pack(mainFrame, ui.Pack{ - Anchor: ui.W, - Expand: true, - PadX: 10, - }) - - label := ui.NewLabel(ui.Label{ - Text: "Hello world", - }) - leftFrame.Pack(label, ui.Pack{ - Anchor: ui.SE, - }) - - err = mw.MainLoop() - if err != nil { - panic("MainLoop:" + err.Error()) - } -} diff --git a/lib/ui/frame.go b/lib/ui/frame.go deleted file mode 100644 index 03da8b6..0000000 --- a/lib/ui/frame.go +++ /dev/null @@ -1,92 +0,0 @@ -package ui - -import ( - "fmt" - - "git.kirsle.net/go/render" -) - -// Frame is a widget that contains other widgets. -type Frame struct { - Name string - BaseWidget - packs map[Anchor][]packedWidget - widgets []Widget -} - -// NewFrame creates a new Frame. -func NewFrame(name string) *Frame { - w := &Frame{ - Name: name, - packs: map[Anchor][]packedWidget{}, - widgets: []Widget{}, - } - w.SetBackground(render.RGBA(1, 0, 0, 0)) // invisible default BG - w.IDFunc(func() string { - return fmt.Sprintf("Frame<%s>", - name, - ) - }) - return w -} - -// Setup ensures all the Frame's data is initialized and not null. -func (w *Frame) Setup() { - if w.packs == nil { - w.packs = map[Anchor][]packedWidget{} - } - if w.widgets == nil { - w.widgets = []Widget{} - } -} - -// Children returns all of the child widgets. -func (w *Frame) Children() []Widget { - return w.widgets -} - -// Compute the size of the Frame. -func (w *Frame) Compute(e render.Engine) { - w.computePacked(e) -} - -// Present the Frame. -func (w *Frame) Present(e render.Engine, P render.Point) { - if w.Hidden() { - return - } - - var ( - S = w.Size() - ) - - // Draw the widget's border and everything. - w.DrawBox(e, P) - - // Draw the background color. - e.DrawBox(w.Background(), render.Rect{ - X: P.X + w.BoxThickness(1), - Y: P.Y + w.BoxThickness(1), - W: S.W - w.BoxThickness(2), - H: S.H - w.BoxThickness(2), - }) - - // Draw the widgets. - for _, child := range w.widgets { - // child.Compute(e) - p := child.Point() - moveTo := render.NewPoint( - P.X+p.X+w.BoxThickness(1), - P.Y+p.Y+w.BoxThickness(1), - ) - // if child.ID() == "Canvas" { - // log.Debug("Frame X=%d Child X=%d Box=%d Point=%s", P.X, p.X, w.BoxThickness(1), p) - // log.Debug("Frame Y=%d Child Y=%d Box=%d MoveTo=%s", P.Y, p.Y, w.BoxThickness(1), moveTo) - // } - // child.MoveTo(moveTo) // TODO: if uncommented the child will creep down the parent each tick - // if child.ID() == "Canvas" { - // log.Debug("New Point: %s", child.Point()) - // } - child.Present(e, moveTo) - } -} diff --git a/lib/ui/frame_pack.go b/lib/ui/frame_pack.go deleted file mode 100644 index f160263..0000000 --- a/lib/ui/frame_pack.go +++ /dev/null @@ -1,318 +0,0 @@ -package ui - -import ( - "git.kirsle.net/go/render" -) - -// Pack provides configuration fields for Frame.Pack(). -type Pack struct { - // Side of the parent to anchor the position to, like N, SE, W. Default - // is Center. - Anchor Anchor - - // If the widget is smaller than its allocated space, grow the widget - // to fill its space in the Frame. - Fill bool - FillX bool - FillY bool - - Padding int32 // Equal padding on X and Y. - PadX int32 - PadY int32 - Expand bool // Widget should grow its allocated space to better fill the parent. -} - -// Pack a widget along a side of the frame. -func (w *Frame) Pack(child Widget, config ...Pack) { - var C Pack - if len(config) > 0 { - C = config[0] - } - - // Initialize the pack list for this anchor? - if _, ok := w.packs[C.Anchor]; !ok { - w.packs[C.Anchor] = []packedWidget{} - } - - // Padding: if the user only provided Padding add it to both - // the X and Y value. If the user additionally provided the X - // and Y value, it will add to the base padding as you'd expect. - C.PadX += C.Padding - C.PadY += C.Padding - - // Fill: true implies both directions. - if C.Fill { - C.FillX = true - C.FillY = true - } - - // Adopt the child widget so it can access the Frame. - child.Adopt(w) - - w.packs[C.Anchor] = append(w.packs[C.Anchor], packedWidget{ - widget: child, - pack: C, - }) - w.widgets = append(w.widgets, child) -} - -// computePacked processes all the Pack layout widgets in the Frame. -func (w *Frame) computePacked(e render.Engine) { - var ( - 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 - // was configured with an explicit Size, the Frame will be that Size, - // but we still calculate how much space the widgets _actually_ take - // so we can expand them to fill remaining space in fixed size widgets. - maxWidth int32 - maxHeight int32 - visited = []packedWidget{} - expanded = []packedWidget{} - ) - - // Iterate through all anchored directions and compute how much space to - // reserve to contain all of their widgets. - for anchor := AnchorMin; anchor <= AnchorMax; anchor++ { - if _, ok := w.packs[anchor]; !ok { - continue - } - - var ( - x int32 - y int32 - yDirection int32 = 1 - xDirection int32 = 1 - ) - - if anchor.IsSouth() { - y = frameSize.H - w.BoxThickness(4) - yDirection = -1 - } else if anchor.IsEast() { - x = frameSize.W - w.BoxThickness(4) - xDirection = -1 - } - - for _, packedWidget := range w.packs[anchor] { - - child := packedWidget.widget - pack := packedWidget.pack - child.Compute(e) - - if child.Hidden() { - continue - } - - x += pack.PadX * xDirection - y += pack.PadY * yDirection - - var ( - // point = child.Point() - size = child.Size() - yStep = y * yDirection - xStep = x * xDirection - ) - - if xStep+size.W+(pack.PadX*2) > maxWidth { - maxWidth = xStep + size.W + (pack.PadX * 2) - } - if yStep+size.H+(pack.PadY*2) > maxHeight { - maxHeight = yStep + size.H + (pack.PadY * 2) - } - - if anchor.IsSouth() { - y -= size.H - pack.PadY - } - if anchor.IsEast() { - x -= size.W - pack.PadX - } - - child.MoveTo(render.NewPoint(x, y)) - - if anchor.IsNorth() { - y += size.H + pack.PadY - } - if anchor == W { - x += size.W + pack.PadX - } - - visited = append(visited, packedWidget) - if pack.Expand { // TODO: don't fuck with children of fixed size - expanded = append(expanded, packedWidget) - } - } - } - - // If we have extra space in the Frame and any expanding widgets, let the - // expanding widgets grow and share the remaining space. - computedSize := render.NewRect(maxWidth, maxHeight) - 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(4), - H: ((frameSize.H - computedSize.H) / int32(len(expanded))) - w.BoxThickness(4), - } - for _, pw := range expanded { - pw.widget.ResizeBy(growBy) - pw.widget.Compute(e) - } - } - - // 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 - pack = pw.pack - point = child.Point() - size = child.Size() - resize = size - resized bool - moved bool - ) - - if pack.Anchor.IsNorth() || pack.Anchor.IsSouth() { - if pack.FillX && resize.W < innerFrameSize.W { - resize.W = innerFrameSize.W - w.BoxThickness(2) - resized = true - } - if resize.W < innerFrameSize.W-w.BoxThickness(4) { - if pack.Anchor.IsCenter() { - point.X = (innerFrameSize.W / 2) - (resize.W / 2) - } else if pack.Anchor.IsWest() { - point.X = pack.PadX - } else if pack.Anchor.IsEast() { - point.X = innerFrameSize.W - resize.W - pack.PadX - } - - moved = true - } - } else if pack.Anchor.IsWest() || pack.Anchor.IsEast() { - 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 < innerFrameSize.H { - if pack.Anchor.IsMiddle() { - 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 = innerFrameSize.H - resize.H - pack.PadY - } - moved = true - } - } else { - panic("unsupported pack.Anchor") - } - - if resized && size != resize { - child.Resize(resize) - child.Compute(e) - } - if moved { - child.MoveTo(point) - } - } - - // if !w.FixedSize() { - w.Resize(render.NewRect( - frameSize.W-w.BoxThickness(2), - frameSize.H-w.BoxThickness(2), - )) - // } -} - -// Anchor is a cardinal direction. -type Anchor uint8 - -// Anchor values. -const ( - Center Anchor = iota - N - NE - E - SE - S - SW - W - NW -) - -// Range of Anchor values. -const ( - AnchorMin = Center - AnchorMax = NW -) - -// IsNorth returns if the anchor is N, NE or NW. -func (a Anchor) IsNorth() bool { - return a == N || a == NE || a == NW -} - -// IsSouth returns if the anchor is S, SE or SW. -func (a Anchor) IsSouth() bool { - return a == S || a == SE || a == SW -} - -// IsEast returns if the anchor is E, NE or SE. -func (a Anchor) IsEast() bool { - return a == E || a == NE || a == SE -} - -// IsWest returns if the anchor is W, NW or SW. -func (a Anchor) IsWest() bool { - return a == W || a == NW || a == SW -} - -// IsCenter returns if the anchor is Center, N or S, to determine -// whether to align text as centered for North/South anchors. -func (a Anchor) IsCenter() bool { - return a == Center || a == N || a == S -} - -// IsMiddle returns if the anchor is Center, E or W, to determine -// whether to align text as middled for East/West anchors. -func (a Anchor) IsMiddle() bool { - return a == Center || a == W || a == E -} - -type packLayout struct { - widgets []packedWidget -} - -type packedWidget struct { - widget Widget - pack Pack - fill uint8 -} - -// packedWidget.fill values -const ( - fillNone uint8 = iota - fillX - fillY - fillBoth -) diff --git a/lib/ui/functions.go b/lib/ui/functions.go deleted file mode 100644 index 9689cd9..0000000 --- a/lib/ui/functions.go +++ /dev/null @@ -1,38 +0,0 @@ -package ui - -import "git.kirsle.net/go/render" - -// AbsolutePosition computes a widget's absolute X,Y position on the -// window on screen by crawling its parent widget tree. -func AbsolutePosition(w Widget) render.Point { - abs := w.Point() - - var ( - node = w - ok bool - ) - - for { - node, ok = node.Parent() - if !ok { // reached the top of the tree - return abs - } - - abs.Add(node.Point()) - } -} - -// AbsoluteRect returns a Rect() offset with the absolute position. -func AbsoluteRect(w Widget) render.Rect { - var ( - P = AbsolutePosition(w) - R = w.Rect() - ) - return render.Rect{ - X: P.X, - Y: P.Y, - W: R.W + P.X, - H: R.H, // TODO: the Canvas in EditMode lets you draw pixels - // below the status bar if we do `+ R.Y` here. - } -} diff --git a/lib/ui/image.go b/lib/ui/image.go deleted file mode 100644 index d58e740..0000000 --- a/lib/ui/image.go +++ /dev/null @@ -1,89 +0,0 @@ -package ui - -import ( - "fmt" - "path/filepath" - "strings" - - "git.kirsle.net/go/render" -) - -// ImageType for supported image formats. -type ImageType string - -// Supported image formats. -const ( - BMP ImageType = "bmp" - PNG = "png" -) - -// Image is a widget that is backed by an image file. -type Image struct { - BaseWidget - - // Configurable fields for the constructor. - Type ImageType - texture render.Texturer -} - -// NewImage creates a new Image. -func NewImage(c Image) *Image { - w := &Image{ - Type: c.Type, - } - if w.Type == "" { - w.Type = BMP - } - - w.IDFunc(func() string { - return fmt.Sprintf(`Image<"%s">`, w.Type) - }) - return w -} - -// ImageFromTexture creates an Image from a texture. -func ImageFromTexture(tex render.Texturer) *Image { - return &Image{ - texture: tex, - } -} - -// OpenImage initializes an Image with a given file name. -// -// The file extension is important and should be a supported ImageType. -func OpenImage(e render.Engine, filename string) (*Image, error) { - w := &Image{} - switch strings.ToLower(filepath.Ext(filename)) { - case ".bmp": - w.Type = BMP - case ".png": - w.Type = PNG - default: - return nil, fmt.Errorf("OpenImage: %s: not a supported image type", filename) - } - - tex, err := e.LoadTexture(filename) - if err != nil { - return nil, err - } - - w.texture = tex - return w, nil -} - -// Compute the widget. -func (w *Image) Compute(e render.Engine) { - w.Resize(w.texture.Size()) -} - -// Present the widget. -func (w *Image) Present(e render.Engine, p render.Point) { - size := w.texture.Size() - dst := render.Rect{ - X: p.X, - Y: p.Y, - W: size.W, - H: size.H, - } - e.Copy(w.texture, size, dst) -} diff --git a/lib/ui/label.go b/lib/ui/label.go deleted file mode 100644 index f670aca..0000000 --- a/lib/ui/label.go +++ /dev/null @@ -1,133 +0,0 @@ -package ui - -import ( - "fmt" - "strings" - - "git.kirsle.net/go/render" -) - -// DefaultFont is the default font settings used for a Label. -var DefaultFont = render.Text{ - Size: 12, - Color: render.Black, -} - -// Label is a simple text label widget. -type Label struct { - BaseWidget - - // Configurable fields for the constructor. - Text string - TextVariable *string - IntVariable *int - Font render.Text - - width int32 - height int32 - lineHeight int -} - -// NewLabel creates a new label. -func NewLabel(c Label) *Label { - w := &Label{ - Text: c.Text, - TextVariable: c.TextVariable, - IntVariable: c.IntVariable, - Font: DefaultFont, - } - if !c.Font.IsZero() { - w.Font = c.Font - } - w.IDFunc(func() string { - 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 - } else if w.IntVariable != nil { - w.Font.Text = fmt.Sprintf("%d", *w.IntVariable) - return w.Font - } - w.Font.Text = w.Text - return w.Font -} - -// Value returns the current text value displayed in the widget, whether it was -// the hardcoded value or a TextVariable. -func (w *Label) Value() string { - return w.text().Text -} - -// Compute the size of the label widget. -func (w *Label) Compute(e render.Engine) { - text := w.text() - lines := strings.Split(text.Text, "\n") - - // Max rect to encompass all lines of text. - var maxRect = render.Rect{} - for _, line := range lines { - if line == "" { - line = "" - } - - text.Text = line // only this line at this time. - rect, err := e.ComputeTextRect(text) - if err != nil { - panic(fmt.Sprintf("%s: failed to compute text rect: %s", w, err)) // TODO return an error - } - - if rect.W > maxRect.W { - maxRect.W = rect.W - } - maxRect.H += rect.H - w.lineHeight = int(rect.H) - } - - var ( - padX = w.Font.Padding + w.Font.PadX - padY = w.Font.Padding + w.Font.PadY - ) - - if !w.FixedSize() { - w.ResizeAuto(render.Rect{ - W: maxRect.W + (padX * 2), - H: maxRect.H + (padY * 2), - }) - } - - w.MoveTo(render.Point{ - X: maxRect.X + w.BoxThickness(1), - Y: maxRect.Y + w.BoxThickness(1), - }) -} - -// Present the label widget. -func (w *Label) Present(e render.Engine, P render.Point) { - if w.Hidden() { - return - } - - border := w.BoxThickness(1) - - var ( - text = w.text() - padX = w.Font.Padding + w.Font.PadX - padY = w.Font.Padding + w.Font.PadY - ) - - w.DrawBox(e, P) - for i, line := range strings.Split(text.Text, "\n") { - text.Text = line - e.DrawText(text, render.Point{ - X: P.X + border + padX, - Y: P.Y + border + padY + int32(i*w.lineHeight), - }) - } -} diff --git a/lib/ui/main_window.go b/lib/ui/main_window.go deleted file mode 100644 index 6d3c837..0000000 --- a/lib/ui/main_window.go +++ /dev/null @@ -1,128 +0,0 @@ -// +build !js - -package ui - -import ( - "fmt" - "time" - - "git.kirsle.net/go/render" - "git.kirsle.net/go/render/sdl" -) - -// Target frames per second for the MainWindow to render at. -var ( - FPS = 60 -) - -// MainWindow is the parent window of a UI application. -type MainWindow struct { - engine render.Engine - supervisor *Supervisor - frame *Frame - w int - h int -} - -// NewMainWindow initializes the MainWindow. You should probably only have one -// of these per application. -func NewMainWindow(title string) (*MainWindow, error) { - mw := &MainWindow{ - w: 800, - h: 600, - supervisor: NewSupervisor(), - } - - mw.engine = sdl.New( - title, - mw.w, - mw.h, - ) - if err := mw.engine.Setup(); err != nil { - return nil, err - } - - // Add a default frame to the window. - mw.frame = NewFrame("MainWindow Body") - mw.frame.SetBackground(render.RGBA(0, 153, 255, 100)) - mw.Add(mw.frame) - - // Compute initial window size. - mw.resized() - - return mw, nil -} - -// Add a child widget to the window. -func (mw *MainWindow) Add(w Widget) { - mw.supervisor.Add(w) -} - -// Pack a child widget into the window's default frame. -func (mw *MainWindow) Pack(w Widget, pack Pack) { - mw.Add(w) - mw.frame.Pack(w, pack) -} - -// resized handles the window being resized. -func (mw *MainWindow) resized() { - mw.frame.Resize(render.Rect{ - W: int32(mw.w), - H: int32(mw.h), - }) -} - -// Present the window. -func (mw *MainWindow) Present() { - mw.supervisor.Present(mw.engine) -} - -// MainLoop starts the main event loop and blocks until there's an error. -func (mw *MainWindow) MainLoop() error { - for true { - if err := mw.Loop(); err != nil { - return err - } - } - return nil -} - -// Loop does one loop of the UI. -func (mw *MainWindow) Loop() error { - mw.engine.Clear(render.White) - - // Record how long this loop took. - start := time.Now() - - // Poll for events. - ev, err := mw.engine.Poll() - if err != nil { - return fmt.Errorf("event poll error: %s", err) - } - - if ev.WindowResized { - w, h := mw.engine.WindowSize() - if w != mw.w || h != mw.h { - mw.w = w - mw.h = h - mw.resized() - } - } - - mw.frame.Compute(mw.engine) - - // Render the child widgets. - mw.supervisor.Present(mw.engine) - mw.engine.Present() - - // Delay to maintain target frames per second. - var delay uint32 - var targetFPS = 1000 / FPS - elapsed := time.Now().Sub(start) / time.Millisecond - if targetFPS-int(elapsed) > 0 { - delay = uint32(targetFPS - int(elapsed)) - } - mw.engine.Delay(delay) - - return nil -} diff --git a/lib/ui/menu.go b/lib/ui/menu.go deleted file mode 100644 index 077773c..0000000 --- a/lib/ui/menu.go +++ /dev/null @@ -1,99 +0,0 @@ -package ui - -import ( - "fmt" - - "git.kirsle.net/go/render" -) - -// Menu is a rectangle that holds menu items. -type Menu struct { - BaseWidget - Name string - - body *Frame -} - -// NewMenu creates a new Menu. It is hidden by default. Usually you'll -// use it with a MenuButton or in a right-click handler. -func NewMenu(name string) *Menu { - w := &Menu{ - Name: name, - body: NewFrame(name + ":Body"), - } - w.body.Configure(Config{ - Width: 150, - BorderSize: 12, - BorderStyle: BorderRaised, - Background: render.Grey, - }) - w.IDFunc(func() string { - return fmt.Sprintf("Menu<%s>", w.Name) - }) - return w -} - -// Compute the menu -func (w *Menu) Compute(e render.Engine) { - w.body.Compute(e) -} - -// Present the menu -func (w *Menu) Present(e render.Engine, p render.Point) { - w.body.Present(e, p) -} - -// AddItem quickly adds an item to a menu. -func (w *Menu) AddItem(label string, command func()) *MenuItem { - menu := NewMenuItem(label, command) - w.Pack(menu) - return menu -} - -// Pack a menu item onto the menu. -func (w *Menu) Pack(item *MenuItem) { - w.body.Pack(item, Pack{ - Anchor: NE, - // Expand: true, - // Padding: 8, - FillX: true, - }) -} - -// MenuItem is an item in a Menu. -type MenuItem struct { - Button - Label string - Accelerator string - Command func() - button *Button -} - -// NewMenuItem creates a new menu item. -func NewMenuItem(label string, command func()) *MenuItem { - w := &MenuItem{ - Label: label, - Command: command, - } - w.IDFunc(func() string { - return fmt.Sprintf("MenuItem<%s>", w.Label) - }) - - font := DefaultFont - font.Color = render.White - font.PadX = 12 - w.Button.child = NewLabel(Label{ - Text: label, - Font: font, - }) - w.Button.Configure(Config{ - Background: render.Blue, - }) - - w.Button.Handle(Click, func(p render.Point) { - w.Command() - }) - - // Assign the button - return w -} diff --git a/lib/ui/supervisor.go b/lib/ui/supervisor.go deleted file mode 100644 index 2d92812..0000000 --- a/lib/ui/supervisor.go +++ /dev/null @@ -1,222 +0,0 @@ -package ui - -import ( - "errors" - "sync" - - "git.kirsle.net/go/render" - "git.kirsle.net/go/render/event" -) - -// Event is a named event that the supervisor will send. -type Event int - -// Events. -const ( - NullEvent Event = iota - MouseOver - MouseOut - MouseDown - MouseUp - Click - KeyDown - KeyUp - KeyPress - Drop -) - -// Supervisor keeps track of widgets of interest to notify them about -// interaction events such as mouse hovers and clicks in their general -// vicinity. -type Supervisor struct { - lock sync.RWMutex - serial int // ID number of each widget added in order - widgets map[int]WidgetSlot // map of widget ID to WidgetSlot - hovering map[int]interface{} // map of widgets under the cursor - clicked map[int]interface{} // map of widgets being clicked - dd *DragDrop -} - -// WidgetSlot holds a widget with a unique ID number in a sorted list. -type WidgetSlot struct { - id int - widget Widget -} - -// NewSupervisor creates a supervisor. -func NewSupervisor() *Supervisor { - return &Supervisor{ - widgets: map[int]WidgetSlot{}, - hovering: map[int]interface{}{}, - clicked: map[int]interface{}{}, - dd: NewDragDrop(), - } -} - -// DragStart sets the drag state. -func (s *Supervisor) DragStart() { - s.dd.Start() -} - -// DragStop stops the drag state. -func (s *Supervisor) DragStop() { - s.dd.Stop() -} - -// IsDragging returns whether the drag state is enabled. -func (s *Supervisor) IsDragging() bool { - return s.dd.IsDragging() -} - -// Error messages that may be returned by Supervisor.Loop() -var ( - // The caller should STOP forwarding any mouse or keyboard events to any - // other handles for the remainder of this tick. - ErrStopPropagation = errors.New("stop all event propagation") -) - -// Loop to check events and pass them to managed widgets. -// -// Useful errors returned by this may be: -// - ErrStopPropagation -func (s *Supervisor) Loop(ev *event.State) error { - var ( - XY = render.Point{ - X: int32(ev.CursorX), - Y: int32(ev.CursorY), - } - ) - - // See if we are hovering over any widgets. - hovering, outside := s.Hovering(XY) - - // If we are dragging something around, do not trigger any mouse events - // to other widgets but DO notify any widget we dropped on top of! - if s.dd.IsDragging() { - if !ev.Button1 && !ev.Button3 { - // The mouse has been released. TODO: make mouse button important? - for _, child := range hovering { - child.widget.Event(Drop, XY) - } - s.DragStop() - } - return ErrStopPropagation - } - - for _, child := range hovering { - var ( - id = child.id - w = child.widget - ) - if w.Hidden() { - // TODO: somehow the Supervisor wasn't triggering hidden widgets - // anyway, but I don't know why. Adding this check for safety. - continue - } - - // Cursor has intersected the widget. - if _, ok := s.hovering[id]; !ok { - w.Event(MouseOver, XY) - s.hovering[id] = nil - } - - _, isClicked := s.clicked[id] - if ev.Button1 { - if !isClicked { - w.Event(MouseDown, XY) - s.clicked[id] = nil - } - } else if isClicked { - w.Event(MouseUp, XY) - w.Event(Click, XY) - delete(s.clicked, id) - } - } - for _, child := range outside { - var ( - id = child.id - w = child.widget - ) - - // Cursor is not intersecting the widget. - if _, ok := s.hovering[id]; ok { - w.Event(MouseOut, XY) - delete(s.hovering, id) - } - - if _, ok := s.clicked[id]; ok { - w.Event(MouseUp, XY) - delete(s.clicked, id) - } - } - - return nil -} - -// Hovering returns all of the widgets managed by Supervisor that are under -// the mouse cursor. Returns the set of widgets below the cursor and the set -// of widgets not below the cursor. -func (s *Supervisor) Hovering(cursor render.Point) (hovering, outside []WidgetSlot) { - var XY = cursor // for shorthand - hovering = []WidgetSlot{} - outside = []WidgetSlot{} - - // Check all the widgets under our care. - for child := range s.Widgets() { - var ( - w = child.widget - P = w.Point() - S = w.Size() - P2 = render.Point{ - X: P.X + S.W, - Y: P.Y + S.H, - } - ) - - if XY.X >= P.X && XY.X < P2.X && XY.Y >= P.Y && XY.Y < P2.Y { - // Cursor intersects the widget. - hovering = append(hovering, child) - } else { - outside = append(outside, child) - } - } - - return hovering, outside -} - -// Widgets returns a channel of widgets managed by the supervisor in the order -// they were added. -func (s *Supervisor) Widgets() <-chan WidgetSlot { - pipe := make(chan WidgetSlot) - go func() { - for i := 0; i < s.serial; i++ { - if w, ok := s.widgets[i]; ok { - pipe <- w - } - } - close(pipe) - }() - return pipe -} - -// Present all widgets managed by the supervisor. -func (s *Supervisor) Present(e render.Engine) { - s.lock.RLock() - defer s.lock.RUnlock() - - for child := range s.Widgets() { - var w = child.widget - w.Present(e, w.Point()) - } -} - -// Add a widget to be supervised. -func (s *Supervisor) Add(w Widget) { - s.lock.Lock() - s.widgets[s.serial] = WidgetSlot{ - id: s.serial, - widget: w, - } - s.serial++ - s.lock.Unlock() -} diff --git a/lib/ui/theme/theme.go b/lib/ui/theme/theme.go deleted file mode 100644 index 33ce4b4..0000000 --- a/lib/ui/theme/theme.go +++ /dev/null @@ -1,12 +0,0 @@ -package theme - -import "git.kirsle.net/go/render" - -// Color schemes. -var ( - ButtonBackgroundColor = render.RGBA(200, 200, 200, 255) - ButtonHoverColor = render.RGBA(200, 255, 255, 255) - ButtonOutlineColor = render.Black - - BorderColorOffset = 40 -) diff --git a/lib/ui/widget.go b/lib/ui/widget.go deleted file mode 100644 index 0f0ec71..0000000 --- a/lib/ui/widget.go +++ /dev/null @@ -1,504 +0,0 @@ -package ui - -import ( - "fmt" - - "git.kirsle.net/go/render" - "git.kirsle.net/apps/doodle/lib/ui/theme" -) - -// BorderStyle options for widget.SetBorderStyle() -type BorderStyle string - -// Styles for a widget border. -const ( - BorderNone BorderStyle = "" - BorderSolid BorderStyle = "solid" - BorderRaised = "raised" - BorderSunken = "sunken" -) - -// Widget is a user interface element. -type Widget interface { - ID() string // Get the widget's string ID. - IDFunc(func() string) // Set a function that returns the widget's ID. - String() string - 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) - BoxSize() render.Rect // Return the full size including the border and outline. - Resize(render.Rect) - ResizeBy(render.Rect) - ResizeAuto(render.Rect) - Rect() render.Rect // Return the full absolute rect combining the Size() and Point() - - Handle(Event, func(render.Point)) - Event(Event, render.Point) // called internally to trigger an event - - // Thickness of the padding + border + outline. - BoxThickness(multiplier int32) int32 - DrawBox(render.Engine, render.Point) - - // Widget configuration getters. - Margin() int32 // Margin away from other widgets - SetMargin(int32) // - Background() render.Color // Background color - SetBackground(render.Color) // - Foreground() render.Color // Foreground color - SetForeground(render.Color) // - BorderStyle() BorderStyle // Border style: none, raised, sunken - SetBorderStyle(BorderStyle) // - BorderColor() render.Color // Border color (default is Background) - SetBorderColor(render.Color) // - BorderSize() int32 // Border size (default 0) - SetBorderSize(int32) // - OutlineColor() render.Color // Outline color (default Invisible) - SetOutlineColor(render.Color) // - OutlineSize() int32 // Outline size (default 0) - SetOutlineSize(int32) // - - // Visibility - Hide() - Show() - Hidden() bool - - // Container widgets like Frames can wire up associations between the - // child widgets and the parent. - Parent() (parent Widget, ok bool) - Adopt(parent Widget) // for the container to assign itself the parent - Children() []Widget // for containers to return their children - - // 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, render.Point) -} - -// Config holds common base widget configs for quick configuration. -type Config struct { - // Size management. If you provide a non-zero value for Width and Height, - // the widget will be resized and the "fixedSize" flag is set, meaning it - // will not re-compute its size dynamically. To set the size while also - // keeping the auto-resize property, pass AutoResize=true too. This is - // mainly used internally when widgets are calculating their automatic sizes. - AutoResize bool - Width int32 - Height int32 - Margin int32 - MarginX int32 - MarginY int32 - Background render.Color - Foreground render.Color - BorderSize int32 - BorderStyle BorderStyle - BorderColor render.Color - OutlineSize int32 - OutlineColor render.Color -} - -// BaseWidget holds common functionality for all widgets, such as managing -// their widths and heights. -type BaseWidget struct { - id string - idFunc func() string - fixedSize bool - hidden bool - width int32 - height int32 - point render.Point - margin int32 - background render.Color - foreground render.Color - borderStyle BorderStyle - borderColor render.Color - borderSize int32 - outlineColor render.Color - outlineSize int32 - handlers map[Event][]func(render.Point) - hasParent bool - parent Widget -} - -// SetID sets a string name for your widget, helpful for debugging purposes. -func (w *BaseWidget) SetID(id string) { - w.id = id -} - -// ID returns the ID that the widget calls itself by. -func (w *BaseWidget) ID() string { - if w.idFunc == nil { - w.IDFunc(func() string { - return "Widget" - }) - } - return w.idFunc() -} - -// IDFunc sets an ID function. -func (w *BaseWidget) IDFunc(fn func() string) { - w.idFunc = fn -} - -func (w *BaseWidget) String() string { - return w.ID() -} - -// Configure the base widget with all the common properties at once. Any -// property left as the zero value will not update the widget. -func (w *BaseWidget) Configure(c Config) { - if c.Width != 0 || c.Height != 0 { - w.fixedSize = !c.AutoResize - if c.Width != 0 { - w.width = c.Width - } - if c.Height != 0 { - w.height = c.Height - } - } - - if c.Margin != 0 { - w.margin = c.Margin - } - if c.Background != render.Invisible { - w.background = c.Background - } - if c.Foreground != render.Invisible { - w.foreground = c.Foreground - } - if c.BorderColor != render.Invisible { - w.borderColor = c.BorderColor - } - if c.OutlineColor != render.Invisible { - w.outlineColor = c.OutlineColor - } - - if c.BorderSize != 0 { - w.borderSize = c.BorderSize - } - if c.BorderStyle != BorderNone { - w.borderStyle = c.BorderStyle - } - if c.OutlineSize != 0 { - w.outlineSize = c.OutlineSize - } -} - -// Rect returns the widget's absolute rectangle, the combined Size and Point. -func (w *BaseWidget) Rect() render.Rect { - return render.Rect{ - X: w.point.X, - Y: w.point.Y, - W: w.width, - H: w.height, - } -} - -// Point returns the X,Y position of the widget on the window. -func (w *BaseWidget) Point() render.Point { - return w.point -} - -// MoveTo updates the X,Y position to the new point. -func (w *BaseWidget) MoveTo(v render.Point) { - w.point = v -} - -// MoveBy adds the X,Y values to the widget's current position. -func (w *BaseWidget) MoveBy(v render.Point) { - w.point.X += v.X - w.point.Y += v.Y -} - -// 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, - } -} - -// 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 { - return w.fixedSize -} - -// Resize sets the size of the widget to the .W and .H attributes of a rect. -func (w *BaseWidget) Resize(v render.Rect) { - w.fixedSize = true - w.width = v.W - w.height = v.H -} - -// ResizeBy resizes by a relative amount. -func (w *BaseWidget) ResizeBy(v render.Rect) { - w.fixedSize = true - w.width += v.W - w.height += v.H -} - -// ResizeAuto sets the size of the widget but doesn't set the fixedSize flag. -func (w *BaseWidget) ResizeAuto(v render.Rect) { - if w.ID() == "Frame" { - fmt.Printf("%s: ResizeAuto Called: %+v\n", - w.ID(), - v, - ) - } - w.width = v.W - w.height = v.H -} - -// BoxThickness returns the full sum of the padding, border and outline. -// m = multiplier, i.e., 1 or 2 -func (w *BaseWidget) BoxThickness(m int32) int32 { - if m == 0 { - m = 1 - } - return (w.Margin() * m) + (w.BorderSize() * m) + (w.OutlineSize() * m) -} - -// Parent returns the parent widget, like a Frame, and a boolean indicating -// whether the widget had a parent. -func (w *BaseWidget) Parent() (Widget, bool) { - return w.parent, w.hasParent -} - -// Adopt sets the widget's parent. This function is called by container -// widgets like Frame when they add a child widget to their care. -// Pass a nil parent to unset the parent. -func (w *BaseWidget) Adopt(parent Widget) { - if parent == nil { - w.hasParent = false - w.parent = nil - } else { - w.hasParent = true - w.parent = parent - } -} - -// Children returns the widget's children, to be implemented by containers. -// The default implementation returns an empty slice. -func (w *BaseWidget) Children() []Widget { - return []Widget{} -} - -// Hide the widget from being rendered. -func (w *BaseWidget) Hide() { - w.hidden = true -} - -// Show the widget. -func (w *BaseWidget) Show() { - w.hidden = false -} - -// Hidden returns whether the widget is hidden. If this widget is not hidden, -// but it has a parent, this will recursively crawl the parents to see if any -// of them are hidden. -func (w *BaseWidget) Hidden() bool { - if w.hidden { - return true - } - - if parent, ok := w.Parent(); ok { - return parent.Hidden() - } - - return false -} - -// DrawBox draws the border and outline. -func (w *BaseWidget) DrawBox(e render.Engine, P render.Point) { - var ( - S = w.Size() - outline = w.OutlineSize() - border = w.BorderSize() - borderColor = w.BorderColor() - highlight = borderColor.Lighten(theme.BorderColorOffset) - shadow = borderColor.Darken(theme.BorderColorOffset) - color render.Color - box = render.Rect{ - X: P.X, - Y: P.Y, - W: S.W, - H: S.H, - } - ) - - if borderColor == render.Invisible { - borderColor = render.Red - } - - // Draw the outline layer as the full size of the widget. - if outline > 0 && w.OutlineColor() != render.Invisible { - e.DrawBox(w.OutlineColor(), render.Rect{ - X: P.X, - Y: P.Y, - W: S.W, - H: S.H, - }) - } - box.X += outline - box.Y += outline - box.W -= outline * 2 - box.H -= outline * 2 - - // Highlight on the top left edge. - if border > 0 { - if w.BorderStyle() == BorderRaised { - color = highlight - } else if w.BorderStyle() == BorderSunken { - color = shadow - } else { - color = borderColor - } - e.DrawBox(color, box) - } - - // Shadow on the bottom right edge. - box.X += border - box.Y += border - box.W -= border - box.H -= border - if w.BorderSize() > 0 { - if w.BorderStyle() == BorderRaised { - color = shadow - } else if w.BorderStyle() == BorderSunken { - color = highlight - } else { - color = borderColor - } - e.DrawBox(color, box) - } - - // Background color of the button. - box.W -= border - box.H -= border - if w.Background() != render.Invisible { - e.DrawBox(w.Background(), box) - } -} - -// Margin returns the margin width. -func (w *BaseWidget) Margin() int32 { - return w.margin -} - -// SetMargin sets the margin width. -func (w *BaseWidget) SetMargin(v int32) { - w.margin = v -} - -// Background returns the background color. -func (w *BaseWidget) Background() render.Color { - return w.background -} - -// SetBackground sets the color. -func (w *BaseWidget) SetBackground(c render.Color) { - w.background = c -} - -// Foreground returns the foreground color. -func (w *BaseWidget) Foreground() render.Color { - return w.foreground -} - -// SetForeground sets the color. -func (w *BaseWidget) SetForeground(c render.Color) { - w.foreground = c -} - -// BorderStyle returns the border style. -func (w *BaseWidget) BorderStyle() BorderStyle { - return w.borderStyle -} - -// SetBorderStyle sets the border style. -func (w *BaseWidget) SetBorderStyle(v BorderStyle) { - w.borderStyle = v -} - -// BorderColor returns the border color, or defaults to the background color. -func (w *BaseWidget) BorderColor() render.Color { - if w.borderColor == render.Invisible { - return w.Background() - } - return w.borderColor -} - -// SetBorderColor sets the border color. -func (w *BaseWidget) SetBorderColor(c render.Color) { - w.borderColor = c -} - -// BorderSize returns the border thickness. -func (w *BaseWidget) BorderSize() int32 { - return w.borderSize -} - -// SetBorderSize sets the border thickness. -func (w *BaseWidget) SetBorderSize(v int32) { - w.borderSize = v -} - -// OutlineColor returns the background color. -func (w *BaseWidget) OutlineColor() render.Color { - return w.outlineColor -} - -// SetOutlineColor sets the color. -func (w *BaseWidget) SetOutlineColor(c render.Color) { - w.outlineColor = c -} - -// OutlineSize returns the outline thickness. -func (w *BaseWidget) OutlineSize() int32 { - return w.outlineSize -} - -// SetOutlineSize sets the outline thickness. -func (w *BaseWidget) SetOutlineSize(v int32) { - w.outlineSize = v -} - -// Event is called internally by Doodle to trigger an event. -func (w *BaseWidget) Event(event Event, p render.Point) { - if handlers, ok := w.handlers[event]; ok { - for _, fn := range handlers { - fn(p) - } - } -} - -// Handle an event in the widget. -func (w *BaseWidget) Handle(event Event, fn func(render.Point)) { - if w.handlers == nil { - w.handlers = map[Event][]func(render.Point){} - } - - if _, ok := w.handlers[event]; !ok { - w.handlers[event] = []func(render.Point){} - } - - w.handlers[event] = append(w.handlers[event], fn) -} - -// OnMouseOut should be overridden on widgets who want this event. -func (w *BaseWidget) OnMouseOut(render.Point) {} diff --git a/lib/ui/window.go b/lib/ui/window.go deleted file mode 100644 index 6d76c04..0000000 --- a/lib/ui/window.go +++ /dev/null @@ -1,114 +0,0 @@ -package ui - -import ( - "fmt" - - "git.kirsle.net/go/render" -) - -// Window is a frame with a title bar. -type Window struct { - BaseWidget - Title string - Active bool - - // Private widgets. - body *Frame - titleBar *Label - content *Frame -} - -// NewWindow creates a new window. -func NewWindow(title string) *Window { - w := &Window{ - Title: title, - body: NewFrame("body:" + title), - } - w.IDFunc(func() string { - return fmt.Sprintf("Window<%s>", - w.Title, - ) - }) - - w.body.Configure(Config{ - Background: render.Grey, - BorderSize: 2, - BorderStyle: BorderRaised, - }) - - // Title bar widget. - titleBar := NewLabel(Label{ - TextVariable: &w.Title, - Font: render.Text{ - Color: render.White, - Size: 10, - Stroke: render.DarkBlue, - Padding: 2, - }, - }) - titleBar.Configure(Config{ - Background: render.Blue, - }) - w.body.Pack(titleBar, Pack{ - Anchor: N, - Fill: true, - }) - w.titleBar = titleBar - - // Window content frame. - content := NewFrame("content:" + title) - content.Configure(Config{ - Background: render.Grey, - }) - w.body.Pack(content, Pack{ - Anchor: N, - Fill: true, - }) - w.content = content - - return w -} - -// Children returns the window's child widgets. -func (w *Window) Children() []Widget { - return []Widget{ - w.body, - } -} - -// TitleBar returns the title bar widget. -func (w *Window) TitleBar() *Label { - return w.titleBar -} - -// Configure the widget. Color and style changes are passed down to the inner -// content frame of the window. -func (w *Window) Configure(C Config) { - w.BaseWidget.Configure(C) - w.body.Configure(C) - - // Don't pass dimensions down any further than the body. - C.Width = 0 - C.Height = 0 - w.content.Configure(C) -} - -// ConfigureTitle configures the title bar widget. -func (w *Window) ConfigureTitle(C Config) { - w.titleBar.Configure(C) -} - -// Compute the window. -func (w *Window) Compute(e render.Engine) { - w.body.Compute(e) -} - -// Present the window. -func (w *Window) Present(e render.Engine, P render.Point) { - w.body.Present(e, P) -} - -// Pack a widget into the window's frame. -func (w *Window) Pack(child Widget, config ...Pack) { - w.content.Pack(child, config...) -}