direct-draw #6

Merged
sashakoshka merged 10 commits from direct-draw into main 2023-02-01 04:05:26 +00:00
21 changed files with 206 additions and 304 deletions

View File

@ -13,6 +13,7 @@ type Window struct {
backend *Backend backend *Backend
xWindow *xwindow.Window xWindow *xwindow.Window
xCanvas *xgraphics.Image xCanvas *xgraphics.Image
canvas tomo.BasicCanvas
child tomo.Element child tomo.Element
onClose func () onClose func ()
skipChildDrawCallback bool skipChildDrawCallback bool
@ -194,6 +195,9 @@ func (window *Window) OnClose (callback func ()) {
} }
func (window *Window) reallocateCanvas () { func (window *Window) reallocateCanvas () {
window.canvas = tomo.NewBasicCanvas (
window.metrics.width,
window.metrics.height)
if window.xCanvas != nil { if window.xCanvas != nil {
window.xCanvas.Destroy() window.xCanvas.Destroy()
} }
@ -203,12 +207,12 @@ func (window *Window) reallocateCanvas () {
0, 0, 0, 0,
window.metrics.width, window.metrics.width,
window.metrics.height)) window.metrics.height))
window.xCanvas.CreatePixmap() window.xCanvas.CreatePixmap()
} }
func (window *Window) redrawChildEntirely () { func (window *Window) redrawChildEntirely () {
window.pushRegion(window.paste(window.child)) window.pushRegion(window.paste(window.child))
} }
func (window *Window) resizeChildToFit () { func (window *Window) resizeChildToFit () {
@ -228,15 +232,10 @@ func (window *Window) resizeChildToFit () {
if window.metrics.height >= minimumHeight && if window.metrics.height >= minimumHeight &&
window.metrics.width >= minimumWidth { window.metrics.width >= minimumWidth {
window.child.DrawTo(window.canvas)
window.child.Resize (
window.metrics.width,
window.metrics.height)
} }
} else { } else {
window.child.Resize ( window.child.DrawTo(window.canvas)
window.metrics.width,
window.metrics.height)
} }
window.skipChildDrawCallback = false window.skipChildDrawCallback = false
} }

View File

@ -63,6 +63,7 @@ func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) {
// Cut returns a sub-canvas of a given canvas. // Cut returns a sub-canvas of a given canvas.
func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) { func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) {
// println(canvas.Bounds().String(), bounds.String())
bounds = bounds.Intersect(canvas.Bounds()) bounds = bounds.Intersect(canvas.Bounds())
if bounds.Empty() { return } if bounds.Empty() { return }
reduced.rect = bounds reduced.rect = bounds

View File

@ -15,9 +15,10 @@ type Element interface {
// instead of the offending dimension(s). // instead of the offending dimension(s).
MinimumSize () (width, height int) MinimumSize () (width, height int)
// Resize resizes the element. This should only be called by the // DrawTo sets this element's canvas. This should only be called by the
// element's parent. // parent element. This is typically a region of the parent element's
Resize (width, height int) // canvas.
DrawTo (canvas Canvas)
// OnDamage sets a function to be called when an area of the element is // OnDamage sets a function to be called when an area of the element is
// drawn on and should be pushed to the screen. // drawn on and should be pushed to the screen.

View File

@ -25,7 +25,7 @@ type Button struct {
// NewButton creates a new button with the specified label text. // NewButton creates a new button with the specified label text.
func NewButton (text string) (element *Button) { func NewButton (text string) (element *Button) {
element = &Button { } element = &Button { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () { if element.core.HasImage () {
@ -38,11 +38,6 @@ func NewButton (text string) (element *Button) {
return return
} }
func (element *Button) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.draw()
}
func (element *Button) HandleMouseDown (x, y int, button tomo.Button) { func (element *Button) HandleMouseDown (x, y int, button tomo.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
if !element.Focused() { element.Focus() } if !element.Focused() { element.Focus() }
@ -126,7 +121,7 @@ func (element *Button) SetText (text string) {
} }
func (element *Button) draw () { func (element *Button) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
pattern, inset := theme.ButtonPattern(theme.PatternState { pattern, inset := theme.ButtonPattern(theme.PatternState {
Case: buttonCase, Case: buttonCase,
@ -135,14 +130,14 @@ func (element *Button) draw () {
Pressed: element.pressed, Pressed: element.pressed,
}) })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
innerBounds := inset.Apply(bounds) innerBounds := inset.Apply(bounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
offset := image.Point { offset := image.Point {
X: innerBounds.Min.X + (innerBounds.Dx() - textBounds.Dx()) / 2, X: innerBounds.Min.X + (innerBounds.Dx() - textBounds.Dx()) / 2,
Y: innerBounds.Min.X + (innerBounds.Dy() - textBounds.Dy()) / 2, Y: innerBounds.Min.Y + (innerBounds.Dy() - textBounds.Dy()) / 2,
} }
// account for the fact that the bounding rectangle will be shifted over // account for the fact that the bounding rectangle will be shifted over
@ -154,5 +149,5 @@ func (element *Button) draw () {
Case: buttonCase, Case: buttonCase,
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
}) })
element.drawer.Draw(element.core, foreground, offset) element.drawer.Draw(element, foreground, offset)
} }

View File

@ -26,7 +26,7 @@ type Checkbox struct {
// NewCheckbox creates a new cbeckbox with the specified label text. // NewCheckbox creates a new cbeckbox with the specified label text.
func NewCheckbox (text string, checked bool) (element *Checkbox) { func NewCheckbox (text string, checked bool) (element *Checkbox) {
element = &Checkbox { checked: checked } element = &Checkbox { checked: checked }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () { if element.core.HasImage () {
@ -39,12 +39,6 @@ func NewCheckbox (text string, checked bool) (element *Checkbox) {
return return
} }
// Resize changes this element's size.
func (element *Checkbox) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.draw()
}
func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) { func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
@ -139,13 +133,13 @@ func (element *Checkbox) SetText (text string) {
} }
func (element *Checkbox) draw () { func (element *Checkbox) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()) boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: checkboxCase, Case: checkboxCase,
}) })
artist.FillRectangle ( element.core, backgroundPattern, bounds) artist.FillRectangle(element, backgroundPattern, bounds)
pattern, inset := theme.ButtonPattern(theme.PatternState { pattern, inset := theme.ButtonPattern(theme.PatternState {
Case: checkboxCase, Case: checkboxCase,
@ -153,12 +147,12 @@ func (element *Checkbox) draw () {
Focused: element.Focused(), Focused: element.Focused(),
Pressed: element.pressed, Pressed: element.pressed,
}) })
artist.FillRectangle(element.core, pattern, boxBounds) artist.FillRectangle(element, pattern, boxBounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
offset := image.Point { offset := bounds.Min.Add(image.Point {
X: bounds.Dy() + theme.Padding(), X: bounds.Dy() + theme.Padding(),
} })
offset.Y -= textBounds.Min.Y offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X offset.X -= textBounds.Min.X
@ -167,10 +161,10 @@ func (element *Checkbox) draw () {
Case: checkboxCase, Case: checkboxCase,
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
}) })
element.drawer.Draw(element.core, foreground, offset) element.drawer.Draw(element, foreground, offset)
if element.checked { if element.checked {
checkBounds := inset.Apply(boxBounds).Inset(2) checkBounds := inset.Apply(boxBounds).Inset(2)
artist.FillRectangle(element.core, foreground, checkBounds) artist.FillRectangle(element, foreground, checkBounds)
} }
} }

