Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 469b27bdfb | |||
| 70aaa79b7d | |||
| e2017f04ff | |||
| a8c72f7391 | |||
| 1d4bc03a7d | |||
| 1030bede90 | |||
| 2570fada95 | |||
| 8894b98c8b | |||
| 1c20acd993 | |||
| 87494c014a | |||
| f6556aa57f | |||
| 35b52c6a3f | |||
| 109283f520 | |||
| dc50e7290d | |||
| e102c032cb | |||
| d35b0532a1 | |||
| dffe766328 | |||
| c1d77028b4 | |||
| 83b2c3c665 | |||
| 89307e0dd6 | |||
| 5d9c584743 | |||
| fd7d2b7ead | |||
| ca4d3e62c4 | |||
| d587e39506 | |||
| f0c092be8a | |||
| 06f45c80e2 | |||
| 71171d762c |
89
attribute.go
Normal file
89
attribute.go
Normal file
@@ -0,0 +1,89 @@
|
||||
package tomo
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "golang.org/x/image/font"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// AttrSet is a set of attributes wherein only one/zero of each attribute type
|
||||
// can exist. Its zero value can be used safely, and you can copy it if you
|
||||
// want, but it will point to the same set of attributes.
|
||||
type AttrSet struct {
|
||||
set map[int] Attr
|
||||
}
|
||||
|
||||
// AS builds an AttrSet out of a vararg list of Attr values.
|
||||
func AS (attrs ...Attr) AttrSet {
|
||||
set := AttrSet { }
|
||||
set.Add(attrs...)
|
||||
return set
|
||||
}
|
||||
|
||||
// Add adds attributes to the set.
|
||||
func (this *AttrSet) Add (attrs ...Attr) {
|
||||
this.ensure()
|
||||
for _, attr := range attrs {
|
||||
this.set[attr.attr()] = 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) {
|
||||
this.ensure()
|
||||
if other.set == nil { return }
|
||||
for _, attr := range other.set {
|
||||
if _, exists := this.set[attr.attr()]; !exists {
|
||||
this.Add(attr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MergeOver takes attributes from another set and adds them, overriding this
|
||||
// one.
|
||||
func (this *AttrSet) MergeOver (other AttrSet) {
|
||||
this.ensure()
|
||||
if other.set == nil { return }
|
||||
for _, attr := range other.set {
|
||||
this.Add(attr)
|
||||
}
|
||||
}
|
||||
|
||||
func (this *AttrSet) ensure () {
|
||||
if this.set == nil { this.set = make(map[int] Attr) }
|
||||
}
|
||||
|
||||
// Attr modifies one thing about a box's style.
|
||||
type Attr interface { attr () int }
|
||||
|
||||
// AttrColor sets the background color of a box.
|
||||
type AttrColor struct { color.Color }
|
||||
// AttrTexture sets the texture of a box to a named texture.
|
||||
type AttrTexture struct { canvas.Texture }
|
||||
// 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 font face, if the box is a TextBox.
|
||||
type AttrFace struct { font.Face }
|
||||
// AttrAlign sets the alignment, if the box is a ContentBox.
|
||||
type AttrAlign struct { X, Y Align }
|
||||
|
||||
func (AttrColor) attr () int { return 0 }
|
||||
func (AttrTexture) attr () int { return 1 }
|
||||
func (AttrBorder) attr () int { return 2 }
|
||||
func (AttrMinimumSize) attr () int { return 3 }
|
||||
func (AttrPadding) attr () int { return 4 }
|
||||
func (AttrGap) attr () int { return 5 }
|
||||
func (AttrTextColor) attr () int { return 6 }
|
||||
func (AttrDotColor) attr () int { return 7 }
|
||||
func (AttrFace) attr () int { return 8 }
|
||||
func (AttrAlign) attr () int { return 9 }
|
||||
11
backend.go
11
backend.go
@@ -33,6 +33,17 @@ 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)
|
||||
|
||||
// SetIconSet 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.
|
||||
SetIconSet (IconSet)
|
||||
|
||||
// Run runs the event loop until Stop() is called, or the backend
|
||||
// experiences a fatal error.
|
||||
Run () error
|
||||
|
||||
116
data/data.go
116
data/data.go
@@ -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)
|
||||
}
|
||||
|
||||
50
icon.go
50
icon.go
@@ -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
|
||||
|
||||
@@ -382,12 +372,44 @@ const (
|
||||
|
||||
// Texture returns a texture of the corresponding icon ID.
|
||||
func (id Icon) Texture (size IconSize) canvas.Texture {
|
||||
if theme == nil { return nil }
|
||||
return theme.Icon(id, size)
|
||||
if iconSet == nil { return nil }
|
||||
return iconSet.Icon(id, size)
|
||||
}
|
||||
|
||||
// MimeIcon returns an icon corresponding to a MIME type.
|
||||
func MimeIcon (mime data.Mime, size IconSize) canvas.Texture {
|
||||
if theme == nil { return nil }
|
||||
return theme.MimeIcon(mime, size)
|
||||
if iconSet == nil { return nil }
|
||||
return iconSet.MimeIcon(mime, size)
|
||||
}
|
||||
|
||||
// IconSet holds a set of icon textures.
|
||||
type IconSet 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 IconSet 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 iconSet IconSet
|
||||
|
||||
// SetIconSet sets the icon set.
|
||||
func SetIconSet (set IconSet) {
|
||||
assertBackend()
|
||||
iconSet = set
|
||||
backend.SetIconSet(set)
|
||||
}
|
||||
|
||||
279
object.go
279
object.go
@@ -1,106 +1,12 @@
|
||||
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"
|
||||
import "git.tebibyte.media/tomo/tomo/input"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// Side represents one side of a rectangle.
|
||||
type Side int; const (
|
||||
SideTop Side = iota
|
||||
SideRight
|
||||
SideBottom
|
||||
SideLeft
|
||||
)
|
||||
|
||||
// Inset represents a rectangle inset that can have a different value for each
|
||||
// side.
|
||||
type Inset [4]int
|
||||
|
||||
// I allows you to create an inset in a CSS-ish way:
|
||||
//
|
||||
// - One argument: all sides are set to this value
|
||||
// - Two arguments: the top and bottom sides are set to the first value, and
|
||||
// the left and right sides are set to the second value.
|
||||
// - Three arguments: the top side is set by the first value, the left and
|
||||
// right sides are set by the second vaue, and the bottom side is set by the
|
||||
// third value.
|
||||
// - Four arguments: each value corresponds to a side.
|
||||
//
|
||||
// This function will panic if an argument count that isn't one of these is
|
||||
// given.
|
||||
func I (sides ...int) Inset {
|
||||
switch len(sides) {
|
||||
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
|
||||
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
|
||||
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
|
||||
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
|
||||
default: panic("I: illegal argument count.")
|
||||
}
|
||||
}
|
||||
|
||||
// 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[1] {
|
||||
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[0] + 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,
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal returns the sum of SideRight and SideLeft.
|
||||
func (inset Inset) Horizontal () int {
|
||||
return inset[SideRight] + inset[SideLeft]
|
||||
}
|
||||
|
||||
// Vertical returns the sum of SideTop and SideBottom.
|
||||
func (inset Inset) Vertical () int {
|
||||
return inset[SideTop] + inset[SideBottom]
|
||||
}
|
||||
|
||||
// Border represents a single border of a box.
|
||||
type Border struct {
|
||||
Width Inset
|
||||
Color [4]color.Color
|
||||
}
|
||||
|
||||
// Align lists basic alignment types.
|
||||
type Align int; const (
|
||||
AlignStart Align = iota // similar to left-aligned text
|
||||
AlignMiddle // similar to center-aligned text
|
||||
AlignEnd // similar to right-aligned text
|
||||
AlignEven // similar to justified text
|
||||
)
|
||||
|
||||
// Object is any onscreen object that is linked to a box (or is that box).
|
||||
// Unlike the Box interface and associated interfaces, Object implementations
|
||||
// may originate from anywhere.
|
||||
@@ -136,43 +42,19 @@ 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 layouts and styling.
|
||||
Tag (string) bool
|
||||
// SetTag adds or removes a named tag.
|
||||
SetTag (string, bool)
|
||||
|
||||
// SetAttr overrides a style attribute.
|
||||
SetAttr(Attr)
|
||||
|
||||
// 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
|
||||
@@ -182,6 +64,8 @@ type Box interface {
|
||||
// If this is nil (which is the default), this Box will reject all
|
||||
// drops.
|
||||
SetDNDAccept (...data.Mime)
|
||||
// Focused returns whether or not this Box has keyboard focus.
|
||||
Focused () bool
|
||||
// SetFocused sets whether or not this Box has keyboard focus. If set to
|
||||
// true, this method will steal focus away from whichever Object
|
||||
// currently has focus.
|
||||
@@ -190,32 +74,29 @@ 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
|
||||
// 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
|
||||
//
|
||||
// Some events will pass a catch function to their callbacks. Calling
|
||||
// this function will prevent the event from being propagated to any
|
||||
// child objects.
|
||||
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
|
||||
OnButtonDown (func (catch func (), button input.Button)) event.Cookie
|
||||
OnButtonUp (func (catch func (), button input.Button)) event.Cookie
|
||||
OnScroll (func (catch func (), deltaX, deltaY float64)) event.Cookie
|
||||
OnKeyDown (func (catch func (), key input.Key, numberPad bool)) event.Cookie
|
||||
OnKeyUp (func (catch func (), key input.Key, numberPad bool)) event.Cookie
|
||||
OnStyleChange (func ()) event.Cookie
|
||||
OnIconSetChange (func ()) event.Cookie
|
||||
}
|
||||
|
||||
// CanvasBox is a box that can be drawn to.
|
||||
@@ -269,15 +150,13 @@ type ContentBox interface {
|
||||
// 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
|
||||
// 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
|
||||
@@ -289,18 +168,12 @@ 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.
|
||||
@@ -328,22 +201,24 @@ 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)
|
||||
|
||||
// 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)
|
||||
// CatchDND controls whether events relating to drag-and-drop (DNDEnter,
|
||||
// DNDLeave, DNDDrop) will be sent to this container instead of child
|
||||
// objects. The default is false, meaning these events will be sent to
|
||||
// child objects.
|
||||
CatchDND (bool)
|
||||
// CatchMouse controls whether events relating to mouse motion
|
||||
// (MouseEnter, MouseLeave, MouseMove) will be sent to this container
|
||||
// instead of child objects. The default is false, meaning these events
|
||||
// will be sent to child objects.
|
||||
CatchMouse (bool)
|
||||
}
|
||||
|
||||
// LayoutHints are passed to a layout to tell it how to arrange child boxes.
|
||||
@@ -368,26 +243,68 @@ 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, 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, BoxQuerier, int) int
|
||||
}
|
||||
|
||||
// Window is an operating system window. It can contain one object.
|
||||
// BoxQuerier allows the attributes of a ContainerBox's children to be queried.
|
||||
type BoxQuerier interface {
|
||||
// Len returns the amount of boxes.
|
||||
Len () int
|
||||
// Tag returns whether a tag on a box is present.
|
||||
Tag (index int, tag string) bool
|
||||
// MinimumSize returns the minimum size of a box.
|
||||
MinimumSize (index int) image.Point
|
||||
// RecommendedWidth returns the reccomended 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.
|
||||
ReccomendedWidth (height int) int
|
||||
// ReccomendedHeight returns the reccomended 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 behavior may or may not be
|
||||
// respected, depending on the situation.
|
||||
ReccomendedHeight (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
|
||||
// themselves are completely transparent, and become opaque once an opaque
|
||||
// object is added as their root.
|
||||
type Window interface {
|
||||
// SetRoot sets the root child of the window. There can only be one at
|
||||
// a time, and setting it will remove the current child if there is one.
|
||||
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)
|
||||
// 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)
|
||||
// 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.
|
||||
SetResizable (x, y bool)
|
||||
// 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)
|
||||
// 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
|
||||
@@ -399,6 +316,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
|
||||
|
||||
94
style.go
Normal file
94
style.go
Normal file
@@ -0,0 +1,94 @@
|
||||
package tomo
|
||||
|
||||
import "fmt"
|
||||
import "image/color"
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
// RGBA satisfies the color.Color interface.
|
||||
func (id Color) RGBA () (r, g, b, a uint32) {
|
||||
if style == nil { return }
|
||||
return style.Colors[id].RGBA()
|
||||
}
|
||||
|
||||
var style *Style
|
||||
|
||||
// SetStyle sets the style.
|
||||
func SetStyle (sty *Style) {
|
||||
assertBackend()
|
||||
style = sty
|
||||
backend.SetStyle(sty)
|
||||
}
|
||||
|
||||
// Style can apply a visual style to different objects.
|
||||
type Style struct {
|
||||
// Rules determines which styles get applied to which Objects.
|
||||
Rules []Rule
|
||||
|
||||
// Colors maps tomo.Color values to color.RGBA values.
|
||||
Colors map[Color] color.Color
|
||||
}
|
||||
|
||||
// Rule describes under what circumstances should certain style attributes be
|
||||
// active.
|
||||
type Rule struct {
|
||||
Role Role
|
||||
Tags []string
|
||||
Default AttrSet
|
||||
Hovered AttrSet
|
||||
Pressed AttrSet
|
||||
Focused AttrSet
|
||||
}
|
||||
|
||||
// Ru is shorthand for creating a rule structure. It is a partially applied
|
||||
// function and is called like this:
|
||||
// Ru(R("package", "Object"), "tag0", "tag1", "tag2") (
|
||||
// AS( ... )
|
||||
// AS( ... )
|
||||
// AS( ... )
|
||||
// AS( ... ))
|
||||
func Ru (role Role, tags ...string) func (defaul, hovered, pressed, focused AttrSet) Rule {
|
||||
return func (defaul, hovered, pressed, focused AttrSet) Rule {
|
||||
return Rule {
|
||||
Role: role,
|
||||
Tags: tags,
|
||||
Default: defaul,
|
||||
Hovered: hovered,
|
||||
Pressed: pressed,
|
||||
Focused: focused,
|
||||
}
|
||||
}
|
||||
}
|
||||
111
theme.go
111
theme.go
@@ -1,111 +0,0 @@
|
||||
package tomo
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/tomo/tomo/data"
|
||||
import "git.tebibyte.media/tomo/tomo/event"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// 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 theme == nil { return }
|
||||
return theme.RGBA(id)
|
||||
}
|
||||
|
||||
// Theme can apply a visual style to different objects.
|
||||
type Theme 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 Theme 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.
|
||||
|
||||
// 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)
|
||||
|
||||
// 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 theme Theme
|
||||
|
||||
// SetTheme sets the theme.
|
||||
func SetTheme (them Theme) {
|
||||
theme = them
|
||||
}
|
||||
|
||||
// Apply applies the current theme to the given object, according its role. This
|
||||
// may register event listeners with the given object; closing the returned
|
||||
// cookie will remove them.
|
||||
func Apply (object Object) event.Cookie {
|
||||
if theme == nil { return event.NoCookie { } }
|
||||
return theme.Apply(object)
|
||||
}
|
||||
2
tomo.go
2
tomo.go
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
96
unit.go
Normal file
96
unit.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package tomo
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
|
||||
// Side represents one side of a rectangle.
|
||||
type Side int; const (
|
||||
SideTop Side = iota
|
||||
SideRight
|
||||
SideBottom
|
||||
SideLeft
|
||||
)
|
||||
|
||||
// Inset represents a rectangle inset that can have a different value for each
|
||||
// side.
|
||||
type Inset [4]int
|
||||
|
||||
// I allows you to create an inset in a CSS-ish way:
|
||||
//
|
||||
// - One argument: all sides are set to this value
|
||||
// - Two arguments: the top and bottom sides are set to the first value, and
|
||||
// the left and right sides are set to the second value.
|
||||
// - Three arguments: the top side is set by the first value, the left and
|
||||
// right sides are set by the second vaue, and the bottom side is set by the
|
||||
// third value.
|
||||
// - Four arguments: each value corresponds to a side.
|
||||
//
|
||||
// This function will panic if an argument count that isn't one of these is
|
||||
// given.
|
||||
func I (sides ...int) Inset {
|
||||
switch len(sides) {
|
||||
case 1: return Inset { sides[0], sides[0], sides[0], sides[0] }
|
||||
case 2: return Inset { sides[0], sides[1], sides[0], sides[1] }
|
||||
case 3: return Inset { sides[0], sides[1], sides[2], sides[1] }
|
||||
case 4: return Inset { sides[0], sides[1], sides[2], sides[3] }
|
||||
default: panic("I: illegal argument count.")
|
||||
}
|
||||
}
|
||||
|
||||
// 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[1] {
|
||||
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[0] + 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,
|
||||
}
|
||||
}
|
||||
|
||||
// Horizontal returns the sum of SideRight and SideLeft.
|
||||
func (inset Inset) Horizontal () int {
|
||||
return inset[SideRight] + inset[SideLeft]
|
||||
}
|
||||
|
||||
// Vertical returns the sum of SideTop and SideBottom.
|
||||
func (inset Inset) Vertical () int {
|
||||
return inset[SideTop] + inset[SideBottom]
|
||||
}
|
||||
|
||||
// Border represents a single border of a box.
|
||||
type Border struct {
|
||||
Width Inset
|
||||
Color [4]color.Color
|
||||
}
|
||||
|
||||
// Align lists basic alignment types.
|
||||
type Align int; const (
|
||||
AlignStart Align = iota // similar to left-aligned text
|
||||
AlignMiddle // similar to center-aligned text
|
||||
AlignEnd // similar to right-aligned text
|
||||
AlignEven // similar to justified text
|
||||
)
|
||||
Reference in New Issue
Block a user