Merge pull request 'direct-draw' (#6) from direct-draw into main
Reviewed-on: sashakoshka/tomo#6
This commit is contained in:
commit
b4a5bc7d03
@ -13,6 +13,7 @@ type Window struct {
|
||||
backend *Backend
|
||||
xWindow *xwindow.Window
|
||||
xCanvas *xgraphics.Image
|
||||
canvas tomo.BasicCanvas
|
||||
child tomo.Element
|
||||
onClose func ()
|
||||
skipChildDrawCallback bool
|
||||
@ -194,6 +195,9 @@ func (window *Window) OnClose (callback func ()) {
|
||||
}
|
||||
|
||||
func (window *Window) reallocateCanvas () {
|
||||
window.canvas = tomo.NewBasicCanvas (
|
||||
window.metrics.width,
|
||||
window.metrics.height)
|
||||
if window.xCanvas != nil {
|
||||
window.xCanvas.Destroy()
|
||||
}
|
||||
@ -203,12 +207,12 @@ func (window *Window) reallocateCanvas () {
|
||||
0, 0,
|
||||
window.metrics.width,
|
||||
window.metrics.height))
|
||||
|
||||
window.xCanvas.CreatePixmap()
|
||||
}
|
||||
|
||||
func (window *Window) redrawChildEntirely () {
|
||||
window.pushRegion(window.paste(window.child))
|
||||
|
||||
}
|
||||
|
||||
func (window *Window) resizeChildToFit () {
|
||||
@ -228,15 +232,10 @@ func (window *Window) resizeChildToFit () {
|
||||
|
||||
if window.metrics.height >= minimumHeight &&
|
||||
window.metrics.width >= minimumWidth {
|
||||
|
||||
window.child.Resize (
|
||||
window.metrics.width,
|
||||
window.metrics.height)
|
||||
window.child.DrawTo(window.canvas)
|
||||
}
|
||||
} else {
|
||||
window.child.Resize (
|
||||
window.metrics.width,
|
||||
window.metrics.height)
|
||||
window.child.DrawTo(window.canvas)
|
||||
}
|
||||
window.skipChildDrawCallback = false
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ func (canvas BasicCanvas) Buffer () (data []color.RGBA, stride int) {
|
||||
|
||||
// Cut returns a sub-canvas of a given canvas.
|
||||
func Cut (canvas Canvas, bounds image.Rectangle) (reduced BasicCanvas) {
|
||||
// println(canvas.Bounds().String(), bounds.String())
|
||||
bounds = bounds.Intersect(canvas.Bounds())
|
||||
if bounds.Empty() { return }
|
||||
reduced.rect = bounds
|
||||
|
@ -15,9 +15,10 @@ type Element interface {
|
||||
// instead of the offending dimension(s).
|
||||
MinimumSize () (width, height int)
|
||||
|
||||
// Resize resizes the element. This should only be called by the
|
||||
// element's parent.
|
||||
Resize (width, height int)
|
||||
// DrawTo sets this element's canvas. This should only be called by the
|
||||
// parent element. This is typically a region of the parent element's
|
||||
// canvas.
|
||||
DrawTo (canvas Canvas)
|
||||
|
||||
// OnDamage sets a function to be called when an area of the element is
|
||||
// drawn on and should be pushed to the screen.
|
||||
|
@ -25,7 +25,7 @@ type Button struct {
|
||||
// NewButton creates a new button with the specified label text.
|
||||
func NewButton (text string) (element *Button) {
|
||||
element = &Button { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
@ -38,11 +38,6 @@ func NewButton (text string) (element *Button) {
|
||||
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) {
|
||||
if !element.Enabled() { return }
|
||||
if !element.Focused() { element.Focus() }
|
||||
@ -126,7 +121,7 @@ func (element *Button) SetText (text string) {
|
||||
}
|
||||
|
||||
func (element *Button) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern, inset := theme.ButtonPattern(theme.PatternState {
|
||||
Case: buttonCase,
|
||||
@ -135,14 +130,14 @@ func (element *Button) draw () {
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
innerBounds := inset.Apply(bounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
offset := image.Point {
|
||||
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
|
||||
@ -154,5 +149,5 @@ func (element *Button) draw () {
|
||||
Case: buttonCase,
|
||||
Disabled: !element.Enabled(),
|
||||
})
|
||||
element.drawer.Draw(element.core, foreground, offset)
|
||||
element.drawer.Draw(element, foreground, offset)
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ type Checkbox struct {
|
||||
// NewCheckbox creates a new cbeckbox with the specified label text.
|
||||
func NewCheckbox (text string, checked bool) (element *Checkbox) {
|
||||
element = &Checkbox { checked: checked }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
@ -39,12 +39,6 @@ func NewCheckbox (text string, checked bool) (element *Checkbox) {
|
||||
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) {
|
||||
if !element.Enabled() { return }
|
||||
element.Focus()
|
||||
@ -139,13 +133,13 @@ func (element *Checkbox) SetText (text string) {
|
||||
}
|
||||
|
||||
func (element *Checkbox) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy())
|
||||
bounds := element.Bounds()
|
||||
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
|
||||
|
||||
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState {
|
||||
Case: checkboxCase,
|
||||
})
|
||||
artist.FillRectangle ( element.core, backgroundPattern, bounds)
|
||||
artist.FillRectangle(element, backgroundPattern, bounds)
|
||||
|
||||
pattern, inset := theme.ButtonPattern(theme.PatternState {
|
||||
Case: checkboxCase,
|
||||
@ -153,12 +147,12 @@ func (element *Checkbox) draw () {
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, boxBounds)
|
||||
artist.FillRectangle(element, pattern, boxBounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
offset := image.Point {
|
||||
offset := bounds.Min.Add(image.Point {
|
||||
X: bounds.Dy() + theme.Padding(),
|
||||
}
|
||||
})
|
||||
|
||||
offset.Y -= textBounds.Min.Y
|
||||
offset.X -= textBounds.Min.X
|
||||
@ -167,10 +161,10 @@ func (element *Checkbox) draw () {
|
||||
Case: checkboxCase,
|
||||
Disabled: !element.Enabled(),
|
||||
})
|
||||
element.drawer.Draw(element.core, foreground, offset)
|
||||
element.drawer.Draw(element, foreground, offset)
|
||||
|
||||
if element.checked {
|
||||
checkBounds := inset.Apply(boxBounds).Inset(2)
|
||||
artist.FillRectangle(element.core, foreground, checkBounds)
|
||||
artist.FillRectangle(element, foreground, checkBounds)
|
||||
}
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ type Container struct {
|
||||
// NewContainer creates a new container.
|
||||
func NewContainer (layout tomo.Layout) (element *Container) {
|
||||
element = &Container { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.redoAll)
|
||||
element.SetLayout(layout)
|
||||
return
|
||||
}
|
||||
@ -39,8 +39,7 @@ func NewContainer (layout tomo.Layout) (element *Container) {
|
||||
func (element *Container) SetLayout (layout tomo.Layout) {
|
||||
element.layout = layout
|
||||
if element.core.HasImage() {
|
||||
element.recalculate()
|
||||
element.draw()
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
@ -51,7 +50,7 @@ func (element *Container) SetLayout (layout tomo.Layout) {
|
||||
func (element *Container) Adopt (child tomo.Element, expand bool) {
|
||||
// set event handlers
|
||||
child.OnDamage (func (region tomo.Canvas) {
|
||||
element.drawChildRegion(child, region)
|
||||
element.core.DamageRegion(region.Bounds())
|
||||
})
|
||||
child.OnMinimumSizeChange(element.updateMinimumSize)
|
||||
if child0, ok := child.(tomo.Flexible); ok {
|
||||
@ -78,8 +77,7 @@ func (element *Container) Adopt (child tomo.Element, expand bool) {
|
||||
element.updateMinimumSize()
|
||||
element.reflectChildProperties()
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.recalculate()
|
||||
element.draw()
|
||||
element.redoAll()
|
||||
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
|
||||
// case, it is not the only one.
|
||||
if element.core.HasImage() {
|
||||
element.recalculate()
|
||||
element.draw()
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
@ -123,13 +120,13 @@ func (element *Container) Disown (child tomo.Element) {
|
||||
element.updateMinimumSize()
|
||||
element.reflectChildProperties()
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.recalculate()
|
||||
element.draw()
|
||||
element.redoAll()
|
||||
element.core.DamageAll()
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) clearChildEventHandlers (child tomo.Element) {
|
||||
child.DrawTo(nil)
|
||||
child.OnDamage(nil)
|
||||
child.OnMinimumSizeChange(nil)
|
||||
if child0, ok := child.(tomo.Focusable); ok {
|
||||
@ -151,8 +148,7 @@ func (element *Container) DisownAll () {
|
||||
element.updateMinimumSize()
|
||||
element.reflectChildProperties()
|
||||
if element.core.HasImage() && !element.warping {
|
||||
element.recalculate()
|
||||
element.draw()
|
||||
element.redoAll()
|
||||
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.
|
||||
func (element *Container) ChildAt (point image.Point) (child tomo.Element) {
|
||||
for _, entry := range element.children {
|
||||
if point.In(entry.Bounds().Add(entry.Position)) {
|
||||
if point.In(entry.Bounds) {
|
||||
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) {
|
||||
for _, entry := range element.children {
|
||||
if entry.Element == child {
|
||||
position = entry.Position
|
||||
position = entry.Bounds.Min
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -200,41 +196,48 @@ func (element *Container) childPosition (child tomo.Element) (position image.Poi
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Container) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
func (element *Container) redoAll () {
|
||||
// do a layout
|
||||
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) {
|
||||
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
|
||||
if !handlesMouse { return }
|
||||
element.drags[button] = child
|
||||
childPosition := element.childPosition(child)
|
||||
child.HandleMouseDown(x - childPosition.X, y - childPosition.Y, button)
|
||||
child.HandleMouseDown(x, y, button)
|
||||
}
|
||||
|
||||
func (element *Container) HandleMouseUp (x, y int, button tomo.Button) {
|
||||
child := element.drags[button]
|
||||
if child == nil { return }
|
||||
element.drags[button] = nil
|
||||
childPosition := element.childPosition(child)
|
||||
child.HandleMouseUp(x - childPosition.X, y - childPosition.Y, button)
|
||||
child.HandleMouseUp(x, y, button)
|
||||
}
|
||||
|
||||
func (element *Container) HandleMouseMove (x, y int) {
|
||||
for _, child := range element.drags {
|
||||
if child == nil { continue }
|
||||
childPosition := element.childPosition(child)
|
||||
child.HandleMouseMove(x - childPosition.X, y - childPosition.Y)
|
||||
child.HandleMouseMove(x, y)
|
||||
}
|
||||
}
|
||||
|
||||
func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
|
||||
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
|
||||
if !handlesMouse { return }
|
||||
childPosition := element.childPosition(child)
|
||||
child.HandleMouseScroll(x - childPosition.X, y - childPosition.Y, deltaX, deltaY)
|
||||
child.HandleMouseScroll(x, y, deltaX, deltaY)
|
||||
}
|
||||
|
||||
func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
|
||||
@ -468,31 +471,5 @@ func (element *Container) updateMinimumSize () {
|
||||
}
|
||||
|
||||
func (element *Container) recalculate () {
|
||||
bounds := 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
|
||||
}
|
||||
}
|
||||
element.layout.Arrange(element.children, element.Bounds())
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
package basic
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||
@ -23,7 +22,7 @@ type Label struct {
|
||||
// wrapped.
|
||||
func NewLabel (text string, wrap bool) (element *Label) {
|
||||
element = &Label { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
face := theme.FontFaceRegular()
|
||||
element.drawer.SetFace(face)
|
||||
element.SetWrap(wrap)
|
||||
@ -31,12 +30,11 @@ func NewLabel (text string, wrap bool) (element *Label) {
|
||||
return
|
||||
}
|
||||
|
||||
// Resize resizes the label and re-wraps the text if wrapping is enabled.
|
||||
func (element *Label) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
func (element *Label) handleResize () {
|
||||
bounds := element.Bounds()
|
||||
if element.wrap {
|
||||
element.drawer.SetMaxWidth(width)
|
||||
element.drawer.SetMaxHeight(height)
|
||||
element.drawer.SetMaxWidth(bounds.Dx())
|
||||
element.drawer.SetMaxHeight(bounds.Dy())
|
||||
}
|
||||
element.draw()
|
||||
return
|
||||
@ -108,20 +106,17 @@ func (element *Label) updateMinimumSize () {
|
||||
}
|
||||
|
||||
func (element *Label) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern, _ := theme.BackgroundPattern(theme.PatternState {
|
||||
Case: labelCase,
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
|
||||
foreground, _ := theme.ForegroundPattern (theme.PatternState {
|
||||
Case: labelCase,
|
||||
})
|
||||
element.drawer.Draw (element.core, foreground, image.Point {
|
||||
X: 0 - textBounds.Min.X,
|
||||
Y: 0 - textBounds.Min.Y,
|
||||
})
|
||||
element.drawer.Draw (element, foreground, bounds.Min.Sub(textBounds.Min))
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ type List struct {
|
||||
// NewList creates a new list element with the specified entries.
|
||||
func NewList (entries ...ListEntry) (element *List) {
|
||||
element = &List { selectedEntry: -1 }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
@ -51,10 +51,7 @@ func NewList (entries ...ListEntry) (element *List) {
|
||||
return
|
||||
}
|
||||
|
||||
// Resize changes the element's size.
|
||||
func (element *List) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
|
||||
func (element *List) handleResize () {
|
||||
for index, entry := range element.entries {
|
||||
element.entries[index] = element.resizeEntryToFit(entry)
|
||||
}
|
||||
@ -379,7 +376,7 @@ func (element *List) draw () {
|
||||
Disabled: !element.Enabled(),
|
||||
Focused: element.Focused(),
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
bounds = inset.Apply(bounds)
|
||||
dot := image.Point {
|
||||
|
@ -16,18 +16,11 @@ type ProgressBar struct {
|
||||
// level.
|
||||
func NewProgressBar (progress float64) (element *ProgressBar) {
|
||||
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)
|
||||
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.
|
||||
func (element *ProgressBar) SetProgress (progress float64) {
|
||||
if progress == element.progress { return }
|
||||
@ -39,15 +32,15 @@ func (element *ProgressBar) SetProgress (progress float64) {
|
||||
}
|
||||
|
||||
func (element *ProgressBar) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern, inset := theme.SunkenPattern(theme.PatternState { })
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
bounds = inset.Apply(bounds)
|
||||
meterBounds := image.Rect (
|
||||
bounds.Min.X, bounds.Min.Y,
|
||||
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
|
||||
bounds.Max.Y)
|
||||
accent, _ := theme.AccentPattern(theme.PatternState { })
|
||||
artist.FillRectangle(element.core, accent, meterBounds)
|
||||
artist.FillRectangle(element, accent, meterBounds)
|
||||
}
|
||||
|
@ -48,20 +48,16 @@ type ScrollContainer struct {
|
||||
// bars.
|
||||
func NewScrollContainer (horizontal, vertical bool) (element *ScrollContainer) {
|
||||
element = &ScrollContainer { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
element.updateMinimumSize()
|
||||
element.horizontal.exists = horizontal
|
||||
element.vertical.exists = vertical
|
||||
return
|
||||
}
|
||||
|
||||
// Resize resizes the scroll box.
|
||||
func (element *ScrollContainer) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
func (element *ScrollContainer) handleResize () {
|
||||
element.recalculate()
|
||||
element.child.Resize (
|
||||
element.childWidth,
|
||||
element.childHeight)
|
||||
element.resizeChildToFit()
|
||||
element.draw()
|
||||
}
|
||||
|
||||
@ -95,10 +91,7 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
|
||||
element.vertical.enabled = element.child.ScrollAxes()
|
||||
|
||||
if element.core.HasImage() {
|
||||
element.child.Resize (
|
||||
element.childWidth,
|
||||
element.childHeight)
|
||||
element.core.DamageAll()
|
||||
element.resizeChildToFit()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -120,7 +113,8 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) {
|
||||
if point.In(element.horizontal.bar) {
|
||||
element.horizontal.dragging = true
|
||||
element.horizontal.dragOffset =
|
||||
point.Sub(element.horizontal.bar.Min).X
|
||||
x - element.horizontal.bar.Min.X +
|
||||
element.Bounds().Min.X
|
||||
element.dragHorizontalBar(point)
|
||||
|
||||
} 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) {
|
||||
element.vertical.dragging = true
|
||||
element.vertical.dragOffset =
|
||||
point.Sub(element.vertical.bar.Min).Y
|
||||
y - element.vertical.bar.Min.Y +
|
||||
element.Bounds().Min.Y
|
||||
element.dragVerticalBar(point)
|
||||
|
||||
} else if point.In(element.vertical.gutter) {
|
||||
@ -259,6 +254,7 @@ func (element *ScrollContainer) childFocusMotionRequestCallback (
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) {
|
||||
child.DrawTo(nil)
|
||||
child.OnDamage(nil)
|
||||
child.OnMinimumSizeChange(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 () {
|
||||
_, gutterInsetHorizontal := theme.GutterPattern(theme.PatternState {
|
||||
Case: scrollBarHorizontalCase,
|
||||
@ -306,6 +310,7 @@ func (element *ScrollContainer) recalculate () {
|
||||
|
||||
// if enabled, give substance to the gutters
|
||||
if horizontal.exists {
|
||||
horizontal.gutter.Min.X = bounds.Min.X
|
||||
horizontal.gutter.Min.Y = bounds.Max.Y - thicknessHorizontal
|
||||
horizontal.gutter.Max.X = bounds.Max.X
|
||||
horizontal.gutter.Max.Y = bounds.Max.Y
|
||||
@ -318,6 +323,7 @@ func (element *ScrollContainer) recalculate () {
|
||||
if vertical.exists {
|
||||
vertical.gutter.Min.X = bounds.Max.X - thicknessVertical
|
||||
vertical.gutter.Max.X = bounds.Max.X
|
||||
vertical.gutter.Min.Y = bounds.Min.Y
|
||||
vertical.gutter.Max.Y = bounds.Max.Y
|
||||
if horizontal.exists {
|
||||
vertical.gutter.Max.Y -= thicknessHorizontal
|
||||
@ -364,7 +370,7 @@ func (element *ScrollContainer) recalculate () {
|
||||
}
|
||||
|
||||
func (element *ScrollContainer) draw () {
|
||||
artist.Paste(element.core, element.child, image.Point { })
|
||||
artist.Paste(element, element.child, image.Point { })
|
||||
deadPattern, _ := theme.DeadPattern(theme.PatternState {
|
||||
Case: scrollContainerCase,
|
||||
})
|
||||
|
@ -18,18 +18,11 @@ type Spacer struct {
|
||||
// will appear as a line.
|
||||
func NewSpacer (line bool) (element *Spacer) {
|
||||
element = &Spacer { line: line }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.core.SetMinimumSize(1, 1)
|
||||
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.
|
||||
func (element *Spacer) SetLine (line bool) {
|
||||
if element.line == line { return }
|
||||
@ -41,19 +34,19 @@ func (element *Spacer) SetLine (line bool) {
|
||||
}
|
||||
|
||||
func (element *Spacer) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
|
||||
if element.line {
|
||||
pattern, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: spacerCase,
|
||||
Disabled: true,
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
} else {
|
||||
pattern, _ := theme.BackgroundPattern(theme.PatternState {
|
||||
Case: spacerCase,
|
||||
Disabled: true,
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ type Switch struct {
|
||||
// NewSwitch creates a new switch with the specified label text.
|
||||
func NewSwitch (text string, on bool) (element *Switch) {
|
||||
element = &Switch { checked: on, text: text }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
@ -41,12 +41,6 @@ func NewSwitch (text string, on bool) (element *Switch) {
|
||||
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) {
|
||||
if !element.Enabled() { return }
|
||||
element.Focus()
|
||||
@ -146,13 +140,13 @@ func (element *Switch) calculateMinimumSize () {
|
||||
}
|
||||
|
||||
func (element *Switch) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy())
|
||||
gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy())
|
||||
bounds := element.Bounds()
|
||||
handleBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy()).Add(bounds.Min)
|
||||
gutterBounds := image.Rect(0, 0, bounds.Dy() * 2, bounds.Dy()).Add(bounds.Min)
|
||||
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState {
|
||||
Case: switchCase,
|
||||
})
|
||||
artist.FillRectangle ( element.core, backgroundPattern, bounds)
|
||||
artist.FillRectangle (element, backgroundPattern, bounds)
|
||||
|
||||
if element.checked {
|
||||
handleBounds.Min.X += bounds.Dy()
|
||||
@ -174,7 +168,7 @@ func (element *Switch) draw () {
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
artist.FillRectangle(element.core, gutterPattern, gutterBounds)
|
||||
artist.FillRectangle(element, gutterPattern, gutterBounds)
|
||||
|
||||
handlePattern, _ := theme.HandlePattern(theme.PatternState {
|
||||
Case: switchCase,
|
||||
@ -182,12 +176,12 @@ func (element *Switch) draw () {
|
||||
Focused: element.Focused(),
|
||||
Pressed: element.pressed,
|
||||
})
|
||||
artist.FillRectangle(element.core, handlePattern, handleBounds)
|
||||
artist.FillRectangle(element, handlePattern, handleBounds)
|
||||
|
||||
textBounds := element.drawer.LayoutBounds()
|
||||
offset := image.Point {
|
||||
offset := bounds.Min.Add(image.Point {
|
||||
X: bounds.Dy() * 2 + theme.Padding(),
|
||||
}
|
||||
})
|
||||
|
||||
offset.Y -= textBounds.Min.Y
|
||||
offset.X -= textBounds.Min.X
|
||||
@ -196,5 +190,5 @@ func (element *Switch) draw () {
|
||||
Case: switchCase,
|
||||
Disabled: !element.Enabled(),
|
||||
})
|
||||
element.drawer.Draw(element.core, foreground, offset)
|
||||
element.drawer.Draw(element, foreground, offset)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ type TextBox struct {
|
||||
// text.
|
||||
func NewTextBox (placeholder, value string) (element *TextBox) {
|
||||
element = &TextBox { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.handleResize)
|
||||
element.FocusableCore,
|
||||
element.focusableControl = core.NewFocusableCore (func () {
|
||||
if element.core.HasImage () {
|
||||
@ -51,8 +51,7 @@ func NewTextBox (placeholder, value string) (element *TextBox) {
|
||||
return
|
||||
}
|
||||
|
||||
func (element *TextBox) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
func (element *TextBox) handleResize () {
|
||||
element.scrollToCursor()
|
||||
element.draw()
|
||||
if element.onScrollBoundsChange != nil {
|
||||
@ -258,7 +257,8 @@ func (element *TextBox) runOnChange () {
|
||||
func (element *TextBox) scrollToCursor () {
|
||||
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()
|
||||
cursorPosition := element.valueDrawer.PositionOf(element.cursor)
|
||||
cursorPosition.X -= element.scroll
|
||||
@ -273,7 +273,7 @@ func (element *TextBox) scrollToCursor () {
|
||||
}
|
||||
|
||||
func (element *TextBox) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
|
||||
// FIXME: take index into account
|
||||
pattern, inset := theme.InputPattern(theme.PatternState {
|
||||
@ -281,36 +281,36 @@ func (element *TextBox) draw () {
|
||||
Disabled: !element.Enabled(),
|
||||
Focused: element.Focused(),
|
||||
})
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
|
||||
if len(element.text) == 0 && !element.Focused() {
|
||||
// draw placeholder
|
||||
textBounds := element.placeholderDrawer.LayoutBounds()
|
||||
offset := image.Point {
|
||||
offset := bounds.Min.Add (image.Point {
|
||||
X: theme.Padding() + inset[3],
|
||||
Y: theme.Padding() + inset[0],
|
||||
}
|
||||
})
|
||||
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
Disabled: true,
|
||||
})
|
||||
element.placeholderDrawer.Draw (
|
||||
element.core,
|
||||
element,
|
||||
foreground,
|
||||
offset.Sub(textBounds.Min))
|
||||
} else {
|
||||
// draw input value
|
||||
textBounds := element.valueDrawer.LayoutBounds()
|
||||
offset := image.Point {
|
||||
offset := bounds.Min.Add (image.Point {
|
||||
X: theme.Padding() + inset[3] - element.scroll,
|
||||
Y: theme.Padding() + inset[0],
|
||||
}
|
||||
})
|
||||
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||
Case: textBoxCase,
|
||||
Disabled: !element.Enabled(),
|
||||
})
|
||||
element.valueDrawer.Draw (
|
||||
element.core,
|
||||
element,
|
||||
foreground,
|
||||
offset.Sub(textBounds.Min))
|
||||
|
||||
@ -322,7 +322,7 @@ func (element *TextBox) draw () {
|
||||
Case: textBoxCase,
|
||||
})
|
||||
artist.Line (
|
||||
element.core,
|
||||
element,
|
||||
foreground, 1,
|
||||
cursorPosition.Add(offset),
|
||||
image.Pt (
|
||||
|
@ -7,21 +7,21 @@ import "git.tebibyte.media/sashakoshka/tomo"
|
||||
// Core is a struct that implements some core functionality common to most
|
||||
// widgets. It is meant to be embedded directly into a struct.
|
||||
type Core struct {
|
||||
canvas tomo.BasicCanvas
|
||||
parent tomo.Element
|
||||
canvas tomo.Canvas
|
||||
|
||||
metrics struct {
|
||||
minimumWidth int
|
||||
minimumHeight int
|
||||
}
|
||||
|
||||
drawSizeChange func ()
|
||||
onMinimumSizeChange func ()
|
||||
onDamage func (region tomo.Canvas)
|
||||
}
|
||||
|
||||
// NewCore creates a new element core and its corresponding control.
|
||||
func NewCore (parent tomo.Element) (core *Core, control CoreControl) {
|
||||
core = &Core { parent: parent }
|
||||
func NewCore (drawSizeChange func ()) (core *Core, control CoreControl) {
|
||||
core = &Core { drawSizeChange: drawSizeChange }
|
||||
control = CoreControl { core: core }
|
||||
return
|
||||
}
|
||||
@ -33,21 +33,25 @@ func (core *Core) ColorModel () (model color.Model) {
|
||||
|
||||
// ColorModel fulfills the draw.Image interface.
|
||||
func (core *Core) At (x, y int) (pixel color.Color) {
|
||||
if core.canvas == nil { return }
|
||||
return core.canvas.At(x, y)
|
||||
}
|
||||
|
||||
// ColorModel fulfills the draw.Image interface.
|
||||
func (core *Core) Bounds () (bounds image.Rectangle) {
|
||||
if core.canvas == nil { return }
|
||||
return core.canvas.Bounds()
|
||||
}
|
||||
|
||||
// ColorModel fulfills the draw.Image interface.
|
||||
func (core *Core) Set (x, y int, c color.Color) () {
|
||||
if core.canvas == nil { return }
|
||||
core.canvas.Set(x, y, c)
|
||||
}
|
||||
|
||||
// Buffer fulfills the tomo.Canvas interface.
|
||||
func (core *Core) Buffer () (data []color.RGBA, stride int) {
|
||||
if core.canvas == nil { return }
|
||||
return core.canvas.Buffer()
|
||||
}
|
||||
|
||||
@ -57,6 +61,15 @@ func (core *Core) MinimumSize () (width, height int) {
|
||||
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
|
||||
// overridden.
|
||||
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
|
||||
// corresponding CoreControl struct is linked to it and returned alongside it.
|
||||
type CoreControl struct {
|
||||
tomo.BasicCanvas
|
||||
core *Core
|
||||
}
|
||||
|
||||
// HasImage returns true if the core has an allocated image buffer, and false if
|
||||
// it doesn't.
|
||||
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
|
||||
// does not need to be called when responding to a resize event.
|
||||
func (control CoreControl) DamageRegion (bounds image.Rectangle) {
|
||||
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
|
||||
// called when responding to a resize event.
|
||||
// called when redrawing in response to a change in size.
|
||||
func (control CoreControl) DamageAll () {
|
||||
control.DamageRegion(control.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
|
||||
control.DamageRegion(control.core.Bounds())
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
|
@ -19,17 +19,11 @@ type AnalogClock struct {
|
||||
// NewAnalogClock creates a new analog clock that displays the specified time.
|
||||
func NewAnalogClock (newTime time.Time) (element *AnalogClock) {
|
||||
element = &AnalogClock { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.core.SetMinimumSize(64, 64)
|
||||
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.
|
||||
func (element *AnalogClock) SetTime (newTime time.Time) {
|
||||
if newTime == element.time { return }
|
||||
@ -41,7 +35,7 @@ func (element *AnalogClock) SetTime (newTime time.Time) {
|
||||
}
|
||||
|
||||
func (element *AnalogClock) draw () {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
|
||||
pattern, inset := theme.SunkenPattern(theme.PatternState {
|
||||
Case: clockCase,
|
||||
@ -87,15 +81,15 @@ func (element *AnalogClock) radialLine (
|
||||
outer float64,
|
||||
radian float64,
|
||||
) {
|
||||
bounds := element.core.Bounds()
|
||||
bounds := element.Bounds()
|
||||
width := float64(bounds.Dx()) / 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.Sin(radian) * inner * height + height))
|
||||
max := image.Pt (
|
||||
int(math.Sin(radian) * inner * height + height)))
|
||||
max := element.Bounds().Min.Add(image.Pt (
|
||||
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())
|
||||
artist.Line(element.core, source, 1, min, max)
|
||||
artist.Line(element, source, 1, min, max)
|
||||
}
|
||||
|
@ -19,16 +19,15 @@ type Artist struct {
|
||||
// NewArtist creates a new artist test element.
|
||||
func NewArtist () (element *Artist) {
|
||||
element = &Artist { }
|
||||
element.Core, element.core = core.NewCore(element)
|
||||
element.Core, element.core = core.NewCore(element.draw)
|
||||
element.core.SetMinimumSize(480, 600)
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Artist) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
func (element *Artist) draw () {
|
||||
bounds := element.Bounds()
|
||||
element.cellBounds.Max.X = bounds.Dx() / 5
|
||||
element.cellBounds.Max.Y = (bounds.Dy() - 48) / 8
|
||||
element.cellBounds.Max.X = bounds.Min.X + bounds.Dx() / 5
|
||||
element.cellBounds.Max.Y = bounds.Min.Y + (bounds.Dy() - 48) / 8
|
||||
|
||||
drawStart := time.Now()
|
||||
|
||||
|
@ -20,29 +20,28 @@ type Mouse struct {
|
||||
// NewMouse creates a new mouse test element.
|
||||
func NewMouse () (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.color = artist.NewUniform(color.Black)
|
||||
return
|
||||
}
|
||||
|
||||
func (element *Mouse) Resize (width, height int) {
|
||||
element.core.AllocateCanvas(width, height)
|
||||
func (element *Mouse) draw () {
|
||||
bounds := element.Bounds()
|
||||
pattern, _ := theme.AccentPattern(theme.PatternState { })
|
||||
artist.FillRectangle(element.core, pattern, bounds)
|
||||
artist.FillRectangle(element, pattern, bounds)
|
||||
artist.StrokeRectangle (
|
||||
element.core,
|
||||
element,
|
||||
artist.NewUniform(color.Black), 1,
|
||||
bounds)
|
||||
artist.Line (
|
||||
element.core, artist.NewUniform(color.White), 1,
|
||||
image.Pt(1, 1),
|
||||
image.Pt(bounds.Dx() - 2, bounds.Dy() - 2))
|
||||
element, artist.NewUniform(color.White), 1,
|
||||
bounds.Min.Add(image.Pt(1, 1)),
|
||||
bounds.Min.Add(image.Pt(bounds.Dx() - 2, bounds.Dy() - 2)))
|
||||
artist.Line (
|
||||
element.core, artist.NewUniform(color.White), 1,
|
||||
image.Pt(1, bounds.Dy() - 2),
|
||||
image.Pt(bounds.Dx() - 2, 1))
|
||||
element, artist.NewUniform(color.White), 1,
|
||||
bounds.Min.Add(image.Pt(1, bounds.Dy() - 2)),
|
||||
bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1)))
|
||||
}
|
||||
|
||||
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
|
||||
mousePos := image.Pt(x, y)
|
||||
element.core.DamageRegion (artist.Line (
|
||||
element.core, element.color, 1,
|
||||
element, element.color, 1,
|
||||
element.lastMousePos, mousePos))
|
||||
element.lastMousePos = mousePos
|
||||
}
|
||||
@ -63,7 +62,7 @@ func (element *Mouse) HandleMouseMove (x, y int) {
|
||||
if !element.drawing { return }
|
||||
mousePos := image.Pt(x, y)
|
||||
element.core.DamageRegion (artist.Line (
|
||||
element.core, element.color, 1,
|
||||
element, element.color, 1,
|
||||
element.lastMousePos, mousePos))
|
||||
element.lastMousePos = mousePos
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import "image"
|
||||
// it can be arranged by a Layout.
|
||||
type LayoutEntry struct {
|
||||
Element
|
||||
Position image.Point
|
||||
Bounds image.Rectangle
|
||||
Expand bool
|
||||
}
|
||||
|
||||
@ -17,7 +17,7 @@ type Layout interface {
|
||||
// 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
|
||||
// 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
|
||||
// needs to properly arrange the given slice of layout entries.
|
||||
|
@ -19,12 +19,9 @@ type Dialog struct {
|
||||
}
|
||||
|
||||
// Arrange arranges a list of entries into a dialog.
|
||||
func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||
if layout.Pad {
|
||||
width -= theme.Margin() * 2
|
||||
height -= theme.Margin() * 2
|
||||
}
|
||||
|
||||
func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
|
||||
if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
|
||||
|
||||
controlRowWidth, controlRowHeight := 0, 0
|
||||
if len(entries) > 1 {
|
||||
controlRowWidth,
|
||||
@ -32,24 +29,18 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||
}
|
||||
|
||||
if len(entries) > 0 {
|
||||
entries[0].Position = image.Point { }
|
||||
if layout.Pad {
|
||||
entries[0].Position.X += theme.Margin()
|
||||
entries[0].Position.Y += theme.Margin()
|
||||
}
|
||||
mainHeight := height - controlRowHeight
|
||||
main := entries[0]
|
||||
main.Bounds.Min = bounds.Min
|
||||
mainHeight := bounds.Dy() - controlRowHeight
|
||||
if layout.Gap {
|
||||
mainHeight -= theme.Margin()
|
||||
}
|
||||
mainBounds := entries[0].Bounds()
|
||||
if mainBounds.Dy() != mainHeight ||
|
||||
mainBounds.Dx() != width {
|
||||
entries[0].Resize(width, mainHeight)
|
||||
}
|
||||
main.Bounds.Max = main.Bounds.Min.Add(image.Pt(bounds.Dx(), mainHeight))
|
||||
entries[0] = main
|
||||
}
|
||||
|
||||
if len(entries) > 1 {
|
||||
freeSpace := width
|
||||
freeSpace := bounds.Dx()
|
||||
expandingElements := 0
|
||||
|
||||
// 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
|
||||
x, y := 0, height - controlRowHeight
|
||||
dot := image.Pt(bounds.Min.X, bounds.Max.Y - controlRowHeight)
|
||||
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
|
||||
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
|
||||
if entry.Expand {
|
||||
entryWidth = expandingElementWidth
|
||||
} else {
|
||||
entryWidth, _ = entry.MinimumSize()
|
||||
}
|
||||
x += entryWidth
|
||||
entryBounds := entry.Bounds()
|
||||
dot.X += entryWidth
|
||||
entryBounds := entry.Bounds
|
||||
if entryBounds.Dy() != controlRowHeight ||
|
||||
entryBounds.Dx() != entryWidth {
|
||||
entry.Resize(entryWidth, controlRowHeight)
|
||||
entry.Bounds.Max = entryBounds.Min.Add (
|
||||
image.Pt(entryWidth, controlRowHeight))
|
||||
}
|
||||
entries[index + 1] = entry
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,36 +17,28 @@ type Horizontal struct {
|
||||
}
|
||||
|
||||
// Arrange arranges a list of entries horizontally.
|
||||
func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||
if layout.Pad {
|
||||
width -= theme.Margin() * 2
|
||||
height -= theme.Margin() * 2
|
||||
}
|
||||
// get width of expanding elements
|
||||
expandingElementWidth := layout.expandingElementWidth(entries, width)
|
||||
func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
|
||||
if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
|
||||
|
||||
x, y := 0, 0
|
||||
if layout.Pad {
|
||||
x += theme.Margin()
|
||||
y += theme.Margin()
|
||||
}
|
||||
// get width of expanding elements
|
||||
expandingElementWidth := layout.expandingElementWidth(entries, bounds.Dx())
|
||||
|
||||
// set the size and position of each element
|
||||
dot := bounds.Min
|
||||
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
|
||||
if entry.Expand {
|
||||
entryWidth = expandingElementWidth
|
||||
} else {
|
||||
entryWidth, _ = entry.MinimumSize()
|
||||
}
|
||||
x += entryWidth
|
||||
entryBounds := entry.Bounds()
|
||||
if entryBounds.Dy() != height || entryBounds.Dx() != entryWidth {
|
||||
entry.Resize(entryWidth, height)
|
||||
}
|
||||
dot.X += entryWidth
|
||||
entry.Bounds.Max = entry.Bounds.Min.Add(image.Pt(entryWidth, bounds.Dy()))
|
||||
|
||||
entries[index] = entry
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,22 +17,19 @@ type Vertical struct {
|
||||
}
|
||||
|
||||
// Arrange arranges a list of entries vertically.
|
||||
func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||
if layout.Pad {
|
||||
width -= theme.Margin() * 2
|
||||
height -= theme.Margin() * 2
|
||||
}
|
||||
freeSpace := height
|
||||
expandingElements := 0
|
||||
func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
|
||||
if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
|
||||
|
||||
// count the number of expanding elements and the amount of free space
|
||||
// for them to collectively occupy, while gathering minimum heights.
|
||||
freeSpace := bounds.Dy()
|
||||
minimumHeights := make([]int, len(entries))
|
||||
expandingElements := 0
|
||||
for index, entry := range entries {
|
||||
var entryMinHeight int
|
||||
|
||||
if child, flexible := entry.Element.(tomo.Flexible); flexible {
|
||||
entryMinHeight = child.FlexibleHeightFor(width)
|
||||
entryMinHeight = child.FlexibleHeightFor(bounds.Dx())
|
||||
} else {
|
||||
_, entryMinHeight = entry.MinimumSize()
|
||||
}
|
||||
@ -47,33 +44,28 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, width, height int) {
|
||||
freeSpace -= theme.Margin()
|
||||
}
|
||||
}
|
||||
|
||||
expandingElementHeight := 0
|
||||
if expandingElements > 0 {
|
||||
expandingElementHeight = freeSpace / expandingElements
|
||||
}
|
||||
|
||||
x, y := 0, 0
|
||||
if layout.Pad {
|
||||
x += theme.Margin()
|
||||
y += theme.Margin()
|
||||
}
|
||||
|
||||
// set the size and position of each element
|
||||
dot := bounds.Min
|
||||
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
|
||||
if entry.Expand {
|
||||
entryHeight = expandingElementHeight
|
||||
} else {
|
||||
entryHeight = minimumHeights[index]
|
||||
}
|
||||
y += entryHeight
|
||||
entryBounds := entry.Bounds()
|
||||
if entryBounds.Dx() != width || entryBounds.Dy() != entryHeight {
|
||||
entry.Resize(width, entryHeight)
|
||||
}
|
||||
dot.Y += entryHeight
|
||||
entryBounds := entry.Bounds
|
||||
entry.Bounds.Max = entryBounds.Min.Add(image.Pt(bounds.Dx(), entryHeight))
|
||||
entries[index] = entry
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user