Added support for horizontal layouts
This commit is contained in:
		
							parent
							
								
									0cd7fb9be9
								
							
						
					
					
						commit
						0a21f605fb
					
				| @ -11,27 +11,40 @@ type scratchEntry struct { | ||||
| 	minimum float64 | ||||
| } | ||||
| 
 | ||||
| type VBox struct { | ||||
| 	entity  tomo.ContainerEntity | ||||
| 	scratch map[tomo.Element] scratchEntry | ||||
| 	theme   theme.Wrapped | ||||
| 	padding bool | ||||
| 	margin  bool | ||||
| // Box is a container that lays out its children horizontally or vertically. | ||||
| // Child elements can be set to contract to their minimum size, or expand to | ||||
| // fill remaining space. Boxes can be nested and used together to create more | ||||
| // complex layouts. | ||||
| type Box struct { | ||||
| 	entity   tomo.ContainerEntity | ||||
| 	scratch  map[tomo.Element] scratchEntry | ||||
| 	theme    theme.Wrapped | ||||
| 	padding  bool | ||||
| 	margin   bool | ||||
| 	vertical bool | ||||
| } | ||||
| 
 | ||||
| func NewVBox (padding, margin bool) (element *VBox) { | ||||
| 	element = &VBox { padding: padding, margin: margin } | ||||
| // NewHBox creates a new horizontal box. | ||||
| func NewHBox (padding, margin bool) (element *Box) { | ||||
| 	element = &Box { padding: padding, margin: margin } | ||||
| 	element.scratch = make(map[tomo.Element] scratchEntry) | ||||
| 	element.theme.Case = tomo.C("tomo", "vBox") | ||||
| 	element.theme.Case = tomo.C("tomo", "box") | ||||
| 	element.entity = tomo.NewEntity(element).(tomo.ContainerEntity) | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) Entity () tomo.Entity { | ||||
| // NewHBox creates a new vertical box. | ||||
| func NewVBox (padding, margin bool) (element *Box) { | ||||
| 	element = NewHBox(padding, margin) | ||||
| 	element.vertical = true | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (element *Box) Entity () tomo.Entity { | ||||
| 	return element.entity | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) Draw (destination canvas.Canvas) { | ||||
| func (element *Box) Draw (destination canvas.Canvas) { | ||||
| 	rocks := make([]image.Rectangle, element.entity.CountChildren()) | ||||
| 	for index := 0; index < element.entity.CountChildren(); index ++ { | ||||
| 		rocks[index] = element.entity.Child(index).Entity().Bounds() | ||||
| @ -43,14 +56,20 @@ func (element *VBox) Draw (destination canvas.Canvas) { | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) Layout () { | ||||
| func (element *Box) Layout () { | ||||
| 	margin  := element.theme.Margin(tomo.PatternBackground) | ||||
| 	padding := element.theme.Padding(tomo.PatternBackground) | ||||
| 	bounds  := element.entity.Bounds() | ||||
| 	if element.padding { bounds = padding.Apply(bounds) } | ||||
| 
 | ||||
| 	var marginSize float64; if element.vertical { | ||||
| 		marginSize = float64(margin.Y) | ||||
| 	} else { | ||||
| 		marginSize = float64(margin.X) | ||||
| 	} | ||||
| 
 | ||||
| 	freeSpace, nExpanding := element.freeSpace() | ||||
| 	expandingElementHeight := freeSpace / nExpanding | ||||
| 	expandingElementSize := freeSpace / nExpanding | ||||
| 
 | ||||
| 	// set the size and position of each element | ||||
| 	x := float64(bounds.Min.X) | ||||
| @ -58,23 +77,31 @@ func (element *VBox) Layout () { | ||||
| 	for index := 0; index < element.entity.CountChildren(); index ++ { | ||||
| 		entry := element.scratch[element.entity.Child(index)] | ||||
| 		 | ||||
| 		var height float64; if entry.expand { | ||||
| 			height = expandingElementHeight | ||||
| 		var size float64; if entry.expand { | ||||
| 			size = expandingElementSize | ||||
| 		} else { | ||||
| 			height = entry.minimum | ||||
| 			size = entry.minimum | ||||
| 		} | ||||
| 
 | ||||
| 		element.entity.PlaceChild (index, tomo.Bounds ( | ||||
| 			int(x),      int(y), | ||||
| 			bounds.Dx(), int(height))) | ||||
| 		var childBounds image.Rectangle; if element.vertical { | ||||
| 			childBounds = tomo.Bounds(int(x), int(y), bounds.Dx(), int(size)) | ||||
| 		} else { | ||||
| 			childBounds = tomo.Bounds(int(x), int(y), int(size), bounds.Dy()) | ||||
| 		} | ||||
| 		element.entity.PlaceChild(index, childBounds) | ||||
| 
 | ||||
| 		y += height | ||||
| 		if element.margin { y += float64(margin.Y) } | ||||
| 		if element.vertical { | ||||
| 			y += size | ||||
| 			if element.margin { y += marginSize } | ||||
| 		} else { | ||||
| 			x += size | ||||
| 			if element.margin { x += marginSize } | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) Adopt (child tomo.Element, expand bool) { | ||||
| func (element *Box) Adopt (child tomo.Element, expand bool) { | ||||
| 	element.entity.Adopt(child) | ||||
| 	element.scratch[child] = scratchEntry { expand: expand } | ||||
| 	element.updateMinimumSize() | ||||
| @ -82,7 +109,7 @@ func (element *VBox) Adopt (child tomo.Element, expand bool) { | ||||
| 	element.entity.InvalidateLayout() | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) Disown (child tomo.Element) { | ||||
| func (element *Box) Disown (child tomo.Element) { | ||||
| 	index := element.entity.IndexOf(child) | ||||
| 	if index < 0 { return } | ||||
| 	element.entity.Disown(index) | ||||
| @ -92,7 +119,7 @@ func (element *VBox) Disown (child tomo.Element) { | ||||
| 	element.entity.InvalidateLayout() | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) DisownAll () { | ||||
| func (element *Box) DisownAll () { | ||||
| 	func () { | ||||
| 		for index := 0; index < element.entity.CountChildren(); index ++ { | ||||
| 			index := index | ||||
| @ -105,18 +132,18 @@ func (element *VBox) DisownAll () { | ||||
| 	element.entity.InvalidateLayout() | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) HandleChildMinimumSizeChange (child tomo.Element) { | ||||
| func (element *Box) HandleChildMinimumSizeChange (child tomo.Element) { | ||||
| 	element.updateMinimumSize() | ||||
| 	element.entity.Invalidate() | ||||
| 	element.entity.InvalidateLayout() | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) DrawBackground (destination canvas.Canvas) { | ||||
| func (element *Box) DrawBackground (destination canvas.Canvas) { | ||||
| 	element.entity.DrawBackground(destination) | ||||
| } | ||||
| 
 | ||||
| // SetTheme sets the element's theme. | ||||
| func (element *VBox) SetTheme (theme tomo.Theme) { | ||||
| func (element *Box) SetTheme (theme tomo.Theme) { | ||||
| 	if theme == element.theme.Theme { return } | ||||
| 	element.theme.Theme = theme | ||||
| 	element.updateMinimumSize() | ||||
| @ -124,10 +151,21 @@ func (element *VBox) SetTheme (theme tomo.Theme) { | ||||
| 	element.entity.InvalidateLayout() | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) freeSpace () (space float64, nExpanding float64) { | ||||
| func (element *Box) freeSpace () (space float64, nExpanding float64) { | ||||
| 	margin  := element.theme.Margin(tomo.PatternBackground) | ||||
| 	padding := element.theme.Padding(tomo.PatternBackground) | ||||
| 	space    = float64(element.entity.Bounds().Dy()) | ||||
| 
 | ||||
| 	var marginSize int; if element.vertical { | ||||
| 		marginSize = margin.Y | ||||
| 	} else { | ||||
| 		marginSize = margin.X | ||||
| 	} | ||||
| 	 | ||||
| 	if element.vertical { | ||||
| 		space = float64(element.entity.Bounds().Dy()) | ||||
| 	} else { | ||||
| 		space = float64(element.entity.Bounds().Dx()) | ||||
| 	} | ||||
| 
 | ||||
| 	for _, entry := range element.scratch { | ||||
| 		if entry.expand { | ||||
| @ -141,34 +179,50 @@ func (element *VBox) freeSpace () (space float64, nExpanding float64) { | ||||
| 		space -= float64(padding.Vertical()) | ||||
| 	} | ||||
| 	if element.margin { | ||||
| 		space -= float64(margin.Y * (len(element.scratch) - 1)) | ||||
| 		space -= float64(marginSize * (len(element.scratch) - 1)) | ||||
| 	} | ||||
| 
 | ||||
| 	return | ||||
| } | ||||
| 
 | ||||
| func (element *VBox) updateMinimumSize () { | ||||
| func (element *Box) updateMinimumSize () { | ||||
| 	margin  := element.theme.Margin(tomo.PatternBackground) | ||||
| 	padding := element.theme.Padding(tomo.PatternBackground) | ||||
| 	var width, height int | ||||
| 	var breadth, size int | ||||
| 	var marginSize int; if element.vertical { | ||||
| 		marginSize = margin.Y | ||||
| 	} else { | ||||
| 		marginSize = margin.X | ||||
| 	} | ||||
| 	 | ||||
| 	for index := 0; index < element.entity.CountChildren(); index ++ { | ||||
| 		childWidth, childHeight :=  element.entity.ChildMinimumSize(index) | ||||
| 		childWidth, childHeight := element.entity.ChildMinimumSize(index) | ||||
| 		var childBreadth, childSize int; if element.vertical { | ||||
| 			childBreadth, childSize = childWidth, childHeight | ||||
| 		} else { | ||||
| 			childBreadth, childSize = childHeight, childWidth | ||||
| 		} | ||||
| 		 | ||||
| 		key   := element.entity.Child(index) | ||||
| 		entry := element.scratch[key] | ||||
| 		entry.minimum = float64(childHeight) | ||||
| 		entry.minimum = float64(childSize) | ||||
| 		element.scratch[key] = entry | ||||
| 		 | ||||
| 		if childWidth > width { | ||||
| 			width = childWidth | ||||
| 		if childBreadth > breadth { | ||||
| 			breadth = childBreadth | ||||
| 		} | ||||
| 		height += childHeight | ||||
| 		size += childSize | ||||
| 		if element.margin && index > 0 { | ||||
| 			height += margin.Y | ||||
| 			size += marginSize | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	var width, height int; if element.vertical { | ||||
| 		width, height = breadth, size | ||||
| 	} else { | ||||
| 		width, height = size, breadth | ||||
| 	} | ||||
| 
 | ||||
| 	if element.padding { | ||||
| 		width  += padding.Horizontal() | ||||
| 		height += padding.Vertical() | ||||
| @ -1,7 +1,7 @@ | ||||
| package main | ||||
| 
 | ||||
| import "git.tebibyte.media/sashakoshka/tomo" | ||||
| // import "git.tebibyte.media/sashakoshka/tomo/popups" | ||||
| import "git.tebibyte.media/sashakoshka/tomo/popups" | ||||
| import "git.tebibyte.media/sashakoshka/tomo/elements" | ||||
| import "git.tebibyte.media/sashakoshka/tomo/elements/containers" | ||||
| import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" | ||||
| @ -34,11 +34,11 @@ func run () { | ||||
| 	vsync := elements.NewCheckbox("Enable vsync", false) | ||||
| 	vsync.OnToggle (func () { | ||||
| 		if vsync.Value() { | ||||
| 			// popups.NewDialog ( | ||||
| 				// popups.DialogKindInfo, | ||||
| 				// window, | ||||
| 				// "Ha!", | ||||
| 				// "That doesn't do anything.") | ||||
| 			popups.NewDialog ( | ||||
| 				popups.DialogKindInfo, | ||||
| 				window, | ||||
| 				"Ha!", | ||||
| 				"That doesn't do anything.") | ||||
| 		} | ||||
| 	}) | ||||
| 	container.Adopt(vsync, false) | ||||
|  | ||||
| @ -2,7 +2,6 @@ package popups | ||||
| 
 | ||||
| import "image" | ||||
| import "git.tebibyte.media/sashakoshka/tomo" | ||||
| import "git.tebibyte.media/sashakoshka/tomo/layouts" | ||||
| import "git.tebibyte.media/sashakoshka/tomo/elements" | ||||
| import "git.tebibyte.media/sashakoshka/tomo/elements/containers" | ||||
| 
 | ||||
| @ -16,6 +15,8 @@ const ( | ||||
| 	DialogKindError | ||||
| ) | ||||
| 
 | ||||
| // TODO: add ability to have an icon for buttons | ||||
| 
 | ||||
| // Button represents a dialog response button. | ||||
| type Button struct { | ||||
| 	// Name contains the text to display on the button. | ||||
| @ -43,10 +44,10 @@ func NewDialog ( | ||||
| 	} | ||||
| 	window.SetTitle(title) | ||||
| 	 | ||||
| 	container := containers.NewContainer(layouts.Dialog { true, true }) | ||||
| 	window.Adopt(container) | ||||
| 	box        := containers.NewVBox(true,  true) | ||||
| 	messageRow := containers.NewHBox(false, true) | ||||
| 	controlRow := containers.NewHBox(false, true) | ||||
| 
 | ||||
| 	messageContainer := containers.NewContainer(layouts.Horizontal { true, false }) | ||||
| 	iconId := tomo.IconInformation | ||||
| 	switch kind { | ||||
| 	case DialogKindInfo:     iconId = tomo.IconInformation | ||||
| @ -55,15 +56,15 @@ func NewDialog ( | ||||
| 	case DialogKindError:    iconId = tomo.IconError | ||||
| 	} | ||||
| 	 | ||||
| 	messageContainer.Adopt(elements.NewIcon(iconId, tomo.IconSizeLarge), false) | ||||
| 	messageContainer.Adopt(elements.NewLabel(message, false), true) | ||||
| 	container.Adopt(messageContainer, true) | ||||
| 	messageRow.Adopt(elements.NewIcon(iconId, tomo.IconSizeLarge), false) | ||||
| 	messageRow.Adopt(elements.NewLabel(message, false), true) | ||||
| 	 | ||||
| 	controlRow.Adopt(elements.NewSpacer(false), true) | ||||
| 	if len(buttons) == 0 { | ||||
| 		button := elements.NewButton("OK") | ||||
| 		button.SetIcon(tomo.IconYes) | ||||
| 		button.OnClick(window.Close) | ||||
| 		container.Adopt(button, false) | ||||
| 		controlRow.Adopt(button, false) | ||||
| 		button.Focus() | ||||
| 	} else { | ||||
| 		var button *elements.Button | ||||
| @ -74,11 +75,14 @@ func NewDialog ( | ||||
| 				buttonDescriptor.OnPress() | ||||
| 				window.Close() | ||||
| 			}) | ||||
| 			container.Adopt(button, false) | ||||
| 			controlRow.Adopt(button, false) | ||||
| 		} | ||||
| 		button.Focus() | ||||
| 	} | ||||
| 	 | ||||
| 	box.Adopt(messageRow, true) | ||||
| 	box.Adopt(controlRow, false) | ||||
| 	window.Adopt(box) | ||||
| 	window.Show() | ||||
| 	return | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user