Compare commits

...

33 Commits

Author SHA1 Message Date
8546f11471 Add config package
Closes #6
2024-09-11 01:49:25 -04:00
46f4c50381 Document the window kinds 2024-09-11 01:06:54 -04:00
b88e32fa49 Introduce the concept of window kinds 2024-09-11 00:54:45 -04:00
8f43fa310c Add new child window constructors
Closes #29
2024-09-11 00:21:57 -04:00
20c7e0fdb1 Add FuncCookie
Closes #27
2024-08-20 22:55:31 -04:00
d8a8ad7e0a Re-organize event.go 2024-08-20 22:55:20 -04:00
3feba5f811 event.Cookie is now just io.Closer
Closes #26
2024-08-20 22:50:51 -04:00
75c654d4ae Add error to Window.Close 2024-08-20 22:42:31 -04:00
da38e5411f Add TryClose event to Window
Closes #24
2024-08-20 22:41:41 -04:00
d47b525e42 Add documentation for AttrKind 2024-08-20 21:29:12 -04:00
862e08edf1 Add AttrCursor 2024-08-20 21:25:54 -04:00
47b2231acd Add list of cursor shapes
Progress on #25
2024-08-20 21:22:19 -04:00
b05e1f5d50 Fixed IconListContract being identical to IconListExpand 2024-08-18 16:16:40 -04:00
608a898be3 Fix deadlock in Stop 2024-08-16 17:40:39 -04:00
91a8ae2fa5 Add some new icons
Closes #23
2024-08-16 17:08:28 -04:00
cacfd20a8a Describe IconUnknown 2024-08-16 17:05:11 -04:00
d08fe845fc Add Bounds, InnerBounds to Window
Closes #22
2024-08-16 17:03:24 -04:00
e23a688103 Remove checkbox icons 2024-08-13 12:30:47 -04:00
f4cc47eb16 Fix meaningless panics 2024-08-13 12:17:54 -04:00
43fb3b8feb Remove AttrIcon
- Has no compelling use case (all use cases need the texture size
  which this attribute cannot provide)
- Duplicates functionality
- The rationale for adding it initially was never that strong
2024-08-11 22:23:42 -04:00
a022fa3ad4 Remove the registry mechanism
Nasin has its own registry system that is way more flexible than
what was in this module, that ought to be used instead.
2024-08-11 01:45:51 -04:00
7e3a9759ee AttrIcon has size information 2024-08-10 21:08:09 -04:00
559490e5e8 Remove AttrSet 2024-08-09 23:24:02 -04:00
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
12 changed files with 347 additions and 348 deletions

View File