View File

@ -30,7 +30,7 @@ type Container struct {
// NewContainer creates a new container. // NewContainer creates a new container.
func NewContainer (layout tomo.Layout) (element *Container) { func NewContainer (layout tomo.Layout) (element *Container) {
element = &Container { } element = &Container { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.redoAll)
element.SetLayout(layout) element.SetLayout(layout)
return return
} }
@ -39,8 +39,7 @@ func NewContainer (layout tomo.Layout) (element *Container) {
func (element *Container) SetLayout (layout tomo.Layout) { func (element *Container) SetLayout (layout tomo.Layout) {
element.layout = layout element.layout = layout
if element.core.HasImage() { if element.core.HasImage() {
element.recalculate() element.redoAll()
element.draw()
element.core.DamageAll() element.core.DamageAll()
} }
} }
@ -51,7 +50,7 @@ func (element *Container) SetLayout (layout tomo.Layout) {
func (element *Container) Adopt (child tomo.Element, expand bool) { func (element *Container) Adopt (child tomo.Element, expand bool) {
// set event handlers // set event handlers
child.OnDamage (func (region tomo.Canvas) { child.OnDamage (func (region tomo.Canvas) {
element.drawChildRegion(child, region) element.core.DamageRegion(region.Bounds())
}) })
child.OnMinimumSizeChange(element.updateMinimumSize) child.OnMinimumSizeChange(element.updateMinimumSize)
if child0, ok := child.(tomo.Flexible); ok { if child0, ok := child.(tomo.Flexible); ok {
@ -78,8 +77,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) {
element.updateMinimumSize() element.updateMinimumSize()
element.reflectChildProperties() element.reflectChildProperties()
if element.core.HasImage() && !element.warping { if element.core.HasImage() && !element.warping {
element.recalculate() element.redoAll()
element.draw()
element.core.DamageAll() element.core.DamageAll()
} }
} }
@ -101,8 +99,7 @@ func (element *Container) Warp (callback func ()) {
// and redraw every time, because although that is the most likely use // and redraw every time, because although that is the most likely use
// case, it is not the only one. // case, it is not the only one.
if element.core.HasImage() { if element.core.HasImage() {
element.recalculate() element.redoAll()
element.draw()
element.core.DamageAll() element.core.DamageAll()
} }
} }
@ -123,13 +120,13 @@ func (element *Container) Disown (child tomo.Element) {
element.updateMinimumSize() element.updateMinimumSize()
element.reflectChildProperties() element.reflectChildProperties()
if element.core.HasImage() && !element.warping { if element.core.HasImage() && !element.warping {
element.recalculate() element.redoAll()
element.draw()
element.core.DamageAll() element.core.DamageAll()
} }
} }
func (element *Container) clearChildEventHandlers (child tomo.Element) { func (element *Container) clearChildEventHandlers (child tomo.Element) {
child.DrawTo(nil)
child.OnDamage(nil) child.OnDamage(nil)
child.OnMinimumSizeChange(nil) child.OnMinimumSizeChange(nil)
if child0, ok := child.(tomo.Focusable); ok { if child0, ok := child.(tomo.Focusable); ok {
@ -151,8 +148,7 @@ func (element *Container) DisownAll () {
element.updateMinimumSize() element.updateMinimumSize()
element.reflectChildProperties() element.reflectChildProperties()
if element.core.HasImage() && !element.warping { if element.core.HasImage() && !element.warping {
element.recalculate() element.redoAll()
element.draw()
element.core.DamageAll() element.core.DamageAll()
} }
} }
@ -182,7 +178,7 @@ func (element *Container) Child (index int) (child tomo.Element) {
// there are no children at the coordinates, this method will return nil. // there are no children at the coordinates, this method will return nil.
func (element *Container) ChildAt (point image.Point) (child tomo.Element) { func (element *Container) ChildAt (point image.Point) (child tomo.Element) {
for _, entry := range element.children { for _, entry := range element.children {
if point.In(entry.Bounds().Add(entry.Position)) { if point.In(entry.Bounds) {
child = entry.Element child = entry.Element
} }
} }
@ -192,7 +188,7 @@ func (element *Container) ChildAt (point image.Point) (child tomo.Element) {
func (element *Container) childPosition (child tomo.Element) (position image.Point) { func (element *Container) childPosition (child tomo.Element) (position image.Point) {
for _, entry := range element.children { for _, entry := range element.children {
if entry.Element == child { if entry.Element == child {
position = entry.Position position = entry.Bounds.Min
break break
} }
} }
@ -200,41 +196,48 @@ func (element *Container) childPosition (child tomo.Element) (position image.Poi
return return
} }
func (element *Container) Resize (width, height int) { func (element *Container) redoAll () {
element.core.AllocateCanvas(width, height) // do a layout
element.recalculate() element.recalculate()
element.draw()
// draw a background
bounds := element.Bounds()
pattern, _ := theme.BackgroundPattern (theme.PatternState {
Case: containerCase,
})
artist.FillRectangle(element, pattern, bounds)
// cut our canvas up and give peices to child elements
for _, entry := range element.children {
entry.DrawTo(tomo.Cut(element, entry.Bounds))
}
} }
func (element *Container) HandleMouseDown (x, y int, button tomo.Button) { func (element *Container) HandleMouseDown (x, y int, button tomo.Button) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
if !handlesMouse { return } if !handlesMouse { return }
element.drags[button] = child element.drags[button] = child
childPosition := element.childPosition(child) child.HandleMouseDown(x, y, button)
child.HandleMouseDown(x - childPosition.X, y - childPosition.Y, button)
} }
func (element *Container) HandleMouseUp (x, y int, button tomo.Button) { func (element *Container) HandleMouseUp (x, y int, button tomo.Button) {
child := element.drags[button] child := element.drags[button]
if child == nil { return } if child == nil { return }
element.drags[button] = nil element.drags[button] = nil
childPosition := element.childPosition(child) child.HandleMouseUp(x, y, button)
child.HandleMouseUp(x - childPosition.X, y - childPosition.Y, button)
} }
func (element *Container) HandleMouseMove (x, y int) { func (element *Container) HandleMouseMove (x, y int) {
for _, child := range element.drags { for _, child := range element.drags {
if child == nil { continue } if child == nil { continue }
childPosition := element.childPosition(child) child.HandleMouseMove(x, y)
child.HandleMouseMove(x - childPosition.X, y - childPosition.Y)
} }
} }
func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) { func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
if !handlesMouse { return } if !handlesMouse { return }
childPosition := element.childPosition(child) child.HandleMouseScroll(x, y, deltaX, deltaY)
child.HandleMouseScroll(x - childPosition.X, y - childPosition.Y, deltaX, deltaY)
} }
func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
@ -468,31 +471,5 @@ func (element *Container) updateMinimumSize () {
} }
func (element *Container) recalculate () { func (element *Container) recalculate () {
bounds := element.Bounds() element.layout.Arrange(element.children, element.Bounds())
element.layout.Arrange(element.children, bounds.Dx(), bounds.Dy())
}
func (element *Container) draw () {
bounds := element.core.Bounds()
pattern, _ := theme.BackgroundPattern (theme.PatternState {
Case: containerCase,
})
artist.FillRectangle(element.core, pattern, bounds)
for _, entry := range element.children {
artist.Paste(element.core, entry, entry.Position)
}
}
func (element *Container) drawChildRegion (child tomo.Element, region tomo.Canvas) {
if element.warping { return }
for _, entry := range element.children {
if entry.Element == child {
artist.Paste(element.core, region, entry.Position)
element.core.DamageRegion (
region.Bounds().Add(entry.Position))
break
}
}
} }

View File

@ -1,6 +1,5 @@
package basic package basic
import "image"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -23,7 +22,7 @@ type Label struct {
// wrapped. // wrapped.
func NewLabel (text string, wrap bool) (element *Label) { func NewLabel (text string, wrap bool) (element *Label) {
element = &Label { } element = &Label { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.handleResize)
face := theme.FontFaceRegular() face := theme.FontFaceRegular()
element.drawer.SetFace(face) element.drawer.SetFace(face)
element.SetWrap(wrap) element.SetWrap(wrap)
@ -31,12 +30,11 @@ func NewLabel (text string, wrap bool) (element *Label) {
return return
} }
// Resize resizes the label and re-wraps the text if wrapping is enabled. func (element *Label) handleResize () {
func (element *Label) Resize (width, height int) { bounds := element.Bounds()
element.core.AllocateCanvas(width, height)
if element.wrap { if element.wrap {
element.drawer.SetMaxWidth(width) element.drawer.SetMaxWidth(bounds.Dx())
element.drawer.SetMaxHeight(height) element.drawer.SetMaxHeight(bounds.Dy())
} }
element.draw() element.draw()
return return
@ -108,20 +106,17 @@ func (element *Label) updateMinimumSize () {
} }
func (element *Label) draw () { func (element *Label) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
pattern, _ := theme.BackgroundPattern(theme.PatternState { pattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: labelCase, Case: labelCase,
}) })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
foreground, _ := theme.ForegroundPattern (theme.PatternState { foreground, _ := theme.ForegroundPattern (theme.PatternState {
Case: labelCase, Case: labelCase,
}) })
element.drawer.Draw (element.core, foreground, image.Point { element.drawer.Draw (element, foreground, bounds.Min.Sub(textBounds.Min))
X: 0 - textBounds.Min.X,
Y: 0 - textBounds.Min.Y,
})
} }

View File

@ -33,7 +33,7 @@ type List struct {
// NewList creates a new list element with the specified entries. // NewList creates a new list element with the specified entries.
func NewList (entries ...ListEntry) (element *List) { func NewList (entries ...ListEntry) (element *List) {
element = &List { selectedEntry: -1 } element = &List { selectedEntry: -1 }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.handleResize)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () { if element.core.HasImage () {
@ -51,10 +51,7 @@ func NewList (entries ...ListEntry) (element *List) {
return return
} }
// Resize changes the element's size. func (element *List) handleResize () {
func (element *List) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
for index, entry := range element.entries { for index, entry := range element.entries {
element.entries[index] = element.resizeEntryToFit(entry) element.entries[index] = element.resizeEntryToFit(entry)
} }
@ -379,7 +376,7 @@ func (element *List) draw () {
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
Focused: element.Focused(), Focused: element.Focused(),
}) })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
bounds = inset.Apply(bounds) bounds = inset.Apply(bounds)
dot := image.Point { dot := image.Point {

View File

@ -16,18 +16,11 @@ type ProgressBar struct {
// level. // level.
func NewProgressBar (progress float64) (element *ProgressBar) { func NewProgressBar (progress float64) (element *ProgressBar) {
element = &ProgressBar { progress: progress } element = &ProgressBar { progress: progress }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(theme.Padding() * 2, theme.Padding() * 2) element.core.SetMinimumSize(theme.Padding() * 2, theme.Padding() * 2)
return return
} }
// Resize resizes the progress bar.
func (element *ProgressBar) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.draw()
return
}
// SetProgress sets the progress level of the bar. // SetProgress sets the progress level of the bar.
func (element *ProgressBar) SetProgress (progress float64) { func (element *ProgressBar) SetProgress (progress float64) {
if progress == element.progress { return } if progress == element.progress { return }
@ -39,15 +32,15 @@ func (element *ProgressBar) SetProgress (progress float64) {
} }
func (element *ProgressBar) draw () { func (element *ProgressBar) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
pattern, inset := theme.SunkenPattern(theme.PatternState { }) pattern, inset := theme.SunkenPattern(theme.PatternState { })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
bounds = inset.Apply(bounds) bounds = inset.Apply(bounds)
meterBounds := image.Rect ( meterBounds := image.Rect (
bounds.Min.X, bounds.Min.Y, bounds.Min.X, bounds.Min.Y,
bounds.Min.X + int(float64(bounds.Dx()) * element.progress), bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
bounds.Max.Y) bounds.Max.Y)
accent, _ := theme.AccentPattern(theme.PatternState { }) accent, _ := theme.AccentPattern(theme.PatternState { })
artist.FillRectangle(element.core, accent, meterBounds) artist.FillRectangle(element, accent, meterBounds)
} }

View File

@ -48,20 +48,16 @@ type ScrollContainer struct {
// bars. // bars.
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) { func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
element = &ScrollContainer { } element = &ScrollContainer { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.handleResize)
element.updateMinimumSize() element.updateMinimumSize()
element.horizontal.exists = horizontal element.horizontal.exists = horizontal
element.vertical.exists = vertical element.vertical.exists = vertical
return return
} }
// Resize resizes the scroll box. func (element *ScrollContainer) handleResize () {
func (element *ScrollContainer) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.recalculate() element.recalculate()
element.child.Resize ( element.resizeChildToFit()
element.childWidth,
element.childHeight)
element.draw() element.draw()
} }
@ -95,10 +91,7 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
element.vertical.enabled = element.child.ScrollAxes() element.vertical.enabled = element.child.ScrollAxes()
if element.core.HasImage() { if element.core.HasImage() {
element.child.Resize ( element.resizeChildToFit()
element.childWidth,
element.childHeight)
element.core.DamageAll()
} }
} }
} }
@ -120,7 +113,8 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) {
if point.In(element.horizontal.bar) { if point.In(element.horizontal.bar) {
element.horizontal.dragging = true element.horizontal.dragging = true
element.horizontal.dragOffset = element.horizontal.dragOffset =
point.Sub(element.horizontal.bar.Min).X x - element.horizontal.bar.Min.X +
element.Bounds().Min.X
element.dragHorizontalBar(point) element.dragHorizontalBar(point)
} else if point.In(element.horizontal.gutter) { } else if point.In(element.horizontal.gutter) {
@ -135,7 +129,8 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) {
} else if point.In(element.vertical.bar) { } else if point.In(element.vertical.bar) {
element.vertical.dragging = true element.vertical.dragging = true
element.vertical.dragOffset = element.vertical.dragOffset =
point.Sub(element.vertical.bar.Min).Y y - element.vertical.bar.Min.Y +
element.Bounds().Min.Y
element.dragVerticalBar(point) element.dragVerticalBar(point)
} else if point.In(element.vertical.gutter) { } else if point.In(element.vertical.gutter) {
@ -259,6 +254,7 @@ func (element *ScrollContainer) childFocusMotionRequestCallback (
} }
func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) { func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) {
child.DrawTo(nil)
child.OnDamage(nil) child.OnDamage(nil)
child.OnMinimumSizeChange(nil) child.OnMinimumSizeChange(nil)
child.OnScrollBoundsChange(nil) child.OnScrollBoundsChange(nil)
@ -274,6 +270,14 @@ func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable)
} }
} }
func (element *ScrollContainer) resizeChildToFit () {
childBounds := image.Rect (
0, 0,
element.childWidth,
element.childHeight).Add(element.Bounds().Min)
element.child.DrawTo(tomo.Cut(element, childBounds))
}
func (element *ScrollContainer) recalculate () { func (element *ScrollContainer) recalculate () {
_, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState { _, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState {
Case: scrollBarHorizontalCase, Case: scrollBarHorizontalCase,
@ -306,6 +310,7 @@ func (element *ScrollContainer) recalculate () {
// if enabled, give substance to the gutters // if enabled, give substance to the gutters
if horizontal.exists { if horizontal.exists {
horizontal.gutter.Min.X = bounds.Min.X
horizontal.gutter.Min.Y = bounds.Max.Y - thicknessHorizontal horizontal.gutter.Min.Y = bounds.Max.Y - thicknessHorizontal
horizontal.gutter.Max.X = bounds.Max.X horizontal.gutter.Max.X = bounds.Max.X
horizontal.gutter.Max.Y = bounds.Max.Y horizontal.gutter.Max.Y = bounds.Max.Y
@ -318,6 +323,7 @@ func (element *ScrollContainer) recalculate () {
if vertical.exists { if vertical.exists {
vertical.gutter.Min.X = bounds.Max.X - thicknessVertical vertical.gutter.Min.X = bounds.Max.X - thicknessVertical
vertical.gutter.Max.X = bounds.Max.X vertical.gutter.Max.X = bounds.Max.X
vertical.gutter.Min.Y = bounds.Min.Y
vertical.gutter.Max.Y = bounds.Max.Y vertical.gutter.Max.Y = bounds.Max.Y
if horizontal.exists { if horizontal.exists {
vertical.gutter.Max.Y -= thicknessHorizontal vertical.gutter.Max.Y -= thicknessHorizontal
@ -364,7 +370,7 @@ func (element *ScrollContainer) recalculate () {
} }
func (element *ScrollContainer) draw () { func (element *ScrollContainer) draw () {
artist.Paste(element.core, element.child, image.Point { }) artist.Paste(element, element.child, image.Point { })
deadPattern, _ := theme.DeadPattern(theme.PatternState { deadPattern, _ := theme.DeadPattern(theme.PatternState {
Case: scrollContainerCase, Case: scrollContainerCase,
}) })

View File

@ -18,18 +18,11 @@ type Spacer struct {
// will appear as a line. // will appear as a line.
func NewSpacer (line bool) (element *Spacer) { func NewSpacer (line bool) (element *Spacer) {
element = &Spacer { line: line } element = &Spacer { line: line }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(1, 1) element.core.SetMinimumSize(1, 1)
return return
} }
// Resize resizes the label and re-wraps the text if wrapping is enabled.
func (element *Spacer) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.draw()
return
}
/// SetLine sets whether or not the spacer will appear as a colored line. /// SetLine sets whether or not the spacer will appear as a colored line.
func (element *Spacer) SetLine (line bool) { func (element *Spacer) SetLine (line bool) {
if element.line == line { return } if element.line == line { return }
@ -41,19 +34,19 @@ func (element *Spacer) SetLine (line bool) {
} }
func (element *Spacer) draw () { func (element *Spacer) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
if element.line { if element.line {
pattern, _ := theme.ForegroundPattern(theme.PatternState { pattern, _ := theme.ForegroundPattern(theme.PatternState {
Case: spacerCase, Case: spacerCase,
Disabled: true, Disabled: true,
}) })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
} else { } else {
pattern, _ := theme.BackgroundPattern(theme.PatternState { pattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: spacerCase, Case: spacerCase,
Disabled: true, Disabled: true,
}) })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
} }
} }

View File

@ -27,7 +27,7 @@ type Switch struct {
// NewSwitch creates a new switch with the specified label text. // NewSwitch creates a new switch with the specified label text.
func NewSwitch (text string, on bool) (element *Switch) { func NewSwitch (text string, on bool) (element *Switch) {
element = &Switch { checked: on, text: text } element = &Switch { checked: on, text: text }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () { if element.core.HasImage () {
@ -41,12 +41,6 @@ func NewSwitch (text string, on bool) (element *Switch) {
return return
} }
// Resize changes this element's size.
func (element *Switch) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.draw()
}
func (element *Switch) HandleMouseDown (x, y int, button tomo.Button) { func (element *Switch) HandleMouseDown (x, y int, button tomo.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
@ -146,13 +140,13 @@ func (element *Switch) calculateMinimumSize () {
} }
func (element *Switch) draw () { func (element *Switch) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()) handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()) gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min)
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState {
Case: switchCase, Case: switchCase,
}) })
artist.FillRectangle ( element.core, backgroundPattern, bounds) artist.FillRectangle (element, backgroundPattern, bounds)
if element.checked { if element.checked {
handleBounds.Min.X += bounds.Dy() handleBounds.Min.X += bounds.Dy()
@ -174,7 +168,7 @@ func (element *Switch) draw () {
Focused: element.Focused(), Focused: element.Focused(),
Pressed: element.pressed, Pressed: element.pressed,
}) })
artist.FillRectangle(element.core, gutterPattern, gutterBounds) artist.FillRectangle(element, gutterPattern, gutterBounds)
handlePattern, _ := theme.HandlePattern(theme.PatternState { handlePattern, _ := theme.HandlePattern(theme.PatternState {
Case: switchCase, Case: switchCase,
@ -182,12 +176,12 @@ func (element *Switch) draw () {
Focused: element.Focused(), Focused: element.Focused(),
Pressed: element.pressed, Pressed: element.pressed,
}) })
artist.FillRectangle(element.core, handlePattern, handleBounds) artist.FillRectangle(element, handlePattern, handleBounds)
textBounds := element.drawer.LayoutBounds() textBounds := element.drawer.LayoutBounds()
offset := image.Point { offset := bounds.Min.Add(image.Point {
X: bounds.Dy() * 2 + theme.Padding(), X: bounds.Dy() * 2 + theme.Padding(),
} })
offset.Y -= textBounds.Min.Y offset.Y -= textBounds.Min.Y
offset.X -= textBounds.Min.X offset.X -= textBounds.Min.X
@ -196,5 +190,5 @@ func (element *Switch) draw () {
Case: switchCase, Case: switchCase,
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
}) })
element.drawer.Draw(element.core, foreground, offset) element.drawer.Draw(element, foreground, offset)
} }

View File

@ -34,7 +34,7 @@ type TextBox struct {
// text. // text.
func NewTextBox (placeholder, value string) (element *TextBox) { func NewTextBox (placeholder, value string) (element *TextBox) {
element = &TextBox { } element = &TextBox { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.handleResize)
element.FocusableCore, element.FocusableCore,
element.focusableControl = core.NewFocusableCore (func () { element.focusableControl = core.NewFocusableCore (func () {
if element.core.HasImage () { if element.core.HasImage () {
@ -51,8 +51,7 @@ func NewTextBox (placeholder, value string) (element *TextBox) {
return return
} }
func (element *TextBox) Resize (width, height int) { func (element *TextBox) handleResize () {
element.core.AllocateCanvas(width, height)
element.scrollToCursor() element.scrollToCursor()
element.draw() element.draw()
if element.onScrollBoundsChange != nil { if element.onScrollBoundsChange != nil {
@ -258,7 +257,8 @@ func (element *TextBox) runOnChange () {
func (element *TextBox) scrollToCursor () { func (element *TextBox) scrollToCursor () {
if !element.core.HasImage() { return } if !element.core.HasImage() { return }
bounds := element.core.Bounds().Inset(theme.Padding()) bounds := element.Bounds().Inset(theme.Padding())
bounds = bounds.Sub(bounds.Min)
bounds.Max.X -= element.valueDrawer.Em().Round() bounds.Max.X -= element.valueDrawer.Em().Round()
cursorPosition := element.valueDrawer.PositionOf(element.cursor) cursorPosition := element.valueDrawer.PositionOf(element.cursor)
cursorPosition.X -= element.scroll cursorPosition.X -= element.scroll
@ -273,7 +273,7 @@ func (element *TextBox) scrollToCursor () {
} }
func (element *TextBox) draw () { func (element *TextBox) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
// FIXME: take index into account // FIXME: take index into account
pattern, inset := theme.InputPattern(theme.PatternState { pattern, inset := theme.InputPattern(theme.PatternState {
@ -281,36 +281,36 @@ func (element *TextBox) draw () {
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
Focused: element.Focused(), Focused: element.Focused(),
}) })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
if len(element.text) == 0 && !element.Focused() { if len(element.text) == 0 && !element.Focused() {
// draw placeholder // draw placeholder
textBounds := element.placeholderDrawer.LayoutBounds() textBounds := element.placeholderDrawer.LayoutBounds()
offset := image.Point { offset := bounds.Min.Add (image.Point {
X: theme.Padding() + inset[3], X: theme.Padding() + inset[3],
Y: theme.Padding() + inset[0], Y: theme.Padding() + inset[0],
} })
foreground, _ := theme.ForegroundPattern(theme.PatternState { foreground, _ := theme.ForegroundPattern(theme.PatternState {
Case: textBoxCase, Case: textBoxCase,
Disabled: true, Disabled: true,
}) })
element.placeholderDrawer.Draw ( element.placeholderDrawer.Draw (
element.core, element,
foreground, foreground,
offset.Sub(textBounds.Min)) offset.Sub(textBounds.Min))
} else { } else {
// draw input value // draw input value
textBounds := element.valueDrawer.LayoutBounds() textBounds := element.valueDrawer.LayoutBounds()
offset := image.Point { offset := bounds.Min.Add (image.Point {
X: theme.Padding() + inset[3] - element.scroll, X: theme.Padding() + inset[3] - element.scroll,
Y: theme.Padding() + inset[0], Y: theme.Padding() + inset[0],
} })
foreground, _ := theme.ForegroundPattern(theme.PatternState { foreground, _ := theme.ForegroundPattern(theme.PatternState {
Case: textBoxCase, Case: textBoxCase,
Disabled: !element.Enabled(), Disabled: !element.Enabled(),
}) })
element.valueDrawer.Draw ( element.valueDrawer.Draw (
element.core, element,
foreground, foreground,
offset.Sub(textBounds.Min)) offset.Sub(textBounds.Min))
@ -322,7 +322,7 @@ func (element *TextBox) draw () {
Case: textBoxCase, Case: textBoxCase,
}) })
artist.Line ( artist.Line (
element.core, element,
foreground, 1, foreground, 1,
cursorPosition.Add(offset), cursorPosition.Add(offset),
image.Pt ( image.Pt (

View File

@ -7,21 +7,21 @@ import "git.tebibyte.media/sashakoshka/tomo"
// Core is a struct that implements some core functionality common to most // Core is a struct that implements some core functionality common to most
// widgets. It is meant to be embedded directly into a struct. // widgets. It is meant to be embedded directly into a struct.
type Core struct { type Core struct {
canvas tomo.BasicCanvas canvas tomo.Canvas
parent tomo.Element
metrics struct { metrics struct {
minimumWidth int minimumWidth int
minimumHeight int minimumHeight int
} }
drawSizeChange func ()
onMinimumSizeChange func () onMinimumSizeChange func ()
onDamage func (region tomo.Canvas) onDamage func (region tomo.Canvas)
} }
// NewCore creates a new element core and its corresponding control. // NewCore creates a new element core and its corresponding control.
func NewCore (parent tomo.Element) (core *Core, control CoreControl) { func NewCore (drawSizeChange func ()) (core *Core, control CoreControl) {
core = &Core { parent: parent } core = &Core { drawSizeChange: drawSizeChange }
control = CoreControl { core: core } control = CoreControl { core: core }
return return
} }
@ -33,21 +33,25 @@ func (core *Core) ColorModel () (model color.Model) {
// ColorModel fulfills the draw.Image interface. // ColorModel fulfills the draw.Image interface.
func (core *Core) At (x, y int) (pixel color.Color) { func (core *Core) At (x, y int) (pixel color.Color) {
if core.canvas == nil { return }
return core.canvas.At(x, y) return core.canvas.At(x, y)
} }
// ColorModel fulfills the draw.Image interface. // ColorModel fulfills the draw.Image interface.
func (core *Core) Bounds () (bounds image.Rectangle) { func (core *Core) Bounds () (bounds image.Rectangle) {
if core.canvas == nil { return }
return core.canvas.Bounds() return core.canvas.Bounds()
} }
// ColorModel fulfills the draw.Image interface. // ColorModel fulfills the draw.Image interface.
func (core *Core) Set (x, y int, c color.Color) () { func (core *Core) Set (x, y int, c color.Color) () {
if core.canvas == nil { return }
core.canvas.Set(x, y, c) core.canvas.Set(x, y, c)
} }
// Buffer fulfills the tomo.Canvas interface. // Buffer fulfills the tomo.Canvas interface.
func (core *Core) Buffer () (data []color.RGBA, stride int) { func (core *Core) Buffer () (data []color.RGBA, stride int) {
if core.canvas == nil { return }
return core.canvas.Buffer() return core.canvas.Buffer()
} }
@ -57,6 +61,15 @@ func (core *Core) MinimumSize () (width, height int) {
return core.metrics.minimumWidth, core.metrics.minimumHeight return core.metrics.minimumWidth, core.metrics.minimumHeight
} }
// DrawTo fulfills the tomo.Element interface. This should not need to be
// overridden.
func (core *Core) DrawTo (canvas tomo.Canvas) {
core.canvas = canvas
if core.drawSizeChange != nil {
core.drawSizeChange()
}
}
// OnDamage fulfils the tomo.Element interface. This should not need to be // OnDamage fulfils the tomo.Element interface. This should not need to be
// overridden. // overridden.
func (core *Core) OnDamage (callback func (region tomo.Canvas)) { func (core *Core) OnDamage (callback func (region tomo.Canvas)) {
@ -74,35 +87,27 @@ func (core *Core) OnMinimumSizeChange (callback func ()) {
// instead kept as a private member. When a Core struct is created, a // instead kept as a private member. When a Core struct is created, a
// corresponding CoreControl struct is linked to it and returned alongside it. // corresponding CoreControl struct is linked to it and returned alongside it.
type CoreControl struct { type CoreControl struct {
tomo.BasicCanvas
core *Core core *Core
} }
// HasImage returns true if the core has an allocated image buffer, and false if // HasImage returns true if the core has an allocated image buffer, and false if
// it doesn't. // it doesn't.
func (control CoreControl) HasImage () (has bool) { func (control CoreControl) HasImage () (has bool) {
return !control.Bounds().Empty() return control.core.canvas != nil && !control.core.canvas.Bounds().Empty()
} }
// DamageRegion pushes the selected region of pixels to the parent element. This // DamageRegion pushes the selected region of pixels to the parent element. This
// does not need to be called when responding to a resize event. // does not need to be called when responding to a resize event.
func (control CoreControl) DamageRegion (bounds image.Rectangle) { func (control CoreControl) DamageRegion (bounds image.Rectangle) {
if control.core.onDamage != nil { if control.core.onDamage != nil {
control.core.onDamage(tomo.Cut(control, bounds)) control.core.onDamage(tomo.Cut(control.core, bounds))
} }
} }
// DamageAll pushes all pixels to the parent element. This does not need to be // DamageAll pushes all pixels to the parent element. This does not need to be
// called when responding to a resize event. // called when redrawing in response to a change in size.
func (control CoreControl) DamageAll () { func (control CoreControl) DamageAll () {
control.DamageRegion(control.Bounds()) control.DamageRegion(control.core.Bounds())
}
// AllocateCanvas resizes the canvas, constraining the width and height so that
// they are not less than the specified minimum width and height.
func (control *CoreControl) AllocateCanvas (width, height int) {
control.core.canvas = tomo.NewBasicCanvas(width, height)
control.BasicCanvas = control.core.canvas
} }
// SetMinimumSize sets the minimum size of this element, notifying the parent // SetMinimumSize sets the minimum size of this element, notifying the parent
@ -119,18 +124,6 @@ func (control CoreControl) SetMinimumSize (width, height int) {
if control.core.onMinimumSizeChange != nil { if control.core.onMinimumSizeChange != nil {
control.core.onMinimumSizeChange() control.core.onMinimumSizeChange()
} }
// if there is an image buffer, and the current size is less
// than this new minimum size, send core.parent a resize event.
if control.HasImage() {
bounds := control.Bounds()
imageWidth,
imageHeight,
constrained := control.ConstrainSize(bounds.Dx(), bounds.Dy())
if constrained {
core.parent.Resize(imageWidth, imageHeight)
}
}
} }
// ConstrainSize contstrains the specified width and height to the minimum width // ConstrainSize contstrains the specified width and height to the minimum width

View File

@ -19,17 +19,11 @@ type AnalogClock struct {
// NewAnalogClock creates a new analog clock that displays the specified time. // NewAnalogClock creates a new analog clock that displays the specified time.
func NewAnalogClock (newTime time.Time) (element *AnalogClock) { func NewAnalogClock (newTime time.Time) (element *AnalogClock) {
element = &AnalogClock { } element = &AnalogClock { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(64, 64) element.core.SetMinimumSize(64, 64)
return return
} }
// Resize changes the size of the clock.
func (element *AnalogClock) Resize (width, height int) {
element.core.AllocateCanvas(width, height)
element.draw()
}
// SetTime changes the time that the clock displays. // SetTime changes the time that the clock displays.
func (element *AnalogClock) SetTime (newTime time.Time) { func (element *AnalogClock) SetTime (newTime time.Time) {
if newTime == element.time { return } if newTime == element.time { return }
@ -41,7 +35,7 @@ func (element *AnalogClock) SetTime (newTime time.Time) {
} }
func (element *AnalogClock) draw () { func (element *AnalogClock) draw () {
bounds := element.core.Bounds() bounds := element.Bounds()
pattern, inset := theme.SunkenPattern(theme.PatternState { pattern, inset := theme.SunkenPattern(theme.PatternState {
Case: clockCase, Case: clockCase,
@ -87,15 +81,15 @@ func (element *AnalogClock) radialLine (
outer float64, outer float64,
radian float64, radian float64,
) { ) {
bounds := element.core.Bounds() bounds := element.Bounds()
width := float64(bounds.Dx()) / 2 width := float64(bounds.Dx()) / 2
height := float64(bounds.Dy()) / 2 height := float64(bounds.Dy()) / 2
min := image.Pt ( min := element.Bounds().Min.Add(image.Pt (
int(math.Cos(radian) * inner * width + width), int(math.Cos(radian) * inner * width + width),
int(math.Sin(radian) * inner * height + height)) int(math.Sin(radian) * inner * height + height)))
max := image.Pt ( max := element.Bounds().Min.Add(image.Pt (
int(math.Cos(radian) * outer * width + width), int(math.Cos(radian) * outer * width + width),
int(math.Sin(radian) * outer * height + height)) int(math.Sin(radian) * outer * height + height)))
// println(min.String(), max.String()) // println(min.String(), max.String())
artist.Line(element.core, source, 1, min, max) artist.Line(element, source, 1, min, max)
} }

View File

@ -19,16 +19,15 @@ type Artist struct {
// NewArtist creates a new artist test element. // NewArtist creates a new artist test element.
func NewArtist () (element *Artist) { func NewArtist () (element *Artist) {
element = &Artist { } element = &Artist { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(480, 600) element.core.SetMinimumSize(480, 600)
return return
} }
func (element *Artist) Resize (width, height int) { func (element *Artist) draw () {
element.core.AllocateCanvas(width, height)
bounds := element.Bounds() bounds := element.Bounds()
element.cellBounds.Max.X = bounds.Dx() / 5 element.cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5
element.cellBounds.Max.Y = (bounds.Dy() - 48) / 8 element.cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 8
drawStart := time.Now() drawStart := time.Now()

View File

@ -20,29 +20,28 @@ type Mouse struct {
// NewMouse creates a new mouse test element. // NewMouse creates a new mouse test element.
func NewMouse () (element *Mouse) { func NewMouse () (element *Mouse) {
element = &Mouse { } element = &Mouse { }
element.Core, element.core = core.NewCore(element) element.Core, element.core = core.NewCore(element.draw)
element.core.SetMinimumSize(32, 32) element.core.SetMinimumSize(32, 32)
element.color = artist.NewUniform(color.Black) element.color = artist.NewUniform(color.Black)
return return
} }
func (element *Mouse) Resize (width, height int) { func (element *Mouse) draw () {
element.core.AllocateCanvas(width, height)
bounds := element.Bounds() bounds := element.Bounds()
pattern, _ := theme.AccentPattern(theme.PatternState { }) pattern, _ := theme.AccentPattern(theme.PatternState { })
artist.FillRectangle(element.core, pattern, bounds) artist.FillRectangle(element, pattern, bounds)
artist.StrokeRectangle ( artist.StrokeRectangle (
element.core, element,
artist.NewUniform(color.Black), 1, artist.NewUniform(color.Black), 1,
bounds) bounds)
artist.Line ( artist.Line (
element.core, artist.NewUniform(color.White), 1, element, artist.NewUniform(color.White), 1,
image.Pt(1, 1), bounds.Min.Add(image.Pt(1, 1)),
image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)) bounds.Min.Add(image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)))
artist.Line ( artist.Line (
element.core, artist.NewUniform(color.White), 1, element, artist.NewUniform(color.White), 1,
image.Pt(1, bounds.Dy() - 2), bounds.Min.Add(image.Pt(1, bounds.Dy() - 2)),
image.Pt(bounds.Dx() - 2, 1)) bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1)))
} }
func (element *Mouse) HandleMouseDown (x, y int, button tomo.Button) { func (element *Mouse) HandleMouseDown (x, y int, button tomo.Button) {
@ -54,7 +53,7 @@ func (element *Mouse) HandleMouseUp (x, y int, button tomo.Button) {
element.drawing = false element.drawing = false
mousePos := image.Pt(x, y) mousePos := image.Pt(x, y)
element.core.DamageRegion (artist.Line ( element.core.DamageRegion (artist.Line (
element.core, element.color, 1, element, element.color, 1,
element.lastMousePos, mousePos)) element.lastMousePos, mousePos))
element.lastMousePos = mousePos element.lastMousePos = mousePos
} }
@ -63,7 +62,7 @@ func (element *Mouse) HandleMouseMove (x, y int) {
if !element.drawing { return } if !element.drawing { return }
mousePos := image.Pt(x, y) mousePos := image.Pt(x, y)
element.core.DamageRegion (artist.Line ( element.core.DamageRegion (artist.Line (
element.core, element.color, 1, element, element.color, 1,
element.lastMousePos, mousePos)) element.lastMousePos, mousePos))
element.lastMousePos = mousePos element.lastMousePos = mousePos
} }

View File

@ -6,7 +6,7 @@ import "image"
// it can be arranged by a Layout. // it can be arranged by a Layout.
type LayoutEntry struct { type LayoutEntry struct {
Element Element
Position image.Point Bounds image.Rectangle
Expand bool Expand bool
} }
@ -17,7 +17,7 @@ type Layout interface {
// and changes the position of the entiries in the slice so that they // and changes the position of the entiries in the slice so that they
// are properly laid out. The given width and height should not be less // are properly laid out. The given width and height should not be less
// than what is returned by MinimumSize. // than what is returned by MinimumSize.
Arrange (entries []LayoutEntry, width, height int) Arrange (entries []LayoutEntry, bounds image.Rectangle)
// MinimumSize returns the minimum width and height that the layout // MinimumSize returns the minimum width and height that the layout
// needs to properly arrange the given slice of layout entries. // needs to properly arrange the given slice of layout entries.

View File

@ -19,12 +19,9 @@ type Dialog struct {
} }
// Arrange arranges a list of entries into a dialog. // Arrange arranges a list of entries into a dialog.
func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) { func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
if layout.Pad { if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
width -= theme.Margin() * 2
height -= theme.Margin() * 2
}
controlRowWidth, controlRowHeight := 0, 0 controlRowWidth, controlRowHeight := 0, 0
if len(entries) > 1 { if len(entries) > 1 {
controlRowWidth, controlRowWidth,
@ -32,24 +29,18 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) {
} }
if len(entries) > 0 { if len(entries) > 0 {
entries[0].Position = image.Point { } main := entries[0]
if layout.Pad { main.Bounds.Min = bounds.Min
entries[0].Position.X += theme.Margin() mainHeight := bounds.Dy() - controlRowHeight
entries[0].Position.Y += theme.Margin()
}
mainHeight := height - controlRowHeight
if layout.Gap { if layout.Gap {
mainHeight -= theme.Margin() mainHeight -= theme.Margin()
} }
mainBounds := entries[0].Bounds() main.Bounds.Max = main.Bounds.Min.Add(image.Pt(bounds.Dx(), mainHeight))
if mainBounds.Dy() != mainHeight || entries[0] = main
mainBounds.Dx() != width {
entries[0].Resize(width, mainHeight)
}
} }
if len(entries) > 1 { if len(entries) > 1 {
freeSpace := width freeSpace := bounds.Dx()
expandingElements := 0 expandingElements := 0
// count the number of expanding elements and the amount of free // count the number of expanding elements and the amount of free
@ -71,33 +62,30 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) {
} }
// determine starting position and dimensions for control row // determine starting position and dimensions for control row
x, y := 0, height - controlRowHeight dot := image.Pt(bounds.Min.X, bounds.Max.Y - controlRowHeight)
if expandingElements == 0 { if expandingElements == 0 {
x = width - controlRowWidth dot.X = bounds.Max.X - controlRowWidth
} }
if layout.Pad {
x += theme.Margin()
y += theme.Margin()
}
height -= controlRowHeight
// set the size and position of each element in the control row // set the size and position of each element in the control row
for index, entry := range entries[1:] { for index, entry := range entries[1:] {
if index > 0 && layout.Gap { x += theme.Margin() } if index > 0 && layout.Gap { dot.X += theme.Margin() }
entries[index + 1].Position = image.Pt(x, y) entry.Bounds.Min = dot
entryWidth := 0 entryWidth := 0
if entry.Expand { if entry.Expand {
entryWidth = expandingElementWidth entryWidth = expandingElementWidth
} else { } else {
entryWidth, _ = entry.MinimumSize() entryWidth, _ = entry.MinimumSize()
} }
x += entryWidth dot.X += entryWidth
entryBounds := entry.Bounds() entryBounds := entry.Bounds
if entryBounds.Dy() != controlRowHeight || if entryBounds.Dy() != controlRowHeight ||
entryBounds.Dx() != entryWidth { entryBounds.Dx() != entryWidth {
entry.Resize(entryWidth, controlRowHeight) entry.Bounds.Max = entryBounds.Min.Add (
image.Pt(entryWidth, controlRowHeight))
} }
entries[index + 1] = entry
} }
} }

View File

@ -17,36 +17,28 @@ type Horizontal struct {
} }
// Arrange arranges a list of entries horizontally. // Arrange arranges a list of entries horizontally.
func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) { func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
if layout.Pad { if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
width -= theme.Margin() * 2
height -= theme.Margin() * 2
}
// get width of expanding elements
expandingElementWidth := layout.expandingElementWidth(entries, width)
x, y := 0, 0 // get width of expanding elements
if layout.Pad { expandingElementWidth := layout.expandingElementWidth(entries, bounds.Dx())
x += theme.Margin()
y += theme.Margin()
}
// set the size and position of each element // set the size and position of each element
dot := bounds.Min
for index, entry := range entries { for index, entry := range entries {
if index > 0 && layout.Gap { x += theme.Margin() } if index > 0 && layout.Gap { dot.X += theme.Margin() }
entries[index].Position = image.Pt(x, y) entry.Bounds.Min = dot
entryWidth := 0 entryWidth := 0
if entry.Expand { if entry.Expand {
entryWidth = expandingElementWidth entryWidth = expandingElementWidth
} else { } else {
entryWidth, _ = entry.MinimumSize() entryWidth, _ = entry.MinimumSize()
} }
x += entryWidth dot.X += entryWidth
entryBounds := entry.Bounds() entry.Bounds.Max = entry.Bounds.Min.Add(image.Pt(entryWidth, bounds.Dy()))
if entryBounds.Dy() != height || entryBounds.Dx() != entryWidth {
entry.Resize(entryWidth, height) entries[index] = entry
}
} }
} }

View File

@ -17,22 +17,19 @@ type Vertical struct {
} }
// Arrange arranges a list of entries vertically. // Arrange arranges a list of entries vertically.
func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) { func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
if layout.Pad { if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
width -= theme.Margin() * 2
height -= theme.Margin() * 2
}
freeSpace := height
expandingElements := 0
// count the number of expanding elements and the amount of free space // count the number of expanding elements and the amount of free space
// for them to collectively occupy, while gathering minimum heights. // for them to collectively occupy, while gathering minimum heights.
freeSpace := bounds.Dy()
minimumHeights := make([]int, len(entries)) minimumHeights := make([]int, len(entries))
expandingElements := 0
for index, entry := range entries { for index, entry := range entries {
var entryMinHeight int var entryMinHeight int
if child, flexible := entry.Element.(tomo.Flexible); flexible { if child, flexible := entry.Element.(tomo.Flexible); flexible {
entryMinHeight = child.FlexibleHeightFor(width) entryMinHeight = child.FlexibleHeightFor(bounds.Dx())
} else { } else {
_, entryMinHeight = entry.MinimumSize() _, entryMinHeight = entry.MinimumSize()
} }
@ -47,33 +44,28 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
freeSpace -= theme.Margin() freeSpace -= theme.Margin()
} }
} }
expandingElementHeight := 0 expandingElementHeight := 0
if expandingElements > 0 { if expandingElements > 0 {
expandingElementHeight = freeSpace / expandingElements expandingElementHeight = freeSpace / expandingElements
} }
x, y := 0, 0
if layout.Pad {
x += theme.Margin()
y += theme.Margin()
}
// set the size and position of each element // set the size and position of each element
dot := bounds.Min
for index, entry := range entries { for index, entry := range entries {
if index > 0 && layout.Gap { y += theme.Margin() } if index > 0 && layout.Gap { dot.Y += theme.Margin() }
entries[index].Position = image.Pt(x, y) entry.Bounds.Min = dot
entryHeight := 0 entryHeight := 0
if entry.Expand { if entry.Expand {
entryHeight = expandingElementHeight entryHeight = expandingElementHeight
} else { } else {
entryHeight = minimumHeights[index] entryHeight = minimumHeights[index]
} }
y += entryHeight dot.Y += entryHeight
entryBounds := entry.Bounds() entryBounds := entry.Bounds
if entryBounds.Dx() != width || entryBounds.Dy() != entryHeight { entry.Bounds.Max = entryBounds.Min.Add(image.Pt(bounds.Dx(), entryHeight))
entry.Resize(width, entryHeight) entries[index] = entry
}
} }
} }