Compare commits
152 Commits
Author | SHA1 | Date | |
---|---|---|---|
34ef447728 | |||
964381eaf4 | |||
0efaee30cc | |||
45ec1338af | |||
6ee6684eea | |||
5ae19bd434 | |||
1fb7fac874 | |||
679ee2c011 | |||
484ead0f76 | |||
018f93cbf9 | |||
8657730c25 | |||
9d63e27ab6 | |||
8a3f41c7db | |||
987bb613dd | |||
7bf62c25fe | |||
763e5db3fc | |||
dcdb411c0e | |||
efbdaef390 | |||
8546f11471 | |||
46f4c50381 | |||
b88e32fa49 | |||
8f43fa310c | |||
20c7e0fdb1 | |||
d8a8ad7e0a | |||
3feba5f811 | |||
75c654d4ae | |||
da38e5411f | |||
d47b525e42 | |||
862e08edf1 | |||
47b2231acd | |||
b05e1f5d50 | |||
608a898be3 | |||
91a8ae2fa5 | |||
cacfd20a8a | |||
d08fe845fc | |||
e23a688103 | |||
f4cc47eb16 | |||
43fb3b8feb | |||
a022fa3ad4 | |||
7e3a9759ee | |||
559490e5e8 | |||
bc38ea14e1 | |||
b264e11ea6 | |||
a01e5f8716 | |||
3de570373f | |||
a1eb53c4db | |||
750882eef1 | |||
a98d09d320 | |||
e6a4b6c70e | |||
03fab6fcc0 | |||
6fd236f96c | |||
cf092b4447 | |||
8403d621a8 | |||
92660ef7de | |||
140de5917f | |||
3bd9de9110 | |||
89f49fee71 | |||
f561b71c56 | |||
75fd31bb24 | |||
5eeb03b113 | |||
3b3ea6d837 | |||
ab4728144f | |||
1bc85775b8 | |||
2b2dca77c8 | |||
d6229459c5 | |||
9914cdeb9c | |||
ba31748b2e | |||
4b56306bd2 | |||
6859a2b0f7 | |||
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 | |||
341eea5b5d | |||
453953bbcd | |||
31ce286bc7 | |||
26cf1f4a88 | |||
6ef1f7ba78 | |||
d7093be696 | |||
64bf8f3b2d | |||
50cbac17a9 | |||
d096d40c57 | |||
34358da6eb | |||
be1b9d77e8 | |||
d71ac02748 | |||
2bbf85ebd8 | |||
320535e7f3 | |||
775fb84e9b | |||
6bf3450799 | |||
874171c9de | |||
6cfb16f9fe | |||
73e9b28eeb | |||
17039059ce | |||
a6b115b591 | |||
7c8b419c65 | |||
ca512af1a5 | |||
b4f8d11817 | |||
9c3ae7afa5 | |||
a18628c26d | |||
a9fc52c55b | |||
e745e80f09 | |||
748996c789 | |||
d4aac7e26c | |||
3d645b8064 | |||
bde1a2bc34 | |||
d43ba6b1af | |||
096c1b5e1b | |||
c44ff4a34a | |||
dc02f4dfb4 | |||
7c30b2bac0 | |||
24264bbc91 | |||
f167af3281 | |||
9a5b4ee7e8 | |||
3e561d7bf0 | |||
b96e8f744f | |||
9aa6f2900e | |||
a3fc0ce66d | |||
65bf341514 | |||
042f2f0131 | |||
4fd5e54e42 | |||
9a9e546b37 | |||
a2a5c16e38 | |||
28cd889254 | |||
e682fdd9d8 | |||
9719391e5d | |||
8a531986eb | |||
c3c6ff61f5 | |||
89f7bf47ce | |||
bebd58dac1 |
12
.editorconfig
Normal file
12
.editorconfig
Normal file
@ -0,0 +1,12 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
indent_style = tab
|
||||
indent_size = 8
|
||||
charset = utf-8
|
||||
|
||||
[*.md]
|
||||
indent_style = space
|
||||
indent_size = 2
|
@ -2,4 +2,7 @@
|
||||
|
||||
[](https://pkg.go.dev/git.tebibyte.media/tomo/tomo)
|
||||
|
||||
Tomo is a lightweight GUI toolkit written in pure Go.
|
||||
Tomo is a lightweight GUI toolkit written in pure Go. This repository defines
|
||||
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.
|
||||
|
113
application.go
113
application.go
@ -1,113 +0,0 @@
|
||||
package tomo
|
||||
|
||||
import "image"
|
||||
|
||||
// Application represents an application object.
|
||||
type Application interface {
|
||||
// Describe returns a description of the application.
|
||||
Describe () ApplicationDescription
|
||||
|
||||
// Init performs the initial setup of the application.
|
||||
Init ()
|
||||
}
|
||||
|
||||
// ApplicationDescription describes the name and type of an application.
|
||||
type ApplicationDescription struct {
|
||||
// The name or ID of the application.
|
||||
Name string
|
||||
|
||||
// Role describes what the application does.
|
||||
Role ApplicationRole
|
||||
}
|
||||
|
||||
// String satisfies the fmt.Stringer interface.
|
||||
func (application ApplicationDescription) String () string {
|
||||
if application.Name == "" {
|
||||
return application.Role.String()
|
||||
} else {
|
||||
return application.Name
|
||||
}
|
||||
}
|
||||
|
||||
// ApplicationRole describes what an application does.
|
||||
type ApplicationRole int; const (
|
||||
RoleUnknown ApplicationRole = iota
|
||||
|
||||
RoleWebBrowser
|
||||
RoleMesssanger
|
||||
RolePhone
|
||||
RoleMail
|
||||
|
||||
RoleTerminalEmulator
|
||||
RoleFileBrowser
|
||||
RoleTextEditor
|
||||
|
||||
RoleDocumentViewer
|
||||
RoleWordProcessor
|
||||
RoleSpreadsheet
|
||||
RoleSlideshow
|
||||
RoleCalculator
|
||||
|
||||
RolePreferences
|
||||
RoleProcessManager
|
||||
RoleSystemInformation
|
||||
RoleManual
|
||||
|
||||
RoleCamera
|
||||
RoleImageViewer
|
||||
RoleMediaPlayer
|
||||
RoleImageEditor
|
||||
RoleAudioEditor
|
||||
RoleVideoEditor
|
||||
|
||||
RoleClock
|
||||
RoleCalendar
|
||||
RoleChecklist
|
||||
)
|
||||
|
||||
// String satisfies the fmt.Stringer interface.
|
||||
func (role ApplicationRole) String () string {
|
||||
switch role {
|
||||
case RoleWebBrowser: return "Web Browser"
|
||||
case RoleMesssanger: return "Messsanger"
|
||||
case RolePhone: return "Phone"
|
||||
case RoleMail: return "Mail"
|
||||
case RoleTerminalEmulator: return "Terminal Emulator"
|
||||
case RoleFileBrowser: return "File Browser"
|
||||
case RoleTextEditor: return "Text Editor"
|
||||
case RoleDocumentViewer: return "Document Viewer"
|
||||
case RoleWordProcessor: return "Word Processor"
|
||||
case RoleSpreadsheet: return "Spreadsheet"
|
||||
case RoleSlideshow: return "Slideshow"
|
||||
case RoleCalculator: return "Calculator"
|
||||
case RolePreferences: return "Preferences"
|
||||
case RoleProcessManager: return "Process Manager"
|
||||
case RoleSystemInformation: return "System Information"
|
||||
case RoleManual: return "Manual"
|
||||
case RoleCamera: return "Camera"
|
||||
case RoleImageViewer: return "Image Viewer"
|
||||
case RoleMediaPlayer: return "Media Player"
|
||||
case RoleImageEditor: return "Image Editor"
|
||||
case RoleAudioEditor: return "Audio Editor"
|
||||
case RoleVideoEditor: return "Video Editor"
|
||||
case RoleClock: return "Clock"
|
||||
case RoleCalendar: return "Calendar"
|
||||
case RoleChecklist: return "Checklist"
|
||||
default: return "unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// RunApplication is like Run, but runs an application.
|
||||
func RunApplication (application Application) error {
|
||||
return Run(application.Init)
|
||||
}
|
||||
|
||||
// NewApplicationWindow creates a window for an application. It will
|
||||
// automatically set window information to signal to the OS that the window is
|
||||
// owned by the application.
|
||||
func NewApplicationWindow (application Application, bounds image.Rectangle) (Window, error) {
|
||||
window, err := NewWindow(bounds)
|
||||
if err != nil { return nil, err }
|
||||
window.SetTitle(application.Describe().String())
|
||||
return window, nil
|
||||
}
|
282
attribute.go
Normal file
282
attribute.go
Normal file
@ -0,0 +1,282 @@
|
||||
package tomo
|
||||
|
||||
import "image"
|
||||
import "image/color"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// Attr modifies one thing about a Box's style.
|
||||
type Attr interface {
|
||||
// Equals returns true if both Attrs can reasonably be declared equal.
|
||||
Equals (Attr) bool
|
||||
Kind () AttrKind
|
||||
attr ()
|
||||
}
|
||||
|
||||
// AttrKind enumerates all attribute kinds. Each Attr- type has one of these.
|
||||
type AttrKind int; const (
|
||||
AttrKindColor AttrKind = iota
|
||||
AttrKindTexture
|
||||
AttrKindTextureMode
|
||||
AttrKindBorder
|
||||
AttrKindMinimumSize
|
||||
AttrKindPadding
|
||||
AttrKindGap
|
||||
AttrKindTextColor
|
||||
AttrKindDotColor
|
||||
AttrKindFace
|
||||
AttrKindWrap
|
||||
AttrKindAlign
|
||||
AttrKindOverflow
|
||||
AttrKindLayout
|
||||
AttrKindCursor
|
||||
)
|
||||
|
||||
// AttrColor sets the background color of a Box.
|
||||
type AttrColor struct { color.Color }
|
||||
// AttrTexture sets the texture of a Box.
|
||||
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 whether or not the text wraps, if the Box is a TextBox.
|
||||
type AttrWrap bool
|
||||
// AttrAlign sets the layout 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 }
|
||||
// AttrCursor sets the mouse cursor shape.
|
||||
type AttrCursor Cursor
|
||||
|
||||
// AColor is a convenience constructor for AttrColor.
|
||||
func AColor (col color.Color) AttrColor {
|
||||
return AttrColor { Color: col }
|
||||
}
|
||||
// ATexture is a convenience constructor for AttrTexture.
|
||||
func ATexture (texture canvas.Texture) AttrTexture {
|
||||
return AttrTexture { Texture: texture }
|
||||
}
|
||||
// ATextureMode is a convenience constructor for AttrTextureMode.
|
||||
func ATextureMode (mode TextureMode) AttrTextureMode {
|
||||
return AttrTextureMode(mode)
|
||||
}
|
||||
// ABorder is a convenience constructor for AttrBorder.
|
||||
func ABorder (borders ...Border) AttrBorder {
|
||||
return AttrBorder(borders)
|
||||
}
|
||||
// AMinimumSize is a convenience constructor for AttrMinimumSize.
|
||||
func AMinimumSize (x, y int) AttrMinimumSize {
|
||||
return AttrMinimumSize(image.Pt(x, y))
|
||||
}
|
||||
// APadding is a convenience constructor for AttrPadding.
|
||||
func APadding (sides ...int) AttrPadding {
|
||||
return AttrPadding(I(sides...))
|
||||
}
|
||||
// AGap is a convenience constructor for AttrGap.
|
||||
func AGap (x, y int) AttrGap {
|
||||
return AttrGap(image.Pt(x, y))
|
||||
}
|
||||
// ATextColor is a convenience constructor for AttrTextColor.
|
||||
func ATextColor (col color.Color) AttrTextColor {
|
||||
return AttrTextColor { Color: col }
|
||||
}
|
||||
// ADotColor is a convenience constructor for AttrDotColor.
|
||||
func ADotColor (col color.Color) AttrDotColor {
|
||||
return AttrDotColor { Color: col }
|
||||
}
|
||||
// AFace is a convenience constructor for AttrFace.
|
||||
func AFace (face Face) AttrFace {
|
||||
return AttrFace(face)
|
||||
}
|
||||
// AWrap is a convenience constructor for AttrWrap.
|
||||
func AWrap (wrap bool) AttrWrap {
|
||||
return AttrWrap(wrap)
|
||||
}
|
||||
// AAlign is a convenience constructor for AttrAlign.
|
||||
func AAlign (x, y Align) AttrAlign {
|
||||
return AttrAlign { X: x, Y: y }
|
||||
}
|
||||
// AOverflow is a convenience constructor for AttrOverflow.
|
||||
func AOverflow (x, y bool) AttrOverflow {
|
||||
return AttrOverflow { X: x, Y: y }
|
||||
}
|
||||
// ALayout is a convenience constructor for AttrLayout.
|
||||
func ALayout (layout Layout) AttrLayout {
|
||||
return AttrLayout { Layout: layout }
|
||||
}
|
||||
// ACursor is a convenience constructor for AttrCursor.
|
||||
func ACursor (cursor Cursor) AttrCursor {
|
||||
return AttrCursor(cursor)
|
||||
}
|
||||
|
||||
// Equals returns true if both Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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 Attrs 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
|
||||
}
|
||||
// Equals returns true if both Attrs 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 }
|
||||
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 (AttrCursor) Kind () AttrKind { return AttrKindCursor }
|
||||
|
||||
func (AttrColor) 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 () { }
|
||||
func (AttrCursor) attr () { }
|
99
backend.go
99
backend.go
@ -1,31 +1,57 @@
|
||||
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,
|
||||
// rendering, and events so that there can be as many platform-specific
|
||||
// optimizations as possible.
|
||||
type Backend interface {
|
||||
// These methods create new objects. The backend must reject any object
|
||||
// These methods create new Objects. The backend must reject any Object
|
||||
// that was not made by it.
|
||||
NewWindow (image.Rectangle) (MainWindow, error)
|
||||
NewBox () Box
|
||||
NewTextBox () TextBox
|
||||
NewCanvasBox () CanvasBox
|
||||
NewSurfaceBox () (SurfaceBox, error)
|
||||
NewContainerBox () ContainerBox
|
||||
|
||||
// NewTexture creates a new texture from an image. The backend must
|
||||
// reject any texture that was not made by it.
|
||||
// NewWindow creates a normal Window and returns it.
|
||||
NewWindow (WindowKind, image.Rectangle) (Window, error)
|
||||
|
||||
// NewTexture creates a new canvs.Texture from an image. The backend
|
||||
// must reject any texture that was not made by it.
|
||||
NewTexture (image.Image) canvas.TextureCloser
|
||||
|
||||
// Run runs the event loop until Stop() is called, or the backend
|
||||
// NewCanvas creates a new canvas.Canvas with the specified bounds. The
|
||||
// backend must reject any canvas that was not made by it.
|
||||
NewCanvas (image.Rectangle) canvas.CanvasCloser
|
||||
|
||||
// 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)
|
||||
|
||||
// IconTexture returns the canvas.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 canvas.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
|
||||
@ -33,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
|
||||
if backend != nil {
|
||||
panic("SetBackend called while another backend was running")
|
||||
}
|
||||
|
||||
func instantiate () (Backend, error) {
|
||||
if len(registered) < 0 {
|
||||
return nil, errors.New("no available backends")
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -2,15 +2,16 @@
|
||||
// primitives.
|
||||
package canvas
|
||||
|
||||
import "io"
|
||||
import "image"
|
||||
import "image/draw"
|
||||
import "image/color"
|
||||
|
||||
// Cap represents a stroke cap type.
|
||||
type Cap int; const (
|
||||
CapButt Cap = iota // Square cap that ends at the point
|
||||
CapRound // Round cap that surrounds the point
|
||||
CapSquare // square cap that surrounds the point
|
||||
CapRound Cap = iota // Round cap that surrounds the point
|
||||
CapSquare // Square cap that surrounds the point
|
||||
CapButt // Square cap that ends at the point
|
||||
)
|
||||
|
||||
// Joint represents a stroke joint type.
|
||||
@ -30,43 +31,67 @@ type StrokeAlign int; const (
|
||||
|
||||
// Pen represents a drawing context that is linked to a canvas. Each canvas can
|
||||
// have multiple pens associated with it, each maintaining their own drawing
|
||||
// context.
|
||||
// state.
|
||||
type Pen interface {
|
||||
// Rectangle draws a rectangle
|
||||
// Rectangle draws an image.Rectangle.
|
||||
Rectangle (image.Rectangle)
|
||||
|
||||
// Path draws a path
|
||||
// Path draws a path, which is a series of connected points.
|
||||
Path (points ...image.Point)
|
||||
|
||||
Closed (bool) // if the path is closed
|
||||
Cap (Cap) // line cap stype
|
||||
Joint (Joint) // line joint style
|
||||
StrokeWeight (int) // how thick the stroke is
|
||||
StrokeAlign (StrokeAlign) // where the stroke is drawn
|
||||
// StrokeWeight sets how thick the stroke is. The default value is zero.
|
||||
StrokeWeight (int)
|
||||
// SetClosed sets whether the path is a closed shape, or has an open
|
||||
// side. This only applies if the stroke weight is greater than zero.
|
||||
Closed (bool)
|
||||
// Cap sets how the ends of the stroke look. This only applies if the
|
||||
// stroke weight is greater than zero, and if the path is not closed.
|
||||
// The default value is CapRound.
|
||||
Cap (Cap)
|
||||
// Joint sets how bend points in the stroke look. This only applies if
|
||||
// the stroke weight is greater than zero. The default value is
|
||||
// JointRound.
|
||||
Joint (Joint)
|
||||
// StrokeAlign sets where the stroke is drawn in relation to the path.
|
||||
// This only applies if the stroke weight is greater than zero. The
|
||||
// default value is StrokeAlignCenter.
|
||||
StrokeAlign (StrokeAlign)
|
||||
|
||||
Stroke (color.Color) // Sets the stroke to a solid color
|
||||
Fill (color.Color) // Sets the fill to a solid color
|
||||
Texture (Texture) // Overlaps a texture onto the fill color
|
||||
// Stroke sets the stroke to a solid color.
|
||||
Stroke (color.Color)
|
||||
// Fill sets the fill to a solid color.
|
||||
Fill (color.Color)
|
||||
// Texture overlaps a texture onto the fill color.
|
||||
Texture (Texture)
|
||||
}
|
||||
|
||||
// Canvas is an image that supports drawing paths.
|
||||
type Canvas interface {
|
||||
draw.Image
|
||||
|
||||
// Pen returns a new pen for this canvas.
|
||||
// Pen returns a new pen for this Canvas.
|
||||
Pen () Pen
|
||||
|
||||
// Clip returns a new canvas that points to a specific area of this one.
|
||||
Clip (image.Rectangle) Canvas
|
||||
// SubCanvas returns a returns a Canvas representing the portion of this
|
||||
// Canvas visible and drawable through a rectangle. The returned value
|
||||
// shares pixels with the original Canvas.
|
||||
SubCanvas (image.Rectangle) Canvas
|
||||
}
|
||||
|
||||
// Drawer is an object that can draw to a canvas.
|
||||
// CanvasCloser is a canvas that can be closed. Anything that receives a
|
||||
// CanvasCloser must close it after use.
|
||||
type CanvasCloser interface {
|
||||
Canvas
|
||||
io.Closer
|
||||
}
|
||||
|
||||
// Drawer can draw to a canvas.
|
||||
type Drawer interface {
|
||||
// Draw draws to the given canvas.
|
||||
// Draw draws to the given Canvas.
|
||||
Draw (Canvas)
|
||||
}
|
||||
|
||||
// PushCanvas is a canvas that can push a region of itself to the screen (or
|
||||
// PushCanvas is a Canvas that can push a region of itself to the screen (or
|
||||
// some other destination).
|
||||
type PushCanvas interface {
|
||||
Canvas
|
||||
|
@ -1,8 +1,13 @@
|
||||
package canvas
|
||||
|
||||
import "image"
|
||||
import "slices"
|
||||
|
||||
// Shatter takes in a bounding rectangle, and several rectangles to be
|
||||
// TODO look into other options besides returning a slice, as that causes memory
|
||||
// allocations. Perhaps an iterator? Maybe we could also *take in* an iterator
|
||||
// for the rocks. This could result in zero allocations.
|
||||
|
||||
// Shatter takes in a bounding rectangle, and several image.Rectangles to be
|
||||
// subtracted from it. It returns a slice of rectangles that tile together to
|
||||
// make up the difference between them. This is intended to be used for figuring
|
||||
// out which areas of a container box's background are covered by other boxes so
|
||||
@ -33,7 +38,7 @@ func Shatter (
|
||||
} else {
|
||||
// the tile was entirely obscured by the rock
|
||||
// and must be wholly removed
|
||||
tiles = remove(tiles, tileIndex)
|
||||
tiles = slices.Delete(tiles, tileIndex, tileIndex + 1)
|
||||
tileIndex --
|
||||
tileLen --
|
||||
}
|
||||
@ -90,7 +95,3 @@ func shatterOnce (glass, rock image.Rectangle) (tiles [4]image.Rectangle, n int)
|
||||
); n ++ }
|
||||
return
|
||||
}
|
||||
|
||||
func remove[ELEMENT any] (slice []ELEMENT, s int) []ELEMENT {
|
||||
return append(slice[:s], slice[s + 1:]...)
|
||||
}
|
||||
|
@ -3,16 +3,17 @@ package canvas
|
||||
import "io"
|
||||
import "image"
|
||||
|
||||
// Texture is a handle that points to a 2D raster image managed by the backend.
|
||||
// Texture is a handle that points to a 2D raster image.
|
||||
type Texture interface {
|
||||
image.Image
|
||||
|
||||
// Clip returns a smaller section of this texture, pointing to the same
|
||||
// internal data.
|
||||
Clip (image.Rectangle) Texture
|
||||
// SubTexture returns a returns a Texture representing the portion of
|
||||
// this Texture visible through a rectangle. The returned value shares
|
||||
// pixels with the original Texture.
|
||||
SubTexture (image.Rectangle) Texture
|
||||
}
|
||||
|
||||
// TextureCloser is a texture that can be closed. Anything that receives a
|
||||
// TextureCloser is a Texture that can be closed. Anything that receives a
|
||||
// TextureCloser must close it after use.
|
||||
type TextureCloser interface {
|
||||
Texture
|
||||
|
44
config/config.go
Normal file
44
config/config.go
Normal file
@ -0,0 +1,44 @@
|
||||
// Package config stores common configuration parameters. They are unmanaged,
|
||||
// and do not broadcast events when changed. Thus, they must be queried each
|
||||
// time they are used, whether that be by backends, applications, or objects.
|
||||
// They are intended to be set by things like application frameworks. Values set
|
||||
// by other things are subject to being overridden.
|
||||
package config
|
||||
|
||||
import "time"
|
||||
import "git.tebibyte.media/tomo/tomo/input"
|
||||
|
||||
// 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
|
||||
|
||||
// KeyChordConfirm is used to activate focused objects, and commonly to signal
|
||||
// to the application that the user wishes to submit a value they have entered
|
||||
// using the keyboard into an input field.
|
||||
var KeyChordConfirm = input.KC(input.KeyEnter, input.ModNone)
|
||||
|
||||
// KeyChordClose is used to break out of some mode or state, such as a modal
|
||||
// dialog.
|
||||
var KeyChordClose = input.KC(input.KeyEscape, input.ModNone)
|
||||
|
||||
// KeyChordFocusNext is used to advance the input focus to the next focusable
|
||||
// object.
|
||||
var KeyChordFocusNext = input.KC(input.KeyTab, input.ModNone)
|
||||
|
||||
// KeyChordFocusPrevious is used to advance the input focus to the previous
|
||||
// focusable object.
|
||||
var KeyChordFocusPrevious = input.KC(input.KeyTab, input.ModShift)
|
||||
|
||||
// ButtonChordInteract is used to select, activate, and drag objects.
|
||||
var ButtonChordInteract = input.BC(input.ButtonLeft, input.ModNone)
|
||||
|
||||
// ButtonChordContextMenu is used to open a context menu on an object.
|
||||
var ButtonChordContextMenu = input.BC(input.ButtonRight, input.ModNone)
|
||||
|
||||
// ButtonChordPan is used to move the content of a content object relative to
|
||||
// its inner bounds.
|
||||
var ButtonChordPan = input.BC(input.ButtonMiddle, input.ModNone)
|
35
cursor.go
Normal file
35
cursor.go
Normal 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"
|
||||
)
|
110
data/data.go
110
data/data.go
@ -3,10 +3,20 @@ 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 {
|
||||
@ -14,46 +24,106 @@ type Mime struct {
|
||||
// half. The separating slash is not included in either. For example,
|
||||
// text/html becomes:
|
||||
// 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,
|
||||
}
|
||||
}
|
||||
|
||||
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 further on in the list will take precedence.
|
||||
// the same mime type, the ones first 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
|
||||
return mergedData(individual)
|
||||
}
|
||||
|
@ -2,11 +2,37 @@
|
||||
// handlers.
|
||||
package event
|
||||
|
||||
// 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 ()
|
||||
import "io"
|
||||
import "iter"
|
||||
import "errors"
|
||||
|
||||
// Cookie is returned when you add an event handler so you can remove it 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.
|
||||
@ -16,7 +42,7 @@ type Broadcaster[L any] struct {
|
||||
}
|
||||
|
||||
// Connect adds a new listener to the broadcaster and returns a corresponding
|
||||
// cookie.
|
||||
// Cookie.
|
||||
func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
|
||||
broadcaster.ensure()
|
||||
|
||||
@ -25,15 +51,19 @@ func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
|
||||
return cookie
|
||||
}
|
||||
|
||||
// Listeners returns a map of all connected listeners.
|
||||
func (broadcaster *Broadcaster[L]) Listeners () map[int] L {
|
||||
// Listeners returns an iterator over all listeners.
|
||||
func (broadcaster *Broadcaster[L]) Listeners () iter.Seq[L] {
|
||||
broadcaster.ensure()
|
||||
return broadcaster.listeners
|
||||
return func (yield func (listener L) bool) {
|
||||
for _, listener := range broadcaster.listeners {
|
||||
if yield(listener) { break }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,41 +75,24 @@ 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.
|
||||
// FuncBroadcaster is a Broadcaster that manages functions with no arguments.
|
||||
type FuncBroadcaster struct {
|
||||
Broadcaster[func ()]
|
||||
}
|
||||
|
||||
// Broadcast calls all connected listener funcs.
|
||||
func (broadcaster *FuncBroadcaster) Broadcast () {
|
||||
for _, listener := range broadcaster.Listeners() {
|
||||
for listener := range broadcaster.Listeners() {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
4
go.mod
4
go.mod
@ -1,5 +1,3 @@
|
||||
module git.tebibyte.media/tomo/tomo
|
||||
|
||||
go 1.20
|
||||
|
||||
require golang.org/x/image v0.11.0
|
||||
go 1.23.0
|
||||
|
36
go.sum
36
go.sum
@ -1,36 +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.8.0 h1:agUcRXV/+w6L9ryntYYsF2x9fQTMd4T8fiiYXAVW6Jg=
|
||||
golang.org/x/image v0.8.0/go.mod h1:PwLxp3opCYg4WR2WO9P0L6ESnsD6bLTWcw8zanLMVFM=
|
||||
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.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
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=
|
400
icon.go
Normal file
400
icon.go
Normal file
@ -0,0 +1,400 @@
|
||||
package tomo
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/tomo/tomo/data"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// IconSize represents the size of an Icon.
|
||||
type IconSize int; const (
|
||||
IconSizeSmall IconSize = iota;
|
||||
IconSizeMedium
|
||||
IconSizeLarge
|
||||
)
|
||||
|
||||
func (size IconSize) String () string {
|
||||
switch size {
|
||||
case IconSizeSmall: return "IconSizeSmall"
|
||||
case IconSizeMedium: return "IconSizeMedium"
|
||||
case IconSizeLarge: return "IconSizeLarge"
|
||||
default: return fmt.Sprintf("IconSize(%d)", size)
|
||||
}
|
||||
}
|
||||
|
||||
// Icon represents an icon ID.
|
||||
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
|
||||
IconAddressBookNew Icon = "AddressBookNew"
|
||||
IconApplicationExit Icon = "ApplicationExit"
|
||||
IconAppointmentNew Icon = "AppointmentNew"
|
||||
IconCallStart Icon = "CallStart"
|
||||
IconCallStop Icon = "CallStop"
|
||||
IconContactNew Icon = "ContactNew"
|
||||
// actions: dialog
|
||||
IconDialogOkay Icon = "DialogOkay"
|
||||
IconDialogCancel Icon = "DialogCancel"
|
||||
// actions: edit
|
||||
IconEditClear Icon = "EditClear"
|
||||
IconEditCopy Icon = "EditCopy"
|
||||
IconEditCut Icon = "EditCut"
|
||||
IconEditDelete Icon = "EditDelete"
|
||||
IconEditFind Icon = "EditFind"
|
||||
IconEditFindReplace Icon = "EditFindReplace"
|
||||
IconEditPaste Icon = "EditPaste"
|
||||
IconEditRedo Icon = "EditRedo"
|
||||
IconEditSelectAll Icon = "EditSelectAll"
|
||||
IconEditUndo Icon = "EditUndo"
|
||||
// actions: file
|
||||
IconFileNew Icon = "FileNew"
|
||||
IconDirectoryNew Icon = "DirectoryNew"
|
||||
IconFileOpen Icon = "FileOpen"
|
||||
IconFileOpenRecent Icon = "FileOpenRecent"
|
||||
IconFilePageSetup Icon = "FilePageSetup"
|
||||
IconFilePrint Icon = "FilePrint"
|
||||
IconFilePrintPreview Icon = "FilePrintPreview"
|
||||
IconFilePermissions Icon = "FilePermissions"
|
||||
IconFileProperties Icon = "FileProperties"
|
||||
IconFileRename Icon = "FileRename"
|
||||
IconFileRevert Icon = "FileRevert"
|
||||
IconFileSave Icon = "FileSave"
|
||||
IconFileSaveAs Icon = "FileSaveAs"
|
||||
IconFileSend Icon = "FileSend"
|
||||
// actions: format
|
||||
IconFormatIndentLess Icon = "FormatIndentLess"
|
||||
IconFormatIndentMore Icon = "FormatIndentMore"
|
||||
IconFormatAlignCenter Icon = "FormatAlignCenter"
|
||||
IconFormatAlignEven Icon = "FormatAlignEven"
|
||||
IconFormatAlignLeft Icon = "FormatAlignLeft"
|
||||
IconFormatAlignRight Icon = "FormatAlignRight"
|
||||
IconFormatTextDirectionLtr Icon = "FormatTextDirectionLtr"
|
||||
IconFormatTextDirectionRtl Icon = "FormatTextDirectionRtl"
|
||||
IconFormatTextBold Icon = "FormatTextBold"
|
||||
IconFormatTextItalic Icon = "FormatTextItalic"
|
||||
IconFormatTextUnderline Icon = "FormatTextUnderline"
|
||||
IconFormatTextStrikethrough Icon = "FormatTextStrikethrough"
|
||||
// actions: go
|
||||
IconGoBottom Icon = "GoBottom"
|
||||
IconGoDown Icon = "GoDown"
|
||||
IconGoFirst Icon = "GoFirst"
|
||||
IconGoHome Icon = "GoHome"
|
||||
IconGoJump Icon = "GoJump"
|
||||
IconGoLast Icon = "GoLast"
|
||||
IconGoNext Icon = "GoNext"
|
||||
IconGoPrevious Icon = "GoPrevious"
|
||||
IconGoTop Icon = "GoTop"
|
||||
IconGoUp Icon = "GoUp"
|
||||
// actions: help
|
||||
IconHelpAbout Icon = "HelpAbout"
|
||||
IconHelpContents Icon = "HelpContents"
|
||||
IconHelpFaq Icon = "HelpFaq"
|
||||
// actions: insert
|
||||
IconInsertImage Icon = "InsertImage"
|
||||
IconInsertLink Icon = "InsertLink"
|
||||
IconInsertObject Icon = "InsertObject"
|
||||
IconInsertText Icon = "InsertText"
|
||||
// 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"
|
||||
IconMailMarkJunk Icon = "MailMarkJunk"
|
||||
IconMailMarkNotJunk Icon = "MailMarkNotJunk"
|
||||
IconMailMarkRead Icon = "MailMarkRead"
|
||||
IconMailMarkUnread Icon = "MailMarkUnread"
|
||||
IconMailMessageNew Icon = "MailMessageNew"
|
||||
IconMailReplyAll Icon = "MailReplyAll"
|
||||
IconMailReplySender Icon = "MailReplySender"
|
||||
IconMailSend Icon = "MailSend"
|
||||
IconMailReceive Icon = "MailReceive"
|
||||
// actions: media
|
||||
IconMediaEject Icon = "MediaEject"
|
||||
IconMediaPlaybackPause Icon = "MediaPlaybackPause"
|
||||
IconMediaPlaybackStart Icon = "MediaPlaybackStart"
|
||||
IconMediaPlaybackStop Icon = "MediaPlaybackStop"
|
||||
IconMediaRecord Icon = "MediaRecord"
|
||||
IconMediaSeekBackward Icon = "MediaSeekBackward"
|
||||
IconMediaSeekForward Icon = "MediaSeekForward"
|
||||
IconMediaSkipBackward Icon = "MediaSkipBackward"
|
||||
IconMediaSkipForward Icon = "MediaSkipForward"
|
||||
// actions: object
|
||||
IconObjectFlipHorizontal Icon = "ObjectFlipHorizontal"
|
||||
IconObjectFlipVertical Icon = "ObjectFlipVertical"
|
||||
IconObjectRotateLeft Icon = "ObjectRotateLeft"
|
||||
IconObjectRotateRight Icon = "ObjectRotateRight"
|
||||
// actions: process
|
||||
IconProcessStop Icon = "ProcessStop"
|
||||
// actions: system
|
||||
IconSystemLockScreen Icon = "SystemLockScreen"
|
||||
IconSystemLogOut Icon = "SystemLogOut"
|
||||
IconSystemRun Icon = "SystemRun"
|
||||
IconSystemSearch Icon = "SystemSearch"
|
||||
IconSystemReboot Icon = "SystemReboot"
|
||||
IconSystemShutdown Icon = "SystemShutdown"
|
||||
// actions: tools
|
||||
IconToolsCheckSpelling Icon = "ToolsCheckSpelling"
|
||||
// actions: value
|
||||
IconValueIncrement Icon = "ValueIncrement"
|
||||
IconValueDecrement Icon = "ValueDecrement"
|
||||
IconValueReset Icon = "ValueReset"
|
||||
// actions: view
|
||||
IconViewFullscreen Icon = "ViewFullscreen"
|
||||
IconViewRefresh Icon = "ViewRefresh"
|
||||
IconViewRestore Icon = "ViewRestore"
|
||||
IconViewSortAscending Icon = "ViewSortAscending"
|
||||
IconViewSortDescending Icon = "ViewSortDescending"
|
||||
// actions: window
|
||||
IconWindowClose Icon = "WindowClose"
|
||||
IconWindowNew Icon = "WindowNew"
|
||||
// actions: zoom
|
||||
IconZoomFitBest Icon = "ZoomFitBest"
|
||||
IconZoomIn Icon = "ZoomIn"
|
||||
IconZoomOriginal Icon = "ZoomOriginal"
|
||||
IconZoomOut Icon = "ZoomOut"
|
||||
|
||||
// applications
|
||||
// Keep these in sync with nasin.ApplicationRole!
|
||||
IconApplication Icon = "Application" // generic
|
||||
IconApplicationWebBrowser Icon = "ApplicationWebBrowser"
|
||||
IconApplicationMesssanger Icon = "ApplicationMesssanger"
|
||||
IconApplicationPhone Icon = "ApplicationPhone"
|
||||
IconApplicationMail Icon = "ApplicationMail"
|
||||
IconApplicationTerminalEmulator Icon = "ApplicationTerminalEmulator"
|
||||
IconApplicationFileBrowser Icon = "ApplicationFileBrowser"
|
||||
IconApplicationTextEditor Icon = "ApplicationTextEditor"
|
||||
IconApplicationDocumentViewer Icon = "ApplicationDocumentViewer"
|
||||
IconApplicationWordProcessor Icon = "ApplicationWordProcessor"
|
||||
IconApplicationSpreadsheet Icon = "ApplicationSpreadsheet"
|
||||
IconApplicationSlideshow Icon = "ApplicationSlideshow"
|
||||
IconApplicationCalculator Icon = "ApplicationCalculator"
|
||||
IconApplicationPreferences Icon = "ApplicationPreferences"
|
||||
IconApplicationProcessManager Icon = "ApplicationProcessManager"
|
||||
IconApplicationSystemInformation Icon = "ApplicationSystemInformation"
|
||||
IconApplicationManual Icon = "ApplicationManual"
|
||||
IconApplicationCamera Icon = "ApplicationCamera"
|
||||
IconApplicationImageViewer Icon = "ApplicationImageViewer"
|
||||
IconApplicationMediaPlayer Icon = "ApplicationMediaPlayer"
|
||||
IconApplicationImageEditor Icon = "ApplicationImageEditor"
|
||||
IconApplicationAudioEditor Icon = "ApplicationAudioEditor"
|
||||
IconApplicationVideoEditor Icon = "ApplicationVideoEditor"
|
||||
IconApplicationClock Icon = "ApplicationClock"
|
||||
IconApplicationCalendar Icon = "ApplicationCalendar"
|
||||
IconApplicationChecklist Icon = "ApplicationChecklist"
|
||||
|
||||
// categories: applications
|
||||
IconApplications Icon = "Applications"
|
||||
IconApplicationsAccessories Icon = "ApplicationsAccessories"
|
||||
IconApplicationsDevelopment Icon = "ApplicationsDevelopment"
|
||||
IconApplicationsEngineering Icon = "ApplicationsEngineering"
|
||||
IconApplicationsGames Icon = "ApplicationsGames"
|
||||
IconApplicationsGraphics Icon = "ApplicationsGraphics"
|
||||
IconApplicationsInternet Icon = "ApplicationsInternet"
|
||||
IconApplicationsMultimedia Icon = "ApplicationsMultimedia"
|
||||
IconApplicationsOffice Icon = "ApplicationsOffice"
|
||||
IconApplicationsScience Icon = "ApplicationsScience"
|
||||
IconApplicationsSystem Icon = "ApplicationsSystem"
|
||||
IconApplicationsUtilities Icon = "ApplicationsUtilities"
|
||||
// categories: preferences
|
||||
IconPreferences Icon = "Preferences"
|
||||
IconPreferencesDesktop Icon = "PreferencesDesktop"
|
||||
IconPreferencesPeripherals Icon = "PreferencesPeripherals"
|
||||
IconPreferencesPersonal Icon = "PreferencesPersonal"
|
||||
IconPreferencesSystem Icon = "PreferencesSystem"
|
||||
IconPreferencesNetwork Icon = "PreferencesNetwork"
|
||||
|
||||
// devices
|
||||
IconDevice Icon = "Device"
|
||||
IconDeviceCamera Icon = "DeviceCamera"
|
||||
IconDeviceWebCamera Icon = "DeviceWebCamera"
|
||||
IconDeviceComputer Icon = "DeviceComputer"
|
||||
IconDevicePda Icon = "DevicePda"
|
||||
IconDevicePhone Icon = "DevicePhone"
|
||||
IconDevicePrinter Icon = "DevicePrinter"
|
||||
IconDeviceScanner Icon = "DeviceScanner"
|
||||
IconDeviceMultimediaPlayer Icon = "DeviceMultimediaPlayer"
|
||||
IconDeviceVideoDisplay Icon = "DeviceVideoDisplay"
|
||||
IconDeviceAudioInput Icon = "DeviceAudioInput"
|
||||
IconDeviceAudioOutput Icon = "DeviceAudioOutput"
|
||||
// devices: hardware
|
||||
IconHardware Icon = "Hardware"
|
||||
IconHardwareCPU Icon = "HardwareCPU"
|
||||
IconHardwareGPU Icon = "HardwareGPU"
|
||||
IconHardwareRAM Icon = "HardwareRAM"
|
||||
IconHardwareSoundCard Icon = "HardwareSoundCard"
|
||||
IconHardwareNetworkAdapter Icon = "HardwareNetworkAdapter"
|
||||
// devices: power
|
||||
IconPowerBattery Icon = "PowerBattery"
|
||||
// devices: storage
|
||||
IconStorageHardDisk Icon = "StorageHardDisk"
|
||||
IconStorageFloppyDisk Icon = "StorageFloppyDisk"
|
||||
IconStorageSolidState Icon = "StorageSolidState"
|
||||
IconStorageOptical Icon = "StorageOptical"
|
||||
IconStorageFlashStick Icon = "StorageFlashStick"
|
||||
IconStorageFlashCard Icon = "StorageFlashCard"
|
||||
IconStorageMagneticTape Icon = "StorageMagneticTape"
|
||||
// devices: input
|
||||
IconInputGaming Icon = "InputGaming"
|
||||
IconInputKeyboard Icon = "InputKeyboard"
|
||||
IconInputMouse Icon = "InputMouse"
|
||||
IconInputTablet Icon = "InputTablet"
|
||||
// devices: network
|
||||
IconNetworkWired Icon = "NetworkWired"
|
||||
IconNetworkWireless Icon = "NetworkWireless"
|
||||
IconNetworkCellular Icon = "NetworkCellular"
|
||||
IconNetworkLocal Icon = "NetworkLocal"
|
||||
IconNetworkInternet Icon = "NetworkInternet"
|
||||
IconNetworkVPN Icon = "NetworkVPN"
|
||||
IconNetworkServer Icon = "NetworkServer"
|
||||
IconNetworkWorkgroup Icon = "NetworkWorkgroup"
|
||||
|
||||
// emblems
|
||||
IconEmblemDefault Icon = "EmblemDefault"
|
||||
IconEmblemEncrypted Icon = "EmblemEncrypted"
|
||||
IconEmblemFavorite Icon = "EmblemFavorite"
|
||||
IconEmblemImportant Icon = "EmblemImportant"
|
||||
IconEmblemReadOnly Icon = "EmblemReadOnly"
|
||||
IconEmblemShared Icon = "EmblemShared"
|
||||
IconEmblemSymbolicLink Icon = "EmblemSymbolicLink"
|
||||
IconEmblemSynchronized Icon = "EmblemSynchronized"
|
||||
IconEmblemSystem Icon = "EmblemSystem"
|
||||
IconEmblemUnreadable Icon = "EmblemUnreadable"
|
||||
|
||||
// places
|
||||
IconPlaceDirectory Icon = "PlaceDirectory"
|
||||
IconPlaceRemote Icon = "PlaceRemote"
|
||||
IconPlaceHome Icon = "PlaceHome"
|
||||
IconPlaceDownloads Icon = "PlaceDownloads"
|
||||
IconPlaceDesktop Icon = "PlaceDesktop"
|
||||
IconPlacePhotos Icon = "PlacePhotos"
|
||||
IconPlaceBooks Icon = "PlaceBooks"
|
||||
IconPlaceBookmarks Icon = "PlaceBookmarks"
|
||||
IconPlaceTrash Icon = "PlaceTrash"
|
||||
IconPlaceDocuments Icon = "PlaceDocuments"
|
||||
IconPlaceRepositories Icon = "PlaceRepositories"
|
||||
IconPlaceMusic Icon = "PlaceMusic"
|
||||
IconPlaceArchives Icon = "PlaceArchives"
|
||||
IconPlaceFonts Icon = "PlaceFonts"
|
||||
IconPlaceBinaries Icon = "PlaceBinaries"
|
||||
IconPlaceVideos Icon = "PlaceVideos"
|
||||
IconPlace3DObjects Icon = "Place3DObjects"
|
||||
IconPlaceHistory Icon = "PlaceHistory"
|
||||
IconPlacePreferences Icon = "PlacePreferences"
|
||||
|
||||
// status: appointments
|
||||
IconAppointmentMissed Icon = "AppointmentMissed"
|
||||
IconAppointmentSoon Icon = "AppointmentSoon"
|
||||
// status: dialogs
|
||||
IconDialogError Icon = "DialogError"
|
||||
IconDialogInformation Icon = "DialogInformation"
|
||||
IconDialogPassword Icon = "DialogPassword"
|
||||
IconDialogQuestion Icon = "DialogQuestion"
|
||||
IconDialogWarning Icon = "DialogWarning"
|
||||
// status: directories
|
||||
IconDirectoryDragAccept Icon = "DirectoryDragAccept"
|
||||
IconDirectoryFull Icon = "DirectoryFull"
|
||||
IconDirectoryOpen Icon = "DirectoryOpen"
|
||||
IconDirectoryVisiting Icon = "DirectoryVisiting"
|
||||
// status: trash
|
||||
IconTrashFull Icon = "TrashFull"
|
||||
// status: resource
|
||||
IconResourceLoading Icon = "ResourceLoading"
|
||||
IconResourceMissing Icon = "ResourceMissing"
|
||||
// status: mail
|
||||
IconMailAttachment Icon = "MailAttachment"
|
||||
IconMailUnread Icon = "MailUnread"
|
||||
IconMailReplied Icon = "MailReplied"
|
||||
IconMailSigned Icon = "MailSigned"
|
||||
IconMailSignedVerified Icon = "MailSignedVerified"
|
||||
// status: network
|
||||
IconCellularSignal0 Icon = "CellularSignal0"
|
||||
IconCellularSignal1 Icon = "CellularSignal1"
|
||||
IconCellularSignal2 Icon = "CellularSignal2"
|
||||
IconCellularSignal3 Icon = "CellularSignal3"
|
||||
IconWirelessSignal0 Icon = "WirelessSignal0"
|
||||
IconWirelessSignal1 Icon = "WirelessSignal1"
|
||||
IconWirelessSignal2 Icon = "WirelessSignal2"
|
||||
IconWirelessSignal3 Icon = "WirelessSignal3"
|
||||
IconNetworkError Icon = "NetworkError"
|
||||
IconNetworkIdle Icon = "NetworkIdle"
|
||||
IconNetworkOffline Icon = "NetworkOffline"
|
||||
IconNetworkReceive Icon = "NetworkReceive"
|
||||
IconNetworkTransmit Icon = "NetworkTransmit"
|
||||
IconNetworkTransmitReceive Icon = "NetworkTransmitReceive"
|
||||
// status: print
|
||||
IconPrintError Icon = "PrintError"
|
||||
IconPrintPrinting Icon = "PrintPrinting"
|
||||
// status: security
|
||||
IconSecurityHigh Icon = "SecurityHigh"
|
||||
IconSecurityMedium Icon = "SecurityMedium"
|
||||
IconSecurityLow Icon = "SecurityLow"
|
||||
// status: software
|
||||
IconSoftwareUpdateAvailable Icon = "SoftwareUpdateAvailable"
|
||||
IconSoftwareUpdateUrgent Icon = "SoftwareUpdateUrgent"
|
||||
IconSoftwareInstalling Icon = "SoftwareInstalling"
|
||||
// status: sync
|
||||
IconSyncError Icon = "SyncError"
|
||||
IconSyncSynchronizing Icon = "SyncSynchronizing"
|
||||
// status: tasks
|
||||
IconTaskDue Icon = "TaskDue"
|
||||
IconTaskPastDue Icon = "TaskPastDue"
|
||||
// status: users
|
||||
IconUserAvailable Icon = "UserAvailable"
|
||||
IconUserAway Icon = "UserAway"
|
||||
IconUserIdle Icon = "UserIdle"
|
||||
IconUserOffline Icon = "UserOffline"
|
||||
// status: power
|
||||
IconBattery0 Icon = "Battery0"
|
||||
IconBattery1 Icon = "Battery1"
|
||||
IconBattery2 Icon = "Battery2"
|
||||
IconBattery3 Icon = "Battery3"
|
||||
IconBrightness0 Icon = "Brightness0"
|
||||
IconBrightness1 Icon = "Brightness1"
|
||||
IconBrightness2 Icon = "Brightness2"
|
||||
IconBrightness3 Icon = "Brightness3"
|
||||
// status: media
|
||||
IconVolume0 Icon = "Volume0"
|
||||
IconVolume1 Icon = "Volume1"
|
||||
IconVolume2 Icon = "Volume2"
|
||||
IconVolume3 Icon = "Volume3"
|
||||
IconPlaylistRepeat Icon = "PlaylistRepeat"
|
||||
IconPlaylistShuffle Icon = "PlaylistShuffle"
|
||||
// status: weather
|
||||
IconWeatherClear Icon = "WeatherClear"
|
||||
IconWeatherClearNight Icon = "WeatherClearNight"
|
||||
IconWeatherFewClouds Icon = "WeatherFewClouds"
|
||||
IconWeatherFewCloudsNight Icon = "WeatherFewCloudsNight"
|
||||
IconWeatherFog Icon = "WeatherFog"
|
||||
IconWeatherOvercast Icon = "WeatherOvercast"
|
||||
IconWeatherSevereAlert Icon = "WeatherSevereAlert"
|
||||
IconWeatherShowers Icon = "WeatherShowers"
|
||||
IconWeatherShowersScattered Icon = "WeatherShowersScattered"
|
||||
IconWeatherSnow Icon = "WeatherSnow"
|
||||
IconWeatherStorm Icon = "WeatherStorm"
|
||||
)
|
||||
|
||||
// Texture returns a canvas.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 {
|
||||
assertBackend()
|
||||
return backend.IconTexture(id, size)
|
||||
}
|
||||
|
||||
// MimeIconTexture returns a canvas.Texture of the 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)
|
||||
}
|
@ -107,11 +107,80 @@ func (key Key) Printable () (printable bool) {
|
||||
// Modifiers lists what modifier keys are being pressed. These should be used
|
||||
// instead of attempting to track the state of the modifier keys, because there
|
||||
// is no guarantee that one press event will be coupled with one release event.
|
||||
type Modifiers struct {
|
||||
Shift bool
|
||||
Control bool
|
||||
Alt bool
|
||||
Meta bool
|
||||
Super bool
|
||||
Hyper bool
|
||||
type Modifiers uint; const (
|
||||
ModNone Modifiers = 0
|
||||
ModShift Modifiers = 1 << iota
|
||||
ModControl
|
||||
ModAlt
|
||||
ModMeta
|
||||
ModSuper
|
||||
ModHyper
|
||||
)
|
||||
|
||||
// Shift returns whether the list of modifiers includes the shift key.
|
||||
func (modifiers Modifiers) Shift () bool {
|
||||
return modifiers & ModShift != ModNone
|
||||
}
|
||||
|
||||
// Control returns whether the list of modifiers includes the control key.
|
||||
func (modifiers Modifiers) Control () bool {
|
||||
return modifiers & ModControl != ModNone
|
||||
}
|
||||
|
||||
// Alt returns whether the list of modifiers includes the alt key.
|
||||
func (modifiers Modifiers) Alt () bool {
|
||||
return modifiers & ModAlt != ModNone
|
||||
}
|
||||
|
||||
// Meta returns whether the list of modifiers includes the meta key.
|
||||
func (modifiers Modifiers) Meta () bool {
|
||||
return modifiers & ModAlt != ModNone
|
||||
}
|
||||
|
||||
// Super returns whether the list of modifiers includes the super key.
|
||||
func (modifiers Modifiers) Super () bool {
|
||||
return modifiers & ModSuper != ModNone
|
||||
}
|
||||
|
||||
// Hyper returns whether the list of modifiers includes the hyper key.
|
||||
func (modifiers Modifiers) Hyper () bool {
|
||||
return modifiers & ModHyper != ModNone
|
||||
}
|
||||
|
||||
// KeyChord combines a keyboard key with Modifiers.
|
||||
type KeyChord struct {
|
||||
Key Key
|
||||
Modifiers Modifiers
|
||||
}
|
||||
|
||||
// KC is a convenience constructor for a KeyChord.
|
||||
func KC (key Key, modifiers Modifiers) KeyChord {
|
||||
return KeyChord {
|
||||
Key: key,
|
||||
Modifiers: modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
// Pressed returns true if the given Key Modifiers match the KeyChord.
|
||||
func (chord KeyChord) Pressed (key Key, modifiers Modifiers) bool {
|
||||
return chord == KC(key, modifiers)
|
||||
}
|
||||
|
||||
// ButtonChord combines a mouse button with a number of modifiers.
|
||||
type ButtonChord struct {
|
||||
Button Button
|
||||
Modifiers Modifiers
|
||||
}
|
||||
|
||||
// BC is a convenience constructor for a ButtonChord.
|
||||
func BC (button Button, modifiers Modifiers) ButtonChord {
|
||||
return ButtonChord {
|
||||
Button: button,
|
||||
Modifiers: modifiers,
|
||||
}
|
||||
}
|
||||
|
||||
// Pressed returns true if the given Button and Modifiers match the ButtonChord.
|
||||
func (chord ButtonChord) Pressed (button Button, modifiers Modifiers) bool {
|
||||
return chord == BC(button, modifiers)
|
||||
}
|
||||
|
469
object.go
469
object.go
@ -1,158 +1,77 @@
|
||||
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 obscreen object. Each object must be linked to a box, even if
|
||||
// it is that box.
|
||||
// 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.
|
||||
type Object interface {
|
||||
GetBox () Box
|
||||
}
|
||||
|
||||
// Box is a basic styled box.
|
||||
// ContentObject is an Object that contains some kind of content.
|
||||
type ContentObject interface {
|
||||
Object
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// Box is a basic box with no content. Implementations of Box, as well as any
|
||||
// interface that embed Box, may only originate from a Backend.
|
||||
type Box interface {
|
||||
Object
|
||||
|
||||
// Window returns the Window this Box is a part of.
|
||||
Window () Window
|
||||
// Bounds returns the outer bounding rectangle of the Box relative to
|
||||
// the Window.
|
||||
// Bounds returns the outer bounding image.Rectangle of the Box relative
|
||||
// to the Window.
|
||||
Bounds () image.Rectangle
|
||||
// InnerBounds returns the inner bounding rectangle of the box. It is
|
||||
// the value of Bounds inset by the Box's border and padding.
|
||||
// InnerBounds returns the inner bounding image.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
|
||||
// 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)
|
||||
// SetTexture sets a repeating background texture. If the texture is
|
||||
// transparent, it will be overlayed atop the color specified by
|
||||
// SetColor().
|
||||
SetTexture (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)
|
||||
// Role returns this Box's role as set by SetRole.
|
||||
Role () Role
|
||||
// SetRole sets what role this Box takes on. It is used by the Backend
|
||||
// for applying styling.
|
||||
SetRole (Role)
|
||||
// Tag returns whether or not a named tag exists. These are used by the
|
||||
// Backend 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 config.ButtonChordInteract
|
||||
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
|
||||
// picked up.
|
||||
SetDNDData (data.Data)
|
||||
// SetDNDAccept sets the type of data that can be dropped onto this Box.
|
||||
// If this is nil (which is the default), this Box will reject all
|
||||
// drops.
|
||||
// SetDNDAccept sets the types of data which can be dropped onto this
|
||||
// Box. If none are specified (which is the default), this Box will
|
||||
// reject all drops.
|
||||
SetDNDAccept (...data.Mime)
|
||||
// SetFocused sets whether or not this Box has keyboard focus. If set to
|
||||
// true, this method will steal focus away from whichever Object
|
||||
@ -162,65 +81,97 @@ 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.
|
||||
// returned 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
|
||||
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
|
||||
// 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. Additionally, when subscribing to an event that is
|
||||
// often paired with a second one (for example, KeyDown and KeyUp), it
|
||||
// is good practice to subscribe to both and return true/false under the
|
||||
// same circumstances.
|
||||
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.
|
||||
// CanvasBox is a Box that can be drawn to.
|
||||
type CanvasBox interface {
|
||||
Box
|
||||
|
||||
// SetDrawer sets the Drawer that will be called upon to draw the Box's
|
||||
// content when it is invalidated.
|
||||
// SetDrawer sets the canvas.Drawer that will be called upon to draw the
|
||||
// Box's content when it is invalidated. The Canvas passed to the Drawer
|
||||
// will have these properties:
|
||||
// - It will have the same origin (0, 0) as the Window which contains
|
||||
// the CanvasBox.
|
||||
// - Its Bounds will describe the portion of the CanvasBox visible on
|
||||
// screen. Therefore, it should not be used to determine the
|
||||
// position of anything drawn within it. The Bounds of the CanvasBox
|
||||
// should be used for this purpose.
|
||||
SetDrawer (canvas.Drawer)
|
||||
|
||||
// Invalidate causes the Box's area to be redrawn at the end of the
|
||||
// event cycle, even if it wouldn't be otherwise.
|
||||
// Invalidate causes the CanvasBox's area to be redrawn at the end of
|
||||
// the event cycle, even if it wouldn't otherwise be. This will call the
|
||||
// canvas.Drawer specified by SetDrawer.
|
||||
Invalidate ()
|
||||
}
|
||||
|
||||
// ContentBox is an abstract box that has some kind of content. Its only purpose
|
||||
// is to be embedded into TextBox and ContainerBox.
|
||||
// SurfaceBox is a Box that can be drawn to via a hardware accelerated (or
|
||||
// platform-specific) graphics context.
|
||||
type SurfaceBox interface {
|
||||
Box
|
||||
|
||||
// Surface returns the underlying graphics context. The result must be
|
||||
// asserted to the expected type or passed through a type switch before
|
||||
// use. The exact type returned here depends on the Backend being used,
|
||||
// and it is up to the application author to ensure that the application
|
||||
// and Backend agree on it. Applications should fail gracefully if the
|
||||
// expected type was not found. If the surface has been destroyed by the
|
||||
// Backend, it will return nil.
|
||||
Surface () any
|
||||
|
||||
// Invalidate causes the data within the surface to be pushed to the
|
||||
// screen at the end of the event cycle, even if it wouldn't otherwise
|
||||
// be.
|
||||
Invalidate ()
|
||||
|
||||
// OnSizeChange specifies a function to be called when the size of the
|
||||
// Box is changed, the surface is re-allocated, or the surface is
|
||||
// destroyed. The application must call Surface() each time this event
|
||||
// fires in order to not draw onto a closed surface.
|
||||
OnSizeChange (func ())
|
||||
}
|
||||
|
||||
// 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 window.
|
||||
// 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.
|
||||
// Box's InnerBounds, offset by the given image.Point.
|
||||
ScrollTo (image.Point)
|
||||
|
||||
// OnContentBoundsChange specifies a function to be called when the
|
||||
// Box's ContentBounds or InnerBounds changes.
|
||||
OnContentBoundsChange (func ()) event.Cookie
|
||||
@ -232,18 +183,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.
|
||||
@ -258,38 +201,40 @@ type TextBox interface {
|
||||
type ContainerBox interface {
|
||||
ContentBox
|
||||
|
||||
// SetPropagateEvents specifies whether or not child Objects will
|
||||
// receive user input events. It is true by default. If it is false, all
|
||||
// user input that would otherwise be directed to a child Box is
|
||||
// directed to this Box.
|
||||
SetPropagateEvents (bool)
|
||||
// SetGap sets the gap between child Objects.
|
||||
SetGap (image.Point)
|
||||
// Add appends a child Object.
|
||||
// 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)
|
||||
// Delete removes a child Object, if it is a child of this Box.
|
||||
Delete (Object)
|
||||
// Remove removes a child Object, if it is a child of this Box.
|
||||
Remove (Object)
|
||||
// Insert inserts a child Object before a specified Object. If the
|
||||
// before Object is nil or is not contained within this Box, the
|
||||
// inserted Object is appended.
|
||||
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)
|
||||
|
||||
// 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.
|
||||
// LayoutHints are passed to a layout to tell it how to arrange child Boxes.
|
||||
type LayoutHints struct {
|
||||
// Bounds is the bounding rectangle that children should be placed
|
||||
// within. Any padding values are already applied.
|
||||
Bounds image.Rectangle
|
||||
// OverflowX and OverflowY control wether child Boxes may be positioned
|
||||
// OverflowX and OverflowY control whether child Boxes may be positioned
|
||||
// outside of Bounds.
|
||||
OverflowX bool
|
||||
OverflowY bool
|
||||
@ -306,54 +251,126 @@ 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
|
||||
// Arrange arranges child boxes according to the given LayoutHints.
|
||||
Arrange (LayoutHints, []Box)
|
||||
MinimumSize (LayoutHints, BoxQuerier) image.Point
|
||||
// Arrange arranges child Boxes according to the given LayoutHints.
|
||||
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
|
||||
// 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 behavior 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)
|
||||
}
|
||||
|
||||
// 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"
|
||||
// Turn is a small Window for menus and panes which have been "torn off"
|
||||
// from their main Window or position. It is usually given special
|
||||
// styling and management by the OS.
|
||||
WindowKindTorn 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 directly contain one object,
|
||||
// which is usually a container. 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
|
||||
// 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)
|
||||
// SetTitle sets the title of the window.
|
||||
// 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 (... image.Image)
|
||||
// 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)
|
||||
// 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)
|
||||
// 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.
|
||||
NewChild (WindowKind, 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
|
||||
// error has occurred, the provided function will be called.
|
||||
Paste (callback func (data.Data, error), accept ...data.Mime)
|
||||
// Show shows the window.
|
||||
Show ()
|
||||
// Hide hides the window.
|
||||
Hide ()
|
||||
// Close closes the window.
|
||||
Close ()
|
||||
// OnClose specifies a function to be called when the window is closed.
|
||||
// SetVisible sets whether or not this window is visible. Windows are
|
||||
// invisible by default.
|
||||
SetVisible (bool)
|
||||
// Visible returns whether or not this window is visible.
|
||||
Visible () bool
|
||||
// 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
|
||||
}
|
||||
|
||||
// MainWindow is a top-level operating system window.
|
||||
type MainWindow interface {
|
||||
Window
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
187
path.go
187
path.go
@ -1,187 +0,0 @@
|
||||
package tomo
|
||||
|
||||
import "io"
|
||||
import "os"
|
||||
import "io/fs"
|
||||
import "strings"
|
||||
import "path/filepath"
|
||||
|
||||
// FS is Tomo's implementation of fs.FS. It provides access to a specific part
|
||||
// of the filesystem.
|
||||
type FS struct {
|
||||
path string
|
||||
}
|
||||
|
||||
// FileWriter is a writable version of fs.File.
|
||||
type FileWriter interface {
|
||||
interface { fs.File; io.Writer }
|
||||
}
|
||||
|
||||
// ApplicationUserDataFS returns an FS that an application can use to store user
|
||||
// data files.
|
||||
func ApplicationUserDataFS (app ApplicationDescription) (*FS, error) {
|
||||
return appFs(userDataDir, app)
|
||||
}
|
||||
|
||||
// ApplicationUserConfigFS returns an FS that an application can use to store
|
||||
// user configuration files.
|
||||
func ApplicationUserConfigFS (app ApplicationDescription) (*FS, error) {
|
||||
configDir, err := os.UserConfigDir()
|
||||
if err != nil { return nil, err }
|
||||
return appFs(configDir, app)
|
||||
}
|
||||
|
||||
// ApplicationUserCacheFS returns an FS that an application can use to store
|
||||
// user cache files.
|
||||
func ApplicationUserCacheFS (app ApplicationDescription) (*FS, error) {
|
||||
cacheDir, err := os.UserCacheDir()
|
||||
if err != nil { return nil, err }
|
||||
return appFs(cacheDir, app)
|
||||
}
|
||||
|
||||
func pathErr (op, path string, err error) error {
|
||||
return &fs.PathError {
|
||||
Op: op,
|
||||
Path: path,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
func appFs (root string, app ApplicationDescription) (*FS, error) {
|
||||
// remove slashes
|
||||
appname := app.String()
|
||||
appname = strings.ReplaceAll(appname, "/", "-")
|
||||
appname = strings.ReplaceAll(appname, "\\", "-")
|
||||
|
||||
path := filepath.Join(root, appname)
|
||||
|
||||
// ensure the directory actually exists
|
||||
err := os.MkdirAll(path, 755)
|
||||
if err != nil { return nil, err }
|
||||
|
||||
return &FS { path: path }, nil
|
||||
}
|
||||
|
||||
func (this FS) subPath (name string) (string, error) {
|
||||
if !fs.ValidPath(name) { return "", fs.ErrInvalid }
|
||||
if strings.Contains(name, "/") { return "", fs.ErrInvalid }
|
||||
return filepath.Join(this.path, name), nil
|
||||
}
|
||||
|
||||
// Open opens the named file.
|
||||
func (this FS) Open (name string) (fs.File, error) {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return nil, pathErr("open", name, err)
|
||||
}
|
||||
|
||||
return os.Open(path)
|
||||
}
|
||||
|
||||
// Create creates or truncates the named file.
|
||||
func (this FS) Create (name string) (FileWriter, error) {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return nil, pathErr("create", name, err)
|
||||
}
|
||||
|
||||
return os.Create(path)
|
||||
}
|
||||
|
||||
// OpenFile is the generalized open call; most users will use Open or Create
|
||||
// instead.
|
||||
func (this FS) OpenFile (
|
||||
name string,
|
||||
flag int,
|
||||
perm os.FileMode,
|
||||
) (
|
||||
FileWriter,
|
||||
error,
|
||||
) {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return nil, pathErr("open", name, err)
|
||||
}
|
||||
|
||||
return os.OpenFile(path, flag, perm)
|
||||
}
|
||||
|
||||
// ReadDir reads the named directory and returns a list of directory entries
|
||||
// sorted by filename.
|
||||
func (this FS) ReadDir (name string) ([]fs.DirEntry, error) {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return nil, pathErr("readdir", name, err)
|
||||
}
|
||||
|
||||
return os.ReadDir(path)
|
||||
}
|
||||
|
||||
// ReadFile reads the named file and returns its contents.
|
||||
// A successful call returns a nil error, not io.EOF.
|
||||
// (Because ReadFile reads the whole file, the expected EOF
|
||||
// from the final Read is not treated as an error to be reported.)
|
||||
//
|
||||
// The caller is permitted to modify the returned byte slice.
|
||||
func (this FS) ReadFile (name string) ([]byte, error) {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return nil, pathErr("readfile", name, err)
|
||||
}
|
||||
|
||||
return os.ReadFile(path)
|
||||
}
|
||||
|
||||
// WriteFile writes data to the named file, creating it if necessary.
|
||||
func (this FS) WriteFile (name string, data []byte, perm os.FileMode) error {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return pathErr("writefile", name, err)
|
||||
}
|
||||
|
||||
return os.WriteFile(path, data, perm)
|
||||
}
|
||||
|
||||
// Stat returns a FileInfo describing the file.
|
||||
func (this FS) Stat (name string) (fs.FileInfo, error) {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return nil, pathErr("stat", name, err)
|
||||
}
|
||||
|
||||
return os.Stat(path)
|
||||
}
|
||||
|
||||
// Remove removes the named file or (empty) directory.
|
||||
func (this FS) Remove (name string) error {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return pathErr("remove", name, err)
|
||||
}
|
||||
|
||||
return os.Remove(path)
|
||||
}
|
||||
|
||||
// RemoveAll removes name and any children it contains.
|
||||
func (this FS) RemoveAll (name string) error {
|
||||
path, err := this.subPath(name)
|
||||
if err != nil {
|
||||
return pathErr("removeall", name, err)
|
||||
}
|
||||
|
||||
return os.RemoveAll(path)
|
||||
}
|
||||
|
||||
// Rename renames (moves) oldname to newname.
|
||||
func (this FS) Rename (oldname, newname string) error {
|
||||
oldpath, err := this.subPath(oldname)
|
||||
if err != nil {
|
||||
return pathErr("rename", oldname, err)
|
||||
}
|
||||
newpath, err := this.subPath(newname)
|
||||
if err != nil {
|
||||
return pathErr("rename", newname, err)
|
||||
}
|
||||
|
||||
return os.Rename(oldpath, newpath)
|
||||
}
|
55
plugin.go
55
plugin.go
@ -1,55 +0,0 @@
|
||||
package tomo
|
||||
|
||||
import "os"
|
||||
import "plugin"
|
||||
import "path/filepath"
|
||||
|
||||
var pluginPaths []string
|
||||
|
||||
func loadPlugins () {
|
||||
for _, dir := range pluginPaths {
|
||||
if dir != "" {
|
||||
loadPluginsFrom(dir)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func loadPluginsFrom (dir string) {
|
||||
entries, err := os.ReadDir(dir)
|
||||
// its no big deal if one of the dirs doesn't exist
|
||||
if err != nil { return }
|
||||
|
||||
for _, entry := range entries {
|
||||
if entry.IsDir() { continue }
|
||||
if filepath.Ext(entry.Name()) != ".so" { continue }
|
||||
pluginPath := filepath.Join(dir, entry.Name())
|
||||
loadPlugin(pluginPath)
|
||||
}
|
||||
}
|
||||
|
||||
func loadPlugin (path string) {
|
||||
die := func (reason string) {
|
||||
println("tomo: could not load plugin at", path + ":", reason)
|
||||
}
|
||||
|
||||
plugin, err := plugin.Open(path)
|
||||
if err != nil {
|
||||
die(err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
// check for and obtain basic plugin functions
|
||||
name, ok := extract[func () string](plugin, "Name")
|
||||
if !ok { die("does not implement Name() string"); return }
|
||||
_, ok = extract[func () string](plugin, "Description")
|
||||
if !ok { die("does not implement Description() string"); return }
|
||||
|
||||
println("tomo: loaded plugin", name())
|
||||
}
|
||||
|
||||
func extract[T any] (plugin *plugin.Plugin, name string) (value T, ok bool) {
|
||||
symbol, err := plugin.Lookup(name)
|
||||
if err != nil { return }
|
||||
value, ok = symbol.(T)
|
||||
return
|
||||
}
|
440
theme/icon.go
440
theme/icon.go
@ -1,440 +0,0 @@
|
||||
package theme
|
||||
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
import "git.tebibyte.media/tomo/tomo/data"
|
||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||
|
||||
// IconSize represents the size of an icon.
|
||||
type IconSize int; const (
|
||||
IconSizeSmall IconSize = iota;
|
||||
IconSizeMedium
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: the Icon type, along with its String method, needs to be codegen'd.
|
||||
|
||||
// Icon represents an icon ID.
|
||||
type Icon int; const (
|
||||
// --- Objects --- //
|
||||
|
||||
// files
|
||||
IconFile Icon = iota
|
||||
IconDirectory
|
||||
IconDirectoryFull
|
||||
|
||||
// places
|
||||
IconDownloads
|
||||
IconPhotos
|
||||
IconBooks
|
||||
IconDocuments
|
||||
IconRepositories
|
||||
IconMusic
|
||||
IconArchives
|
||||
IconFonts
|
||||
IconBinaries
|
||||
IconVideos
|
||||
Icon3DObjects
|
||||
IconHistory
|
||||
IconPreferences
|
||||
|
||||
// storage
|
||||
IconStorage // generic
|
||||
IconMagneticTape
|
||||
IconFloppyDisk
|
||||
IconHardDisk
|
||||
IconSolidStateDrive
|
||||
IconFlashDrive
|
||||
IconMemoryCard
|
||||
IconROMDisk
|
||||
IconRAMDisk
|
||||
IconCD
|
||||
IconDVD
|
||||
|
||||
// network
|
||||
IconNetwork // generic
|
||||
IconLocalNetwork
|
||||
IconInternet
|
||||
IconEthernet
|
||||
IconWireless
|
||||
IconCell
|
||||
IconBluetooth
|
||||
IconRadio
|
||||
|
||||
// devices
|
||||
IconDevice // generic
|
||||
IconRouter
|
||||
IconServer
|
||||
IconDesktop
|
||||
IconLaptop
|
||||
IconTablet
|
||||
IconPhone
|
||||
IconWatch
|
||||
IconCamera
|
||||
|
||||
// peripherals
|
||||
IconPeripheral // generic
|
||||
IconKeyboard
|
||||
IconMouse
|
||||
IconMonitor
|
||||
IconWebcam
|
||||
IconMicrophone
|
||||
IconSpeaker
|
||||
IconPenTablet
|
||||
IconTrackpad
|
||||
IconController
|
||||
|
||||
// i/o
|
||||
IconPort // generic
|
||||
IconEthernetPort
|
||||
IconUSBPort
|
||||
IconParallelPort
|
||||
IconSerialPort
|
||||
IconPS2Port
|
||||
IconDisplayConnector
|
||||
IconCGAPort
|
||||
IconVGAPort
|
||||
IconHDMIPort
|
||||
IconDisplayPort
|
||||
IconInfrared
|
||||
|
||||
// --- Actions --- //
|
||||
|
||||
// files
|
||||
IconOpen
|
||||
IconOpenIn
|
||||
IconSave
|
||||
IconSaveAs
|
||||
IconPrint
|
||||
IconNew
|
||||
IconNewDirectory
|
||||
IconDelete
|
||||
IconRename
|
||||
IconGetInformation
|
||||
IconChangePermissions
|
||||
IconRevert
|
||||
|
||||
// list management
|
||||
IconAdd
|
||||
IconRemove
|
||||
IconAddBookmark
|
||||
IconRemoveBookmark
|
||||
IconAddFavorite
|
||||
IconRemoveFavorite
|
||||
|
||||
// media
|
||||
IconPlay
|
||||
IconPause
|
||||
IconStop
|
||||
IconFastForward
|
||||
IconRewind
|
||||
IconToBeginning
|
||||
IconToEnd
|
||||
IconRecord
|
||||
IconVolumeUp
|
||||
IconVolumeDown
|
||||
IconMute
|
||||
|
||||
// editing
|
||||
IconUndo
|
||||
IconRedo
|
||||
IconCut
|
||||
IconCopy
|
||||
IconPaste
|
||||
IconFind
|
||||
IconReplace
|
||||
IconSelectAll
|
||||
IconSelectNone
|
||||
IconIncrement
|
||||
IconDecrement
|
||||
|
||||
// window management
|
||||
IconClose
|
||||
IconQuit
|
||||
IconIconify
|
||||
IconShade
|
||||
IconMaximize
|
||||
IconFullScreen
|
||||
IconRestore
|
||||
|
||||
// view controls
|
||||
IconExpand
|
||||
IconContract
|
||||
IconBack
|
||||
IconForward
|
||||
IconUp
|
||||
IconDown
|
||||
IconReload
|
||||
IconZoomIn
|
||||
IconZoomOut
|
||||
IconZoomReset
|
||||
IconMove
|
||||
IconResize
|
||||
IconGoTo
|
||||
|
||||
// tools
|
||||
IconTransform
|
||||
IconTranslate
|
||||
IconRotate
|
||||
IconScale
|
||||
IconWarp
|
||||
IconCornerPin
|
||||
IconSelectRectangle
|
||||
IconSelectEllipse
|
||||
IconSelectLasso
|
||||
IconSelectGeometric
|
||||
IconSelectAuto
|
||||
IconCrop
|
||||
IconFill
|
||||
IconGradient
|
||||
IconPencil
|
||||
IconBrush
|
||||
IconEraser
|
||||
IconText
|
||||
IconEyedropper
|
||||
|
||||
// --- Status --- //
|
||||
|
||||
// dialogs
|
||||
IconInformation
|
||||
IconQuestion
|
||||
IconWarning
|
||||
IconError
|
||||
IconCancel
|
||||
IconOkay
|
||||
|
||||
// network
|
||||
IconCellSignal0
|
||||
IconCellSignal1
|
||||
IconCellSignal2
|
||||
IconCellSignal3
|
||||
IconWirelessSignal0
|
||||
IconWirelessSignal1
|
||||
IconWirelessSignal2
|
||||
IconWirelessSignal3
|
||||
|
||||
// power
|
||||
IconBattery0
|
||||
IconBattery1
|
||||
IconBattery2
|
||||
IconBattery3
|
||||
IconBrightness0
|
||||
IconBrightness1
|
||||
IconBrightness2
|
||||
IconBrightness3
|
||||
|
||||
// media
|
||||
IconVolume0
|
||||
IconVolume1
|
||||
IconVolume2
|
||||
IconVolume3
|
||||
)
|
||||
|
||||
// String satisfies the fmt.Stringer interface.
|
||||
func (id Icon) String () string {
|
||||
switch id {
|
||||
case IconFile: return "File"
|
||||
case IconDirectory: return "Directory"
|
||||
case IconDirectoryFull: return "DirectoryFull"
|
||||
case IconDownloads: return "Downloads"
|
||||
case IconPhotos: return "Photos"
|
||||
case IconBooks: return "Books"
|
||||
case IconDocuments: return "Documents"
|
||||
case IconRepositories: return "Repositories"
|
||||
case IconMusic: return "Music"
|
||||
case IconArchives: return "Archives"
|
||||
case IconFonts: return "Fonts"
|
||||
case IconBinaries: return "Binaries"
|
||||
case IconVideos: return "Videos"
|
||||
case Icon3DObjects: return "3DObjects"
|
||||
case IconHistory: return "History"
|
||||
case IconPreferences: return "Preferences"
|
||||
case IconStorage: return "Storage"
|
||||
case IconMagneticTape: return "MagneticTape"
|
||||
case IconFloppyDisk: return "FloppyDisk"
|
||||
case IconHardDisk: return "HardDisk"
|
||||
case IconSolidStateDrive: return "SolidStateDrive"
|
||||
case IconFlashDrive: return "FlashDrive"
|
||||
case IconMemoryCard: return "MemoryCard"
|
||||
case IconROMDisk: return "ROMDisk"
|
||||
case IconRAMDisk: return "RAMDisk"
|
||||
case IconCD: return "CD"
|
||||
case IconDVD: return "DVD"
|
||||
case IconNetwork: return "Network"
|
||||
case IconLocalNetwork: return "LocalNetwork"
|
||||
case IconInternet: return "Internet"
|
||||
case IconEthernet: return "Ethernet"
|
||||
case IconWireless: return "Wireless"
|
||||
case IconCell: return "Cell"
|
||||
case IconBluetooth: return "Bluetooth"
|
||||
case IconRadio: return "Radio"
|
||||
case IconDevice: return "Device"
|
||||
case IconRouter: return "Router"
|
||||
case IconServer: return "Server"
|
||||
case IconDesktop: return "Desktop"
|
||||
case IconLaptop: return "Laptop"
|
||||
case IconTablet: return "Tablet"
|
||||
case IconPhone: return "Phone"
|
||||
case IconWatch: return "Watch"
|
||||
case IconCamera: return "Camera"
|
||||
case IconPeripheral: return "Peripheral"
|
||||
case IconKeyboard: return "Keyboard"
|
||||
case IconMouse: return "Mouse"
|
||||
case IconMonitor: return "Monitor"
|
||||
case IconWebcam: return "Webcam"
|
||||
case IconMicrophone: return "Microphone"
|
||||
case IconSpeaker: return "Speaker"
|
||||
case IconPenTablet: return "PenTablet"
|
||||
case IconTrackpad: return "Trackpad"
|
||||
case IconController: return "Controller"
|
||||
case IconPort: return "Port"
|
||||
case IconEthernetPort: return "EthernetPort"
|
||||
case IconUSBPort: return "USBPort"
|
||||
case IconParallelPort: return "ParallelPort"
|
||||
case IconSerialPort: return "SerialPort"
|
||||
case IconPS2Port: return "PS2Port"
|
||||
case IconDisplayConnector: return "DisplayConnector"
|
||||
case IconCGAPort: return "CGAPort"
|
||||
case IconVGAPort: return "VGAPort"
|
||||
case IconHDMIPort: return "HDMIPort"
|
||||
case IconDisplayPort: return "DisplayPort"
|
||||
case IconInfrared: return "Infrared"
|
||||
case IconOpen: return "Open"
|
||||
case IconOpenIn: return "OpenIn"
|
||||
case IconSave: return "Save"
|
||||
case IconSaveAs: return "SaveAs"
|
||||
case IconPrint: return "Print"
|
||||
case IconNew: return "New"
|
||||
case IconNewDirectory: return "NewDirectory"
|
||||
case IconDelete: return "Delete"
|
||||
case IconRename: return "Rename"
|
||||
case IconGetInformation: return "GetInformation"
|
||||
case IconChangePermissions: return "ChangePermissions"
|
||||
case IconRevert: return "Revert"
|
||||
case IconAdd: return "Add"
|
||||
case IconRemove: return "Remove"
|
||||
case IconAddBookmark: return "AddBookmark"
|
||||
case IconRemoveBookmark: return "RemoveBookmark"
|
||||
case IconAddFavorite: return "AddFavorite"
|
||||
case IconRemoveFavorite: return "RemoveFavorite"
|
||||
case IconPlay: return "Play"
|
||||
case IconPause: return "Pause"
|
||||
case IconStop: return "Stop"
|
||||
case IconFastForward: return "FastForward"
|
||||
case IconRewind: return "Rewind"
|
||||
case IconToBeginning: return "ToBeginning"
|
||||
case IconToEnd: return "ToEnd"
|
||||
case IconRecord: return "Record"
|
||||
case IconVolumeUp: return "VolumeUp"
|
||||
case IconVolumeDown: return "VolumeDown"
|
||||
case IconMute: return "Mute"
|
||||
case IconUndo: return "Undo"
|
||||
case IconRedo: return "Redo"
|
||||
case IconCut: return "Cut"
|
||||
case IconCopy: return "Copy"
|
||||
case IconPaste: return "Paste"
|
||||
case IconFind: return "Find"
|
||||
case IconReplace: return "Replace"
|
||||
case IconSelectAll: return "SelectAll"
|
||||
case IconSelectNone: return "SelectNone"
|
||||
case IconIncrement: return "Increment"
|
||||
case IconDecrement: return "Decrement"
|
||||
case IconClose: return "Close"
|
||||
case IconQuit: return "Quit"
|
||||
case IconIconify: return "Iconify"
|
||||
case IconShade: return "Shade"
|
||||
case IconMaximize: return "Maximize"
|
||||
case IconFullScreen: return "FullScreen"
|
||||
case IconRestore: return "Restore"
|
||||
case IconExpand: return "Expand"
|
||||
case IconContract: return "Contract"
|
||||
case IconBack: return "Back"
|
||||
case IconForward: return "Forward"
|
||||
case IconUp: return "Up"
|
||||
case IconDown: return "Down"
|
||||
case IconReload: return "Reload"
|
||||
case IconZoomIn: return "ZoomIn"
|
||||
case IconZoomOut: return "ZoomOut"
|
||||
case IconZoomReset: return "ZoomReset"
|
||||
case IconMove: return "Move"
|
||||
case IconResize: return "Resize"
|
||||
case IconGoTo: return "GoTo"
|
||||
case IconTransform: return "Transform"
|
||||
case IconTranslate: return "Translate"
|
||||
case IconRotate: return "Rotate"
|
||||
case IconScale: return "Scale"
|
||||
case IconWarp: return "Warp"
|
||||
case IconCornerPin: return "CornerPin"
|
||||
case IconSelectRectangle: return "SelectRectangle"
|
||||
case IconSelectEllipse: return "SelectEllipse"
|
||||
case IconSelectLasso: return "SelectLasso"
|
||||
case IconSelectGeometric: return "SelectGeometric"
|
||||
case IconSelectAuto: return "SelectAuto"
|
||||
case IconCrop: return "Crop"
|
||||
case IconFill: return "Fill"
|
||||
case IconGradient: return "Gradient"
|
||||
case IconPencil: return "Pencil"
|
||||
case IconBrush: return "Brush"
|
||||
case IconEraser: return "Eraser"
|
||||
case IconText: return "Text"
|
||||
case IconEyedropper: return "Eyedropper"
|
||||
case IconInformation: return "Information"
|
||||
case IconQuestion: return "Question"
|
||||
case IconWarning: return "Warning"
|
||||
case IconError: return "Error"
|
||||
case IconCancel: return "Cancel"
|
||||
case IconOkay: return "Okay"
|
||||
case IconCellSignal0: return "CellSignal0"
|
||||
case IconCellSignal1: return "CellSignal1"
|
||||
case IconCellSignal2: return "CellSignal2"
|
||||
case IconCellSignal3: return "CellSignal3"
|
||||
case IconWirelessSignal0: return "WirelessSignal0"
|
||||
case IconWirelessSignal1: return "WirelessSignal1"
|
||||
case IconWirelessSignal2: return "WirelessSignal2"
|
||||
case IconWirelessSignal3: return "WirelessSignal3"
|
||||
case IconBattery0: return "Battery0"
|
||||
case IconBattery1: return "Battery1"
|
||||
case IconBattery2: return "Battery2"
|
||||
case IconBattery3: return "Battery3"
|
||||
case IconBrightness0: return "Brightness0"
|
||||
case IconBrightness1: return "Brightness1"
|
||||
case IconBrightness2: return "Brightness2"
|
||||
case IconBrightness3: return "Brightness3"
|
||||
case IconVolume0: return "Volume0"
|
||||
case IconVolume1: return "Volume1"
|
||||
case IconVolume2: return "Volume2"
|
||||
case IconVolume3: return "Volume3"
|
||||
|
||||
default: return "Unknown"
|
||||
}
|
||||
}
|
||||
|
||||
// Texture returns a texture of the corresponding icon ID.
|
||||
func (id Icon) Texture (size IconSize) canvas.Texture {
|
||||
if current == nil { return nil }
|
||||
return current.Icon(id, size)
|
||||
}
|
||||
|
||||
// MimeIcon returns an icon corresponding to a MIME type.
|
||||
func MimeIcon (mime data.Mime, size IconSize) canvas.Texture {
|
||||
if current == nil { return nil }
|
||||
return current.MimeIcon(mime, size)
|
||||
}
|
||||
|
||||
// ApplicationIcon describes the icon of the application.
|
||||
type ApplicationIcon tomo.ApplicationDescription
|
||||
|
||||
// Texture returns a texture of the corresponding icon ID.
|
||||
func (icon ApplicationIcon) Texture (size IconSize) canvas.Texture {
|
||||
if current == nil { return nil }
|
||||
return current.ApplicationIcon(icon, size)
|
||||
}
|
112
theme/theme.go
112
theme/theme.go
@ -1,112 +0,0 @@
|
||||
package theme
|
||||
|
||||
import "fmt"
|
||||
import "git.tebibyte.media/tomo/tomo"
|
||||
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 current == nil { return }
|
||||
return current.RGBA(id)
|
||||
}
|
||||
|
||||
// Theme is an object that 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 the given
|
||||
// role. This may register event listeners with the given object;
|
||||
// closing the returned cookie must remove them.
|
||||
Apply (tomo.Object, Role) 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.
|
||||
Icon (Icon, IconSize) canvas.Texture
|
||||
|
||||
// MimeIcon returns an icon corresponding to a MIME type.
|
||||
MimeIcon (data.Mime, IconSize) canvas.Texture
|
||||
|
||||
// ApplicationIcon returns an icon corresponding to an application.
|
||||
ApplicationIcon (ApplicationIcon, IconSize) canvas.Texture
|
||||
}
|
||||
|
||||
var current Theme
|
||||
|
||||
// SetTheme sets the theme.
|
||||
func SetTheme (theme Theme) {
|
||||
current = theme
|
||||
}
|
||||
|
||||
// Apply applies the current theme to the given object, according to the given
|
||||
// role. This may register event listeners with the given object; closing the
|
||||
// returned cookie will remove them.
|
||||
func Apply (object tomo.Object, role Role) event.Cookie {
|
||||
if current == nil { return event.NoCookie { } }
|
||||
return current.Apply(object, role)
|
||||
}
|
73
tomo.go
73
tomo.go
@ -2,59 +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 {
|
||||
loadPlugins()
|
||||
|
||||
if backend != nil {
|
||||
return errors.New("there is already a backend running")
|
||||
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
|
||||
}
|
||||
|
||||
back, err := Initialize()
|
||||
if err != nil { return err }
|
||||
|
||||
backendLock.Lock()
|
||||
backend = back
|
||||
backendLock.Unlock()
|
||||
|
||||
callback()
|
||||
return backend.Run()
|
||||
}
|
||||
|
||||
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) (MainWindow, error) {
|
||||
// NewWindow creates and returns a Window within the specified bounds on screen.
|
||||
func NewWindow (kind WindowKind, bounds image.Rectangle) (Window, error) {
|
||||
assertBackend()
|
||||
return backend.NewWindow(bounds)
|
||||
return backend.NewWindow(kind, bounds)
|
||||
}
|
||||
|
||||
// NewBox creates and returns a basic Box.
|
||||
@ -81,9 +71,16 @@ func NewContainerBox () ContainerBox {
|
||||
return backend.NewContainerBox()
|
||||
}
|
||||
|
||||
// NewTexture creates a new texture from an image. When no longer in use, it
|
||||
// must be freed using Close().
|
||||
// NewTexture creates a new canvas.Texture from an image. When no longer in use,
|
||||
// it must be freed using Close().
|
||||
func NewTexture (source image.Image) canvas.TextureCloser {
|
||||
assertBackend()
|
||||
return backend.NewTexture(source)
|
||||
}
|
||||
|
||||
// NewCanvas creates a new canvas with the specified bounds. When no longer in
|
||||
// use, it must be freed using Close().
|
||||
func NewCanvas (bounds image.Rectangle) canvas.CanvasCloser {
|
||||
assertBackend()
|
||||
return backend.NewCanvas(bounds)
|
||||
}
|
||||
|
225
unit.go
Normal file
225
unit.go
Normal file
@ -0,0 +1,225 @@
|
||||
package tomo
|
||||
|
||||
import "fmt"
|
||||
import "image"
|
||||
import "image/color"
|
||||
|
||||
// Side represents one side of a rectangle.
|
||||
type Side int; const (
|
||||
SideTop Side = iota
|
||||
SideRight
|
||||
SideBottom
|
||||
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
|
||||
|
||||
// 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.")
|
||||
}
|
||||
}
|
||||
|
||||
func (inset Inset) String () string {
|
||||
return fmt.Sprintf("%d %d %d %d", inset[0], inset[1], inset[2], inset[3])
|
||||
}
|
||||
|
||||
// Apply returns the given image.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
|
||||
}
|
||||
|
||||
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
|
||||
AlignMiddle // similar to center-aligned text
|
||||
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 a convenience constructor for Role.
|
||||
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)
|
||||
}
|
26
unix.go
26
unix.go
@ -1,26 +0,0 @@
|
||||
//go:build linux || darwin || freebsd
|
||||
|
||||
package tomo
|
||||
|
||||
import "os"
|
||||
import "strings"
|
||||
import "path/filepath"
|
||||
|
||||
var userDataDir string
|
||||
|
||||
func init () {
|
||||
pathVariable := os.Getenv("TOMO_PLUGIN_PATH")
|
||||
pluginPaths = strings.Split(pathVariable, ":")
|
||||
pluginPaths = append (
|
||||
pluginPaths,
|
||||
"/usr/lib/tomo/plugins",
|
||||
"/usr/local/lib/tomo/plugins")
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
pluginPaths = append (
|
||||
pluginPaths,
|
||||
filepath.Join(homeDir, ".local/lib/tomo/plugins"))
|
||||
}
|
||||
|
||||
userDataDir = filepath.Join(homeDir, ".local/share")
|
||||
}
|
Loading…
Reference in New Issue
Block a user