Overhauled the theme system
Also added a toggle switch :)
This commit is contained in:
parent
9422ff6198
commit
92aeb48a1f
@ -114,9 +114,9 @@ func (element *Button) SetText (text string) {
|
|||||||
element.text = text
|
element.text = text
|
||||||
element.drawer.SetText([]rune(text))
|
element.drawer.SetText([]rune(text))
|
||||||
textBounds := element.drawer.LayoutBounds()
|
textBounds := element.drawer.LayoutBounds()
|
||||||
element.core.SetMinimumSize (
|
_, inset := theme.ButtonPattern(theme.PatternState { })
|
||||||
theme.Padding() * 2 + textBounds.Dx(),
|
minimumSize := inset.Inverse().Apply(textBounds).Inset(-theme.Padding())
|
||||||
theme.Padding() * 2 + textBounds.Dy())
|
element.core.SetMinimumSize(minimumSize.Dx(), minimumSize.Dy())
|
||||||
if element.core.HasImage () {
|
if element.core.HasImage () {
|
||||||
element.draw()
|
element.draw()
|
||||||
element.core.DamageAll()
|
element.core.DamageAll()
|
||||||
@ -126,24 +126,20 @@ func (element *Button) SetText (text string) {
|
|||||||
func (element *Button) draw () {
|
func (element *Button) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
pattern, inset := theme.ButtonPattern(theme.PatternState {
|
||||||
element.core,
|
Disabled: !element.Enabled(),
|
||||||
theme.ButtonPattern (
|
Selected: element.Selected(),
|
||||||
element.Enabled(),
|
Pressed: element.pressed,
|
||||||
element.Selected(),
|
})
|
||||||
element.pressed),
|
|
||||||
bounds)
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
|
|
||||||
innerBounds := bounds
|
innerBounds := inset.Apply(bounds)
|
||||||
innerBounds.Min.X += theme.Padding()
|
|
||||||
innerBounds.Min.Y += theme.Padding()
|
|
||||||
innerBounds.Max.X -= theme.Padding()
|
|
||||||
innerBounds.Max.Y -= theme.Padding()
|
|
||||||
|
|
||||||
textBounds := element.drawer.LayoutBounds()
|
textBounds := element.drawer.LayoutBounds()
|
||||||
offset := image.Point {
|
offset := image.Point {
|
||||||
X: theme.Padding() + (innerBounds.Dx() - textBounds.Dx()) / 2,
|
X: innerBounds.Min.X + (innerBounds.Dx() - textBounds.Dx()) / 2,
|
||||||
Y: theme.Padding() + (innerBounds.Dy() - textBounds.Dy()) / 2,
|
Y: innerBounds.Min.X + (innerBounds.Dy() - textBounds.Dy()) / 2,
|
||||||
}
|
}
|
||||||
|
|
||||||
// account for the fact that the bounding rectangle will be shifted over
|
// account for the fact that the bounding rectangle will be shifted over
|
||||||
@ -151,10 +147,8 @@ func (element *Button) draw () {
|
|||||||
offset.Y -= textBounds.Min.Y
|
offset.Y -= textBounds.Min.Y
|
||||||
offset.X -= textBounds.Min.X
|
offset.X -= textBounds.Min.X
|
||||||
|
|
||||||
if element.pressed {
|
foreground, _ := theme.ForegroundPattern (theme.PatternState {
|
||||||
offset = offset.Add(theme.SinkOffsetVector())
|
Disabled: !element.Enabled(),
|
||||||
}
|
})
|
||||||
|
|
||||||
foreground := theme.ForegroundPattern(element.Enabled())
|
|
||||||
element.drawer.Draw(element.core, foreground, offset)
|
element.drawer.Draw(element.core, foreground, offset)
|
||||||
}
|
}
|
||||||
|
@ -140,14 +140,15 @@ func (element *Checkbox) draw () {
|
|||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy())
|
boxBounds := image.Rect(0, 0, bounds.Dy(), bounds.Dy())
|
||||||
|
|
||||||
artist.FillRectangle ( element.core, theme.BackgroundPattern(), bounds)
|
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { })
|
||||||
artist.FillRectangle (
|
artist.FillRectangle ( element.core, backgroundPattern, bounds)
|
||||||
element.core,
|
|
||||||
theme.ButtonPattern (
|
pattern, inset := theme.ButtonPattern(theme.PatternState {
|
||||||
element.Enabled(),
|
Disabled: !element.Enabled(),
|
||||||
element.Selected(),
|
Selected: element.Selected(),
|
||||||
element.pressed),
|
Pressed: element.pressed,
|
||||||
boxBounds)
|
})
|
||||||
|
artist.FillRectangle(element.core, pattern, boxBounds)
|
||||||
|
|
||||||
textBounds := element.drawer.LayoutBounds()
|
textBounds := element.drawer.LayoutBounds()
|
||||||
offset := image.Point {
|
offset := image.Point {
|
||||||
@ -157,17 +158,13 @@ func (element *Checkbox) draw () {
|
|||||||
offset.Y -= textBounds.Min.Y
|
offset.Y -= textBounds.Min.Y
|
||||||
offset.X -= textBounds.Min.X
|
offset.X -= textBounds.Min.X
|
||||||
|
|
||||||
foreground := theme.ForegroundPattern(element.Enabled())
|
foreground, _ := theme.ForegroundPattern (theme.PatternState {
|
||||||
|
Disabled: !element.Enabled(),
|
||||||
|
})
|
||||||
element.drawer.Draw(element.core, foreground, offset)
|
element.drawer.Draw(element.core, foreground, offset)
|
||||||
|
|
||||||
if element.checked {
|
if element.checked {
|
||||||
checkBounds := boxBounds.Inset(4)
|
checkBounds := inset.Apply(boxBounds).Inset(2)
|
||||||
if element.pressed {
|
artist.FillRectangle(element.core, foreground, checkBounds)
|
||||||
checkBounds = checkBounds.Add(theme.SinkOffsetVector())
|
|
||||||
}
|
|
||||||
artist.FillRectangle (
|
|
||||||
element.core,
|
|
||||||
theme.ForegroundPattern(element.Enabled()),
|
|
||||||
checkBounds)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -473,10 +473,8 @@ func (element *Container) recalculate () {
|
|||||||
func (element *Container) draw () {
|
func (element *Container) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
pattern, _ := theme.BackgroundPattern(theme.PatternState { })
|
||||||
element.core,
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
theme.BackgroundPattern(),
|
|
||||||
bounds)
|
|
||||||
|
|
||||||
for _, entry := range element.children {
|
for _, entry := range element.children {
|
||||||
artist.Paste(element.core, entry, entry.Position)
|
artist.Paste(element.core, entry, entry.Position)
|
||||||
|
@ -108,14 +108,12 @@ func (element *Label) updateMinimumSize () {
|
|||||||
func (element *Label) draw () {
|
func (element *Label) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
pattern, _ := theme.BackgroundPattern(theme.PatternState { })
|
||||||
element.core,
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
theme.BackgroundPattern(),
|
|
||||||
bounds)
|
|
||||||
|
|
||||||
textBounds := element.drawer.LayoutBounds()
|
textBounds := element.drawer.LayoutBounds()
|
||||||
|
|
||||||
foreground := theme.ForegroundPattern(true)
|
foreground, _ := theme.ForegroundPattern (theme.PatternState { })
|
||||||
element.drawer.Draw (element.core, foreground, image.Point {
|
element.drawer.Draw (element.core, foreground, image.Point {
|
||||||
X: 0 - textBounds.Min.X,
|
X: 0 - textBounds.Min.X,
|
||||||
Y: 0 - textBounds.Min.Y,
|
Y: 0 - textBounds.Min.Y,
|
||||||
|
@ -164,7 +164,8 @@ func (element *List) ScrollAxes () (horizontal, vertical bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (element *List) scrollViewportHeight () (height int) {
|
func (element *List) scrollViewportHeight () (height int) {
|
||||||
return element.Bounds().Dy() - theme.Padding()
|
_, inset := theme.ListPattern(theme.PatternState { })
|
||||||
|
return element.Bounds().Dy() - theme.Padding() - inset[0] - inset[2]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *List) maxScrollHeight () (height int) {
|
func (element *List) maxScrollHeight () (height int) {
|
||||||
@ -328,7 +329,8 @@ func (element *List) changeSelectionBy (delta int) (updated bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
|
func (element *List) resizeEntryToFit (entry ListEntry) (resized ListEntry) {
|
||||||
entry.Collapse(element.forcedMinimumWidth)
|
_, inset := theme.ListPattern(theme.PatternState { })
|
||||||
|
entry.Collapse(element.forcedMinimumWidth - inset[3] - inset[1])
|
||||||
return entry
|
return entry
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -355,17 +357,24 @@ func (element *List) updateMinimumSize () {
|
|||||||
minimumHeight = element.contentHeight + theme.Padding()
|
minimumHeight = element.contentHeight + theme.Padding()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_, inset := theme.ListPattern(theme.PatternState { })
|
||||||
|
minimumWidth += inset[1] + inset[3]
|
||||||
|
minimumHeight += inset[0] + inset[2]
|
||||||
|
|
||||||
element.core.SetMinimumSize(minimumWidth, minimumHeight)
|
element.core.SetMinimumSize(minimumWidth, minimumHeight)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *List) draw () {
|
func (element *List) draw () {
|
||||||
bounds := element.Bounds()
|
bounds := element.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
pattern, inset := theme.ListPattern(theme.PatternState {
|
||||||
element,
|
Disabled: !element.Enabled(),
|
||||||
theme.ListPattern(element.Selected()),
|
Selected: element.Selected(),
|
||||||
bounds)
|
})
|
||||||
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
|
|
||||||
|
bounds = inset.Apply(bounds)
|
||||||
dot := image.Point {
|
dot := image.Point {
|
||||||
bounds.Min.X,
|
bounds.Min.X,
|
||||||
bounds.Min.Y - element.scroll + theme.Padding() / 2,
|
bounds.Min.Y - element.scroll + theme.Padding() / 2,
|
||||||
@ -377,9 +386,12 @@ func (element *List) draw () {
|
|||||||
if entryPosition.Y > bounds.Max.Y { break }
|
if entryPosition.Y > bounds.Max.Y { break }
|
||||||
|
|
||||||
if element.selectedEntry == index {
|
if element.selectedEntry == index {
|
||||||
|
pattern, _ := theme.ItemPattern(theme.PatternState {
|
||||||
|
On: true,
|
||||||
|
})
|
||||||
artist.FillRectangle (
|
artist.FillRectangle (
|
||||||
element,
|
element,
|
||||||
theme.ListEntryPattern(true),
|
pattern,
|
||||||
entry.Bounds().Add(entryPosition))
|
entry.Bounds().Add(entryPosition))
|
||||||
}
|
}
|
||||||
entry.Draw (
|
entry.Draw (
|
||||||
|
@ -44,8 +44,11 @@ func (entry *ListEntry) updateBounds () {
|
|||||||
entry.drawer.LayoutBounds().Dx() + padding * 2
|
entry.drawer.LayoutBounds().Dx() + padding * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_, inset := theme.ItemPattern(theme.PatternState { })
|
||||||
|
entry.bounds.Max.Y += inset[0] + inset[2]
|
||||||
|
|
||||||
entry.textPoint =
|
entry.textPoint =
|
||||||
image.Pt(padding, padding / 2).
|
image.Pt(inset[3] + padding, inset[0] + padding / 2).
|
||||||
Sub(entry.drawer.LayoutBounds().Min)
|
Sub(entry.drawer.LayoutBounds().Min)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,9 +59,10 @@ func (entry *ListEntry) Draw (
|
|||||||
) (
|
) (
|
||||||
updatedRegion image.Rectangle,
|
updatedRegion image.Rectangle,
|
||||||
) {
|
) {
|
||||||
|
foreground, _ := theme.ForegroundPattern (theme.PatternState { })
|
||||||
return entry.drawer.Draw (
|
return entry.drawer.Draw (
|
||||||
destination,
|
destination,
|
||||||
theme.ForegroundPattern(true),
|
foreground,
|
||||||
offset.Add(entry.textPoint))
|
offset.Add(entry.textPoint))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,16 +41,13 @@ func (element *ProgressBar) SetProgress (progress float64) {
|
|||||||
func (element *ProgressBar) draw () {
|
func (element *ProgressBar) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
pattern, inset := theme.SunkenPattern(theme.PatternState { })
|
||||||
element.core,
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
theme.SunkenPattern(false),
|
bounds = inset.Apply(bounds)
|
||||||
bounds)
|
|
||||||
meterBounds := image.Rect (
|
meterBounds := image.Rect (
|
||||||
bounds.Min.X + 2, bounds.Min.Y + 2,
|
bounds.Min.X, bounds.Min.Y,
|
||||||
bounds.Min.X - 1 + int(float64(bounds.Dx()) * element.progress),
|
bounds.Min.X + int(float64(bounds.Dx()) * element.progress),
|
||||||
bounds.Dy() - 1)
|
bounds.Max.Y)
|
||||||
artist.FillRectangle (
|
accent, _ := theme.AccentPattern(theme.PatternState { })
|
||||||
element.core,
|
artist.FillRectangle(element.core, accent, meterBounds)
|
||||||
theme.AccentPattern(),
|
|
||||||
meterBounds)
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ type ScrollContainer struct {
|
|||||||
dragging bool
|
dragging bool
|
||||||
dragOffset int
|
dragOffset int
|
||||||
gutter image.Rectangle
|
gutter image.Rectangle
|
||||||
|
track image.Rectangle
|
||||||
bar image.Rectangle
|
bar image.Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,6 +32,7 @@ type ScrollContainer struct {
|
|||||||
dragging bool
|
dragging bool
|
||||||
dragOffset int
|
dragOffset int
|
||||||
gutter image.Rectangle
|
gutter image.Rectangle
|
||||||
|
track image.Rectangle
|
||||||
bar image.Rectangle
|
bar image.Rectangle
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -271,10 +273,12 @@ func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) recalculate () {
|
func (element *ScrollContainer) recalculate () {
|
||||||
|
_, gutterInset := theme.GutterPattern(theme.PatternState { })
|
||||||
|
|
||||||
horizontal := &element.horizontal
|
horizontal := &element.horizontal
|
||||||
vertical := &element.vertical
|
vertical := &element.vertical
|
||||||
bounds := element.Bounds()
|
bounds := element.Bounds()
|
||||||
thickness := theme.ScrollBarWidth()
|
thickness := theme.HandleWidth() + gutterInset[3] + gutterInset[1]
|
||||||
|
|
||||||
// calculate child size
|
// calculate child size
|
||||||
element.childWidth = bounds.Dx()
|
element.childWidth = bounds.Dx()
|
||||||
@ -295,6 +299,7 @@ func (element *ScrollContainer) recalculate () {
|
|||||||
horizontal.gutter.Max.X -= thickness
|
horizontal.gutter.Max.X -= thickness
|
||||||
}
|
}
|
||||||
element.childHeight -= thickness
|
element.childHeight -= thickness
|
||||||
|
horizontal.track = gutterInset.Apply(horizontal.gutter)
|
||||||
}
|
}
|
||||||
if vertical.exists {
|
if vertical.exists {
|
||||||
vertical.gutter.Min.X = bounds.Max.X - thickness
|
vertical.gutter.Min.X = bounds.Max.X - thickness
|
||||||
@ -304,49 +309,51 @@ func (element *ScrollContainer) recalculate () {
|
|||||||
vertical.gutter.Max.Y -= thickness
|
vertical.gutter.Max.Y -= thickness
|
||||||
}
|
}
|
||||||
element.childWidth -= thickness
|
element.childWidth -= thickness
|
||||||
|
vertical.track = gutterInset.Apply(vertical.gutter)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if enabled, calculate the positions of the bars
|
// if enabled, calculate the positions of the bars
|
||||||
contentBounds := element.child.ScrollContentBounds()
|
contentBounds := element.child.ScrollContentBounds()
|
||||||
viewportBounds := element.child.ScrollViewportBounds()
|
viewportBounds := element.child.ScrollViewportBounds()
|
||||||
if horizontal.exists && horizontal.enabled {
|
if horizontal.exists && horizontal.enabled {
|
||||||
horizontal.bar.Min.Y = horizontal.gutter.Min.Y
|
horizontal.bar.Min.Y = horizontal.track.Min.Y
|
||||||
horizontal.bar.Max.Y = horizontal.gutter.Max.Y
|
horizontal.bar.Max.Y = horizontal.track.Max.Y
|
||||||
|
|
||||||
scale := float64(horizontal.gutter.Dx()) /
|
scale := float64(horizontal.track.Dx()) /
|
||||||
float64(contentBounds.Dx())
|
float64(contentBounds.Dx())
|
||||||
horizontal.bar.Min.X = int(float64(viewportBounds.Min.X) * scale)
|
horizontal.bar.Min.X = int(float64(viewportBounds.Min.X) * scale)
|
||||||
horizontal.bar.Max.X = int(float64(viewportBounds.Max.X) * scale)
|
horizontal.bar.Max.X = int(float64(viewportBounds.Max.X) * scale)
|
||||||
|
|
||||||
horizontal.bar.Min.X += horizontal.gutter.Min.X
|
horizontal.bar.Min.X += horizontal.track.Min.X
|
||||||
horizontal.bar.Max.X += horizontal.gutter.Min.X
|
horizontal.bar.Max.X += horizontal.track.Min.X
|
||||||
}
|
}
|
||||||
if vertical.exists && vertical.enabled {
|
if vertical.exists && vertical.enabled {
|
||||||
vertical.bar.Min.X = vertical.gutter.Min.X
|
vertical.bar.Min.X = vertical.track.Min.X
|
||||||
vertical.bar.Max.X = vertical.gutter.Max.X
|
vertical.bar.Max.X = vertical.track.Max.X
|
||||||
|
|
||||||
scale := float64(vertical.gutter.Dy()) /
|
scale := float64(vertical.track.Dy()) /
|
||||||
float64(contentBounds.Dy())
|
float64(contentBounds.Dy())
|
||||||
vertical.bar.Min.Y = int(float64(viewportBounds.Min.Y) * scale)
|
vertical.bar.Min.Y = int(float64(viewportBounds.Min.Y) * scale)
|
||||||
vertical.bar.Max.Y = int(float64(viewportBounds.Max.Y) * scale)
|
vertical.bar.Max.Y = int(float64(viewportBounds.Max.Y) * scale)
|
||||||
|
|
||||||
vertical.bar.Min.Y += vertical.gutter.Min.Y
|
vertical.bar.Min.Y += vertical.track.Min.Y
|
||||||
vertical.bar.Max.Y += vertical.gutter.Min.Y
|
vertical.bar.Max.Y += vertical.track.Min.Y
|
||||||
}
|
}
|
||||||
|
|
||||||
// if the scroll bars are out of bounds, don't display them.
|
// if the scroll bars are out of bounds, don't display them.
|
||||||
if horizontal.bar.Dx() >= horizontal.gutter.Dx() {
|
if horizontal.bar.Dx() >= horizontal.track.Dx() {
|
||||||
horizontal.bar = image.Rectangle { }
|
horizontal.bar = image.Rectangle { }
|
||||||
}
|
}
|
||||||
if vertical.bar.Dy() >= vertical.gutter.Dy() {
|
if vertical.bar.Dy() >= vertical.track.Dy() {
|
||||||
vertical.bar = image.Rectangle { }
|
vertical.bar = image.Rectangle { }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) draw () {
|
func (element *ScrollContainer) draw () {
|
||||||
artist.Paste(element.core, element.child, image.Point { })
|
artist.Paste(element.core, element.child, image.Point { })
|
||||||
|
deadPattern, _ := theme.DeadPattern(theme.PatternState { })
|
||||||
artist.FillRectangle (
|
artist.FillRectangle (
|
||||||
element, theme.DeadPattern(),
|
element, deadPattern,
|
||||||
image.Rect (
|
image.Rect (
|
||||||
element.vertical.gutter.Min.X,
|
element.vertical.gutter.Min.X,
|
||||||
element.horizontal.gutter.Min.Y,
|
element.horizontal.gutter.Min.Y,
|
||||||
@ -357,35 +364,35 @@ func (element *ScrollContainer) draw () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) drawHorizontalBar () {
|
func (element *ScrollContainer) drawHorizontalBar () {
|
||||||
artist.FillRectangle (
|
gutterPattern, _ := theme.GutterPattern (theme.PatternState {
|
||||||
element,
|
Disabled: !element.horizontal.enabled,
|
||||||
theme.ScrollGutterPattern(true, element.horizontal.enabled),
|
})
|
||||||
element.horizontal.gutter)
|
artist.FillRectangle(element, gutterPattern, element.horizontal.gutter)
|
||||||
artist.FillRectangle (
|
|
||||||
element,
|
handlePattern, _ := theme.HandlePattern (theme.PatternState {
|
||||||
theme.ScrollBarPattern (
|
Disabled: !element.horizontal.enabled,
|
||||||
true, element.horizontal.enabled,
|
Pressed: element.horizontal.dragging,
|
||||||
element.horizontal.dragging),
|
})
|
||||||
element.horizontal.bar)
|
artist.FillRectangle(element, handlePattern, element.horizontal.bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) drawVerticalBar () {
|
func (element *ScrollContainer) drawVerticalBar () {
|
||||||
artist.FillRectangle (
|
gutterPattern, _ := theme.GutterPattern (theme.PatternState {
|
||||||
element,
|
Disabled: !element.vertical.enabled,
|
||||||
theme.ScrollGutterPattern(false, element.vertical.enabled),
|
})
|
||||||
element.vertical.gutter)
|
artist.FillRectangle(element, gutterPattern, element.vertical.gutter)
|
||||||
artist.FillRectangle (
|
|
||||||
element,
|
handlePattern, _ := theme.HandlePattern (theme.PatternState {
|
||||||
theme.ScrollBarPattern (
|
Disabled: !element.vertical.enabled,
|
||||||
false, element.vertical.enabled,
|
Pressed: element.vertical.dragging,
|
||||||
element.vertical.dragging),
|
})
|
||||||
element.vertical.bar)
|
artist.FillRectangle(element, handlePattern, element.vertical.bar)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
|
func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
|
||||||
scrollX :=
|
scrollX :=
|
||||||
float64(element.child.ScrollContentBounds().Dx()) /
|
float64(element.child.ScrollContentBounds().Dx()) /
|
||||||
float64(element.horizontal.gutter.Dx()) *
|
float64(element.horizontal.track.Dx()) *
|
||||||
float64(mousePosition.X - element.horizontal.dragOffset)
|
float64(mousePosition.X - element.horizontal.dragOffset)
|
||||||
scrollY := element.child.ScrollViewportBounds().Min.Y
|
scrollY := element.child.ScrollViewportBounds().Min.Y
|
||||||
element.child.ScrollTo(image.Pt(int(scrollX), scrollY))
|
element.child.ScrollTo(image.Pt(int(scrollX), scrollY))
|
||||||
@ -394,15 +401,18 @@ func (element *ScrollContainer) dragHorizontalBar (mousePosition image.Point) {
|
|||||||
func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
|
func (element *ScrollContainer) dragVerticalBar (mousePosition image.Point) {
|
||||||
scrollY :=
|
scrollY :=
|
||||||
float64(element.child.ScrollContentBounds().Dy()) /
|
float64(element.child.ScrollContentBounds().Dy()) /
|
||||||
float64(element.vertical.gutter.Dy()) *
|
float64(element.vertical.track.Dy()) *
|
||||||
float64(mousePosition.Y - element.vertical.dragOffset)
|
float64(mousePosition.Y - element.vertical.dragOffset)
|
||||||
scrollX := element.child.ScrollViewportBounds().Min.X
|
scrollX := element.child.ScrollViewportBounds().Min.X
|
||||||
element.child.ScrollTo(image.Pt(scrollX, int(scrollY)))
|
element.child.ScrollTo(image.Pt(scrollX, int(scrollY)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *ScrollContainer) updateMinimumSize () {
|
func (element *ScrollContainer) updateMinimumSize () {
|
||||||
width := theme.ScrollBarWidth()
|
_, gutterInset := theme.GutterPattern(theme.PatternState { })
|
||||||
height := theme.ScrollBarWidth()
|
thickness := theme.HandleWidth() + gutterInset[3] + gutterInset[1]
|
||||||
|
|
||||||
|
width := thickness
|
||||||
|
height := thickness
|
||||||
if element.child != nil {
|
if element.child != nil {
|
||||||
childWidth, childHeight := element.child.MinimumSize()
|
childWidth, childHeight := element.child.MinimumSize()
|
||||||
width += childWidth
|
width += childWidth
|
||||||
|
@ -42,14 +42,14 @@ func (element *Spacer) draw () {
|
|||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
if element.line {
|
if element.line {
|
||||||
artist.FillRectangle (
|
pattern, _ := theme.ForegroundPattern(theme.PatternState {
|
||||||
element.core,
|
Disabled: true,
|
||||||
theme.ForegroundPattern(false),
|
})
|
||||||
bounds)
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
} else {
|
} else {
|
||||||
artist.FillRectangle (
|
pattern, _ := theme.BackgroundPattern(theme.PatternState {
|
||||||
element.core,
|
Disabled: true,
|
||||||
theme.BackgroundPattern(),
|
})
|
||||||
bounds)
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1 +1,193 @@
|
|||||||
package basic
|
package basic
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/theme"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
|
||||||
|
|
||||||
|
// Switch is a toggle-able on/off switch with an optional label. It is
|
||||||
|
// functionally identical to Checkbox, but plays a different semantic role.
|
||||||
|
type Switch struct {
|
||||||
|
*core.Core
|
||||||
|
*core.SelectableCore
|
||||||
|
core core.CoreControl
|
||||||
|
selectableControl core.SelectableCoreControl
|
||||||
|
drawer artist.TextDrawer
|
||||||
|
|
||||||
|
pressed bool
|
||||||
|
checked bool
|
||||||
|
text string
|
||||||
|
|
||||||
|
onToggle func ()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.SelectableCore,
|
||||||
|
element.selectableControl = core.NewSelectableCore (func () {
|
||||||
|
if element.core.HasImage () {
|
||||||
|
element.draw()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
element.drawer.SetFace(theme.FontFaceRegular())
|
||||||
|
element.drawer.SetText([]rune(text))
|
||||||
|
element.calculateMinimumSize()
|
||||||
|
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.Select()
|
||||||
|
element.pressed = true
|
||||||
|
if element.core.HasImage() {
|
||||||
|
element.draw()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Switch) HandleMouseUp (x, y int, button tomo.Button) {
|
||||||
|
if button != tomo.ButtonLeft || !element.pressed { return }
|
||||||
|
|
||||||
|
element.pressed = false
|
||||||
|
within := image.Point { x, y }.
|
||||||
|
In(element.Bounds())
|
||||||
|
if within {
|
||||||
|
element.checked = !element.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
if element.core.HasImage() {
|
||||||
|
element.draw()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
if within && element.onToggle != nil {
|
||||||
|
element.onToggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Switch) HandleMouseMove (x, y int) { }
|
||||||
|
func (element *Switch) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
|
||||||
|
|
||||||
|
func (element *Switch) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
|
||||||
|
if key == tomo.KeyEnter {
|
||||||
|
element.pressed = true
|
||||||
|
if element.core.HasImage() {
|
||||||
|
element.draw()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Switch) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
|
||||||
|
if key == tomo.KeyEnter && element.pressed {
|
||||||
|
element.pressed = false
|
||||||
|
element.checked = !element.checked
|
||||||
|
if element.core.HasImage() {
|
||||||
|
element.draw()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
if element.onToggle != nil {
|
||||||
|
element.onToggle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnToggle sets the function to be called when the switch is flipped.
|
||||||
|
func (element *Switch) OnToggle (callback func ()) {
|
||||||
|
element.onToggle = callback
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value reports whether or not the switch is currently on.
|
||||||
|
func (element *Switch) Value () (on bool) {
|
||||||
|
return element.checked
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEnabled sets whether this switch can be flipped or not.
|
||||||
|
func (element *Switch) SetEnabled (enabled bool) {
|
||||||
|
element.selectableControl.SetEnabled(enabled)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetText sets the checkbox's label text.
|
||||||
|
func (element *Switch) SetText (text string) {
|
||||||
|
if element.text == text { return }
|
||||||
|
|
||||||
|
element.text = text
|
||||||
|
element.drawer.SetText([]rune(text))
|
||||||
|
element.calculateMinimumSize()
|
||||||
|
|
||||||
|
if element.core.HasImage () {
|
||||||
|
element.draw()
|
||||||
|
element.core.DamageAll()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (element *Switch) calculateMinimumSize () {
|
||||||
|
textBounds := element.drawer.LayoutBounds()
|
||||||
|
lineHeight := element.drawer.LineHeight().Round()
|
||||||
|
|
||||||
|
if element.text == "" {
|
||||||
|
element.core.SetMinimumSize(lineHeight * 2, lineHeight)
|
||||||
|
} else {
|
||||||
|
element.core.SetMinimumSize (
|
||||||
|
lineHeight * 2 + theme.Padding() + textBounds.Dx(),
|
||||||
|
lineHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
backgroundPattern, _ := theme.BackgroundPattern(theme.PatternState { })
|
||||||
|
artist.FillRectangle ( element.core, backgroundPattern, bounds)
|
||||||
|
|
||||||
|
if element.checked {
|
||||||
|
handleBounds.Min.X += bounds.Dy()
|
||||||
|
handleBounds.Max.X += bounds.Dy()
|
||||||
|
if element.pressed {
|
||||||
|
handleBounds.Min.X -= 2
|
||||||
|
handleBounds.Max.X -= 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if element.pressed {
|
||||||
|
handleBounds.Min.X += 2
|
||||||
|
handleBounds.Max.X += 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gutterPattern, _ := theme.GutterPattern(theme.PatternState {
|
||||||
|
Disabled: !element.Enabled(),
|
||||||
|
Selected: element.Selected(),
|
||||||
|
Pressed: element.pressed,
|
||||||
|
})
|
||||||
|
artist.FillRectangle(element.core, gutterPattern, gutterBounds)
|
||||||
|
|
||||||
|
handlePattern, _ := theme.HandlePattern(theme.PatternState {
|
||||||
|
Disabled: !element.Enabled(),
|
||||||
|
Selected: element.Selected(),
|
||||||
|
Pressed: element.pressed,
|
||||||
|
})
|
||||||
|
artist.FillRectangle(element.core, handlePattern, handleBounds)
|
||||||
|
|
||||||
|
textBounds := element.drawer.LayoutBounds()
|
||||||
|
offset := image.Point {
|
||||||
|
X: bounds.Dy() * 2 + theme.Padding(),
|
||||||
|
}
|
||||||
|
|
||||||
|
offset.Y -= textBounds.Min.Y
|
||||||
|
offset.X -= textBounds.Min.X
|
||||||
|
|
||||||
|
foreground, _ := theme.ForegroundPattern (theme.PatternState {
|
||||||
|
Disabled: !element.Enabled(),
|
||||||
|
})
|
||||||
|
element.drawer.Draw(element.core, foreground, offset)
|
||||||
|
}
|
||||||
|
@ -237,11 +237,12 @@ func (element *TextBox) OnScrollBoundsChange (callback func ()) {
|
|||||||
|
|
||||||
func (element *TextBox) updateMinimumSize () {
|
func (element *TextBox) updateMinimumSize () {
|
||||||
textBounds := element.placeholderDrawer.LayoutBounds()
|
textBounds := element.placeholderDrawer.LayoutBounds()
|
||||||
|
_, inset := theme.InputPattern(theme.PatternState { })
|
||||||
element.core.SetMinimumSize (
|
element.core.SetMinimumSize (
|
||||||
textBounds.Dx() +
|
textBounds.Dx() +
|
||||||
theme.Padding() * 2,
|
theme.Padding() * 2 + inset[3] + inset[1],
|
||||||
element.placeholderDrawer.LineHeight().Round() +
|
element.placeholderDrawer.LineHeight().Round() +
|
||||||
theme.Padding() * 2)
|
theme.Padding() * 2 + inset[0] + inset[2])
|
||||||
}
|
}
|
||||||
|
|
||||||
func (element *TextBox) runOnChange () {
|
func (element *TextBox) runOnChange () {
|
||||||
@ -270,21 +271,23 @@ func (element *TextBox) scrollToCursor () {
|
|||||||
func (element *TextBox) draw () {
|
func (element *TextBox) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
// FIXME: take index into account
|
||||||
element.core,
|
pattern, inset := theme.InputPattern(theme.PatternState {
|
||||||
theme.InputPattern (
|
Disabled: !element.Enabled(),
|
||||||
element.Enabled(),
|
Selected: element.Selected(),
|
||||||
element.Selected()),
|
})
|
||||||
bounds)
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
|
|
||||||
if len(element.text) == 0 && !element.Selected() {
|
if len(element.text) == 0 && !element.Selected() {
|
||||||
// draw placeholder
|
// draw placeholder
|
||||||
textBounds := element.placeholderDrawer.LayoutBounds()
|
textBounds := element.placeholderDrawer.LayoutBounds()
|
||||||
offset := image.Point {
|
offset := image.Point {
|
||||||
X: theme.Padding(),
|
X: theme.Padding() + inset[3],
|
||||||
Y: theme.Padding(),
|
Y: theme.Padding() + inset[0],
|
||||||
}
|
}
|
||||||
foreground := theme.ForegroundPattern(false)
|
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||||
|
Disabled: true,
|
||||||
|
})
|
||||||
element.placeholderDrawer.Draw (
|
element.placeholderDrawer.Draw (
|
||||||
element.core,
|
element.core,
|
||||||
foreground,
|
foreground,
|
||||||
@ -293,10 +296,12 @@ func (element *TextBox) draw () {
|
|||||||
// draw input value
|
// draw input value
|
||||||
textBounds := element.valueDrawer.LayoutBounds()
|
textBounds := element.valueDrawer.LayoutBounds()
|
||||||
offset := image.Point {
|
offset := image.Point {
|
||||||
X: theme.Padding() - element.scroll,
|
X: theme.Padding() + inset[3] - element.scroll,
|
||||||
Y: theme.Padding(),
|
Y: theme.Padding() + inset[0],
|
||||||
}
|
}
|
||||||
foreground := theme.ForegroundPattern(element.Enabled())
|
foreground, _ := theme.ForegroundPattern(theme.PatternState {
|
||||||
|
Disabled: !element.Enabled(),
|
||||||
|
})
|
||||||
element.valueDrawer.Draw (
|
element.valueDrawer.Draw (
|
||||||
element.core,
|
element.core,
|
||||||
foreground,
|
foreground,
|
||||||
@ -306,9 +311,10 @@ func (element *TextBox) draw () {
|
|||||||
// cursor
|
// cursor
|
||||||
cursorPosition := element.valueDrawer.PositionOf (
|
cursorPosition := element.valueDrawer.PositionOf (
|
||||||
element.cursor)
|
element.cursor)
|
||||||
|
foreground, _ := theme.ForegroundPattern(theme.PatternState { })
|
||||||
artist.Line (
|
artist.Line (
|
||||||
element.core,
|
element.core,
|
||||||
theme.ForegroundPattern(true), 1,
|
foreground, 1,
|
||||||
cursorPosition.Add(offset),
|
cursorPosition.Add(offset),
|
||||||
image.Pt (
|
image.Pt (
|
||||||
cursorPosition.X,
|
cursorPosition.X,
|
||||||
|
@ -41,14 +41,17 @@ func (element *AnalogClock) SetTime (newTime time.Time) {
|
|||||||
func (element *AnalogClock) draw () {
|
func (element *AnalogClock) draw () {
|
||||||
bounds := element.core.Bounds()
|
bounds := element.core.Bounds()
|
||||||
|
|
||||||
artist.FillRectangle (
|
pattern, inset := theme.SunkenPattern(theme.PatternState { })
|
||||||
element,
|
artist.FillRectangle(element, pattern, bounds)
|
||||||
theme.SunkenPattern(false),
|
|
||||||
bounds)
|
bounds = inset.Apply(bounds)
|
||||||
|
|
||||||
|
foreground, _ := theme.ForegroundPattern(theme.PatternState { })
|
||||||
|
accent, _ := theme.AccentPattern(theme.PatternState { })
|
||||||
|
|
||||||
for hour := 0; hour < 12; hour ++ {
|
for hour := 0; hour < 12; hour ++ {
|
||||||
element.radialLine (
|
element.radialLine (
|
||||||
theme.ForegroundPattern(true),
|
foreground,
|
||||||
0.8, 0.9, float64(hour) / 6 * math.Pi)
|
0.8, 0.9, float64(hour) / 6 * math.Pi)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,15 +59,9 @@ func (element *AnalogClock) draw () {
|
|||||||
minute := float64(element.time.Minute()) + second / 60
|
minute := float64(element.time.Minute()) + second / 60
|
||||||
hour := float64(element.time.Hour()) + minute / 60
|
hour := float64(element.time.Hour()) + minute / 60
|
||||||
|
|
||||||
element.radialLine (
|
element.radialLine(foreground, 0, 0.5, (hour - 3) / 6 * math.Pi)
|
||||||
theme.ForegroundPattern(true),
|
element.radialLine(foreground, 0, 0.7, (minute - 15) / 30 * math.Pi)
|
||||||
0, 0.5, (hour - 3) / 6 * math.Pi)
|
element.radialLine(accent, 0, 0.7, (second - 15) / 30 * math.Pi)
|
||||||
element.radialLine (
|
|
||||||
theme.ForegroundPattern(true),
|
|
||||||
0, 0.7, (minute - 15) / 30 * math.Pi)
|
|
||||||
element.radialLine (
|
|
||||||
theme.AccentPattern(),
|
|
||||||
0, 0.7, (second - 15) / 30 * math.Pi)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FlexibleHeightFor constrains the clock's minimum size to a 1:1 aspect ratio.
|
// FlexibleHeightFor constrains the clock's minimum size to a 1:1 aspect ratio.
|
||||||
|
@ -29,10 +29,8 @@ func NewMouse () (element *Mouse) {
|
|||||||
func (element *Mouse) Resize (width, height int) {
|
func (element *Mouse) Resize (width, height int) {
|
||||||
element.core.AllocateCanvas(width, height)
|
element.core.AllocateCanvas(width, height)
|
||||||
bounds := element.Bounds()
|
bounds := element.Bounds()
|
||||||
artist.FillRectangle (
|
pattern, _ := theme.AccentPattern(theme.PatternState { })
|
||||||
element.core,
|
artist.FillRectangle(element.core, pattern, bounds)
|
||||||
theme.AccentPattern(),
|
|
||||||
bounds)
|
|
||||||
artist.StrokeRectangle (
|
artist.StrokeRectangle (
|
||||||
element.core,
|
element.core,
|
||||||
artist.NewUniform(color.Black), 1,
|
artist.NewUniform(color.Black), 1,
|
||||||
|
25
examples/switch/main.go
Normal file
25
examples/switch/main.go
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/layouts"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
|
||||||
|
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
|
||||||
|
|
||||||
|
func main () {
|
||||||
|
tomo.Run(run)
|
||||||
|
}
|
||||||
|
|
||||||
|
func run () {
|
||||||
|
window, _ := tomo.NewWindow(2, 2)
|
||||||
|
window.SetTitle("Switches")
|
||||||
|
|
||||||
|
container := basic.NewContainer(layouts.Vertical { true, true })
|
||||||
|
window.Adopt(container)
|
||||||
|
|
||||||
|
container.Adopt(basic.NewSwitch("hahahah", false), false)
|
||||||
|
container.Adopt(basic.NewSwitch("hehehehheheh", false), false)
|
||||||
|
container.Adopt(basic.NewSwitch("you can flick da swicth", false), false)
|
||||||
|
|
||||||
|
window.OnClose(tomo.Stop)
|
||||||
|
window.Show()
|
||||||
|
}
|
@ -46,23 +46,3 @@ var pressedSelectedButtonPattern = artist.NewMultiBordered (
|
|||||||
var disabledButtonPattern = artist.NewMultiBordered (
|
var disabledButtonPattern = artist.NewMultiBordered (
|
||||||
artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
|
artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
|
||||||
artist.Stroke { Pattern: backgroundPattern })
|
artist.Stroke { Pattern: backgroundPattern })
|
||||||
|
|
||||||
func ButtonPattern (enabled, selected, pressed bool) (artist.Pattern) {
|
|
||||||
if enabled {
|
|
||||||
if pressed {
|
|
||||||
if selected {
|
|
||||||
return pressedSelectedButtonPattern
|
|
||||||
} else {
|
|
||||||
return pressedButtonPattern
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if selected {
|
|
||||||
return selectedButtonPattern
|
|
||||||
} else {
|
|
||||||
return buttonPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return disabledButtonPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -19,15 +19,3 @@ var selectedInputPattern = artist.NewMultiBordered (
|
|||||||
var disabledInputPattern = artist.NewMultiBordered (
|
var disabledInputPattern = artist.NewMultiBordered (
|
||||||
artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
|
artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
|
||||||
artist.Stroke { Pattern: backgroundPattern })
|
artist.Stroke { Pattern: backgroundPattern })
|
||||||
|
|
||||||
func InputPattern (enabled, selected bool) (artist.Pattern) {
|
|
||||||
if enabled {
|
|
||||||
if selected {
|
|
||||||
return selectedInputPattern
|
|
||||||
} else {
|
|
||||||
return inputPattern
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return disabledInputPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -38,19 +38,3 @@ var selectedListEntryPattern = artist.NewMultiBordered (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
|
artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
|
||||||
|
|
||||||
func ListPattern (selected bool) (pattern artist.Pattern) {
|
|
||||||
if selected {
|
|
||||||
return selectedListPattern
|
|
||||||
} else {
|
|
||||||
return listPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ListEntryPattern (selected bool) (pattern artist.Pattern) {
|
|
||||||
if selected {
|
|
||||||
return selectedListEntryPattern
|
|
||||||
} else {
|
|
||||||
return listEntryPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
211
theme/patterns.go
Normal file
211
theme/patterns.go
Normal file
@ -0,0 +1,211 @@
|
|||||||
|
package theme
|
||||||
|
|
||||||
|
import "image"
|
||||||
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||||
|
|
||||||
|
// PatternState lists parameters which can change the appearance of some
|
||||||
|
// patterns. For example, passing a PatternState with Selected set to true may
|
||||||
|
// result in a pattern that has a colored border within it.
|
||||||
|
type PatternState struct {
|
||||||
|
// On should be set to true if the element that is using this pattern is
|
||||||
|
// in some sort of "on" state, such as if a checkbox is checked or a
|
||||||
|
// switch is toggled on. This is only necessary if the element in
|
||||||
|
// question is capable of being toggled.
|
||||||
|
On bool
|
||||||
|
|
||||||
|
// Selected should be set to true if the element that is using this
|
||||||
|
// pattern is currently selected.
|
||||||
|
Selected bool
|
||||||
|
|
||||||
|
// Pressed should be set to true if the element that is using this
|
||||||
|
// pattern is being pressed down by the mouse. This is only necessary if
|
||||||
|
// the element in question processes mouse button events.
|
||||||
|
Pressed bool
|
||||||
|
|
||||||
|
// Disabled should be set to true if the element that is using this
|
||||||
|
// pattern is locked and cannot be interacted with. Disabled variations
|
||||||
|
// of patterns are typically flattened and greyed-out.
|
||||||
|
Disabled bool
|
||||||
|
|
||||||
|
// Invalid should be set to true if th element that is using this
|
||||||
|
// pattern wants to warn the user of an invalid interaction or data
|
||||||
|
// entry. Invalid variations typically have some sort of reddish tint
|
||||||
|
// or outline.
|
||||||
|
Invalid bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inset represents an inset amount for all four sides of a rectangle. The top
|
||||||
|
// side is at index zero, the right at index one, the bottom at index two, and
|
||||||
|
// the left at index three. These values may be negative.
|
||||||
|
type Inset [4]int
|
||||||
|
|
||||||
|
// Apply returns the given rectangle, shrunk on all four sides by the given
|
||||||
|
// inset. If a measurment of the inset is negative, that side will instead be
|
||||||
|
// expanded outward. If the rectangle's dimensions cannot be reduced any
|
||||||
|
// further, an empty rectangle near its center will be returned.
|
||||||
|
func (inset Inset) Apply (bigger image.Rectangle) (smaller image.Rectangle) {
|
||||||
|
smaller = bigger
|
||||||
|
if smaller.Dx() < inset[3] + inset[0] {
|
||||||
|
smaller.Min.X = (smaller.Min.X + smaller.Max.X) / 2
|
||||||
|
smaller.Max.X = smaller.Min.X
|
||||||
|
} else {
|
||||||
|
smaller.Min.X += inset[3]
|
||||||
|
smaller.Max.X -= inset[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if smaller.Dy() < inset[1] + inset[2] {
|
||||||
|
smaller.Min.Y = (smaller.Min.Y + smaller.Max.Y) / 2
|
||||||
|
smaller.Max.Y = smaller.Min.Y
|
||||||
|
} else {
|
||||||
|
smaller.Min.Y += inset[0]
|
||||||
|
smaller.Max.Y -= inset[2]
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inverse returns a negated version of the inset.
|
||||||
|
func (inset Inset) Inverse () (prime Inset) {
|
||||||
|
return Inset {
|
||||||
|
inset[0] * -1,
|
||||||
|
inset[1] * -1,
|
||||||
|
inset[2] * -1,
|
||||||
|
inset[3] * -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AccentPattern returns the accent pattern, which is usually just a solid
|
||||||
|
// color.
|
||||||
|
func AccentPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
return accentPattern, Inset { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// BackgroundPattern returns the main background pattern.
|
||||||
|
func BackgroundPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
return backgroundPattern, Inset { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeadPattern returns a pattern that can be used to mark an area or gap that
|
||||||
|
// serves no purpose, but still needs aesthetic structure.
|
||||||
|
func DeadPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
return deadPattern, Inset { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForegroundPattern returns the color text should be.
|
||||||
|
func ForegroundPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Disabled {
|
||||||
|
return weakForegroundPattern, Inset { }
|
||||||
|
} else {
|
||||||
|
return foregroundPattern, Inset { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// InputPattern returns a background pattern for any input field that can be
|
||||||
|
// edited by typing with the keyboard.
|
||||||
|
func InputPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Disabled {
|
||||||
|
return disabledInputPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
if state.Selected {
|
||||||
|
return selectedInputPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
return inputPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: for list and item patterns, have all that bizarre padding/2 information
|
||||||
|
// in the insets.
|
||||||
|
|
||||||
|
// ListPattern returns a background pattern for a list of things.
|
||||||
|
func ListPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Selected {
|
||||||
|
return selectedListPattern, Inset { }
|
||||||
|
} else {
|
||||||
|
return listPattern, Inset { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ItemPattern returns a background pattern for a list item.
|
||||||
|
func ItemPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.On {
|
||||||
|
return selectedListEntryPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
return listEntryPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ButtonPattern returns a pattern to be displayed on buttons.
|
||||||
|
func ButtonPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Disabled {
|
||||||
|
return disabledButtonPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
if state.Pressed {
|
||||||
|
if state.Selected {
|
||||||
|
return pressedSelectedButtonPattern, Inset {
|
||||||
|
2, 0, 0, 2 }
|
||||||
|
} else {
|
||||||
|
return pressedButtonPattern, Inset { 2, 0, 0, 2 }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if state.Selected {
|
||||||
|
return selectedButtonPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
return buttonPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GutterPattern returns a pattern to be used to mark a track along which
|
||||||
|
// something slides.
|
||||||
|
func GutterPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Disabled {
|
||||||
|
return disabledScrollGutterPattern, Inset { 0, 0, 0, 0 }
|
||||||
|
} else {
|
||||||
|
return scrollGutterPattern, Inset { 0, 0, 0, 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandlePattern returns a pattern to be displayed on a grab handle that slides
|
||||||
|
// along a gutter.
|
||||||
|
func HandlePattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Disabled {
|
||||||
|
return disabledScrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
if state.Selected {
|
||||||
|
if state.Pressed {
|
||||||
|
return pressedSelectedScrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
return selectedScrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if state.Pressed {
|
||||||
|
return pressedScrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
return scrollBarPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SunkenPattern returns a general purpose pattern that is sunken/engraved into
|
||||||
|
// the background.
|
||||||
|
func SunkenPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
return sunkenPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
// RaisedPattern returns a general purpose pattern that is raised up out of the
|
||||||
|
// background.
|
||||||
|
func RaisedPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
if state.Selected {
|
||||||
|
return selectedRaisedPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
} else {
|
||||||
|
return raisedPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PinboardPattern returns a textured backdrop pattern. Anything drawn within it
|
||||||
|
// should have its own background pattern.
|
||||||
|
func PinboardPattern (state PatternState) (pattern artist.Pattern, inset Inset) {
|
||||||
|
return texturedSunkenPattern, Inset { 1, 1, 1, 1 }
|
||||||
|
}
|
@ -25,6 +25,17 @@ var scrollBarPattern = artist.NewMultiBordered (
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
|
artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
|
||||||
|
var selectedScrollBarPattern = artist.NewMultiBordered (
|
||||||
|
artist.Stroke { Weight: 1, Pattern: strokePattern },
|
||||||
|
artist.Stroke {
|
||||||
|
Weight: 1,
|
||||||
|
Pattern: artist.Beveled {
|
||||||
|
artist.NewUniform(hex(0xCCD5D2FF)),
|
||||||
|
artist.NewUniform(hex(0x4B5B59FF)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artist.Stroke { Weight: 1, Pattern: accentPattern },
|
||||||
|
artist.Stroke { Pattern: artist.NewUniform(hex(0x8D9894FF)) })
|
||||||
var pressedScrollBarPattern = artist.NewMultiBordered (
|
var pressedScrollBarPattern = artist.NewMultiBordered (
|
||||||
artist.Stroke { Weight: 1, Pattern: strokePattern },
|
artist.Stroke { Weight: 1, Pattern: strokePattern },
|
||||||
artist.Stroke {
|
artist.Stroke {
|
||||||
@ -36,26 +47,17 @@ var pressedScrollBarPattern = artist.NewMultiBordered (
|
|||||||
},
|
},
|
||||||
artist.Stroke { Weight: 1, Pattern: artist.NewUniform(hex(0x8D9894FF)) },
|
artist.Stroke { Weight: 1, Pattern: artist.NewUniform(hex(0x8D9894FF)) },
|
||||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) })
|
artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) })
|
||||||
|
var pressedSelectedScrollBarPattern = artist.NewMultiBordered (
|
||||||
|
artist.Stroke { Weight: 1, Pattern: strokePattern },
|
||||||
|
artist.Stroke {
|
||||||
|
Weight: 1,
|
||||||
|
Pattern: artist.Beveled {
|
||||||
|
artist.NewUniform(hex(0xCCD5D2FF)),
|
||||||
|
artist.NewUniform(hex(0x4B5B59FF)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artist.Stroke { Weight: 1, Pattern: accentPattern },
|
||||||
|
artist.Stroke { Pattern: artist.NewUniform(hex(0x7f8c89FF)) })
|
||||||
var disabledScrollBarPattern = artist.NewMultiBordered (
|
var disabledScrollBarPattern = artist.NewMultiBordered (
|
||||||
artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
|
artist.Stroke { Weight: 1, Pattern: weakForegroundPattern },
|
||||||
artist.Stroke { Pattern: backgroundPattern })
|
artist.Stroke { Pattern: backgroundPattern })
|
||||||
|
|
||||||
func ScrollGutterPattern (horizontal, enabled bool) (artist.Pattern) {
|
|
||||||
if enabled {
|
|
||||||
return scrollGutterPattern
|
|
||||||
} else {
|
|
||||||
return disabledScrollGutterPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScrollBarPattern (horizontal, enabled, pressed bool) (artist.Pattern) {
|
|
||||||
if enabled {
|
|
||||||
if pressed {
|
|
||||||
return pressedScrollBarPattern
|
|
||||||
} else {
|
|
||||||
return scrollBarPattern
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return disabledScrollBarPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package theme
|
package theme
|
||||||
|
|
||||||
import "image"
|
|
||||||
import "image/color"
|
import "image/color"
|
||||||
import "golang.org/x/image/font"
|
import "golang.org/x/image/font"
|
||||||
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
import "git.tebibyte.media/sashakoshka/tomo/artist"
|
||||||
@ -81,34 +80,6 @@ var deadPattern = artist.NewMultiBordered (
|
|||||||
artist.Stroke { Weight: 1, Pattern: strokePattern },
|
artist.Stroke { Weight: 1, Pattern: strokePattern },
|
||||||
artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
|
artist.Stroke { Pattern: artist.NewUniform(hex(0x97a09cFF)) })
|
||||||
|
|
||||||
func AccentPattern () (artist.Pattern) { return accentPattern }
|
|
||||||
func BackgroundPattern () (artist.Pattern) { return backgroundPattern }
|
|
||||||
func DeadPattern () (artist.Pattern) { return deadPattern }
|
|
||||||
|
|
||||||
func SunkenPattern (textured bool) (artist.Pattern) {
|
|
||||||
if textured {
|
|
||||||
return texturedSunkenPattern
|
|
||||||
} else {
|
|
||||||
return sunkenPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func RaisedPattern (selected bool) (artist.Pattern) {
|
|
||||||
if selected {
|
|
||||||
return selectedRaisedPattern
|
|
||||||
} else {
|
|
||||||
return raisedPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ForegroundPattern (enabled bool) (artist.Pattern) {
|
|
||||||
if enabled {
|
|
||||||
return foregroundPattern
|
|
||||||
} else {
|
|
||||||
return weakForegroundPattern
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: load fonts from an actual source instead of using defaultfont
|
// TODO: load fonts from an actual source instead of using defaultfont
|
||||||
|
|
||||||
// FontFaceRegular returns the font face to be used for normal text.
|
// FontFaceRegular returns the font face to be used for normal text.
|
||||||
@ -138,12 +109,8 @@ func Padding () int {
|
|||||||
return 8
|
return 8
|
||||||
}
|
}
|
||||||
|
|
||||||
func ScrollBarWidth () int {
|
// HandleWidth returns how large grab handles should typically be. This is
|
||||||
|
// important for accessibility reasons.
|
||||||
|
func HandleWidth () int {
|
||||||
return Padding() * 2
|
return Padding() * 2
|
||||||
}
|
}
|
||||||
|
|
||||||
// SinkOffsetVector specifies a vector for things such as text to move by when a
|
|
||||||
// "sinking in" effect is desired, such as a button label during a button press.
|
|
||||||
func SinkOffsetVector () image.Point {
|
|
||||||
return image.Point { 1, 1 }
|
|
||||||
}
|
|
||||||
|
Reference in New Issue
Block a user