Compare commits

...

42 Commits

Author SHA1 Message Date
bc38ea14e1 Add String methods for all types in unit.go 2024-08-07 19:13:17 -04:00
b264e11ea6 Merge style.go and unit.go 2024-08-03 22:09:16 -04:00
a01e5f8716 Change the name of MimeIcon to MimeIconTexture 2024-08-03 22:04:59 -04:00
3de570373f Remove Style as per #21 2024-08-03 22:04:06 -04:00
a1eb53c4db Added functions to get icon textures from the backend 2024-08-03 21:57:10 -04:00
750882eef1 Address icon attribute changes in #21 2024-08-03 21:52:36 -04:00
a98d09d320 golang.org/x/image is no longer a dependency 2024-08-03 21:26:05 -04:00
e6a4b6c70e Change AttrFont to AttrFace as per #21 2024-08-02 19:12:25 -04:00
03fab6fcc0 Remove the font interface and add a Face struct as per #21 2024-08-02 19:08:48 -04:00
6fd236f96c AttrFace is now AttrFont
Closes #19
2024-07-31 00:35:29 -04:00
cf092b4447 Add UnsetAttr
Fully address #20
2024-07-31 00:19:39 -04:00
8403d621a8 AttrKind values are now part of the API 2024-07-30 18:23:37 -04:00
92660ef7de AttrLayout no longer attempts to compare itself 2024-07-25 18:09:10 -04:00
140de5917f Make text wrapping an attribute 2024-07-25 04:01:46 -04:00
3bd9de9110 AttrSet is now just a map 2024-07-25 03:57:55 -04:00
89f49fee71 Removed Tag from BoxQuerier 2024-07-25 02:59:32 -04:00
f561b71c56 Layouts are now attributes 2024-07-24 20:06:20 -04:00
75fd31bb24 Way better event API for objects 2024-07-24 18:37:45 -04:00
5eeb03b113 Replace the catch methods on ContainerBox with SetInputMask 2024-07-24 14:21:39 -04:00
3b3ea6d837 Various attribute improvements 2024-07-24 14:17:31 -04:00
ab4728144f Tags are now used for checking hover, focus, pressed status 2024-07-24 14:15:35 -04:00
1bc85775b8 Add equality checking for attributes 2024-07-21 22:06:10 -04:00
2b2dca77c8 Fix grammar in readme 2024-07-21 22:06:01 -04:00
d6229459c5 Fix ATextureMode 2024-07-21 10:59:35 -04:00
9914cdeb9c Add TextureMode attribute 2024-07-21 02:55:29 -04:00
ba31748b2e Add convenience constructors for attributes 2024-07-21 00:19:16 -04:00
4b56306bd2 Fix spelling of recommended 2024-07-21 00:10:16 -04:00
6859a2b0f7 Fix BoxQuerier interface 2024-07-21 00:00:00 -04:00
469b27bdfb Rename Icons to IconSize 2024-07-20 23:09:32 -04:00
70aaa79b7d Changed Rule contstructor to Ru 2024-07-20 17:12:54 -04:00
e2017f04ff Create constructor for rules 2024-07-20 17:12:31 -04:00
a8c72f7391 Remove the map of named textures from Style 2024-07-20 14:37:06 -04:00
1d4bc03a7d Remove unnecessary String methods 2024-07-20 14:32:59 -04:00
1030bede90 Clean up Box methods a bit 2024-07-20 14:21:41 -04:00
2570fada95 Redid style system with a focus on attributes 2024-07-20 14:18:49 -04:00
8894b98c8b Add BoxQuerier and BoxArranger for use in Layout 2024-07-20 01:23:46 -04:00
1c20acd993 Revise event API for boxes 2024-07-20 01:14:31 -04:00
87494c014a Change Text, Bytes to FromText, FromBytes 2024-07-19 15:26:03 -04:00
f6556aa57f Fix mergedData.Supported 2024-07-19 15:24:44 -04:00
35b52c6a3f Completely redo the data API 2024-07-19 15:24:16 -04:00
109283f520 Dont't crash if tomo.Run callback calls tomo.Stop 2024-07-19 13:40:56 -04:00
dc50e7290d Remove Window.Widget 2024-06-11 23:41:06 -04:00
11 changed files with 672 additions and 312 deletions

View File

