Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cba0151ce | |||
| d8d24632bb | |||
| 05b6490095 | |||
| 98bf754282 | |||
| 8a7b2832df | |||
| 84313885df |
@@ -1,4 +1,6 @@
|
|||||||
# objects
|
# objects
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/pkg.go.dev/git.tebibyte.media/tomo/objects)
|
||||||
|
|
||||||
Objects contains a standard collection of re-usable objects. All objects in this
|
Objects contains a standard collection of re-usable objects. All objects in this
|
||||||
module visually conform to whatever the theme is set to.
|
module visually conform to whatever the theme is set to.
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -2,6 +2,6 @@ module git.tebibyte.media/tomo/objects
|
|||||||
|
|
||||||
go 1.20
|
go 1.20
|
||||||
|
|
||||||
require git.tebibyte.media/tomo/tomo v0.23.0
|
require git.tebibyte.media/tomo/tomo v0.27.0
|
||||||
|
|
||||||
require golang.org/x/image v0.11.0 // indirect
|
require golang.org/x/image v0.11.0 // indirect
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -1,5 +1,5 @@
|
|||||||
git.tebibyte.media/tomo/tomo v0.23.0 h1:OZwI4oLKMP7JqFc98VxBoRxwjL+W9NyTQZLB/m1BvaA=
|
git.tebibyte.media/tomo/tomo v0.27.0 h1:gCwxQe0qm1hZLfHkMI3OccNMC/lB1cfs4BbaMz/bXug=
|
||||||
git.tebibyte.media/tomo/tomo v0.23.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
|
git.tebibyte.media/tomo/tomo v0.27.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
|
||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
|
|||||||
47
icon.go
Normal file
47
icon.go
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
package objects
|
||||||
|
|
||||||
|
import "git.tebibyte.media/tomo/tomo"
|
||||||
|
import "git.tebibyte.media/tomo/tomo/data"
|
||||||
|
import "git.tebibyte.media/tomo/tomo/theme"
|
||||||
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||||
|
|
||||||
|
// Icon displays a single icon.
|
||||||
|
type Icon struct {
|
||||||
|
tomo.Box
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIcon creates a new icon from an icon ID.
|
||||||
|
func NewIcon (id theme.Icon, size theme.IconSize) *Icon {
|
||||||
|
this := &Icon {
|
||||||
|
Box: tomo.NewBox(),
|
||||||
|
}
|
||||||
|
theme.Apply(this, theme.R("objects", "Icon", size.String()))
|
||||||
|
this.SetTexture(id.Texture(size))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMimeIcon creates a new icon from a MIME type.
|
||||||
|
func NewMimeIcon (mime data.Mime, size theme.IconSize) *Icon {
|
||||||
|
this := &Icon {
|
||||||
|
Box: tomo.NewBox(),
|
||||||
|
}
|
||||||
|
theme.Apply(this, theme.R("objects", "Icon", size.String()))
|
||||||
|
this.SetTexture(theme.MimeIcon(mime, size))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApplicationIcon creates a new icon from an application description.
|
||||||
|
func NewApplicationIcon (id theme.ApplicationIcon, size theme.IconSize) *Icon {
|
||||||
|
this := &Icon {
|
||||||
|
Box: tomo.NewBox(),
|
||||||
|
}
|
||||||
|
theme.Apply(this, theme.R("objects", "Icon", size.String()))
|
||||||
|
this.SetTexture(id.Texture(size))
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Icon) SetTexture (texture canvas.Texture) {
|
||||||
|
bounds := texture.Bounds()
|
||||||
|
this.Box.SetTexture(texture)
|
||||||
|
this.SetMinimumSize(bounds.Max.Sub(bounds.Min))
|
||||||
|
}
|
||||||
@@ -5,87 +5,46 @@ import "git.tebibyte.media/tomo/tomo"
|
|||||||
|
|
||||||
// Cut is a layout that can be divided into smaller and smaller sections.
|
// Cut is a layout that can be divided into smaller and smaller sections.
|
||||||
type Cut struct {
|
type Cut struct {
|
||||||
branches [2]*Cut
|
branches []*Cut
|
||||||
expand [2]bool
|
expand []bool
|
||||||
vertical bool
|
vertical bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertical divides the layout in equal halves vertically.
|
// NewCut creates and returns a new Cut layout.
|
||||||
func (this *Cut) Vertical () (top, bottom *Cut) {
|
func NewCut () *Cut {
|
||||||
this.fill()
|
return new(Cut)
|
||||||
this.even()
|
}
|
||||||
|
|
||||||
|
// Vertical divides the layout vertically. Sections are specified using
|
||||||
|
// booleans. If a section is true, it will expand. If false, it will contract.
|
||||||
|
func (this *Cut) Vertical (expand ...bool) {
|
||||||
|
this.expand = expand
|
||||||
this.vertical = true
|
this.vertical = true
|
||||||
return this.Branches()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Top divides the layout vertically, expanding the top half and contracting the
|
|
||||||
// bottom half.
|
|
||||||
func (this *Cut) Top () (top, bottom *Cut) {
|
|
||||||
this.fill()
|
this.fill()
|
||||||
this.first()
|
|
||||||
this.vertical = true
|
|
||||||
return this.Branches()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom divides the layout vertically, expanding the bottom half and
|
// Horizontal divides the layout horizontally. Sections are specified using
|
||||||
// contracting the top half.
|
// booleans. If a section is true, it will expand. If false, it will contract.
|
||||||
func (this *Cut) Bottom () (top, bottom *Cut) {
|
func (this *Cut) Horizontal (expand ...bool) {
|
||||||
|
this.expand = expand
|
||||||
|
this.vertical = false
|
||||||
this.fill()
|
this.fill()
|
||||||
this.second()
|
|
||||||
this.vertical = true
|
|
||||||
return this.Branches()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Horizontal divides the layout in equal halves horizontally.
|
// At returns the section of this layout at the specified index.
|
||||||
func (this *Cut) Horizontal () (top, bottom *Cut) {
|
func (this *Cut) At (index int) *Cut {
|
||||||
this.fill()
|
return this.branches[index]
|
||||||
this.even()
|
|
||||||
return this.Branches()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Left divides the layout horizontally, expanding the left half and contracting
|
|
||||||
// the right half.
|
|
||||||
func (this *Cut) Left () (top, bottom *Cut) {
|
|
||||||
this.fill()
|
|
||||||
this.first()
|
|
||||||
return this.Branches()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Right divides the layout horizontally, expanding the right half and
|
|
||||||
// contracting the left half.
|
|
||||||
func (this *Cut) Right () (top, bottom *Cut) {
|
|
||||||
this.fill()
|
|
||||||
this.second()
|
|
||||||
return this.Branches()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Branches returns the two halves of this layout.
|
|
||||||
func (this *Cut) Branches () (first, second *Cut) {
|
|
||||||
return this.branches[0], this.branches[1]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Cut) real () bool {
|
func (this *Cut) real () bool {
|
||||||
return this != nil && this.branches[0] != nil && this.branches[1] != nil
|
return this != nil && this.branches != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Cut) fill () {
|
func (this *Cut) fill () {
|
||||||
this.branches[0] = &Cut { }
|
this.branches = make([]*Cut, len(this.expand))
|
||||||
this.branches[1] = &Cut { }
|
for index := range this.branches {
|
||||||
}
|
this.branches[index] = new(Cut)
|
||||||
|
}
|
||||||
func (this *Cut) first () {
|
|
||||||
this.expand[0] = true
|
|
||||||
this.expand[1] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Cut) second () {
|
|
||||||
this.expand[0] = false
|
|
||||||
this.expand[1] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (this *Cut) even () {
|
|
||||||
this.expand[0] = true
|
|
||||||
this.expand[1] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (this *Cut) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
|
func (this *Cut) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
|
||||||
@@ -126,9 +85,11 @@ func (this *Cut) minimumSize (hints tomo.LayoutHints, boxes []tomo.Box) (image.P
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *Cut) arrange (hints tomo.LayoutHints, boxes []tomo.Box) []tomo.Box {
|
func (this *Cut) arrange (hints tomo.LayoutHints, boxes []tomo.Box) []tomo.Box {
|
||||||
|
nChildren := len(this.branches)
|
||||||
|
|
||||||
// collect minimum sizes and physical endpoints
|
// collect minimum sizes and physical endpoints
|
||||||
var minimums [2]image.Point
|
var minimums = make([]image.Point, nChildren)
|
||||||
var leaves [2]tomo.Box
|
var leaves = make([]tomo.Box, nChildren)
|
||||||
var nBranches int
|
var nBranches int
|
||||||
remaining := boxes
|
remaining := boxes
|
||||||
for index, branch := range this.branches {
|
for index, branch := range this.branches {
|
||||||
@@ -163,7 +124,7 @@ func (this *Cut) arrange (hints tomo.LayoutHints, boxes []tomo.Box) []tomo.Box {
|
|||||||
expandingSize := freeSpace / nExpanding
|
expandingSize := freeSpace / nExpanding
|
||||||
|
|
||||||
// calculate the size and position of branches
|
// calculate the size and position of branches
|
||||||
var bounds [2]image.Rectangle
|
var bounds = make([]image.Rectangle, nChildren)
|
||||||
x := float64(hints.Bounds.Min.X)
|
x := float64(hints.Bounds.Min.X)
|
||||||
y := float64(hints.Bounds.Min.Y)
|
y := float64(hints.Bounds.Min.Y)
|
||||||
for index, minimum := range minimums {
|
for index, minimum := range minimums {
|
||||||
|
|||||||
108
layouts/grid.go
Normal file
108
layouts/grid.go
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
package layouts
|
||||||
|
|
||||||
|
import "math"
|
||||||
|
import "image"
|
||||||
|
import "git.tebibyte.media/tomo/tomo"
|
||||||
|
|
||||||
|
// Grid is a layout that arranges boxes in a grid formation with distinct rows
|
||||||
|
// and columns. It is great for creating forms.
|
||||||
|
type Grid struct {
|
||||||
|
xExpand []bool
|
||||||
|
yExpand []bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGrid creates a new grid layout. Rows and columns are specified as slices
|
||||||
|
// of booleans, where true means a row or column will expand and false means it
|
||||||
|
// will contract. Boxes are laid out left to right, then top to bottom. Boxes
|
||||||
|
// that go beyond the lengh of rows will be laid out according to columns, but
|
||||||
|
// they will not expand vertically.
|
||||||
|
func NewGrid (columns, rows []bool) *Grid {
|
||||||
|
this := &Grid {
|
||||||
|
xExpand: columns,
|
||||||
|
yExpand: rows,
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Grid) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
|
||||||
|
cols, rows := this.minimums(boxes)
|
||||||
|
size := image.Pt (
|
||||||
|
(len(cols) - 1) * hints.Gap.X,
|
||||||
|
(len(rows) - 1) * hints.Gap.Y)
|
||||||
|
for _, width := range cols { size.X += width }
|
||||||
|
for _, height := range rows { size.Y += height }
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Grid) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
||||||
|
xExpand := func (index int) bool {
|
||||||
|
return this.xExpand[index]
|
||||||
|
}
|
||||||
|
yExpand := func (index int) bool {
|
||||||
|
if index < len(this.yExpand) { return this.yExpand[index] }
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
cols, rows := this.minimums(boxes)
|
||||||
|
expand(hints, cols, hints.Bounds.Dx(), xExpand)
|
||||||
|
expand(hints, rows, hints.Bounds.Dy(), yExpand)
|
||||||
|
|
||||||
|
position := hints.Bounds.Min
|
||||||
|
for index, box := range boxes {
|
||||||
|
col, row := index % len(cols), index / len(cols)
|
||||||
|
box.SetBounds(image.Rectangle {
|
||||||
|
Min: position,
|
||||||
|
Max: position.Add(image.Pt(cols[col], rows[row])),
|
||||||
|
})
|
||||||
|
if col == len(cols) - 1 {
|
||||||
|
position.X = hints.Bounds.Min.X
|
||||||
|
position.Y += rows[row] + hints.Gap.Y
|
||||||
|
} else {
|
||||||
|
position.X += cols[col] + hints.Gap.X
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Grid) minimums (boxes []tomo.Box) ([]int, []int) {
|
||||||
|
nCols, nRows := this.dimensions(boxes)
|
||||||
|
cols, rows := make([]int, nCols), make([]int, nRows)
|
||||||
|
|
||||||
|
for index, box := range boxes {
|
||||||
|
col, row := index % len(cols), index / len(cols)
|
||||||
|
minimum := box.MinimumSize()
|
||||||
|
if cols[col] < minimum.X {
|
||||||
|
cols[col] = minimum.X
|
||||||
|
}
|
||||||
|
if rows[row] < minimum.Y {
|
||||||
|
rows[row] = minimum.Y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return cols, rows
|
||||||
|
}
|
||||||
|
|
||||||
|
func (this *Grid) dimensions (boxes []tomo.Box) (int, int) {
|
||||||
|
return len(this.xExpand), ceilDiv(len(boxes), len(this.xExpand))
|
||||||
|
}
|
||||||
|
|
||||||
|
func expand (hints tomo.LayoutHints, sizes []int, space int, expands func (int) bool) {
|
||||||
|
gaps := len(sizes) - 1
|
||||||
|
freeSpace := float64(space - hints.Gap.Y * gaps)
|
||||||
|
nExpanding := 0; for index, minimum := range sizes {
|
||||||
|
if expands(index) {
|
||||||
|
nExpanding ++
|
||||||
|
} else {
|
||||||
|
freeSpace -= float64(minimum)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expandingSize := freeSpace / float64(nExpanding)
|
||||||
|
for index := range sizes {
|
||||||
|
if expands(index) {
|
||||||
|
sizes[index] = int(expandingSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ceilDiv (x, y int) int {
|
||||||
|
return int(math.Ceil(float64(x) / float64(y)))
|
||||||
|
}
|
||||||
@@ -16,4 +16,3 @@ func NewSeparator () *Separator {
|
|||||||
theme.Apply(this, theme.R("objects", "Separator", ""))
|
theme.Apply(this, theme.R("objects", "Separator", ""))
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
17
slider.go
17
slider.go
@@ -74,18 +74,24 @@ func (this *Slider) OnValueChange (callback func ()) event.Cookie {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (this *Slider) handleKeyDown (key input.Key, numpad bool) {
|
func (this *Slider) handleKeyDown (key input.Key, numpad bool) {
|
||||||
|
var increment float64; if this.layout.vertical {
|
||||||
|
increment = -0.05
|
||||||
|
} else {
|
||||||
|
increment = 0.05
|
||||||
|
}
|
||||||
|
|
||||||
switch key {
|
switch key {
|
||||||
case input.KeyUp, input.KeyLeft:
|
case input.KeyUp, input.KeyLeft:
|
||||||
if this.Modifiers().Alt {
|
if this.Modifiers().Alt {
|
||||||
this.SetValue(0)
|
this.SetValue(0)
|
||||||
} else {
|
} else {
|
||||||
this.SetValue(this.Value() - 0.05)
|
this.SetValue(this.Value() - increment)
|
||||||
}
|
}
|
||||||
case input.KeyDown, input.KeyRight:
|
case input.KeyDown, input.KeyRight:
|
||||||
if this.Modifiers().Alt {
|
if this.Modifiers().Alt {
|
||||||
this.SetValue(1)
|
this.SetValue(1)
|
||||||
} else {
|
} else {
|
||||||
this.SetValue(this.Value() + 0.05)
|
this.SetValue(this.Value() + increment)
|
||||||
}
|
}
|
||||||
case input.KeyHome:
|
case input.KeyHome:
|
||||||
this.SetValue(0)
|
this.SetValue(0)
|
||||||
@@ -152,6 +158,7 @@ func (this *Slider) drag () {
|
|||||||
|
|
||||||
if this.layout.vertical {
|
if this.layout.vertical {
|
||||||
this.SetValue (
|
this.SetValue (
|
||||||
|
1 -
|
||||||
float64(pointer.Y) /
|
float64(pointer.Y) /
|
||||||
float64(gutter.Dy() - handle.Dy()))
|
float64(gutter.Dy() - handle.Dy()))
|
||||||
} else {
|
} else {
|
||||||
@@ -188,8 +195,8 @@ func (this sliderLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
|||||||
|
|
||||||
if this.vertical {
|
if this.vertical {
|
||||||
height := gutter.Dy() - handle.Dy()
|
height := gutter.Dy() - handle.Dy()
|
||||||
offset := int(float64(height) * this.value)
|
offset := int(float64(height) * (1 - this.value))
|
||||||
gutter.Max.X = handle.Max.X
|
handle.Max.X = gutter.Dx()
|
||||||
boxes[0].SetBounds (
|
boxes[0].SetBounds (
|
||||||
handle.
|
handle.
|
||||||
Add(image.Pt(0, offset)).
|
Add(image.Pt(0, offset)).
|
||||||
@@ -197,7 +204,7 @@ func (this sliderLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
|||||||
} else {
|
} else {
|
||||||
width := gutter.Dx() - handle.Dx()
|
width := gutter.Dx() - handle.Dx()
|
||||||
offset := int(float64(width) * this.value)
|
offset := int(float64(width) * this.value)
|
||||||
gutter.Max.Y = handle.Max.Y
|
handle.Max.Y = gutter.Dy()
|
||||||
boxes[0].SetBounds (
|
boxes[0].SetBounds (
|
||||||
handle.
|
handle.
|
||||||
Add(image.Pt(offset, 0)).
|
Add(image.Pt(offset, 0)).
|
||||||
|
|||||||
Reference in New Issue
Block a user