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,11 +19,8 @@ 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 { | ||||
| @ -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