Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 49f010a8d6 | |||
| 39541e1b78 | |||
| c8bcb9e428 | |||
| 858129ec33 | |||
| 02551987a4 | |||
| e45e391f6d |
10
button.go
10
button.go
@@ -46,10 +46,14 @@ func (this *Button) SetText (text string) {
|
||||
this.label.SetText(text)
|
||||
}
|
||||
|
||||
// SetIcon sets an icon for this button.
|
||||
// TODO: use tomo.Icon instead, use small size icons
|
||||
func (this *Button) SetIcon (icon *Icon) {
|
||||
// SetIcon sets an icon for this button. Setting the icon to IconUnknown will
|
||||
// remove it.
|
||||
func (this *Button) SetIcon (id theme.Icon) {
|
||||
if this.icon != nil { this.Delete(this.icon) }
|
||||
|
||||
var icon *Icon; if id != theme.IconUnknown {
|
||||
icon = NewIcon(id, theme.IconSizeSmall)
|
||||
}
|
||||
this.icon = icon
|
||||
|
||||
if this.icon == nil {
|
||||
|
||||
2
go.mod
2
go.mod
@@ -2,6 +2,6 @@ module git.tebibyte.media/tomo/objects
|
||||
|
||||
go 1.20
|
||||
|
||||
require git.tebibyte.media/tomo/tomo v0.29.0
|
||||
require git.tebibyte.media/tomo/tomo v0.31.0
|
||||
|
||||
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.29.0 h1:uvdPaEQYcWH965y85SjIKwhLklnTbs+x6MRwLfdRvfo=
|
||||
git.tebibyte.media/tomo/tomo v0.29.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
|
||||
git.tebibyte.media/tomo/tomo v0.31.0 h1:LHPpj3AWycochnC8F441aaRNS6Tq6w6WnBrp/LGjyhM=
|
||||
git.tebibyte.media/tomo/tomo v0.31.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
|
||||
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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
|
||||
10
icon.go
10
icon.go
@@ -31,16 +31,6 @@ func NewMimeIcon (mime data.Mime, size theme.IconSize) *Icon {
|
||||
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) {
|
||||
this.Box.SetTexture(texture)
|
||||
if texture == nil {
|
||||
|
||||
4
input.go
4
input.go
@@ -20,6 +20,7 @@ type TextInput struct {
|
||||
func NewTextInput (text string) *TextInput {
|
||||
this := &TextInput { TextBox: tomo.NewTextBox() }
|
||||
theme.Apply(this, theme.R("objects", "TextInput", ""))
|
||||
this.SetAlign(tomo.AlignStart, tomo.AlignMiddle)
|
||||
this.SetText(text)
|
||||
this.SetFocusable(true)
|
||||
this.SetSelectable(true)
|
||||
@@ -53,6 +54,9 @@ func (this *TextInput) handleKeyDown (key input.Key, numpad bool) {
|
||||
sel := modifiers.Shift
|
||||
changed := false
|
||||
|
||||
// TODO all this (except editing stuff) really should be moved into the
|
||||
// backend
|
||||
|
||||
switch {
|
||||
case key == input.KeyEnter:
|
||||
this.on.enter.Broadcast()
|
||||
|
||||
1
label.go
1
label.go
@@ -15,4 +15,3 @@ func NewLabel (text string) *Label {
|
||||
this.SetText(text)
|
||||
return this
|
||||
}
|
||||
|
||||
|
||||
44
scrollbar.go
44
scrollbar.go
@@ -173,15 +173,15 @@ func (this *Scrollbar) handleMouseDown (button input.Button) {
|
||||
}
|
||||
case input.ButtonMiddle:
|
||||
if above {
|
||||
this.SetValue(0)
|
||||
this.scrollBy(-this.pageSize())
|
||||
} else {
|
||||
this.SetValue(1)
|
||||
this.scrollBy(this.pageSize())
|
||||
}
|
||||
case input.ButtonRight:
|
||||
if above {
|
||||
this.SetValue(this.Value() - 0.05)
|
||||
this.scrollBy(-this.stepSize())
|
||||
} else {
|
||||
this.SetValue(this.Value() + 0.05)
|
||||
this.scrollBy(this.stepSize())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -229,6 +229,34 @@ func (this *Scrollbar) fallbackDragOffset () image.Point {
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Scrollbar) pageSize () int {
|
||||
if this.layout.linked == nil { return 0 }
|
||||
viewport := this.layout.linked.InnerBounds()
|
||||
if this.layout.vertical {
|
||||
return viewport.Dy()
|
||||
} else {
|
||||
return viewport.Dx()
|
||||
}
|
||||
}
|
||||
|
||||
func (this *Scrollbar) stepSize () int {
|
||||
// FIXME: this should not be hardcoded, need to get base font metrics
|
||||
// from theme somehow. should be (emspace, lineheight)
|
||||
return 16
|
||||
}
|
||||
|
||||
func (this *Scrollbar) scrollBy (distance int) {
|
||||
if this.layout.linked == nil { return }
|
||||
var vector image.Point; if this.layout.vertical {
|
||||
vector.Y = distance
|
||||
} else {
|
||||
vector.X = distance
|
||||
}
|
||||
this.layout.linked.ScrollTo (
|
||||
this.layout.linked.ContentBounds().Min.
|
||||
Add(vector))
|
||||
}
|
||||
|
||||
type scrollbarCookie struct {
|
||||
owner *Scrollbar
|
||||
subCookies []event.Cookie
|
||||
@@ -278,7 +306,13 @@ func (this scrollbarLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
||||
// calculate handle length
|
||||
handleLength := gutterLength * this.viewportContentRatio()
|
||||
if handleLength < handleMin { handleLength = handleMin }
|
||||
if handleLength > gutterLength { handleLength = gutterLength }
|
||||
if handleLength >= gutterLength {
|
||||
// just hide the handle if it isn't needed.
|
||||
// TODO: we need a way to hide and show boxes, this is janky as
|
||||
// fuck
|
||||
boxes[0].SetBounds(image.Rect(-16, -16, 0, 0))
|
||||
return
|
||||
}
|
||||
if this.vertical {
|
||||
handle.Max.Y = int(handleLength)
|
||||
} else {
|
||||
|
||||
167
scrollcontainer.go
Normal file
167
scrollcontainer.go
Normal file
@@ -0,0 +1,167 @@
|
||||
package objects
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/tomo/event"
|
||||
import "git.tebibyte.media/tomo/tomo/theme"
|
||||
|
||||
// ScrollSide determines which Scrollbars are active in a ScrollContainer.
|
||||
type ScrollSide int; const (
|
||||
ScrollVertical ScrollSide = 1 << iota
|
||||
ScrollHorizontal
|
||||
ScrollBoth = ScrollVertical | ScrollHorizontal
|
||||
)
|
||||
|
||||
// Horizontal returns true if the side includes a horizontal bar.
|
||||
func (sides ScrollSide) Horizontal () bool {
|
||||
return sides & ScrollHorizontal > 0
|
||||
}
|
||||
|
||||
// Vertical returns true if the side includes a vertical bar.
|
||||
func (sides ScrollSide) Vertical () bool {
|
||||
return sides & ScrollVertical > 0
|
||||
}
|
||||
|
||||
// String returns one of:
|
||||
// - both
|
||||
// - horizontal
|
||||
// - vertical
|
||||
// - none
|
||||
func (sides ScrollSide) String () string {
|
||||
switch {
|
||||
case sides.Horizontal() && sides.Vertical(): return "both"
|
||||
case sides.Horizontal(): return "horizontal"
|
||||
case sides.Vertical(): return "vertical"
|
||||
default: return "none"
|
||||
}
|
||||
}
|
||||
|
||||
// ScrollContainer couples a ContentBox with one or two Scrollbars.
|
||||
type ScrollContainer struct {
|
||||
tomo.ContainerBox
|
||||
layout *scrollContainerLayout
|
||||
|
||||
horizontalCookie event.Cookie
|
||||
verticalCookie event.Cookie
|
||||
}
|
||||
|
||||
// NewScrollContainer creates a new scroll container.
|
||||
func NewScrollContainer (sides ScrollSide) *ScrollContainer {
|
||||
this := &ScrollContainer {
|
||||
ContainerBox: tomo.NewContainerBox(),
|
||||
layout: &scrollContainerLayout { },
|
||||
}
|
||||
if sides.Vertical() {
|
||||
this.layout.vertical = NewVerticalScrollbar()
|
||||
this.Add(this.layout.vertical)
|
||||
}
|
||||
if sides.Horizontal() {
|
||||
this.layout.horizontal = NewHorizontalScrollbar()
|
||||
this.Add(this.layout.horizontal)
|
||||
}
|
||||
this.CaptureScroll(true)
|
||||
this.OnScroll(this.handleScroll)
|
||||
theme.Apply(this, theme.R("objects", "ScrollContainer", sides.String()))
|
||||
this.SetLayout(this.layout)
|
||||
return this
|
||||
}
|
||||
|
||||
// SetRoot sets the root child of the ScrollContainer. There can only be one at
|
||||
// a time, and setting it will remove and unlink the current child if there is
|
||||
// one.
|
||||
func (this *ScrollContainer) SetRoot (root tomo.ContentBox) {
|
||||
if this.layout.root != nil {
|
||||
// delete root and close cookies
|
||||
this.Delete(this.layout.root)
|
||||
if this.horizontalCookie != nil {
|
||||
this.horizontalCookie.Close()
|
||||
this.horizontalCookie = nil
|
||||
}
|
||||
if this.verticalCookie != nil {
|
||||
this.verticalCookie.Close()
|
||||
this.verticalCookie = nil
|
||||
}
|
||||
}
|
||||
this.layout.root = root
|
||||
if root != nil {
|
||||
// insert root at the beginning (for keynav)
|
||||
switch {
|
||||
case this.layout.vertical != nil:
|
||||
this.Insert(root, this.layout.vertical)
|
||||
case this.layout.horizontal != nil:
|
||||
this.Insert(root, this.layout.horizontal)
|
||||
default:
|
||||
this.Add(root)
|
||||
}
|
||||
|
||||
// link root and remember cookies
|
||||
if this.layout.horizontal != nil {
|
||||
this.horizontalCookie = this.layout.horizontal.Link(root)
|
||||
}
|
||||
if this.layout.vertical != nil {
|
||||
this.verticalCookie = this.layout.vertical.Link(root)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (this *ScrollContainer) handleScroll (x, y float64) {
|
||||
if this.layout.root == nil { return }
|
||||
this.layout.root.ScrollTo (
|
||||
this.layout.root.ContentBounds().Min.
|
||||
Add(image.Pt(int(x), int(y))))
|
||||
}
|
||||
|
||||
type scrollContainerLayout struct {
|
||||
root tomo.ContentBox
|
||||
horizontal *Scrollbar
|
||||
vertical *Scrollbar
|
||||
}
|
||||
|
||||
func (this *scrollContainerLayout) MinimumSize (hints tomo.LayoutHints, boxes []tomo.Box) image.Point {
|
||||
var minimum image.Point; if this.root != nil {
|
||||
minimum = this.root.MinimumSize()
|
||||
}
|
||||
if this.horizontal != nil {
|
||||
minimum.Y += this.horizontal.MinimumSize().Y
|
||||
}
|
||||
if this.vertical != nil {
|
||||
minimum.X += this.vertical.MinimumSize().X
|
||||
minimum.Y = max(minimum.Y, this.vertical.MinimumSize().Y)
|
||||
}
|
||||
if this.horizontal != nil {
|
||||
minimum.X = max(minimum.X, this.horizontal.MinimumSize().X)
|
||||
}
|
||||
return minimum
|
||||
}
|
||||
|
||||
func (this *scrollContainerLayout) Arrange (hints tomo.LayoutHints, boxes []tomo.Box) {
|
||||
rootBounds := hints.Bounds
|
||||
if this.horizontal != nil {
|
||||
rootBounds.Max.Y -= this.horizontal.MinimumSize().Y
|
||||
}
|
||||
if this.vertical != nil {
|
||||
rootBounds.Max.X -= this.vertical.MinimumSize().X
|
||||
}
|
||||
if this.root != nil {
|
||||
this.root.SetBounds(rootBounds)
|
||||
}
|
||||
if this.horizontal != nil {
|
||||
this.horizontal.SetBounds(image.Rect (
|
||||
hints.Bounds.Min.X,
|
||||
rootBounds.Max.Y,
|
||||
rootBounds.Max.X,
|
||||
hints.Bounds.Max.Y))
|
||||
}
|
||||
if this.vertical != nil {
|
||||
this.vertical.SetBounds(image.Rect (
|
||||
rootBounds.Max.X,
|
||||
hints.Bounds.Min.Y,
|
||||
hints.Bounds.Max.X,
|
||||
rootBounds.Max.Y))
|
||||
}
|
||||
}
|
||||
|
||||
func max (x, y int) int {
|
||||
if x > y { return x }
|
||||
return y
|
||||
}
|
||||
27
textview.go
Normal file
27
textview.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package objects
|
||||
|
||||
import "image"
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/tomo/theme"
|
||||
|
||||
// TextView is an area for displaying a large amount of multi-line text.
|
||||
type TextView struct {
|
||||
tomo.TextBox
|
||||
}
|
||||
|
||||
// NewTextView creates a new text view.
|
||||
func NewTextView (text string) *TextView {
|
||||
this := &TextView { TextBox: tomo.NewTextBox() }
|
||||
theme.Apply(this, theme.R("objects", "TextView", ""))
|
||||
this.SetFocusable(true)
|
||||
this.SetSelectable(true)
|
||||
this.SetText(text)
|
||||
this.SetOverflow(false, true)
|
||||
this.SetWrap(true)
|
||||
this.OnScroll(this.handleScroll)
|
||||
return this
|
||||
}
|
||||
|
||||
func (this *TextView) handleScroll (x, y float64) {
|
||||
this.ScrollTo(this.ContentBounds().Min.Add(image.Pt(int(x), int(y))))
|
||||
}
|
||||
Reference in New Issue
Block a user