@@ -2,49 +2,8 @@ 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. 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
@@ -54,6 +13,8 @@ type Attr interface {
attr ()
}
// AttrKind enumerates all attribute kinds. There is one of these for each
// attribute type.
type AttrKind int; const (
AttrKindColor AttrKind = iota
AttrKindTexture
@@ -69,11 +30,12 @@ type AttrKind int; const (
AttrKindAlign
AttrKindOverflow
AttrKindLayout
AttrKindCursor
)
// AttrColor sets the background color of a box.
type AttrColor struct { color.Color }
// AttrTexture sets the texture of a box to a named texture.
// 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
@@ -89,8 +51,8 @@ type AttrGap image.Point
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 }
// 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.
@@ -99,6 +61,8 @@ type AttrAlign struct { X, Y Align }
type AttrOverflow struct { X, Y bool }
// AttrLayout sets the layout, if the box is a ContentBox.
type AttrLayout struct { Layout }
// AttrCursor sets the mouse cursor shape.
type AttrCursor Cursor
// AColor is a convenience constructor for the color attribute.
func AColor (col color.Color) AttrColor {
@@ -137,8 +101,8 @@ func ADotColor (col color.Color) AttrDotColor {
return AttrDotColor { Color: col }
}
// AFace is a convenience constructor for the face attribute.
func AFace (face font.Face) AttrFace {
return AttrFace { Face: face }
func AFace (face Face) AttrFace {
return AttrFace(face)
}
// AWrap is a convenience constructor for the wrap attribute.
func AWrap (wrap bool) AttrWrap {
@@ -156,6 +120,10 @@ func AOverflow (x, y bool) AttrOverflow {
func ALayout (layout Layout) AttrLayout {
return AttrLayout { Layout: layout }
}
// ACursor is a convenience constructor for the cursor attribute.
func ACursor (cursor Cursor) AttrCursor {
return AttrCursor(cursor)
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrColor) Equals (other Attr) bool {
@@ -274,6 +242,14 @@ func (this AttrLayout) Equals (other Attr) bool {
// two layouts cannot "reasonably" be declared equal
return false
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrCursor) Equals (other Attr) bool {
if other, ok := other.(AttrCursor); ok {
return this == other
} else {
return false
}
}
func (AttrColor) Kind () AttrKind { return AttrKindColor }
func (AttrTexture) Kind () AttrKind { return AttrKindTexture }
@@ -289,6 +265,7 @@ 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 (AttrCursor) Kind () AttrKind { return AttrKindCursor }
func (AttrColor) attr () { }
func (AttrTexture) attr () { }
@@ -304,3 +281,4 @@ func (AttrWrap) attr () { }
func (AttrAlign) attr () { }
func (AttrOverflow) attr () { }
func (AttrLayout) attr () { }
func (AttrCursor) attr () { }

View File

@@ -1,8 +1,8 @@
package tomo
import "sort"
import "sync"
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,
@@ -18,12 +18,7 @@ type Backend interface {
NewContainerBox () ContainerBox
// NewWindow creates a normal Window and returns it.
NewWindow (image.Rectangle) (Window, error)
// NewPlainWindow creates an undecorated window that does not appear in
// window lists and returns it. This is intended for making things like
// panels, docks, etc.
NewPlainWindow (image.Rectangle) (Window, error)
NewWindow (WindowKind, image.Rectangle) (Window, error)
// NewTexture creates a new texture from an image. The backend must
// reject any texture that was not made by it.
@@ -33,22 +28,30 @@ 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)
// 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)
// 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
// Run runs the event loop until Stop() is called, or the backend
// 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.
Run () error
// Stop must unblock run.
// Stop must unblock run. This behavior may only be called from within
// tomo.Stop.
Stop ()
// Do performs a callback function in the event loop thread as soon as
@@ -56,54 +59,21 @@ type Backend interface {
Do (func ())
}
// Factory is a function that attempts to instatiate a backend. If the
// instantiation process fails at any point, it must clean up all resources and
// return nil and an error explaining what happened.
type Factory func () (Backend, error)
var backendLock sync.Mutex
var backend Backend
var registered []factoryEntry
type factoryEntry struct {
Factory
priority int
func assertBackend () {
if backend == nil { panic("nil backend") }
}
// Register registers a backend factory with the given priority number.
func Register (priority int, factory Factory) {
registered = append(registered, factoryEntry {
priority: priority,
Factory: factory,
})
}
// SetBackend sets the backend that functions in this package will call upon.
// This function will panic if there is already a backend running.
func SetBackend (back Backend) {
backendLock.Lock()
defer backendLock.Unlock()
// initialize instantiates a backend. The first backend (sorted by priority)
// that does not throw an error when initialized is used. If no backend could be
// instantiated, this function returns an error. This function should be called
// only once.
func initialize () (Backend, error) {
backend, err := instantiate()
if err != nil { return nil, err }
return backend, err
}
func instantiate () (Backend, error) {
if len(registered) < 0 {
return nil, errors.New("no available backends")
if backend != nil {
panic("SetBackend called while another backend was running")
}
// sort backends by priority
sort.Slice(registered, func (left, right int) bool {
return registered[left].priority < registered[right].priority
})
// attempt to instantiate
errorLog := ""
for _, factory := range registered {
backend, err := factory.Factory()
if err == nil {
return backend, nil
} else {
errorLog += " " + err.Error() + "\n"
}
}
return nil, errors.New("all backends failed:\n" + errorLog)
backend = back
}

15
config/config.go Normal file
View File

@@ -0,0 +1,15 @@
// Package config stores common configuration parameters. These are unmanaged,
// and must be queried each time they are used. These are intended to be set by
// things like application frameworks. Values set by objects or applications are
// subject to being overridden.
package config
import "time"
// DoubleClickDelay is the maximum amount of time that can pass between two
// consecutive clicks for them to be considered a double-click.
var DoubleClickDelay time.Duration = time.Second
// ScrollSpeed is how many units (pixels at a scale of 1.0) the content of a
// ContentBox should be moved in response to a scroll delta of 1.0.
var ScrollSpeed int = 16

35
cursor.go Normal file
View File

@@ -0,0 +1,35 @@
package tomo
// Cursor represents a mouse cursor shape.
type Cursor string
// A list of standard cursor shapes. This is based off of the XDG Cursor
// Conventions Specification
// (https://www.freedesktop.org/wiki/Specifications/cursor-spec/)
const (
CursorUnknown Cursor = ""
CursorDefault Cursor = "Default"
CursorText Cursor = "Text"
CursorPointer Cursor = "Pointer"
CursorHelp Cursor = "Help"
CursorProgress Cursor = "Progress"
CursorWait Cursor = "Wait"
CursorCopy Cursor = "Copy"
CursorAlias Cursor = "Alias"
CursorNoDrop Cursor = "NoDrop"
CursorNotAllowed Cursor = "NotAllowed"
CursorAllScroll Cursor = "AllScroll"
CursorRowResize Cursor = "RowResize"
CursorColResize Cursor = "ColResize"
CursorEResize Cursor = "EResize"
CursorNEResize Cursor = "NEResize"
CursorNWResize Cursor = "NWResize"
CursorNResize Cursor = "NResize"
CursorSEResize Cursor = "SEResize"
CursorSWResize Cursor = "SWResize"
CursorSResize Cursor = "SResize"
CursorWResize Cursor = "WResize"
CursorVerticalText Cursor = "VerticalText"
CursorCrosshair Cursor = "Crosshair"
CursorCell Cursor = "Cell"
)

View File

@@ -2,11 +2,36 @@
// handlers.
package event
import "io"
import "errors"
// A cookie is returned when you add an event handler so you can remove it
// later if you so choose.
type Cookie interface {
// Close removes the event handler this cookie is for.
Close ()
// later if you so choose. When the Close behavior is called, the handler must
// be removed, even if an error is returned.
type Cookie io.Closer
// FuncCookie is a cookie that calls a function (itself) when closed.
type FuncCookie func () error
func (cookie FuncCookie) Close () error { return cookie () }
// NoCookie is a cookie that does nothing when closed.
type NoCookie struct { }
func (NoCookie) Close () error { return nil }
type multiCookie []Cookie
// MultiCookie creates a single cookie that, when closed, closes a list of other
// cookies.
func MultiCookie (cookies ...Cookie) Cookie {
return multiCookie(cookies)
}
func (cookies multiCookie) Close () error {
errs := make([]error, len(cookies))
for index, cookie := range cookies {
errs[index] = cookie.Close()
}
return errors.Join(errs...)
}
// Broadcaster manages event listeners.
@@ -31,9 +56,9 @@ func (broadcaster *Broadcaster[L]) Listeners () map[int] L {
return broadcaster.listeners
}
func (broadcaster *Broadcaster[L]) newCookie () cookie[L] {
func (broadcaster *Broadcaster[L]) newCookie () broadcasterCookie[L] {
broadcaster.lastID ++
return cookie[L] {
return broadcasterCookie[L] {
id: broadcaster.lastID,
broadcaster: broadcaster,
}
@@ -45,17 +70,14 @@ func (broadcaster *Broadcaster[L]) ensure () {
}
}
// NoCookie is a cookie that does nothing when closed.
type NoCookie struct { }
func (NoCookie) Close () { }
type cookie[L any] struct {
type broadcasterCookie[L any] struct {
id int
broadcaster *Broadcaster[L]
}
func (cookie cookie[L]) Close () {
func (cookie broadcasterCookie[L]) Close () error {
delete(cookie.broadcaster.listeners, cookie.id)
return nil
}
// FuncBroadcaster is a broadcaster that manages functions with no arguments.
@@ -69,17 +91,3 @@ func (broadcaster *FuncBroadcaster) Broadcast () {
listener()
}
}
type multiCookie []Cookie
// MultiCookie creates a single cookie that, when closed, closes a list of other
// cookies.
func MultiCookie (cookies ...Cookie) Cookie {
return multiCookie(cookies)
}
func (cookies multiCookie) Close () {
for _, cookie := range cookies {
cookie.Close()
}
}

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=

61
icon.go
View File

@@ -16,6 +16,7 @@ type Icon string
// A list of standard icon IDs. This is roughly based off of the XDG Icon Naming
// Specification (https://specifications.freedesktop.org/icon-naming-spec/icon-naming-spec-latest.html).
const (
// IconUnknown should be a blank space the size of a regular icon.
IconUnknown Icon = ""
// actions
@@ -90,6 +91,9 @@ const (
// actions: list
IconListAdd Icon = "ListAdd"
IconListRemove Icon = "ListRemove"
IconListChoose Icon = "ListChoose"
IconListExpand Icon = "ListExpand"
IconListContract Icon = "ListContract"
// actions: mail
IconMailForward Icon = "MailForward"
IconMailMarkImportant Icon = "MailMarkImportant"
@@ -275,9 +279,6 @@ const (
IconPlaceHistory Icon = "PlaceHistory"
IconPlacePreferences Icon = "PlacePreferences"
// status: checkbox
IconCheckboxChecked Icon = "CheckboxChecked"
IconCheckboxUnchecked Icon = "CheckboxUnchecked"
// status: appointments
IconAppointmentMissed Icon = "AppointmentMissed"
IconAppointmentSoon Icon = "AppointmentSoon"
@@ -370,46 +371,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 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 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)
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)
}

View File

@@ -283,10 +283,43 @@ type BoxArranger interface {
SetBounds (index int, bounds image.Rectangle)
}
// WindowKind specifies a window's kind, which determines how it is displayed
// and managed by the operating system.
type WindowKind string; const (
// Normal is a normal window.
WindowKindNormal WindowKind = "Normal"
// Plain is an undecorated window that does not appear in window lists.
// It is intended for things like docks, panels, etc.
WindowKindPlain WindowKind = "Plain"
// Utility is a small window for toolboxes, command palletes, etc. It is
// usually given special styling and management by the OS.
WindowKindUtility WindowKind = "Utility"
// Toolbar is a small window for menus and window panes which have been
// "torn off" from their main window or position. It is usually given
// special styling and management by the OS.
WindowKindToolbar WindowKind = "Toolbar"
// Menu is an undecorated window for drop down menus, context menus,
// etc. It closes once the user interacts outside of it.
WindowKindMenu WindowKind = "Menu"
// Modal, while open, blocks all user input from reaching its parent
// window, forcing the user's attention. It is usually given special
// styling and management by the OS. Note that in some environments it
// will not be given window controls, so it should contain some "Close"
// or "Cancel" button.
WindowKindModal WindowKind = "Modal"
)
// 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 {
// Bounds returns the bounds of the window including its frame, if
// possible. This means that the top-left point of the bounds will be
// either zero or negative.
Bounds () image.Rectangle
// InnerBounds returns the inner bounds of the window, not including its
// frame. This means that the top-left point of the bounds will be zero.
InnerBounds () image.Rectangle
// 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)
@@ -303,15 +336,8 @@ type Window interface {
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
// toolboxes and tear-off menus.
NewChild (image.Rectangle) (Window, error)
// NewMenu creates a new menu window. This window is undecorated and
// will close once the user clicks outside of it.
NewMenu (image.Rectangle) (Window, error)
// NewModal creates a new modal window that blocks all input to this
// window until it is closed.
NewModal (image.Rectangle) (Window, error)
// linked to it via some other means.
NewChild (WindowKind, image.Rectangle) (Window, error)
// Modifiers returns which modifier keys on the keyboard are currently
// being held down.
Modifiers () input.Modifiers
@@ -328,8 +354,13 @@ type Window interface {
SetVisible (bool)
// Visible returns whether or not this window is visible.
Visible () bool
// Close closes the window.
Close ()
// Close closes the window. This does not trigger the TryClose event.
Close () error
// OnTryClose specifies a function to be called when the user attempts
// to close the window. If any registered handlers returns false, the
// window will not be closed. This can be used to display some sort of
// "Unsaved changes" warning to the user.
OnTryClose (func () bool) event.Cookie
// OnClose specifies a function to be called when the window is closed.
OnClose (func ()) event.Cookie
}

View File

@@ -1,80 +0,0 @@
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
Set AttrSet
}
// Ru is shorthand for creating a rule structure
func Ru (set AttrSet, role Role, tags ...string) Rule {
return Rule {
Role: role,
Tags: tags,
Set: set,
}
}

68
tomo.go
View File

@@ -2,67 +2,49 @@ package tomo
import "sync"
import "image"
import "errors"
import "git.tebibyte.media/tomo/tomo/canvas"
var backendLock sync.Mutex
var backend Backend
// TODO this really sucks. It might be a good idea to have Do be the entry point
// for every off-thread call, and Stop should just call backend.Stop within
// backend.Do. This is because Do is a queue and is not vulnerable to recursive
// locking.
// Run initializes a backend, runs the specified callback function, and runs the
// event loop in that order. This function blocks until Stop is called, or the
// backend experiences a fatal error.
func Run (callback func ()) error {
if backend != nil {
return errors.New("there is already a backend running")
}
back, err := initialize()
if err != nil { return err }
backendLock.Lock()
backend = back
backendLock.Unlock()
callback()
// callback may have called tomo.Stop
if backend == nil { return nil }
return backend.Run()
var stopping bool
var stoppingLock sync.Mutex
func isStopping () bool {
stoppingLock.Lock()
defer stoppingLock.Unlock()
return stopping
}
func setStopping (is bool) {
stoppingLock.Lock()
defer stoppingLock.Unlock()
stopping = is
}
func assertBackend () {
if backend == nil { panic("nil backend") }
}
// Stop stops the backend, unblocking run. Run may be called again after calling
// Stop.
// Stop stops the currently running backend.
func Stop () {
assertBackend()
backend.Stop()
if isStopping() { return }
setStopping(true)
backendLock.Lock()
defer backendLock.Unlock()
if backend == nil { return }
backend.Stop()
backend = nil
backendLock.Unlock()
setStopping(false)
}
// Do performs a callback function in the event loop thread as soon as possible.
func Do (callback func ()) {
backendLock.Lock()
defer backendLock.Unlock()
if backend != nil { backend.Do(callback) }
backendLock.Unlock()
}
// NewWindow creates and returns a window within the specified bounds on screen.
func NewWindow (bounds image.Rectangle) (Window, error) {
func NewWindow (kind WindowKind, bounds image.Rectangle) (Window, error) {
assertBackend()
return backend.NewWindow(bounds)
}
// NewPlainWindow is like NewWindow, but it creates an undecorated window that
// does not appear in window lists. It is intended for creating things like
// docks, panels, etc.
func NewPlainWindow (bounds image.Rectangle) (Window, error) {
assertBackend()
return backend.NewPlainWindow(bounds)
return backend.NewWindow(kind, bounds)
}
// NewBox creates and returns a basic Box.

120
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
@@ -95,6 +116,16 @@ type Align int; const (
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
@@ -103,3 +134,92 @@ type TextureMode int; const (
// 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)
}