Lists are a thing now
Looks like child bounds arent clipped properly though, ugh
This commit is contained in:
		
							parent
							
								
									6b13e772a9
								
							
						
					
					
						commit
						0bf5c3b86c
					
				| @ -102,10 +102,10 @@ func (entity *entity) scrollTargetChildAt (point image.Point) *entity { | |||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (entity *entity) forMouseTargetContainers (callback func (tomo.MouseTargetContainer)) { | func (entity *entity) forMouseTargetContainers (callback func (tomo.MouseTargetContainer, tomo.Element)) { | ||||||
| 	if entity.parent == nil { return } | 	if entity.parent == nil { return } | ||||||
| 	if parent, ok := entity.parent.element.(tomo.MouseTargetContainer); ok { | 	if parent, ok := entity.parent.element.(tomo.MouseTargetContainer); ok { | ||||||
| 		callback(parent) | 		callback(parent, entity.element) | ||||||
| 	} | 	} | ||||||
| 	entity.parent.forMouseTargetContainers(callback) | 	entity.parent.forMouseTargetContainers(callback) | ||||||
| } | } | ||||||
| @ -211,7 +211,8 @@ func (entity *entity) PlaceChild (index int, bounds image.Rectangle) { | |||||||
| 
 | 
 | ||||||
| func (entity *entity) SelectChild (index int, selected bool) { | func (entity *entity) SelectChild (index int, selected bool) { | ||||||
| 	child := entity.children[index] | 	child := entity.children[index] | ||||||
| 	if element, ok := entity.element.(tomo.Selectable); ok { | 	if element, ok := child.element.(tomo.Selectable); ok { | ||||||
|  | 		if child.selected == selected { return } | ||||||
| 		child.selected = selected | 		child.selected = selected | ||||||
| 		element.HandleSelectionChange() | 		element.HandleSelectionChange() | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -212,11 +212,11 @@ func (window *window) handleButtonPress ( | |||||||
| 				point.X, point.Y, | 				point.X, point.Y, | ||||||
| 				input.Button(buttonEvent.Detail)) | 				input.Button(buttonEvent.Detail)) | ||||||
| 		} | 		} | ||||||
| 		callback := func (container tomo.MouseTargetContainer) { | 		callback := func (container tomo.MouseTargetContainer, child tomo.Element) { | ||||||
| 			container.HandleChildMouseDown ( | 			container.HandleChildMouseDown ( | ||||||
| 				point.X, point.Y, | 				point.X, point.Y, | ||||||
| 				input.Button(buttonEvent.Detail), | 				input.Button(buttonEvent.Detail), | ||||||
| 				underneath.element) | 				child) | ||||||
| 		} | 		} | ||||||
| 		underneath.forMouseTargetContainers(callback) | 		underneath.forMouseTargetContainers(callback) | ||||||
| 	} | 	} | ||||||
| @ -238,12 +238,12 @@ func (window *window) handleButtonRelease ( | |||||||
| 				int(buttonEvent.EventY), | 				int(buttonEvent.EventY), | ||||||
| 				input.Button(buttonEvent.Detail)) | 				input.Button(buttonEvent.Detail)) | ||||||
| 		} | 		} | ||||||
| 		callback := func (container tomo.MouseTargetContainer) { | 		callback := func (container tomo.MouseTargetContainer, child tomo.Element) { | ||||||
| 			container.HandleChildMouseUp ( | 			container.HandleChildMouseUp ( | ||||||
| 			int(buttonEvent.EventX), | 			int(buttonEvent.EventX), | ||||||
| 			int(buttonEvent.EventY), | 			int(buttonEvent.EventY), | ||||||
| 			input.Button(buttonEvent.Detail), | 			input.Button(buttonEvent.Detail), | ||||||
| 			dragging.element) | 			child) | ||||||
| 		} | 		} | ||||||
| 		dragging.forMouseTargetContainers(callback) | 		dragging.forMouseTargetContainers(callback) | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -212,14 +212,9 @@ func (Default) Pattern (id tomo.Pattern, state tomo.State, c tomo.Case) artist.P | |||||||
| 	switch id { | 	switch id { | ||||||
| 	case tomo.PatternBackground: return patterns.Uhex(0xaaaaaaFF) | 	case tomo.PatternBackground: return patterns.Uhex(0xaaaaaaFF) | ||||||
| 	case tomo.PatternDead:       return defaultTextures[0][offset] | 	case tomo.PatternDead:       return defaultTextures[0][offset] | ||||||
| 	case tomo.PatternRaised: | 	case tomo.PatternRaised:     return defaultTextures[1][offset] | ||||||
| 		if c.Match("tomo", "listEntry", "") { | 	case tomo.PatternSunken:     return defaultTextures[2][offset] | ||||||
| 			return defaultTextures[10][offset] | 	case tomo.PatternPinboard:   return defaultTextures[3][offset] | ||||||
| 		} else { |  | ||||||
| 			return defaultTextures[1][offset] |  | ||||||
| 		} |  | ||||||
| 	case tomo.PatternSunken:   return defaultTextures[2][offset] |  | ||||||
| 	case tomo.PatternPinboard: return defaultTextures[3][offset] |  | ||||||
| 	case tomo.PatternButton: | 	case tomo.PatternButton: | ||||||
| 		switch { | 		switch { | ||||||
| 		case c.Match("tomo", "checkbox", ""):   | 		case c.Match("tomo", "checkbox", ""):   | ||||||
| @ -272,16 +267,8 @@ func (Default) Color (id tomo.Color, state tomo.State, c tomo.Case) color.RGBA { | |||||||
| // Padding returns the default padding value for the given pattern. | // Padding returns the default padding value for the given pattern. | ||||||
| func (Default) Padding (id tomo.Pattern, c tomo.Case) artist.Inset { | func (Default) Padding (id tomo.Pattern, c tomo.Case) artist.Inset { | ||||||
| 	switch id { | 	switch id { | ||||||
| 	case tomo.PatternRaised: |  | ||||||
| 		if c.Match("tomo", "listEntry", "") { |  | ||||||
| 			return artist.I(4, 8) |  | ||||||
| 		} else { |  | ||||||
| 			return artist.I(8) |  | ||||||
| 		} |  | ||||||
| 	case tomo.PatternSunken: | 	case tomo.PatternSunken: | ||||||
| 		if c.Match("tomo", "list", "") { | 		if c.Match("tomo", "progressBar", "") { | ||||||
| 			return artist.I(4, 0, 3) |  | ||||||
| 		} else if c.Match("tomo", "progressBar", "") { |  | ||||||
| 			return artist.I(2, 1, 1, 2) | 			return artist.I(2, 1, 1, 2) | ||||||
| 		} else { | 		} else { | ||||||
| 			return artist.I(8) | 			return artist.I(8) | ||||||
|  | |||||||
| @ -13,18 +13,19 @@ type cellEntity interface { | |||||||
| // Cell is a single-element container that satisfies tomo.Selectable. It | // Cell is a single-element container that satisfies tomo.Selectable. It | ||||||
| // provides styling based on whether or not it is selected. | // provides styling based on whether or not it is selected. | ||||||
| type Cell struct { | type Cell struct { | ||||||
| 	entity   cellEntity | 	entity  cellEntity | ||||||
| 	child    tomo.Element | 	child   tomo.Element | ||||||
| 	enabled  bool | 	enabled bool | ||||||
| 	padding  bool | 	theme   theme.Wrapped | ||||||
| 	theme    theme.Wrapped | 
 | ||||||
|  | 	onSelectionChange func () | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // NewCell creates a new cell element. If padding is true, the cell will have | // NewCell creates a new cell element. If padding is true, the cell will have | ||||||
| // padding on all sides. Child can be nil and added later with the Adopt() | // padding on all sides. Child can be nil and added later with the Adopt() | ||||||
| // method. | // method. | ||||||
| func NewCell (child tomo.Element, padding bool) (element *Cell) { | func NewCell (child tomo.Element) (element *Cell) { | ||||||
| 	element = &Cell { padding: padding } | 	element = &Cell { enabled: true } | ||||||
| 	element.theme.Case = tomo.C("tomo", "cell") | 	element.theme.Case = tomo.C("tomo", "cell") | ||||||
| 	element.entity = tomo.NewEntity(element).(cellEntity) | 	element.entity = tomo.NewEntity(element).(cellEntity) | ||||||
| 	element.Adopt(child) | 	element.Adopt(child) | ||||||
| @ -42,7 +43,7 @@ func (element *Cell) Draw (destination canvas.Canvas) { | |||||||
| 	pattern := element.theme.Pattern(tomo.PatternTableCell, element.state()) | 	pattern := element.theme.Pattern(tomo.PatternTableCell, element.state()) | ||||||
| 	if element.child == nil { | 	if element.child == nil { | ||||||
| 		pattern.Draw(destination, bounds) | 		pattern.Draw(destination, bounds) | ||||||
| 	} else if element.padding { | 	} else { | ||||||
| 		artist.DrawShatter ( | 		artist.DrawShatter ( | ||||||
| 			destination, pattern, bounds, | 			destination, pattern, bounds, | ||||||
| 			element.child.Entity().Bounds()) | 			element.child.Entity().Bounds()) | ||||||
| @ -54,9 +55,7 @@ func (element *Cell) Layout () { | |||||||
| 	if element.child == nil { return } | 	if element.child == nil { return } | ||||||
| 	 | 	 | ||||||
| 	bounds := element.entity.Bounds() | 	bounds := element.entity.Bounds() | ||||||
| 	if element.padding { | 	bounds = element.theme.Padding(tomo.PatternTableCell).Apply(bounds) | ||||||
| 		bounds = element.theme.Padding(tomo.PatternTableCell).Apply(bounds) |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	element.entity.PlaceChild(0, bounds) | 	element.entity.PlaceChild(0, bounds) | ||||||
| } | } | ||||||
| @ -80,6 +79,7 @@ func (element *Cell) Adopt (child tomo.Element) { | |||||||
| 
 | 
 | ||||||
| 	element.updateMinimumSize() | 	element.updateMinimumSize() | ||||||
| 	element.entity.Invalidate() | 	element.entity.Invalidate() | ||||||
|  | 	element.invalidateChild() | ||||||
| 	element.entity.InvalidateLayout() | 	element.entity.InvalidateLayout() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -93,9 +93,7 @@ func (element *Cell) SetEnabled (enabled bool) { | |||||||
| 	if element.enabled == enabled { return } | 	if element.enabled == enabled { return } | ||||||
| 	element.enabled = enabled | 	element.enabled = enabled | ||||||
| 	element.entity.Invalidate() | 	element.entity.Invalidate() | ||||||
| 	if child, ok := element.child.(tomo.Enableable); ok { | 	element.invalidateChild() | ||||||
| 		child.SetEnabled(enabled) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // SetTheme sets this element's theme. | // SetTheme sets this element's theme. | ||||||
| @ -104,11 +102,26 @@ func (element *Cell) SetTheme (theme tomo.Theme) { | |||||||
| 	element.theme.Theme = theme | 	element.theme.Theme = theme | ||||||
| 	element.updateMinimumSize() | 	element.updateMinimumSize() | ||||||
| 	element.entity.Invalidate() | 	element.entity.Invalidate() | ||||||
|  | 	element.invalidateChild() | ||||||
| 	element.entity.InvalidateLayout() | 	element.entity.InvalidateLayout() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // OnSelectionChange sets a function to be called when this element is selected | ||||||
|  | // or unselected. | ||||||
|  | func (element *Cell) OnSelectionChange (callback func ()) { | ||||||
|  | 	element.onSelectionChange = callback | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *Cell) Selected () bool { | ||||||
|  | 	return element.entity.Selected() | ||||||
|  | } | ||||||
|  | 
 | ||||||
| func (element *Cell) HandleSelectionChange () { | func (element *Cell) HandleSelectionChange () { | ||||||
| 	element.entity.Invalidate() | 	element.entity.Invalidate() | ||||||
|  | 	element.invalidateChild() | ||||||
|  | 	if element.onSelectionChange != nil { | ||||||
|  | 		element.onSelectionChange() | ||||||
|  | 	} | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (element *Cell) HandleChildMinimumSizeChange (tomo.Element) { | func (element *Cell) HandleChildMinimumSizeChange (tomo.Element) { | ||||||
| @ -132,11 +145,15 @@ func (element *Cell) updateMinimumSize () { | |||||||
| 		width  += childWidth | 		width  += childWidth | ||||||
| 		height += childHeight | 		height += childHeight | ||||||
| 	} | 	} | ||||||
| 	if element.padding { | 	padding := element.theme.Padding(tomo.PatternTableCell) | ||||||
| 		padding := element.theme.Padding(tomo.PatternTableCell) | 	width  += padding.Horizontal() | ||||||
| 		width  += padding.Horizontal() | 	height += padding.Vertical() | ||||||
| 		height += padding.Vertical() |  | ||||||
| 	} |  | ||||||
| 	 | 	 | ||||||
| 	element.entity.SetMinimumSize(width, height) | 	element.entity.SetMinimumSize(width, height) | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | func (element *Cell) invalidateChild () { | ||||||
|  | 	if element.child != nil { | ||||||
|  | 		element.child.Entity().Invalidate() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | |||||||
							
								
								
									
										315
									
								
								elements/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										315
									
								
								elements/list.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,315 @@ | |||||||
|  | package elements | ||||||
|  | 
 | ||||||
|  | import "image" | ||||||
|  | import "git.tebibyte.media/sashakoshka/tomo" | ||||||
|  | import "git.tebibyte.media/sashakoshka/tomo/input" | ||||||
|  | import "git.tebibyte.media/sashakoshka/tomo/canvas" | ||||||
|  | import "git.tebibyte.media/sashakoshka/tomo/artist" | ||||||
|  | import "git.tebibyte.media/sashakoshka/tomo/default/theme" | ||||||
|  | 
 | ||||||
|  | type listEntity interface { | ||||||
|  | 	tomo.ContainerEntity | ||||||
|  | 	tomo.ScrollableEntity | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type List struct { | ||||||
|  | 	entity listEntity | ||||||
|  | 	 | ||||||
|  | 	scratch       map[tomo.Element] scratchEntry | ||||||
|  | 	scroll        image.Point | ||||||
|  | 	contentBounds image.Rectangle | ||||||
|  | 	columnSizes   []int | ||||||
|  | 	selected      int | ||||||
|  | 	 | ||||||
|  | 	forcedMinimumWidth  int | ||||||
|  | 	forcedMinimumHeight int | ||||||
|  | 
 | ||||||
|  | 	theme theme.Wrapped | ||||||
|  | 	 | ||||||
|  | 	onScrollBoundsChange func () | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func NewList (columns int, children ...tomo.Selectable) (element *List) { | ||||||
|  | 	if columns < 1 { columns = 1 } | ||||||
|  | 	element = &List { selected: -1 } | ||||||
|  | 	element.scratch = make(map[tomo.Element] scratchEntry) | ||||||
|  | 	element.columnSizes = make([]int, columns) | ||||||
|  | 	element.theme.Case = tomo.C("tomo", "list") | ||||||
|  | 	element.entity = tomo.NewEntity(element).(listEntity) | ||||||
|  | 
 | ||||||
|  | 	for _, child := range children { | ||||||
|  | 		element.Adopt(child) | ||||||
|  | 	} | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) Entity () tomo.Entity { | ||||||
|  | 	return element.entity | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) 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() | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	pattern := element.theme.Pattern(tomo.PatternSunken, tomo.State { }) | ||||||
|  | 	artist.DrawShatter(destination, pattern, element.entity.Bounds(), rocks...) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) Layout () { | ||||||
|  | 	margin := element.theme.Margin(tomo.PatternSunken) | ||||||
|  | 	padding := element.theme.Padding(tomo.PatternSunken) | ||||||
|  | 	bounds := padding.Apply(element.entity.Bounds()) | ||||||
|  | 	element.contentBounds = image.Rectangle { } | ||||||
|  | 
 | ||||||
|  | 	dot         := bounds.Min.Sub(element.scroll) | ||||||
|  | 	xStart      := dot.X | ||||||
|  | 	rowHeight   := 0 | ||||||
|  | 	columnIndex := 0 | ||||||
|  | 	nextLine := func () { | ||||||
|  | 		dot.X = xStart | ||||||
|  | 		dot.Y += margin.Y | ||||||
|  | 		dot.Y += rowHeight | ||||||
|  | 		rowHeight   = 0 | ||||||
|  | 		columnIndex = 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for index := 0; index < element.entity.CountChildren(); index ++ { | ||||||
|  | 		child := element.entity.Child(index) | ||||||
|  | 		entry := element.scratch[child] | ||||||
|  | 	 | ||||||
|  | 		if columnIndex >= len(element.columnSizes) { | ||||||
|  | 			nextLine() | ||||||
|  | 		} | ||||||
|  | 		width  := element.columnSizes[columnIndex] | ||||||
|  | 		height := int(entry.minSize) | ||||||
|  | 
 | ||||||
|  | 		if len(element.columnSizes) == 1 && width < bounds.Dx() { | ||||||
|  | 			width = bounds.Dx() | ||||||
|  | 		} | ||||||
|  | 		 | ||||||
|  | 		if rowHeight < height { | ||||||
|  | 			rowHeight = height | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		childBounds := tomo.Bounds ( | ||||||
|  | 			dot.X, dot.Y, | ||||||
|  | 			width, height) | ||||||
|  | 		element.entity.PlaceChild(index, childBounds) | ||||||
|  | 		element.contentBounds = element.contentBounds.Union(childBounds) | ||||||
|  | 		 | ||||||
|  | 		dot.X += width + margin.X | ||||||
|  | 
 | ||||||
|  | 		columnIndex ++ | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	element.contentBounds = | ||||||
|  | 		element.contentBounds.Sub(element.contentBounds.Min) | ||||||
|  | 		 | ||||||
|  | 	element.entity.NotifyScrollBoundsChange() | ||||||
|  | 	if element.onScrollBoundsChange != nil { | ||||||
|  | 		element.onScrollBoundsChange() | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) Adopt (child tomo.Element) { | ||||||
|  | 	element.entity.Adopt(child) | ||||||
|  | 	element.scratch[child] = scratchEntry { } | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) Disown (child tomo.Element) { | ||||||
|  | 	index := element.entity.IndexOf(child) | ||||||
|  | 	if index < 0 { return } | ||||||
|  | 	if index == element.selected { | ||||||
|  | 		element.selected = -1 | ||||||
|  | 		element.entity.SelectChild(index, false) | ||||||
|  | 	} | ||||||
|  | 	element.entity.Disown(index) | ||||||
|  | 	delete(element.scratch, child) | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) DisownAll () { | ||||||
|  | 	func () { | ||||||
|  | 		for index := 0; index < element.entity.CountChildren(); index ++ { | ||||||
|  | 			index := index | ||||||
|  | 			defer element.entity.Disown(index) | ||||||
|  | 		} | ||||||
|  | 	} () | ||||||
|  | 	element.scratch = make(map[tomo.Element] scratchEntry) | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) HandleChildMouseDown (x, y int, button input.Button, child tomo.Element) { | ||||||
|  | 	if child, ok := child.(tomo.Selectable); ok { | ||||||
|  | 		index := element.entity.IndexOf(child) | ||||||
|  | 		if element.selected == index { return } | ||||||
|  | 		if element.selected >= 0 { | ||||||
|  | 			element.entity.SelectChild(element.selected, false) | ||||||
|  | 		} | ||||||
|  | 		element.selected = index | ||||||
|  | 		element.entity.SelectChild(index, true) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) HandleChildMouseUp (int, int, input.Button, tomo.Element) { } | ||||||
|  | 
 | ||||||
|  | func (element *List) HandleChildMinimumSizeChange (child tomo.Element) { | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) HandleChildFlexibleHeightChange (child tomo.Flexible) { | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) DrawBackground (destination canvas.Canvas) { | ||||||
|  | 	element.entity.DrawBackground(destination) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // SetTheme sets the element's theme. | ||||||
|  | func (element *List) SetTheme (theme tomo.Theme) { | ||||||
|  | 	if theme == element.theme.Theme { return } | ||||||
|  | 	element.theme.Theme = theme | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Collapse forces a minimum width and height upon the list. If a zero value is | ||||||
|  | // given for a dimension, its minimum will be determined by the list's content. | ||||||
|  | // If the list's height goes beyond the forced size, it will need to be accessed | ||||||
|  | // via scrolling. If an entry's width goes beyond the forced size, its text will | ||||||
|  | // be truncated so that it fits. | ||||||
|  | func (element *List) Collapse (width, height int) { | ||||||
|  | 	if | ||||||
|  | 		element.forcedMinimumWidth == width && | ||||||
|  | 		element.forcedMinimumHeight == height { | ||||||
|  | 		 | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 	 | ||||||
|  | 	element.forcedMinimumWidth  = width | ||||||
|  | 	element.forcedMinimumHeight = height | ||||||
|  | 	 | ||||||
|  | 	element.updateMinimumSize() | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ScrollContentBounds returns the full content size of the element. | ||||||
|  | func (element *List) ScrollContentBounds () image.Rectangle { | ||||||
|  | 	return element.contentBounds | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ScrollViewportBounds returns the size and position of the element's | ||||||
|  | // viewport relative to ScrollBounds. | ||||||
|  | func (element *List) ScrollViewportBounds () image.Rectangle { | ||||||
|  | 	padding := element.theme.Padding(tomo.PatternBackground) | ||||||
|  | 	bounds  := padding.Apply(element.entity.Bounds()) | ||||||
|  | 	bounds   = bounds.Sub(bounds.Min).Add(element.scroll) | ||||||
|  | 	return bounds | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ScrollTo scrolls the viewport to the specified point relative to | ||||||
|  | // ScrollBounds. | ||||||
|  | func (element *List) ScrollTo (position image.Point) { | ||||||
|  | 	if position.Y < 0 { | ||||||
|  | 		position.Y = 0 | ||||||
|  | 	} | ||||||
|  | 	maxScrollHeight := element.maxScrollHeight() | ||||||
|  | 	if position.Y > maxScrollHeight { | ||||||
|  | 		position.Y = maxScrollHeight | ||||||
|  | 	} | ||||||
|  | 	element.scroll = position | ||||||
|  | 	element.entity.Invalidate() | ||||||
|  | 	element.entity.InvalidateLayout() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // OnScrollBoundsChange sets a function to be called when the element's viewport | ||||||
|  | // bounds, content bounds, or scroll axes change. | ||||||
|  | func (element *List) OnScrollBoundsChange (callback func ()) { | ||||||
|  | 	element.onScrollBoundsChange = callback | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // ScrollAxes returns the supported axes for scrolling. | ||||||
|  | func (element *List) ScrollAxes () (horizontal, vertical bool) { | ||||||
|  | 	return false, true | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) maxScrollHeight () (height int) { | ||||||
|  | 	padding := element.theme.Padding(tomo.PatternSunken) | ||||||
|  | 	viewportHeight := element.entity.Bounds().Dy() - padding.Vertical() | ||||||
|  | 	height = element.contentBounds.Dy() - viewportHeight | ||||||
|  | 	if height < 0 { height = 0 } | ||||||
|  | 	return | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (element *List) updateMinimumSize () { | ||||||
|  | 	margin := element.theme.Margin(tomo.PatternSunken) | ||||||
|  | 	padding := element.theme.Padding(tomo.PatternSunken) | ||||||
|  | 
 | ||||||
|  | 	for index := range element.columnSizes { | ||||||
|  | 		element.columnSizes[index] = 0 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	height      := 0 | ||||||
|  | 	rowHeight   := 0 | ||||||
|  | 	columnIndex := 0 | ||||||
|  | 	nextLine := func () { | ||||||
|  | 		height += rowHeight | ||||||
|  | 		rowHeight   = 0 | ||||||
|  | 		columnIndex = 0 | ||||||
|  | 	} | ||||||
|  | 	for index := 0; index < element.entity.CountChildren(); index ++ { | ||||||
|  | 		if columnIndex >= len(element.columnSizes) { | ||||||
|  | 			if index > 0 { height += margin.Y } | ||||||
|  | 			nextLine() | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		child := element.entity.Child(index) | ||||||
|  | 		entry := element.scratch[child] | ||||||
|  | 		 | ||||||
|  | 		entryWidth, entryHeight := element.entity.ChildMinimumSize(index) | ||||||
|  | 		entry.minBreadth = float64(entryWidth) | ||||||
|  | 		entry.minSize    = float64(entryHeight) | ||||||
|  | 		element.scratch[child] = entry | ||||||
|  | 		 | ||||||
|  | 		if rowHeight < entryHeight { | ||||||
|  | 			rowHeight = entryHeight | ||||||
|  | 		} | ||||||
|  | 		if element.columnSizes[columnIndex] < entryWidth { | ||||||
|  | 			element.columnSizes[columnIndex] = entryWidth | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		columnIndex ++ | ||||||
|  | 	} | ||||||
|  | 	nextLine() | ||||||
|  | 
 | ||||||
|  | 	width := 0; for index, size := range element.columnSizes { | ||||||
|  | 		width += size | ||||||
|  | 		if index > 0 { width += margin.X } | ||||||
|  | 	} | ||||||
|  | 	width  += padding.Horizontal() | ||||||
|  | 	height += padding.Vertical() | ||||||
|  | 
 | ||||||
|  | 	if element.forcedMinimumHeight > 0 { | ||||||
|  | 		height = element.forcedMinimumHeight | ||||||
|  | 	} | ||||||
|  | 	if element.forcedMinimumWidth > 0 { | ||||||
|  | 		width = element.forcedMinimumWidth | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	element.entity.SetMinimumSize(width, height) | ||||||
|  | } | ||||||
| @ -2,10 +2,8 @@ package main | |||||||
| 
 | 
 | ||||||
| import "git.tebibyte.media/sashakoshka/tomo" | 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/layouts" |  | ||||||
| import "git.tebibyte.media/sashakoshka/tomo/elements" | import "git.tebibyte.media/sashakoshka/tomo/elements" | ||||||
| import "git.tebibyte.media/sashakoshka/tomo/elements/testing" | import "git.tebibyte.media/sashakoshka/tomo/elements/testing" | ||||||
| import "git.tebibyte.media/sashakoshka/tomo/elements/containers" |  | ||||||
| import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" | import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" | ||||||
| 
 | 
 | ||||||
| func main () { | func main () { | ||||||
| @ -16,18 +14,16 @@ func run () { | |||||||
| 	window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 300, 0)) | 	window, _ := tomo.NewWindow(tomo.Bounds(0, 0, 300, 0)) | ||||||
| 	window.SetTitle("List Sidebar") | 	window.SetTitle("List Sidebar") | ||||||
| 
 | 
 | ||||||
| 	container := containers.NewContainer(layouts.Horizontal { true, true }) | 	container := elements.NewHBox(true, true) | ||||||
| 	window.Adopt(container) | 	window.Adopt(container) | ||||||
| 
 | 
 | ||||||
| 	var currentPage tomo.Element | 	var currentPage tomo.Element | ||||||
| 	turnPage := func (newPage tomo.Element) { | 	turnPage := func (newPage tomo.Element) { | ||||||
| 		container.Warp (func () { | 		if currentPage != nil { | ||||||
| 			if currentPage != nil { | 			container.Disown(currentPage) | ||||||
| 				container.Disown(currentPage) | 		} | ||||||
| 			} | 		container.Adopt(newPage, true) | ||||||
| 			container.Adopt(newPage, true) | 		currentPage = newPage | ||||||
| 			currentPage = newPage |  | ||||||
| 		}) |  | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	intro := elements.NewLabel ( | 	intro := elements.NewLabel ( | ||||||
| @ -39,7 +35,7 @@ func run () { | |||||||
| 	}) | 	}) | ||||||
| 	mouse  := testing.NewMouse() | 	mouse  := testing.NewMouse() | ||||||
| 	input  := elements.NewTextBox("Write some text", "") | 	input  := elements.NewTextBox("Write some text", "") | ||||||
| 	form := containers.NewContainer(layouts.Vertical { true, false}) | 	form := elements.NewVBox(false, true) | ||||||
| 		form.Adopt(elements.NewLabel("I have:", false), false) | 		form.Adopt(elements.NewLabel("I have:", false), false) | ||||||
| 		form.Adopt(elements.NewSpacer(true), false) | 		form.Adopt(elements.NewSpacer(true), false) | ||||||
| 		form.Adopt(elements.NewCheckbox("Skin", true), false) | 		form.Adopt(elements.NewCheckbox("Skin", true), false) | ||||||
| @ -47,13 +43,21 @@ func run () { | |||||||
| 		form.Adopt(elements.NewCheckbox("Bone", false), false) | 		form.Adopt(elements.NewCheckbox("Bone", false), false) | ||||||
| 	art := testing.NewArtist() | 	art := testing.NewArtist() | ||||||
| 
 | 
 | ||||||
|  | 	makePage := func (name string, callback func ()) tomo.Selectable { | ||||||
|  | 		cell := elements.NewCell(elements.NewLabel(name, false)) | ||||||
|  | 		cell.OnSelectionChange (func () { | ||||||
|  | 			if cell.Selected() { callback() } | ||||||
|  | 		}) | ||||||
|  | 		return cell | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	list := elements.NewList ( | 	list := elements.NewList ( | ||||||
| 		elements.NewListEntry("button", func () { turnPage(button) }), | 		1, | ||||||
| 		elements.NewListEntry("mouse",  func () { turnPage(mouse) }), | 		makePage("button", func () { turnPage(button) }), | ||||||
| 		elements.NewListEntry("input",  func () { turnPage(input) }), | 		makePage("mouse",  func () { turnPage(mouse) }), | ||||||
| 		elements.NewListEntry("form",   func () { turnPage(form) }), | 		makePage("input",  func () { turnPage(input) }), | ||||||
| 		elements.NewListEntry("art",    func () { turnPage(art) })) | 		makePage("form",   func () { turnPage(form) }), | ||||||
| 	list.OnNoEntrySelected(func () { turnPage (intro) }) | 		makePage("art",    func () { turnPage(art) })) | ||||||
| 	list.Collapse(96, 0) | 	list.Collapse(96, 0) | ||||||
| 	 | 	 | ||||||
| 	container.Adopt(list, false) | 	container.Adopt(list, false) | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| package main | package main | ||||||
| 
 | 
 | ||||||
| // import "image" | import "image" | ||||||
| import "git.tebibyte.media/sashakoshka/tomo" | import "git.tebibyte.media/sashakoshka/tomo" | ||||||
| import "git.tebibyte.media/sashakoshka/tomo/elements" | import "git.tebibyte.media/sashakoshka/tomo/elements" | ||||||
| import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" | import _ "git.tebibyte.media/sashakoshka/tomo/backends/all" | ||||||
| @ -18,48 +18,49 @@ func run () { | |||||||
| 	textBox := elements.NewTextBox("", copypasta) | 	textBox := elements.NewTextBox("", copypasta) | ||||||
| 
 | 
 | ||||||
| 	disconnectedContainer := elements.NewHBox(false, true) | 	disconnectedContainer := elements.NewHBox(false, true) | ||||||
| 	// list := elements.NewList ( | 	list := elements.NewList ( | ||||||
| 		// elements.NewListEntry("This is list item 0", nil), | 		1, | ||||||
| 		// elements.NewListEntry("This is list item 1", nil), | 		elements.NewCell(elements.NewLabel("This is list item 0", false)), | ||||||
| 		// elements.NewListEntry("This is list item 2", nil), | 		elements.NewCell(elements.NewLabel("This is list item 1", false)), | ||||||
| 		// elements.NewListEntry("This is list item 3", nil), | 		elements.NewCell(elements.NewLabel("This is list item 2", false)), | ||||||
| 		// elements.NewListEntry("This is list item 4", nil), | 		elements.NewCell(elements.NewLabel("This is list item 3", false)), | ||||||
| 		// elements.NewListEntry("This is list item 5", nil), | 		elements.NewCell(elements.NewLabel("This is list item 4", false)), | ||||||
| 		// elements.NewListEntry("This is list item 6", nil), | 		elements.NewCell(elements.NewLabel("This is list item 5", false)), | ||||||
| 		// elements.NewListEntry("This is list item 7", nil), | 		elements.NewCell(elements.NewLabel("This is list item 6", false)), | ||||||
| 		// elements.NewListEntry("This is list item 8", nil), | 		elements.NewCell(elements.NewLabel("This is list item 7", false)), | ||||||
| 		// elements.NewListEntry("This is list item 9", nil), | 		elements.NewCell(elements.NewLabel("This is list item 8", false)), | ||||||
| 		// elements.NewListEntry("This is list item 10", nil), | 		elements.NewCell(elements.NewLabel("This is list item 9", false)), | ||||||
| 		// elements.NewListEntry("This is list item 11", nil), | 		elements.NewCell(elements.NewLabel("This is list item 10", false)), | ||||||
| 		// elements.NewListEntry("This is list item 12", nil), | 		elements.NewCell(elements.NewLabel("This is list item 11", false)), | ||||||
| 		// elements.NewListEntry("This is list item 13", nil), | 		elements.NewCell(elements.NewLabel("This is list item 12", false)), | ||||||
| 		// elements.NewListEntry("This is list item 14", nil), | 		elements.NewCell(elements.NewLabel("This is list item 13", false)), | ||||||
| 		// elements.NewListEntry("This is list item 15", nil), | 		elements.NewCell(elements.NewLabel("This is list item 14", false)), | ||||||
| 		// elements.NewListEntry("This is list item 16", nil), | 		elements.NewCell(elements.NewLabel("This is list item 15", false)), | ||||||
| 		// elements.NewListEntry("This is list item 17", nil), | 		elements.NewCell(elements.NewLabel("This is list item 16", false)), | ||||||
| 		// elements.NewListEntry("This is list item 18", nil), | 		elements.NewCell(elements.NewLabel("This is list item 17", false)), | ||||||
| 		// elements.NewListEntry("This is list item 19", nil), | 		elements.NewCell(elements.NewLabel("This is list item 18", false)), | ||||||
| 		// elements.NewListEntry("This is list item 20", nil)) | 		elements.NewCell(elements.NewLabel("This is list item 19", false)), | ||||||
| 	// list.Collapse(0, 32) | 		elements.NewCell(elements.NewLabel("This is list item 20", false))) | ||||||
| 	// scrollBar := elements.NewScrollBar(true) | 	list.Collapse(0, 32) | ||||||
| 	// list.OnScrollBoundsChange (func () { | 	scrollBar := elements.NewScrollBar(true) | ||||||
| 		// scrollBar.SetBounds ( | 	list.OnScrollBoundsChange (func () { | ||||||
| 			// list.ScrollContentBounds(), | 		scrollBar.SetBounds ( | ||||||
| 			// list.ScrollViewportBounds()) | 			list.ScrollContentBounds(), | ||||||
| 	// }) | 			list.ScrollViewportBounds()) | ||||||
| 	// scrollBar.OnScroll (func (viewport image.Point) { | 	}) | ||||||
| 		// list.ScrollTo(viewport) | 	scrollBar.OnScroll (func (viewport image.Point) { | ||||||
| 	// }) | 		list.ScrollTo(viewport) | ||||||
|  | 	}) | ||||||
| 	 | 	 | ||||||
| 	container.Adopt(elements.NewLabel("A ScrollContainer:", false), false) | 	container.Adopt(elements.NewLabel("A ScrollContainer:", false), false) | ||||||
| 	container.Adopt(elements.NewScroll(textBox, true, false), false) | 	container.Adopt(elements.NewScroll(textBox, true, false), false) | ||||||
| 	// disconnectedContainer.Adopt(list, false) | 	disconnectedContainer.Adopt(list, false) | ||||||
| 	disconnectedContainer.Adopt (elements.NewLabel ( | 	disconnectedContainer.Adopt (elements.NewLabel ( | ||||||
| 		"Notice how the scroll bar to the right can be used to " + | 		"Notice how the scroll bar to the right can be used to " + | ||||||
| 		"control the list, despite not even touching it. It is " + | 		"control the list, despite not even touching it. It is " + | ||||||
| 		"indeed a thing you can do. It is also terrible UI design so " + | 		"indeed a thing you can do. It is also terrible UI design so " + | ||||||
| 		"don't do it.", true), true) | 		"don't do it.", true), true) | ||||||
| 	// disconnectedContainer.Adopt(scrollBar, false) | 	disconnectedContainer.Adopt(scrollBar, false) | ||||||
| 	container.Adopt(disconnectedContainer, true) | 	container.Adopt(disconnectedContainer, true) | ||||||
| 	 | 	 | ||||||
| 	window.OnClose(tomo.Stop) | 	window.OnClose(tomo.Stop) | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user