@@ -3,6 +3,6 @@
[![Go Reference](https://pkg.go.dev/badge/git.tebibyte.media/tomo/tomo.svg)](https://pkg.go.dev/git.tebibyte.media/tomo/tomo)
Tomo is a lightweight GUI toolkit written in pure Go. This repository defines
the API that other components of the toolkit agree on. In order to use Tomo in
the API that other components of the toolkit agree upon. In order to use Tomo in
an application, use [Nasin](https://git.tebibyte.media/tomo/nasin), which builds
an application framework on top of Tomo.

323
attribute.go Normal file
View File

@@ -0,0 +1,323 @@
package tomo
import "image"
import "image/color"
import "git.tebibyte.media/tomo/tomo/canvas"
// AttrSet is a set of attributes wherein only one/zero of each attribute type
// can exist. It is keyed by the AttrKind of each attribute and must not be
// modified directly.
type AttrSet map[AttrKind] Attr
// AS builds an AttrSet out of a vararg list of Attr values. If multiple Attrs
// of the same kind are specified, the last one will override the others.
func AS (attrs ...Attr) AttrSet {
set := AttrSet { }
set.Add(attrs...)
return set
}
// Add adds attributes to the set.
func (this AttrSet) Add (attrs ...Attr) {
for _, attr := range attrs {
this[attr.Kind()] = attr
}
}
// MergeUnder takes attributes from another set and adds them if they don't
// already exist in this one.
func (this AttrSet) MergeUnder (other AttrSet) {
if other == nil { return }
for _, attr := range other {
if _, exists := this[attr.Kind()]; !exists {
this.Add(attr)
}
}
}
// MergeOver takes attributes from another set and adds them, overriding this
// one.
func (this AttrSet) MergeOver (other AttrSet) {
if other == nil { return }
for _, attr := range other {
this.Add(attr)
}
}
// Attr modifies one thing about a box's style.
type Attr interface {
// Equals returns true if both attributes can reasonably be declared
// equal.
Equals (Attr) bool
Kind () AttrKind
attr ()
}
type AttrKind int; const (
AttrKindColor AttrKind = iota
AttrKindIcon
AttrKindTexture
AttrKindTextureMode
AttrKindBorder
AttrKindMinimumSize
AttrKindPadding
AttrKindGap
AttrKindTextColor
AttrKindDotColor
AttrKindFace
AttrKindWrap
AttrKindAlign
AttrKindOverflow
AttrKindLayout
)
// AttrColor sets the background color of a box.
type AttrColor struct { color.Color }
// AttrIcon sets the icon of a box to a named icon. It has the same end result
// as setting the texture of a box to a centered icon texture.
type AttrIcon Icon
// AttrTexture sets the texture of a box to a texture.
type AttrTexture struct { canvas.Texture }
// AttrTextureMode sets the rendering mode of a box's texture.
type AttrTextureMode TextureMode
// AttrBorder sets the border of a box.
type AttrBorder []Border
// AttrMinimumSize sets the minimum size of a box.
type AttrMinimumSize image.Point
// AttrPadding sets the inner padding of a box.
type AttrPadding Inset
// AttrGap sets the gap between child boxes, if the box is a ContainerBox.
type AttrGap image.Point
// AttrTextColor sets the text color, if the box is a TextBox.
type AttrTextColor struct { color.Color }
// AttrDotColor sets the text selection color, if the box is a TextBox.
type AttrDotColor struct { color.Color }
// AttrFace sets the type face, if the box is a TextBox.
type AttrFace Face
// AttrWrap sets if the text wraps, if the box is a TextBox.
type AttrWrap bool
// AttrAlign sets the alignment, if the box is a ContentBox.
type AttrAlign struct { X, Y Align }
// AttrOverflow sets the overflow, if the box is a ContentBox.
type AttrOverflow struct { X, Y bool }
// AttrLayout sets the layout, if the box is a ContentBox.
type AttrLayout struct { Layout }
// AColor is a convenience constructor for the color attribute.
func AColor (col color.Color) AttrColor {
return AttrColor { Color: col }
}
// AIcon is a convenience constructor for the icon attribute.
func AIcon (icon Icon) AttrIcon {
return AttrIcon(icon)
}
// ATexture is a convenience constructor for the texture attribute.
func ATexture (texture canvas.Texture) AttrTexture {
return AttrTexture { Texture: texture }
}
// ATextureMode is a convenience constructor for the texture mode attribute.
func ATextureMode (mode TextureMode) AttrTextureMode {
return AttrTextureMode(mode)
}
// ABorder is a convenience constructor for the border attribute.
func ABorder (borders ...Border) AttrBorder {
return AttrBorder(borders)
}
// AMinimumSize is a convenience constructor for the minimum size attribute.
func AMinimumSize (x, y int) AttrMinimumSize {
return AttrMinimumSize(image.Pt(x, y))
}
// APadding is a convenience constructor for the padding attribute.
func APadding (sides ...int) AttrPadding {
return AttrPadding(I(sides...))
}
// AGap is a convenience constructor for the gap attribute.
func AGap (x, y int) AttrGap {
return AttrGap(image.Pt(x, y))
}
// ATextColor is a convenience constructor for the text color attribute.
func ATextColor (col color.Color) AttrTextColor {
return AttrTextColor { Color: col }
}
// ADotColor is a convenience constructor for the dot color attribute.
func ADotColor (col color.Color) AttrDotColor {
return AttrDotColor { Color: col }
}
// AFace is a convenience constructor for the face attribute.
func AFace (face Face) AttrFace {
return AttrFace(face)
}
// AWrap is a convenience constructor for the wrap attribute.
func AWrap (wrap bool) AttrWrap {
return AttrWrap(wrap)
}
// AAlign is a convenience constructor for the align attribute.
func AAlign (x, y Align) AttrAlign {
return AttrAlign { X: x, Y: y }
}
// AOverflow is a convenience constructor for the overflow attribute.
func AOverflow (x, y bool) AttrOverflow {
return AttrOverflow { X: x, Y: y }
}
// ALayout is a convenience constructor for the overflow attribute.
func ALayout (layout Layout) AttrLayout {
return AttrLayout { Layout: layout }
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrColor) Equals (other Attr) bool {
if other, ok := other.(AttrColor); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrIcon) Equals (other Attr) bool {
if other, ok := other.(AttrIcon); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrTexture) Equals (other Attr) bool {
if other, ok := other.(AttrTexture); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrTextureMode) Equals (other Attr) bool {
if other, ok := other.(AttrTextureMode); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrBorder) Equals (other Attr) bool {
if other, ok := other.(AttrBorder); ok {
if len(this) != len(other) { return false }
for index := range this {
thisBorder := this[index]
otherBorder := other[index]
if thisBorder != otherBorder { return false }
}
return true
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrMinimumSize) Equals (other Attr) bool {
if other, ok := other.(AttrMinimumSize); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrPadding) Equals (other Attr) bool {
if other, ok := other.(AttrPadding); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrGap) Equals (other Attr) bool {
if other, ok := other.(AttrGap); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrTextColor) Equals (other Attr) bool {
if other, ok := other.(AttrTextColor); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrDotColor) Equals (other Attr) bool {
if other, ok := other.(AttrDotColor); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrFace) Equals (other Attr) bool {
if other, ok := other.(AttrFace); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrWrap) Equals (other Attr) bool {
if other, ok := other.(AttrWrap); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrAlign) Equals (other Attr) bool {
if other, ok := other.(AttrAlign); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrOverflow) Equals (other Attr) bool {
if other, ok := other.(AttrOverflow); ok {
return this == other
} else {
return false
}
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrLayout) Equals (other Attr) bool {
// for some goofy reason if we try to compare two AttrLayouts we get a
// fucking runtime error????? its probably for the best anyways because
// two layouts cannot "reasonably" be declared equal
return false
}
func (AttrColor) Kind () AttrKind { return AttrKindColor }
func (AttrIcon) Kind () AttrKind { return AttrKindIcon }
func (AttrTexture) Kind () AttrKind { return AttrKindTexture }
func (AttrTextureMode) Kind () AttrKind { return AttrKindTextureMode }
func (AttrBorder) Kind () AttrKind { return AttrKindBorder }
func (AttrMinimumSize) Kind () AttrKind { return AttrKindMinimumSize }
func (AttrPadding) Kind () AttrKind { return AttrKindPadding }
func (AttrGap) Kind () AttrKind { return AttrKindGap }
func (AttrTextColor) Kind () AttrKind { return AttrKindTextColor }
func (AttrDotColor) Kind () AttrKind { return AttrKindDotColor }
func (AttrFace) Kind () AttrKind { return AttrKindFace }
func (AttrWrap) Kind () AttrKind { return AttrKindWrap }
func (AttrAlign) Kind () AttrKind { return AttrKindAlign }
func (AttrOverflow) Kind () AttrKind { return AttrKindOverflow }
func (AttrLayout) Kind () AttrKind { return AttrKindLayout }
func (AttrColor) attr () { }
func (AttrIcon) attr () { }
func (AttrTexture) attr () { }
func (AttrTextureMode) attr () { }
func (AttrBorder) attr () { }
func (AttrMinimumSize) attr () { }
func (AttrPadding) attr () { }
func (AttrGap) attr () { }
func (AttrTextColor) attr () { }
func (AttrDotColor) attr () { }
func (AttrFace) attr () { }
func (AttrWrap) attr () { }
func (AttrAlign) attr () { }
func (AttrOverflow) attr () { }
func (AttrLayout) attr () { }

View File

@@ -3,6 +3,7 @@ package tomo
import "sort"
import "image"
import "errors"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/canvas"
// Backend is any Tomo implementation. Backends handle window creation, layout,
@@ -33,16 +34,23 @@ type Backend interface {
// must reject any canvas that was not made by it.
NewCanvas (image.Rectangle) canvas.CanvasCloser
// SetStyle sets the style that will be used on objects. The backend is
// in charge of applying the style to objects. When this method is
// called, it must propagate a StyleChange event to all boxes it is
// keeping track of.
SetStyle (Style)
// ColorRGBA returns the RGBA of a color according to the current style,
// as specified in color.Color.RGBA. It may be rendered invalid if the
// visual style changes, but the backend must send a StyleChange event
// to all managed boxes when this happens.
ColorRGBA (id Color) (r, g, b, a uint32)
// SetIcons sets the icon set that icons will be pulled from. When this
// method is called, it must propagate an IconChange event to all boxes
// it is keeping track of.
SetIcons (Icons)
// IconTexture returns the texture of an icon. It may be closed and
// therefore rendered invalid if the icon set changes, but the backend
// must send an IconSetChange event to all managed boxes when this
// happens.
IconTexture (Icon, IconSize) canvas.Texture
// MimeIconTexture returns the texture of an icon corresponding to the
// specified MIME type. It may be closed and therefore rendered invalid
// if the icon set changes, but the backend must send an IconSetChange
// event to all managed boxes when this happens.
MimeIconTexture (data.Mime, IconSize) canvas.Texture
// Run runs the event loop until Stop() is called, or the backend
// experiences a fatal error.

View File

@@ -3,57 +3,127 @@ package data
import "io"
import "bytes"
import "strings"
// Data represents arbitrary polymorphic data that can be used for data transfer
// between applications.
type Data map[Mime] io.ReadSeekCloser
type Data interface {
// Convert converts the data to the specified MIME type and returns it.
// If the type is not supported, this behavior will return nil, and
// false for ok. Note that Convert may be called multiple times for the
// same MIME type, so it must not return the same reader.
Convert (Mime) (reader io.ReadSeekCloser, ok bool)
// Supported returns a slice of MIME types that Convert can accept.
Supported () []Mime
}
// Mime represents a MIME type.
type Mime struct {
// Type is the first half of the MIME type, and Subtype is the second
// half. The separating slash is not included in either. For example,
// text/html becomes:
// Mime { Type: "text", Subtype: "html" }
// Mime { Type: "text", Subtype: "html" }
// The subtype is stored here including the tree and the suffix.
Type, Subtype string
// Charset is an optional field applicable to text types that specifies
// the character encoding of the data. If empty, UTF-8 is conventionally
// assumed because it's the only text encoding worth using.
Charset string
}
// M is shorthand for creating a MIME type.
func M (ty, subtype string) Mime {
return Mime { ty, subtype }
return Mime {
Type: strings.ToLower(ty),
Subtype: strings.ToLower(subtype),
}
}
// String returns the string representation of the MIME type.
func (mime Mime) String () string {
return mime.Type + "/" + mime.Subtype
// ParseMime parses a MIME type from text.
func ParseMime (text string) Mime {
ty, subty, _ := strings.Cut(text, "/")
subty, parameters, _ := strings.Cut(subty, ";")
mime := M(ty, subty)
for _, parameter := range strings.Split(parameters, " ") {
if parameter == "" { continue }
key, val, _ := strings.Cut(parameter, "=")
// TODO handle things like quoted values
val = strings.TrimSpace(val)
switch strings.TrimSpace(strings.ToLower(key)) {
case "charset":
mime.Charset = val
}
}
return mime
}
// MimePlain returns the MIME type of plain text.
func MimePlain () Mime { return Mime { "text", "plain" } }
func MimePlain () Mime { return Mime { Type: "text", Subtype: "plain" } }
// MimeFile returns the MIME type of a file path/URI.
func MimeFile () Mime { return Mime { "text", "uri-list" } }
func MimeFile () Mime { return Mime { Type: "text", Subtype: "uri-list" } }
// String returns the string representation of the MIME type.
func (mime Mime) String () string {
out := mime.Type + "/" + mime.Subtype
if mime.Charset != "" {
out += "; charset=" + mime.Charset
}
return out
}
// FromText returns plain text Data given a string.
func FromText (text string) Data {
return FromBytes(MimePlain(), []byte(text))
}
type byteReadCloser struct { *bytes.Reader }
func (byteReadCloser) Close () error { return nil }
// Text returns plain text Data given a string.
func Text (text string) Data {
return Bytes(MimePlain(), []byte(text))
type bytesData struct {
mime Mime
buffer []byte
}
// Bytes constructs a Data given a buffer and a mime type.
func Bytes (mime Mime, buffer []byte) Data {
return Data {
mime: byteReadCloser { bytes.NewReader(buffer) },
// FromBytes constructs a Data given a buffer and a mime type.
func FromBytes (mime Mime, buffer []byte) Data {
return bytesData {
mime: mime,
buffer: buffer,
}
}
// Merge combines several Datas together. If multiple Datas provide a reader for
// the same mime type, the ones further on in the list will take precedence.
func Merge (individual ...Data) (combined Data) {
for _, data := range individual {
for mime, reader := range data {
combined[mime] = reader
}}
return
func (bytesDat bytesData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
if mime != bytesDat.mime { return nil, false }
return byteReadCloser { bytes.NewReader(bytesDat.buffer) }, true
}
func (bytesDat bytesData) Supported () []Mime {
return []Mime { bytesDat.mime }
}
type mergedData []Data
func (merged mergedData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
for _, individual := range merged {
if reader, ok := individual.Convert(mime); ok {
return reader, ok
}
}
return nil, false
}
func (merged mergedData) Supported () (supported []Mime) {
for _, individual := range merged {
supported = append(individual.Supported(), supported...)
}
return supported
}
// Merge combines several Datas together. If multiple Datas provide a reader for
// the same mime type, the ones first in the list will take precedence.
func Merge (individual ...Data) (combined Data) {
return mergedData(individual)
}

2
go.mod
View File

@@ -1,5 +1,3 @@
module git.tebibyte.media/tomo/tomo
go 1.20
require golang.org/x/image v0.11.0

33
go.sum
View File

@@ -1,33 +0,0 @@
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=
golang.org/x/image v0.11.0 h1:ds2RoQvBvYTiJkwpSFDwCcDFNX7DqjL2WsUgTNk0Ooo=
golang.org/x/image v0.11.0/go.mod h1:bglhjqbqVuEb9e9+eNR45Jfu7D+T4Qan+NhQk8Ck2P8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

64
icon.go
View File

@@ -10,16 +10,6 @@ type IconSize int; const (
IconSizeLarge
)
// String satisfies the fmt.Stringer interface.
func (size IconSize) String () string {
switch size {
case IconSizeSmall: return "small"
case IconSizeMedium: return "medium"
case IconSizeLarge: return "large"
default: return "unknown"
}
}
// Icon represents an icon ID.
type Icon string
@@ -380,46 +370,20 @@ const (
IconWeatherStorm Icon = "WeatherStorm"
)
// Texture returns a texture of the corresponding icon ID.
// Texture returns a texture of the corresponding icon ID. It may be closed and
// therefore rendered invalid if the icon set changes, so it is necessary to
// subscribe to the IconSetChange event in order to get a new icon texture when
// this happens.
func (id Icon) Texture (size IconSize) canvas.Texture {
if icons == nil { return nil }
return icons.Icon(id, size)
}
// MimeIcon returns an icon corresponding to a MIME type.
func MimeIcon (mime data.Mime, size IconSize) canvas.Texture {
if icons == nil { return nil }
return icons.MimeIcon(mime, size)
}
// Icons holds a set of icon textures.
type Icons interface {
// A word on textures:
//
// Because textures can be linked to some resource that is outside of
// the control of Go's garbage collector, methods of Icons must not
// allocate new copies of a texture each time they are called. It is
// fine to lazily load textures and save them for later use, but the
// same texture must never be allocated multiple times as this could
// cause a memory leak.
//
// As such, textures returned by these methods must be protected.
// Icon returns a texture of the corresponding icon ID. If there is no
// suitable option, it should return nil.
Icon (Icon, IconSize) canvas.Texture
// MimeIcon returns a texture of an icon corresponding to a MIME type.
// If there is no suitable specific option, it should return a more
// generic icon or a plain file icon.
MimeIcon (data.Mime, IconSize) canvas.Texture
}
var icons Icons
// SetIcons sets the icon set.
func SetIcons (icns Icons) {
assertBackend()
icons = icns
backend.SetIcons(icns)
return backend.IconTexture(id, size)
}
// MimeIconTexture returns an icon corresponding to a MIME type. It may be
// closed and therefore rendered invalid if the icon set changes, so it is
// necessary to subscribe to the IconSetChange event in order to get a new icon
// texture when this happens.
func MimeIconTexture (mime data.Mime, size IconSize) canvas.Texture {
assertBackend()
return backend.MimeIconTexture(mime, size)
}

204
object.go
View File

@@ -1,8 +1,6 @@
package tomo
import "image"
import "image/color"
import "golang.org/x/image/font"
import "git.tebibyte.media/tomo/tomo/text"
import "git.tebibyte.media/tomo/tomo/data"
import "git.tebibyte.media/tomo/tomo/event"
@@ -44,43 +42,27 @@ type Box interface {
// InnerBounds returns the inner bounding rectangle of the box. It is
// the value of Bounds inset by the Box's border and padding.
InnerBounds () image.Rectangle
// MinimumSize returns the minimum width and height this Box's bounds
// can be set to. This will return the value of whichever of these is
// greater:
// - The size as set by SetMinimumSize
// - The size taken up by the Box's border and padding. If there is
// internal content that does not overflow, the size of that is also
// taken into account here.
MinimumSize () image.Point
// Role returns this Box's role as set by SetRole.
Role () Role
// SetBounds sets the bounding rectangle of this Box relative to the
// Window.
SetBounds (image.Rectangle)
// SetColor sets the background color of this Box.
SetColor (color.Color)
// SetTextureTile sets a repeating background texture. If the texture is
// transparent, it will be overlayed atop the color specified by
// SetColor(). This will remove any previous texture set by this method
// or another method.
SetTextureTile (canvas.Texture)
// SetTextureCenter sets a centered background texture. If the texture
// is transparent, it will be overlayed atop the color specified by
// SetColor(). This will remove any previous texture set by this method
// or another method.
SetTextureCenter (canvas.Texture)
// SetBorder sets the Border(s) of the box. The first Border will be the
// most outset, and the last Border will be the most inset.
SetBorder (...Border)
// SetMinimumSize sets the minimum width and height of the box, as
// described in MinimumSize.
SetMinimumSize (image.Point)
// SetPadding sets the padding between the Box's innermost Border and
// its content.
SetPadding (Inset)
// SetRole sets what role this Box takes on. It is used to apply styling
// from a theme.
SetRole (Role)
// Tag returns whether or not a named tag exists. These are used for
// applying styling, among other things. There are some special tags
// that are only and always extant during certain user input states:
// - hovered: The mouse pointer is within the box
// - focused: The box has keyboard focus
// - pressed: The box is being pressed by the left mouse button
Tag (string) bool
// SetTag adds or removes a named tag.
SetTag (string, bool)
// SetAttr sets a style attribute, overriding the currently applied
// style.
SetAttr (Attr)
// UnsetAttr reverts a style attribute to whatever is specified by the
// currently applied style.
UnsetAttr (AttrKind)
// SetDNDData sets the data that will be picked up if this Box is
// dragged. If this is nil (which is the default), this Box will not be
@@ -98,34 +80,32 @@ type Box interface {
// If set to false and the Box is already focused. the focus is removed.
SetFocusable (bool)
// Focused returns whether or not this Box has keyboard focus.
Focused () bool
// Modifiers returns which modifier keys on the keyboard are currently
// being held down.
Modifiers () input.Modifiers
// MousePosition returns the position of the mouse pointer relative to
// the Window.
MousePosition () image.Point
// These are event subscription functions that allow callbacks to be
// These are event subscription behaviors that allow callbacks to be
// connected to particular events. Multiple callbacks may be connected
// to the same event at once. Callbacks can be removed by closing the
// returned cookie.
OnFocusEnter (func ()) event.Cookie
OnFocusLeave (func ()) event.Cookie
OnDNDEnter (func ()) event.Cookie
OnDNDLeave (func ()) event.Cookie
OnDNDDrop (func (data.Data)) event.Cookie
OnMouseEnter (func ()) event.Cookie
OnMouseLeave (func ()) event.Cookie
OnMouseMove (func ()) event.Cookie
OnMouseDown (func (input.Button)) event.Cookie
OnMouseUp (func (input.Button)) event.Cookie
OnScroll (func (deltaX, deltaY float64)) event.Cookie
OnKeyDown (func (key input.Key, numberPad bool)) event.Cookie
OnKeyUp (func (key input.Key, numberPad bool)) event.Cookie
OnStyleChange (func ()) event.Cookie
OnIconsChange (func ()) event.Cookie
OnFocusEnter (func () ) event.Cookie
OnFocusLeave (func () ) event.Cookie
OnStyleChange (func () ) event.Cookie
OnIconSetChange (func () ) event.Cookie
OnDNDEnter (func () ) event.Cookie
OnDNDLeave (func () ) event.Cookie
OnDNDDrop (func (data.Data)) event.Cookie
OnMouseEnter (func () ) event.Cookie
OnMouseLeave (func () ) event.Cookie
// These event subscription behaviors require their callbacks to return
// a bool value. Under normal circumstances, these events are propagated
// to the Box which is most directly affected by them, and then to all
// of its parents from the bottom-up. Returning true from the callback
// will cause the propagation to stop immediately, thereby "catching"
// the event. Generally, if the event was successfully handled, the
// callbacks ought to return true.
OnMouseMove (func () bool) event.Cookie
OnButtonDown (func (button input.Button) bool) event.Cookie
OnButtonUp (func (button input.Button) bool) event.Cookie
OnScroll (func (deltaX, deltaY float64) bool) event.Cookie
OnKeyDown (func (key input.Key, numberPad bool) bool) event.Cookie
OnKeyUp (func (key input.Key, numberPad bool) bool) event.Cookie
}
// CanvasBox is a box that can be drawn to.
@@ -174,28 +154,14 @@ type SurfaceBox interface {
// ContentBox is a box that has some kind of content.
type ContentBox interface {
Box
// SetOverflow sets whether or not the Box's content overflows
// horizontally and vertically. Overflowing content is clipped to the
// bounds of the Box inset by all Borders (but not padding).
SetOverflow (horizontal, vertical bool)
// SetAlign sets how the Box's content is distributed horizontally and
// vertically.
SetAlign (x, y Align)
// ContentBounds returns the bounds of the inner content of the Box
// relative to the Box's InnerBounds.
ContentBounds () image.Rectangle
// RecommendedHeight returns the recommended height for a given width,
// if supported by the current content or layout. Otherwise, it should
// just return the minimum height.
RecommendedHeight (width int) int
// RecommendedWidth returns the recommended width for a given height, if
// supported by the current content or layout. Otherwise, it should just
// return the minimum width.
RecommendedWidth (height int) int
// ScrollTo shifts the origin of the Box's content to the origin of the
// Box's InnerBounds, offset by the given point.
ScrollTo (image.Point)
// OnContentBoundsChange specifies a function to be called when the
// Box's ContentBounds or InnerBounds changes.
OnContentBoundsChange (func ()) event.Cookie
@@ -207,18 +173,10 @@ type TextBox interface {
// SetText sets the text content of the Box.
SetText (string)
// SetTextColor sets the text color.
SetTextColor (color.Color)
// SetFace sets the font face text is rendered in.
SetFace (font.Face)
// SetWrap sets whether or not the text wraps.
SetWrap (bool)
// SetSelectable sets whether or not the text content can be
// highlighted/selected.
SetSelectable (bool)
// SetDotColor sets the highlight color of selected text.
SetDotColor (color.Color)
// Select sets the text cursor or selection.
Select (text.Dot)
// Dot returns the text cursor or selection.
@@ -233,8 +191,6 @@ type TextBox interface {
type ContainerBox interface {
ContentBox
// SetGap sets the gap between child Objects.
SetGap (image.Point)
// Add appends a child Object. If the object is already a child of
// another object, it will be removed from that object first.
Add (Object)
@@ -246,22 +202,21 @@ type ContainerBox interface {
Insert (child Object, before Object)
// Clear removes all child Objects.
Clear ()
// Length returns the amount of child Objects.
Length () int
// Len returns the amount of child Objects.
Len () int
// At returns the child Object at the specified index.
At (int) Object
// SetLayout sets the layout of this Box. Child Objects will be
// positioned according to it.
SetLayout (Layout)
// SetInputMask sets whether or not user input events will be sent to
// this Box's children. If false, which is the default, input events
// will be sent to this Box as well as all of its children. If true,
// any input events that would otherwise go to this Box's children are
// sent to it instead. This prevents children from performing event
// catching as described in the documentation for Box.
SetInputMask (bool)
// These methods control whether certain user input events get
// propagated to child Objects. If set to true, the relevant events will
// be sent to this container. If set to false (which is the default),
// the events will be sent to the appropriate child Object.
CaptureDND (bool)
CaptureMouse (bool)
CaptureScroll (bool)
CaptureKeyboard (bool)
// TODO: if it turns out selecting specific kinds of events to mask off
// is a good idea, have SetInputMask take in a vararg list of event
// types.
}
// LayoutHints are passed to a layout to tell it how to arrange child boxes.
@@ -286,19 +241,46 @@ type LayoutHints struct {
type Layout interface {
// MinimumSize returns the minimum width and height of
// LayoutHints.Bounds needed to properly lay out all child Boxes.
MinimumSize (LayoutHints, []Box) image.Point
MinimumSize (LayoutHints, BoxQuerier) image.Point
// Arrange arranges child boxes according to the given LayoutHints.
Arrange (LayoutHints, []Box)
Arrange (LayoutHints, BoxArranger)
// RecommendedHeight returns the recommended height for a given width,
// if supported. Otherwise, it should just return the minimum height.
// The result of this behavior may or may not be respected, depending on
// the situation.
RecommendedHeight (LayoutHints, []Box, int) int
RecommendedHeight (LayoutHints, BoxQuerier, int) int
// RecommendedWidth returns the recommended width for a given height, if
// supported. Otherwise, it should just return the minimum width. The
// result of this behavior may or may not be respected, depending on the
// situation.
RecommendedWidth (LayoutHints, []Box, int) int
RecommendedWidth (LayoutHints, BoxQuerier, int) int
}
// BoxQuerier allows the attributes of a ContainerBox's children to be queried.
type BoxQuerier interface {
// Len returns the amount of boxes.
Len () int
// MinimumSize returns the minimum size of a box.
MinimumSize (index int) image.Point
// RecommendedWidth returns the recommended width for a given height for
// a box, if supported. Otherwise, it should just return the minimum
// width of that box. The result of this behavior may or may not be
// respected, depending on the situation.
RecommendedWidth (index int, height int) int
// RecommendedHeight returns the recommended height for a given width
// for a box, if supported. Otherwise, it should just return the minimum
// width of that box. The result of this behavireor may or may not be
// respected, depending on the situation.
RecommendedHeight (index int, width int) int
}
// BoxArranger is a BoxQuerier that allows arranging child boxes according to a
// layout.
type BoxArranger interface {
BoxQuerier
// SetBounds sets the bounds of a box.
SetBounds (index int, bounds image.Rectangle)
}
// Window is an operating system window. It can contain one object. Windows
@@ -310,9 +292,8 @@ type Window interface {
SetRoot (Object)
// SetTitle sets the title of the window.
SetTitle (string)
// SetIcon sets the icon of the window. When multiple icon sizes are
// provided, the best fitting one is chosen for display.
SetIcon (... canvas.Texture)
// SetIcon sets the icon of the window.
SetIcon (Icon)
// SetResizable sets whether the window can be resized by the user in
// the X and Y directions. If one or both axes are false, the ones that
// are will be shrunk to the window's minimum size.
@@ -320,11 +301,6 @@ type Window interface {
// SetBounds sets this window's bounds. This may or may not have any
// effect on the window's position on screen depending on the platform.
SetBounds (image.Rectangle)
// Widget returns a window representing a smaller iconified form of this
// window. How exactly this window is used depends on the platform.
// Subsequent calls to this method on the same window will return the
// same window object.
Widget () (Window, error)
// NewChild creates a new window that is semantically a child of this
// window. It does not actually reside within this window, but it may be
// linked to it via some other means. This is intended for things like
@@ -336,6 +312,12 @@ type Window interface {
// NewModal creates a new modal window that blocks all input to this
// window until it is closed.
NewModal (image.Rectangle) (Window, error)
// Modifiers returns which modifier keys on the keyboard are currently
// being held down.
Modifiers () input.Modifiers
// MousePosition returns the position of the mouse pointer relative to
// the Window.
MousePosition () image.Point
// Copy copies data to the clipboard.
Copy (data.Data)
// Paste reads data from the clipboard. When the data is available or an

View File

@@ -1,83 +0,0 @@
package tomo
import "fmt"
import "git.tebibyte.media/tomo/tomo/event"
// Role describes the role of an object.
type Role struct {
// Package is an optional namespace field. If specified, it should be
// the package name or module name the object is from.
Package string
// Object specifies what type of object it is. For example:
// - TextInput
// - Table
// - Label
// - Dial
// This should correspond directly to the type name of the object.
Object string
// Variant is an optional field to be used when an object has one or
// more soft variants under one type. For example, an object "Slider"
// may have variations "horizontal" and "vertical".
Variant string
}
// String satisfies the fmt.Stringer interface.
// It follows the format of:
// Package.Object[Variant]
func (r Role) String () string {
return fmt.Sprintf("%s.%s[%s]", r.Package, r.Object, r.Variant)
}
// R is shorthand for creating a Role structure.
func R (pack, object, variant string) Role {
return Role { Package: pack, Object: object, Variant: variant }
}
// Color represents a color ID.
type Color int; const (
ColorBackground Color = iota
ColorForeground
ColorRaised
ColorSunken
ColorAccent
)
// String satisfies the fmt.Stringer interface.
func (c Color) String () string {
switch c {
case ColorBackground: return "background"
case ColorForeground: return "foreground"
case ColorRaised: return "raised"
case ColorSunken: return "sunken"
case ColorAccent: return "accent"
default: return "unknown"
}
}
// RGBA satisfies the color.Color interface.
func (id Color) RGBA () (r, g, b, a uint32) {
if style == nil { return }
return style.RGBA(id)
}
// Style can apply a visual style to different objects.
type Style interface {
// Apply applies the theme to the given object, according to its role.
// This may register event listeners with the given object; closing the
// returned cookie must remove them.
Apply (Object) event.Cookie
// RGBA returns the RGBA values of the corresponding color ID.
RGBA (Color) (r, g, b, a uint32)
}
var style Style
// SetStyle sets the style.
func SetStyle (sty Style) {
assertBackend()
style = sty
backend.SetStyle(sty)
}

View File

@@ -24,6 +24,8 @@ func Run (callback func ()) error {
backendLock.Unlock()
callback()
// callback may have called tomo.Stop
if backend == nil { return nil }
return backend.Run()
}

129
unit.go
View File

@@ -1,5 +1,6 @@
package tomo
import "fmt"
import "image"
import "image/color"
@@ -11,6 +12,16 @@ type Side int; const (
SideLeft
)
func (side Side) String () string {
switch side {
case SideTop: return "SideTop"
case SideRight: return "SideRight"
case SideBottom: return "SideBottom"
case SideLeft: return "SideLeft"
default: return fmt.Sprintf("Side(%d)", side)
}
}
// Inset represents a rectangle inset that can have a different value for each
// side.
type Inset [4]int
@@ -37,6 +48,10 @@ func I (sides ...int) Inset {
}
}
func (inset Inset) String () string {
return fmt.Sprintf("%d %d %d %d", inset[0], inset[1], inset[2], inset[3])
}
// 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
@@ -87,6 +102,12 @@ type Border struct {
Color [4]color.Color
}
func (border Border) String () string {
return fmt.Sprintf("%v %v %v %v / %v",
border.Color[0], border.Color[1], border.Color[2], border.Color[3],
border.Width)
}
// Align lists basic alignment types.
type Align int; const (
AlignStart Align = iota // similar to left-aligned text
@@ -94,3 +115,111 @@ type Align int; const (
AlignEnd // similar to right-aligned text
AlignEven // similar to justified text
)
func (align Align) String () string {
switch align {
case AlignStart: return "AlignStart"
case AlignMiddle: return "AlignMiddle"
case AlignEnd: return "AlignEnd"
case AlignEven: return "AlignEven"
default: return fmt.Sprintf("Align(%d)", align)
}
}
// TextureMode lists texture rendering modes.
type TextureMode int; const (
TextureModeTile TextureMode = iota
TextureModeCenter
// TODO more modes: fit, fill, and stretch
// Of course canvas will need to have a concept of this.
)
func (mode TextureMode) String () string {
switch mode {
case TextureModeTile: return "TextureModeTile"
case TextureModeCenter: return "TextureModeCenter"
default: return fmt.Sprintf("TextureMode(%d)", mode)
}
}
// Face represents a typeface.
type Face struct {
// Font specifies the font name. This should be searched for in a list
// of installed fonts.
Font string
// Size is the point size of the face.
Size float64
// Weight is the weight of the face. If zero, it should be interpreted
// as normal (equivalent to 400).
Weight int
// Italic is how italicized the face is. It ranges from 0 to 1. It is
// different from Slant in that it may alter the design of the glyphs
// instead of simply skewing them.
Italic float64
// Slant is how slanted the face is. It ranges from 0 to 1. It is
// different from Italic in that it simply skews the glyphs without
// altering their design.
Slant float64
}
func (face Face) String () string {
return fmt.Sprintf (
"%s %fpt W%d I%f S%f",
face.Font, face.Size, face.Weight, face.Italic, face.Slant)
}
// Role describes the role of an object.
type Role struct {
// Package is an optional namespace field. If specified, it should be
// the package name or module name the object is from.
Package string
// Object specifies what type of object it is. For example:
// - TextInput
// - Table
// - Label
// - Dial
// This should correspond directly to the type name of the object.
Object string
}
// String satisfies the fmt.Stringer interface. It follows the format of:
// Package.Object
func (r Role) String () string {
return fmt.Sprintf("%s.%s", r.Package, r.Object)
}
// R is shorthand for creating a role structure.
func R (pack, object string) Role {
return Role { Package: pack, Object: object }
}
// Color represents a color ID.
type Color int; const (
ColorBackground Color = iota
ColorForeground
ColorRaised
ColorSunken
ColorAccent
)
func (id Color) String () string {
switch id {
case ColorBackground: return "ColorBackground"
case ColorForeground: return "ColorForeground"
case ColorRaised: return "ColorRaised"
case ColorSunken: return "ColorSunken"
case ColorAccent: return "ColorAccent"
default: return fmt.Sprintf("Color(%d)", id)
}
}
// RGBA satisfies the color.Color interface. The result of this method may be
// rendered invalid if the visual style changes, so it is often necessary to
// subscribe to the StyleChange event in order to get new RGBA values when this
// happens.
func (id Color) RGBA () (r, g, b, a uint32) {
assertBackend()
return backend.ColorRGBA(id)
}