Compare commits
145 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 |
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)
|
[](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) (MainWindow, 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 () { }
|
104
backend.go
104
backend.go
@ -1,38 +1,57 @@
|
|||||||
package tomo
|
package tomo
|
||||||
|
|
||||||
import "sort"
|
import "sync"
|
||||||
import "image"
|
import "image"
|
||||||
import "errors"
|
import "git.tebibyte.media/tomo/tomo/data"
|
||||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||||
|
|
||||||
// Backend is any Tomo implementation. Backends handle window creation, layout,
|
// Backend is any Tomo implementation. Backends handle window creation, layout,
|
||||||
// rendering, and events so that there can be as many platform-specific
|
// rendering, and events so that there can be as many platform-specific
|
||||||
// optimizations as possible.
|
// optimizations as possible.
|
||||||
type Backend interface {
|
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.
|
// that was not made by it.
|
||||||
NewBox () Box
|
NewBox () Box
|
||||||
NewTextBox () TextBox
|
NewTextBox () TextBox
|
||||||
NewCanvasBox () CanvasBox
|
NewCanvasBox () CanvasBox
|
||||||
|
NewSurfaceBox () (SurfaceBox, error)
|
||||||
NewContainerBox () ContainerBox
|
NewContainerBox () ContainerBox
|
||||||
|
|
||||||
// NewWindow creates a normal MainWindow and returns it.
|
// NewWindow creates a normal Window and returns it.
|
||||||
NewWindow (image.Rectangle) (MainWindow, error)
|
NewWindow (WindowKind, image.Rectangle) (Window, error)
|
||||||
|
|
||||||
// NewPlainWindow creates an undecorated window that does not appear in
|
// NewTexture creates a new canvs.Texture from an image. The backend
|
||||||
// window lists and returns it. This is intended for making things like
|
// must reject any texture that was not made by it.
|
||||||
// panels, docks, etc.
|
|
||||||
NewPlainWindow (image.Rectangle) (MainWindow, error)
|
|
||||||
|
|
||||||
// NewTexture creates a new texture from an image. The backend must
|
|
||||||
// reject any texture that was not made by it.
|
|
||||||
NewTexture (image.Image) canvas.TextureCloser
|
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.
|
// experiences a fatal error.
|
||||||
Run () error
|
Run () error
|
||||||
|
|
||||||
// Stop must unblock run.
|
// Stop must unblock Run. This behavior may only be called from within
|
||||||
|
// tomo.Stop.
|
||||||
Stop ()
|
Stop ()
|
||||||
|
|
||||||
// Do performs a callback function in the event loop thread as soon as
|
// Do performs a callback function in the event loop thread as soon as
|
||||||
@ -40,54 +59,21 @@ type Backend interface {
|
|||||||
Do (func ())
|
Do (func ())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Factory is a function that attempts to instatiate a backend. If the
|
var backendLock sync.Mutex
|
||||||
// instantiation process fails at any point, it must clean up all resources and
|
var backend Backend
|
||||||
// return nil and an error explaining what happened.
|
|
||||||
type Factory func () (Backend, error)
|
|
||||||
|
|
||||||
var registered []factoryEntry
|
func assertBackend () {
|
||||||
type factoryEntry struct {
|
if backend == nil { panic("nil backend") }
|
||||||
Factory
|
|
||||||
priority int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register registers a backend factory with the given priority number.
|
// SetBackend sets the Backend that functions in this package will call upon.
|
||||||
func Register (priority int, factory Factory) {
|
// This function will panic if there is already a Backend running.
|
||||||
registered = append(registered, factoryEntry {
|
func SetBackend (back Backend) {
|
||||||
priority: priority,
|
backendLock.Lock()
|
||||||
Factory: factory,
|
defer backendLock.Unlock()
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize instantiates a backend. The first backend (sorted by priority)
|
if backend != nil {
|
||||||
// that does not throw an error when initialized is used. If no backend could be
|
panic("SetBackend called while another backend was running")
|
||||||
// instantiated, this function returns an error. This function should be called
|
|
||||||
// only once.
|
|
||||||
func Initialize () (Backend, error) {
|
|
||||||
backend, err := instantiate()
|
|
||||||
if err != nil { return nil, err }
|
|
||||||
return backend, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func instantiate () (Backend, error) {
|
|
||||||
if len(registered) < 0 {
|
|
||||||
return nil, errors.New("no available backends")
|
|
||||||
}
|
}
|
||||||
|
backend = back
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,16 @@
|
|||||||
// primitives.
|
// primitives.
|
||||||
package canvas
|
package canvas
|
||||||
|
|
||||||
|
import "io"
|
||||||
import "image"
|
import "image"
|
||||||
import "image/draw"
|
import "image/draw"
|
||||||
import "image/color"
|
import "image/color"
|
||||||
|
|
||||||
// Cap represents a stroke cap type.
|
// Cap represents a stroke cap type.
|
||||||
type Cap int; const (
|
type Cap int; const (
|
||||||
CapButt Cap = iota // Square cap that ends at the point
|
CapRound Cap = iota // Round cap that surrounds the point
|
||||||
CapRound // Round cap that surrounds the point
|
CapSquare // Square 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.
|
// 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
|
// 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
|
// have multiple pens associated with it, each maintaining their own drawing
|
||||||
// context.
|
// state.
|
||||||
type Pen interface {
|
type Pen interface {
|
||||||
// Rectangle draws a rectangle
|
// Rectangle draws an image.Rectangle.
|
||||||
Rectangle (image.Rectangle)
|
Rectangle (image.Rectangle)
|
||||||
|
|
||||||
// Path draws a path
|
// Path draws a path, which is a series of connected points.
|
||||||
Path (points ...image.Point)
|
Path (points ...image.Point)
|
||||||
|
|
||||||
Closed (bool) // if the path is closed
|
// StrokeWeight sets how thick the stroke is. The default value is zero.
|
||||||
Cap (Cap) // line cap stype
|
StrokeWeight (int)
|
||||||
Joint (Joint) // line joint style
|
// SetClosed sets whether the path is a closed shape, or has an open
|
||||||
StrokeWeight (int) // how thick the stroke is
|
// side. This only applies if the stroke weight is greater than zero.
|
||||||
StrokeAlign (StrokeAlign) // where the stroke is drawn
|
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
|
// Stroke sets the stroke to a solid color.
|
||||||
Fill (color.Color) // Sets the fill to a solid color
|
Stroke (color.Color)
|
||||||
Texture (Texture) // Overlaps a texture onto the fill 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.
|
// Canvas is an image that supports drawing paths.
|
||||||
type Canvas interface {
|
type Canvas interface {
|
||||||
draw.Image
|
draw.Image
|
||||||
|
|
||||||
// Pen returns a new pen for this canvas.
|
// Pen returns a new pen for this Canvas.
|
||||||
Pen () Pen
|
Pen () Pen
|
||||||
|
|
||||||
// Clip returns a new canvas that points to a specific area of this one.
|
// SubCanvas returns a returns a Canvas representing the portion of this
|
||||||
Clip (image.Rectangle) Canvas
|
// 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 {
|
type Drawer interface {
|
||||||
// Draw draws to the given canvas.
|
// Draw draws to the given Canvas.
|
||||||
Draw (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).
|
// some other destination).
|
||||||
type PushCanvas interface {
|
type PushCanvas interface {
|
||||||
Canvas
|
Canvas
|
||||||
|
@ -1,8 +1,13 @@
|
|||||||
package canvas
|
package canvas
|
||||||
|
|
||||||
import "image"
|
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
|
// 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
|
// 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
|
// out which areas of a container box's background are covered by other boxes so
|
||||||
@ -33,7 +38,7 @@ func Shatter (
|
|||||||
} else {
|
} else {
|
||||||
// the tile was entirely obscured by the rock
|
// the tile was entirely obscured by the rock
|
||||||
// and must be wholly removed
|
// and must be wholly removed
|
||||||
tiles = remove(tiles, tileIndex)
|
tiles = slices.Delete(tiles, tileIndex, tileIndex + 1)
|
||||||
tileIndex --
|
tileIndex --
|
||||||
tileLen --
|
tileLen --
|
||||||
}
|
}
|
||||||
@ -90,7 +95,3 @@ func shatterOnce (glass, rock image.Rectangle) (tiles [4]image.Rectangle, n int)
|
|||||||
); n ++ }
|
); n ++ }
|
||||||
return
|
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 "io"
|
||||||
import "image"
|
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 {
|
type Texture interface {
|
||||||
image.Image
|
image.Image
|
||||||
|
|
||||||
// Clip returns a smaller section of this texture, pointing to the same
|
// SubTexture returns a returns a Texture representing the portion of
|
||||||
// internal data.
|
// this Texture visible through a rectangle. The returned value shares
|
||||||
Clip (image.Rectangle) Texture
|
// 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.
|
// TextureCloser must close it after use.
|
||||||
type TextureCloser interface {
|
type TextureCloser interface {
|
||||||
Texture
|
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"
|
||||||
|
)
|
116
data/data.go
116
data/data.go
@ -3,57 +3,127 @@ package data
|
|||||||
|
|
||||||
import "io"
|
import "io"
|
||||||
import "bytes"
|
import "bytes"
|
||||||
|
import "strings"
|
||||||
|
|
||||||
// Data represents arbitrary polymorphic data that can be used for data transfer
|
// Data represents arbitrary polymorphic data that can be used for data transfer
|
||||||
// between applications.
|
// 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.
|
// Mime represents a MIME type.
|
||||||
type Mime struct {
|
type Mime struct {
|
||||||
// Type is the first half of the MIME type, and Subtype is the second
|
// Type is the first half of the MIME type, and Subtype is the second
|
||||||
// half. The separating slash is not included in either. For example,
|
// half. The separating slash is not included in either. For example,
|
||||||
// text/html becomes:
|
// text/html becomes:
|
||||||
// Mime { Type: "text", Subtype: "html" }
|
// Mime { Type: "text", Subtype: "html" }
|
||||||
|
// The subtype is stored here including the tree and the suffix.
|
||||||
Type, Subtype string
|
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.
|
// M is shorthand for creating a MIME type.
|
||||||
func M (ty, subtype string) Mime {
|
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.
|
// ParseMime parses a MIME type from text.
|
||||||
func (mime Mime) String () string {
|
func ParseMime (text string) Mime {
|
||||||
return mime.Type + "/" + mime.Subtype
|
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.
|
// 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.
|
// 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 }
|
type byteReadCloser struct { *bytes.Reader }
|
||||||
func (byteReadCloser) Close () error { return nil }
|
func (byteReadCloser) Close () error { return nil }
|
||||||
|
|
||||||
// Text returns plain text Data given a string.
|
type bytesData struct {
|
||||||
func Text (text string) Data {
|
mime Mime
|
||||||
return Bytes(MimePlain(), []byte(text))
|
buffer []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes constructs a Data given a buffer and a mime type.
|
// FromBytes constructs a Data given a buffer and a mime type.
|
||||||
func Bytes (mime Mime, buffer []byte) Data {
|
func FromBytes (mime Mime, buffer []byte) Data {
|
||||||
return Data {
|
return bytesData {
|
||||||
mime: byteReadCloser { bytes.NewReader(buffer) },
|
mime: mime,
|
||||||
|
buffer: buffer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge combines several Datas together. If multiple Datas provide a reader for
|
func (bytesDat bytesData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
|
||||||
// the same mime type, the ones further on in the list will take precedence.
|
if mime != bytesDat.mime { return nil, false }
|
||||||
func Merge (individual ...Data) (combined Data) {
|
return byteReadCloser { bytes.NewReader(bytesDat.buffer) }, true
|
||||||
for _, data := range individual {
|
}
|
||||||
for mime, reader := range data {
|
|
||||||
combined[mime] = reader
|
func (bytesDat bytesData) Supported () []Mime {
|
||||||
}}
|
return []Mime { bytesDat.mime }
|
||||||
return
|
}
|
||||||
|
|
||||||
|
type mergedData []Data
|
||||||
|
|
||||||
|
func (merged mergedData) Convert (mime Mime) (io.ReadSeekCloser, bool) {
|
||||||
|
for _, individual := range merged {
|
||||||
|
if reader, ok := individual.Convert(mime); ok {
|
||||||
|
return reader, ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (merged mergedData) Supported () (supported []Mime) {
|
||||||
|
for _, individual := range merged {
|
||||||
|
supported = append(individual.Supported(), supported...)
|
||||||
|
}
|
||||||
|
return supported
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge combines several Datas together. If multiple Datas provide a reader for
|
||||||
|
// the same mime type, the ones first in the list will take precedence.
|
||||||
|
func Merge (individual ...Data) (combined Data) {
|
||||||
|
return mergedData(individual)
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,37 @@
|
|||||||
// handlers.
|
// handlers.
|
||||||
package event
|
package event
|
||||||
|
|
||||||
// A cookie is returned when you add an event handler so you can remove it
|
import "io"
|
||||||
// later if you so choose.
|
import "iter"
|
||||||
type Cookie interface {
|
import "errors"
|
||||||
// Close removes the event handler this cookie is for.
|
|
||||||
Close ()
|
// 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.
|
// 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
|
// Connect adds a new listener to the broadcaster and returns a corresponding
|
||||||
// cookie.
|
// Cookie.
|
||||||
func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
|
func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
|
||||||
broadcaster.ensure()
|
broadcaster.ensure()
|
||||||
|
|
||||||
@ -25,15 +51,19 @@ func (broadcaster *Broadcaster[L]) Connect (listener L) Cookie {
|
|||||||
return cookie
|
return cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listeners returns a map of all connected listeners.
|
// Listeners returns an iterator over all listeners.
|
||||||
func (broadcaster *Broadcaster[L]) Listeners () map[int] L {
|
func (broadcaster *Broadcaster[L]) Listeners () iter.Seq[L] {
|
||||||
broadcaster.ensure()
|
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 ++
|
broadcaster.lastID ++
|
||||||
return cookie[L] {
|
return broadcasterCookie[L] {
|
||||||
id: broadcaster.lastID,
|
id: broadcaster.lastID,
|
||||||
broadcaster: broadcaster,
|
broadcaster: broadcaster,
|
||||||
}
|
}
|
||||||
@ -45,41 +75,24 @@ func (broadcaster *Broadcaster[L]) ensure () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NoCookie is a cookie that does nothing when closed.
|
type broadcasterCookie[L any] struct {
|
||||||
type NoCookie struct { }
|
|
||||||
func (NoCookie) Close () { }
|
|
||||||
|
|
||||||
type cookie[L any] struct {
|
|
||||||
id int
|
id int
|
||||||
broadcaster *Broadcaster[L]
|
broadcaster *Broadcaster[L]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cookie cookie[L]) Close () {
|
func (cookie broadcasterCookie[L]) Close () error {
|
||||||
delete(cookie.broadcaster.listeners, cookie.id)
|
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 {
|
type FuncBroadcaster struct {
|
||||||
Broadcaster[func ()]
|
Broadcaster[func ()]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broadcast calls all connected listener funcs.
|
// Broadcast calls all connected listener funcs.
|
||||||
func (broadcaster *FuncBroadcaster) Broadcast () {
|
func (broadcaster *FuncBroadcaster) Broadcast () {
|
||||||
for _, listener := range broadcaster.Listeners() {
|
for listener := range broadcaster.Listeners() {
|
||||||
listener()
|
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
|
module git.tebibyte.media/tomo/tomo
|
||||||
|
|
||||||
go 1.20
|
go 1.23.0
|
||||||
|
|
||||||
require golang.org/x/image v0.11.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
|
// 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
|
// 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.
|
// is no guarantee that one press event will be coupled with one release event.
|
||||||
type Modifiers struct {
|
type Modifiers uint; const (
|
||||||
Shift bool
|
ModNone Modifiers = 0
|
||||||
Control bool
|
ModShift Modifiers = 1 << iota
|
||||||
Alt bool
|
ModControl
|
||||||
Meta bool
|
ModAlt
|
||||||
Super bool
|
ModMeta
|
||||||
Hyper bool
|
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)
|
||||||
}
|
}
|
||||||
|
533
object.go
533
object.go
@ -1,226 +1,177 @@
|
|||||||
package tomo
|
package tomo
|
||||||
|
|
||||||
import "image"
|
import "image"
|
||||||
import "image/color"
|
|
||||||
import "golang.org/x/image/font"
|
|
||||||
import "git.tebibyte.media/tomo/tomo/text"
|
import "git.tebibyte.media/tomo/tomo/text"
|
||||||
import "git.tebibyte.media/tomo/tomo/data"
|
import "git.tebibyte.media/tomo/tomo/data"
|
||||||
import "git.tebibyte.media/tomo/tomo/event"
|
import "git.tebibyte.media/tomo/tomo/event"
|
||||||
import "git.tebibyte.media/tomo/tomo/input"
|
import "git.tebibyte.media/tomo/tomo/input"
|
||||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||||
|
|
||||||
// Side represents one side of a rectangle.
|
// Object is any onscreen object that is linked to a box (or is that box).
|
||||||
type Side int; const (
|
// Unlike the Box interface and associated interfaces, Object implementations
|
||||||
SideTop Side = iota
|
// may originate from anywhere.
|
||||||
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.
|
|
||||||
type Object interface {
|
type Object interface {
|
||||||
GetBox () Box
|
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 {
|
type Box interface {
|
||||||
Object
|
Object
|
||||||
|
|
||||||
// Window returns the Window this Box is a part of.
|
// Window returns the Window this Box is a part of.
|
||||||
Window () Window
|
Window () Window
|
||||||
// Bounds returns the outer bounding rectangle of the Box relative to
|
// Bounds returns the outer bounding image.Rectangle of the Box relative
|
||||||
// the Window.
|
// to the Window.
|
||||||
Bounds () image.Rectangle
|
Bounds () image.Rectangle
|
||||||
// InnerBounds returns the inner bounding rectangle of the box. It is
|
// InnerBounds returns the inner bounding image.Rectangle of the box. It
|
||||||
// the value of Bounds inset by the Box's border and padding.
|
// is the value of Bounds inset by the Box's border and padding.
|
||||||
InnerBounds () image.Rectangle
|
InnerBounds () image.Rectangle
|
||||||
// MinimumSize returns the minimum width and height this Box's bounds
|
// Role returns this Box's role as set by SetRole.
|
||||||
// can be set to. This will return the value of whichever of these is
|
Role () Role
|
||||||
// greater:
|
// SetRole sets what role this Box takes on. It is used by the Backend
|
||||||
// - The size as set by SetMinimumSize
|
// for applying styling.
|
||||||
// - The size taken up by the Box's border and padding. If there is
|
SetRole (Role)
|
||||||
// internal content that does not overflow, the size of that is also
|
// Tag returns whether or not a named tag exists. These are used by the
|
||||||
// taken into account here.
|
// Backend for applying styling, among other things. There are some
|
||||||
MinimumSize () image.Point
|
// special tags that are only and always extant during certain user
|
||||||
// SetBounds sets the bounding rectangle of this Box relative to the
|
// input states:
|
||||||
// Window.
|
// - hovered: The mouse pointer is within the Box
|
||||||
SetBounds (image.Rectangle)
|
// - focused: The Box has keyboard focus
|
||||||
// SetColor sets the background color of this Box.
|
// - pressed: The Box is being pressed by config.ButtonChordInteract
|
||||||
SetColor (color.Color)
|
Tag (string) bool
|
||||||
// SetTexture sets a repeating background texture. If the texture is
|
// SetTag adds or removes a named tag.
|
||||||
// transparent, it will be overlayed atop the color specified by
|
SetTag (string, bool)
|
||||||
// SetColor().
|
|
||||||
SetTexture (canvas.Texture)
|
// SetAttr sets a style attribute, overriding the currently applied
|
||||||
// SetBorder sets the Border(s) of the box. The first Border will be the
|
// style.
|
||||||
// most outset, and the last Border will be the most inset.
|
SetAttr (Attr)
|
||||||
SetBorder (...Border)
|
// UnsetAttr reverts a style attribute to whatever is specified by the
|
||||||
// SetMinimumSize sets the minimum width and height of the box, as
|
// currently applied style.
|
||||||
// described in MinimumSize.
|
UnsetAttr (AttrKind)
|
||||||
SetMinimumSize (image.Point)
|
|
||||||
// SetPadding sets the padding between the Box's innermost Border and
|
|
||||||
// its content.
|
|
||||||
SetPadding (Inset)
|
|
||||||
|
|
||||||
// SetDNDData sets the data that will be picked up if this Box is
|
// 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
|
// dragged. If this is nil (which is the default), this Box will not be
|
||||||
// picked up.
|
// picked up.
|
||||||
SetDNDData (data.Data)
|
SetDNDData (data.Data)
|
||||||
// SetDNDAccept sets the type of data that can be dropped onto this Box.
|
// SetDNDAccept sets the types of data which can be dropped onto this
|
||||||
// If this is nil (which is the default), this Box will reject all
|
// Box. If none are specified (which is the default), this Box will
|
||||||
// drops.
|
// reject all drops.
|
||||||
SetDNDAccept (...data.Mime)
|
SetDNDAccept (...data.Mime)
|
||||||
// SetFocused sets whether or not this Box has keyboard focus. If set to
|
// SetFocused sets whether or not this Box has keyboard focus. If set to
|
||||||
// true, this method will steal focus away from whichever Object
|
// true, this method will steal focus away from whichever Object
|
||||||
// currently has focus.
|
// currently has focus.
|
||||||
SetFocused (bool)
|
SetFocused (bool)
|
||||||
// SetFocusable sets whether or not this Box can receive keyboard focus.
|
// SetFocusable sets whether or not this Box can receive keyboard focus.
|
||||||
// If set to false and the Box is already focused. the focus is removed.
|
// If set to false and the Box is already focused. the focus is removed.
|
||||||
SetFocusable (bool)
|
SetFocusable (bool)
|
||||||
|
|
||||||
// Focused returns whether or not this Box has keyboard focus.
|
// These are event subscription behaviors that allow callbacks to be
|
||||||
Focused () bool
|
|
||||||
// Modifiers returns which modifier keys on the keyboard are currently
|
|
||||||
// being held down.
|
|
||||||
Modifiers () input.Modifiers
|
|
||||||
// MousePosition returns the position of the mouse pointer relative to
|
|
||||||
// the Window.
|
|
||||||
MousePosition () image.Point
|
|
||||||
|
|
||||||
// These are event subscription functions that allow callbacks to be
|
|
||||||
// connected to particular events. Multiple callbacks may be connected
|
// connected to particular events. Multiple callbacks may be connected
|
||||||
// to the same event at once. Callbacks can be removed by closing the
|
// to the same event at once. Callbacks can be removed by closing the
|
||||||
// returned cookie.
|
// returned event.Cookie.
|
||||||
OnFocusEnter (func ()) event.Cookie
|
OnFocusEnter (func () ) event.Cookie
|
||||||
OnFocusLeave (func ()) event.Cookie
|
OnFocusLeave (func () ) event.Cookie
|
||||||
OnDNDEnter (func ()) event.Cookie
|
OnStyleChange (func () ) event.Cookie
|
||||||
OnDNDLeave (func ()) event.Cookie
|
OnIconSetChange (func () ) event.Cookie
|
||||||
OnDNDDrop (func (data.Data)) event.Cookie
|
OnDNDEnter (func () ) event.Cookie
|
||||||
OnMouseEnter (func ()) event.Cookie
|
OnDNDLeave (func () ) event.Cookie
|
||||||
OnMouseLeave (func ()) event.Cookie
|
OnDNDDrop (func (data.Data)) event.Cookie
|
||||||
OnMouseMove (func ()) event.Cookie
|
OnMouseEnter (func () ) event.Cookie
|
||||||
OnMouseDown (func (input.Button)) event.Cookie
|
OnMouseLeave (func () ) event.Cookie
|
||||||
OnMouseUp (func (input.Button)) event.Cookie
|
// These event subscription behaviors require their callbacks to return
|
||||||
OnScroll (func (deltaX, deltaY float64)) event.Cookie
|
// a bool value. Under normal circumstances, these events are propagated
|
||||||
OnKeyDown (func (key input.Key, numberPad bool)) event.Cookie
|
// to the Box which is most directly affected by them, and then to all
|
||||||
OnKeyUp (func (key input.Key, numberPad bool)) event.Cookie
|
// 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 {
|
type CanvasBox interface {
|
||||||
Box
|
Box
|
||||||
|
|
||||||
// SetDrawer sets the Drawer that will be called upon to draw the Box's
|
// SetDrawer sets the canvas.Drawer that will be called upon to draw the
|
||||||
// content when it is invalidated.
|
// Box's content when it is invalidated. The Canvas passed to the Drawer
|
||||||
SetDrawer (canvas.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
|
// Invalidate causes the CanvasBox's area to be redrawn at the end of
|
||||||
// event cycle, even if it wouldn't be otherwise.
|
// the event cycle, even if it wouldn't otherwise be. This will call the
|
||||||
|
// canvas.Drawer specified by SetDrawer.
|
||||||
Invalidate ()
|
Invalidate ()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContentBox is an abstract box that has some kind of content. Its only purpose
|
// SurfaceBox is a Box that can be drawn to via a hardware accelerated (or
|
||||||
// is to be embedded into TextBox and ContainerBox.
|
// platform-specific) graphics context.
|
||||||
type ContentBox interface {
|
type SurfaceBox interface {
|
||||||
Box
|
Box
|
||||||
|
|
||||||
// SetOverflow sets whether or not the Box's content overflows
|
// Surface returns the underlying graphics context. The result must be
|
||||||
// horizontally and vertically. Overflowing content is clipped to the
|
// asserted to the expected type or passed through a type switch before
|
||||||
// bounds of the Box inset by all Borders (but not padding).
|
// use. The exact type returned here depends on the Backend being used,
|
||||||
SetOverflow (horizontal, vertical bool)
|
// and it is up to the application author to ensure that the application
|
||||||
// SetAlign sets how the Box's content is distributed horizontally and
|
// and Backend agree on it. Applications should fail gracefully if the
|
||||||
// vertically.
|
// expected type was not found. If the surface has been destroyed by the
|
||||||
SetAlign (x, y Align)
|
// 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
|
||||||
|
|
||||||
// ContentBounds returns the bounds of the inner content of the Box
|
// ContentBounds returns the bounds of the inner content of the Box
|
||||||
// relative to the window.
|
// relative to the Box's InnerBounds.
|
||||||
ContentBounds () image.Rectangle
|
ContentBounds () image.Rectangle
|
||||||
// ScrollTo shifts the origin of the Box's content to the origin of the
|
// 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)
|
ScrollTo (image.Point)
|
||||||
|
|
||||||
// OnContentBoundsChange specifies a function to be called when the
|
// OnContentBoundsChange specifies a function to be called when the
|
||||||
// Box's ContentBounds or InnerBounds changes.
|
// Box's ContentBounds or InnerBounds changes.
|
||||||
OnContentBoundsChange (func ()) event.Cookie
|
OnContentBoundsChange (func ()) event.Cookie
|
||||||
@ -231,26 +182,18 @@ type TextBox interface {
|
|||||||
ContentBox
|
ContentBox
|
||||||
|
|
||||||
// SetText sets the text content of the Box.
|
// SetText sets the text content of the Box.
|
||||||
SetText (string)
|
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
|
// SetSelectable sets whether or not the text content can be
|
||||||
// highlighted/selected.
|
// highlighted/selected.
|
||||||
SetSelectable (bool)
|
SetSelectable (bool)
|
||||||
// SetDotColor sets the highlight color of selected text.
|
|
||||||
SetDotColor (color.Color)
|
|
||||||
// Select sets the text cursor or selection.
|
// Select sets the text cursor or selection.
|
||||||
Select (text.Dot)
|
Select (text.Dot)
|
||||||
// Dot returns the text cursor or selection.
|
// Dot returns the text cursor or selection.
|
||||||
Dot () text.Dot
|
Dot () text.Dot
|
||||||
// OnDotChange specifies a function to be called when the text cursor or
|
// OnDotChange specifies a function to be called when the text cursor or
|
||||||
// selection changes.
|
// selection changes.
|
||||||
OnDotChange (func ()) event.Cookie
|
OnDotChange (func ()) event.Cookie
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContentBox is a box that can contain child Objects. It arranges them
|
// ContentBox is a box that can contain child Objects. It arranges them
|
||||||
@ -258,49 +201,47 @@ type TextBox interface {
|
|||||||
type ContainerBox interface {
|
type ContainerBox interface {
|
||||||
ContentBox
|
ContentBox
|
||||||
|
|
||||||
// SetGap sets the gap between child Objects.
|
// Add appends a child Object. If the Object is already a child of
|
||||||
SetGap (image.Point)
|
// another Object, it will be removed from that Object first.
|
||||||
// Add appends a child Object.
|
Add (Object)
|
||||||
Add (Object)
|
// Remove removes a child Object, if it is a child of this Box.
|
||||||
// Delete removes a child Object, if it is a child of this Box.
|
Remove (Object)
|
||||||
Delete (Object)
|
|
||||||
// Insert inserts a child Object before a specified Object. If the
|
// Insert inserts a child Object before a specified Object. If the
|
||||||
// before Object is nil or is not contained within this Box, the
|
// before Object is nil or is not contained within this Box, the
|
||||||
// inserted Object is appended.
|
// inserted Object is appended.
|
||||||
Insert (child Object, before Object)
|
Insert (child Object, before Object)
|
||||||
// Clear removes all child Objects.
|
// Clear removes all child Objects.
|
||||||
Clear ()
|
Clear ()
|
||||||
// Length returns the amount of child Objects.
|
// Len returns the amount of child Objects.
|
||||||
Length () int
|
Len () int
|
||||||
// At returns the child Object at the specified index.
|
// At returns the child Object at the specified index.
|
||||||
At (int) Object
|
At (int) Object
|
||||||
// SetLayout sets the layout of this Box. Child Objects will be
|
// SetInputMask sets whether or not user input events will be sent to
|
||||||
// positioned according to it.
|
// this Box's children. If false, which is the default, input events
|
||||||
SetLayout (Layout)
|
// will be sent to this Box as well as all of its children. If true,
|
||||||
|
// any input events that would otherwise go to this Box's children are
|
||||||
|
// sent to it instead. This prevents children from performing event
|
||||||
|
// catching as described in the documentation for Box.
|
||||||
|
SetInputMask (bool)
|
||||||
|
|
||||||
// These methods control whether certain user input events get
|
// TODO: if it turns out selecting specific kinds of events to mask off
|
||||||
// propagated to child Objects. If set to true, the relevant events will
|
// is a good idea, have SetInputMask take in a vararg list of event
|
||||||
// be sent to this container. If set to false (which is the default),
|
// types.
|
||||||
// the events will be sent to the appropriate child Object.
|
|
||||||
CaptureDND (bool)
|
|
||||||
CaptureMouse (bool)
|
|
||||||
CaptureScroll (bool)
|
|
||||||
CaptureKeyboard (bool)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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 {
|
type LayoutHints struct {
|
||||||
// Bounds is the bounding rectangle that children should be placed
|
// Bounds is the bounding rectangle that children should be placed
|
||||||
// within. Any padding values are already applied.
|
// within. Any padding values are already applied.
|
||||||
Bounds image.Rectangle
|
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.
|
// outside of Bounds.
|
||||||
OverflowX bool
|
OverflowX bool
|
||||||
OverflowY bool
|
OverflowY bool
|
||||||
// AlignX and AlignY control how child Boxes are aligned horizontally
|
// AlignX and AlignY control how child Boxes are aligned horizontally
|
||||||
// and vertically. The effect of this may vary depending on the Layout.
|
// and vertically. The effect of this may vary depending on the Layout.
|
||||||
AlignX Align
|
AlignX Align
|
||||||
AlignY Align
|
AlignY Align
|
||||||
// Gap controls the amount of horizontal and vertical spacing in-between
|
// Gap controls the amount of horizontal and vertical spacing in-between
|
||||||
// child Boxes.
|
// child Boxes.
|
||||||
Gap image.Point
|
Gap image.Point
|
||||||
@ -310,54 +251,126 @@ type LayoutHints struct {
|
|||||||
type Layout interface {
|
type Layout interface {
|
||||||
// MinimumSize returns the minimum width and height of
|
// MinimumSize returns the minimum width and height of
|
||||||
// LayoutHints.Bounds needed to properly lay out all child Boxes.
|
// LayoutHints.Bounds needed to properly lay out all child Boxes.
|
||||||
MinimumSize (LayoutHints, []Box) image.Point
|
MinimumSize (LayoutHints, BoxQuerier) image.Point
|
||||||
// Arrange arranges child boxes according to the given LayoutHints.
|
// Arrange arranges child Boxes according to the given LayoutHints.
|
||||||
Arrange (LayoutHints, []Box)
|
Arrange (LayoutHints, BoxArranger)
|
||||||
|
// RecommendedHeight returns the recommended height for a given width,
|
||||||
|
// if supported. Otherwise, it should just return the minimum height.
|
||||||
|
// The result of this behavior may or may not be respected, depending on
|
||||||
|
// the situation.
|
||||||
|
RecommendedHeight (LayoutHints, BoxQuerier, int) int
|
||||||
|
// RecommendedWidth returns the recommended width for a given height, if
|
||||||
|
// supported. Otherwise, it should just return the minimum width. The
|
||||||
|
// result of this behavior may or may not be respected, depending on the
|
||||||
|
// situation.
|
||||||
|
RecommendedWidth (LayoutHints, BoxQuerier, int) int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window is an operating system window. It can contain one object.
|
// BoxQuerier allows the attributes of a ContainerBox's children to be queried.
|
||||||
|
type BoxQuerier interface {
|
||||||
|
// Len returns the amount of Boxes.
|
||||||
|
Len () int
|
||||||
|
// 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 {
|
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.
|
// a time, and setting it will remove the current child if there is one.
|
||||||
SetRoot (Object)
|
SetRoot (Object)
|
||||||
// SetTitle sets the title of the window.
|
// SetTitle sets the title of the Window.
|
||||||
SetTitle (string)
|
SetTitle (string)
|
||||||
// SetIcon sets the icon of the window. When multiple icon sizes are
|
// SetIcon sets the icon of the Window.
|
||||||
// provided, the best fitting one is chosen for display.
|
SetIcon (Icon)
|
||||||
SetIcon (... image.Image)
|
// SetResizable sets whether the Window can be resized by the user in
|
||||||
// Widget returns a window representing a smaller iconified form of this
|
// the X and Y directions. If one or both axes are false, the ones that
|
||||||
// window. How exactly this window is used depends on the platform.
|
// are will be shrunk to the Window's minimum size.
|
||||||
// Subsequent calls to this method on the same window will return the
|
SetResizable (x, y bool)
|
||||||
// same window object.
|
// SetBounds sets this Window's bounds. This may or may not have any
|
||||||
Widget () (Window, error)
|
// effect on the Window's position on screen depending on the platform.
|
||||||
// NewMenu creates a new menu window. This window is undecorated and
|
SetBounds (image.Rectangle)
|
||||||
// will close once the user clicks outside of it.
|
// NewChild creates a new Window that is semantically a child of this
|
||||||
NewMenu (image.Rectangle) (Window, error)
|
// Window. It does not actually reside within this Window, but it may be
|
||||||
// NewModal creates a new modal window that blocks all input to this
|
// linked to it via some other means.
|
||||||
// window until it is closed.
|
NewChild (WindowKind, image.Rectangle) (Window, error)
|
||||||
NewModal (image.Rectangle) (Window, error)
|
// Modifiers returns which modifier keys on the keyboard are currently
|
||||||
|
// being held down.
|
||||||
|
Modifiers () input.Modifiers
|
||||||
|
// MousePosition returns the position of the mouse pointer relative to
|
||||||
|
// the Window.
|
||||||
|
MousePosition () image.Point
|
||||||
// Copy copies data to the clipboard.
|
// Copy copies data to the clipboard.
|
||||||
Copy (data.Data)
|
Copy (data.Data)
|
||||||
// Paste reads data from the clipboard. When the data is available or an
|
// Paste reads data from the clipboard. When the data is available or an
|
||||||
// error has occurred, the provided function will be called.
|
// error has occurred, the provided function will be called.
|
||||||
Paste (callback func (data.Data, error), accept ...data.Mime)
|
Paste (callback func (data.Data, error), accept ...data.Mime)
|
||||||
// Show shows the window.
|
// SetVisible sets whether or not this window is visible. Windows are
|
||||||
Show ()
|
// invisible by default.
|
||||||
// Hide hides the window.
|
SetVisible (bool)
|
||||||
Hide ()
|
// Visible returns whether or not this window is visible.
|
||||||
// Close closes the window.
|
Visible () bool
|
||||||
Close ()
|
// Close closes the window. This does not trigger the TryClose event.
|
||||||
// OnClose specifies a function to be called when the window is closed.
|
Close () error
|
||||||
OnClose (func ()) event.Cookie
|
// 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
|
||||||
// MainWindow is a top-level operating system window.
|
// "Unsaved changes" warning to the user.
|
||||||
type MainWindow interface {
|
OnTryClose (func () bool) event.Cookie
|
||||||
Window
|
// OnClose specifies a function to be called when the Window is closed.
|
||||||
|
OnClose (func ()) event.Cookie
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
|
188
path.go
188
path.go
@ -1,188 +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 {
|
|
||||||
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
|
|
||||||
}
|
|
441
theme/icon.go
441
theme/icon.go
@ -1,441 +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
|
|
||||||
IconSwitch
|
|
||||||
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)
|
|
||||||
}
|
|
81
tomo.go
81
tomo.go
@ -2,67 +2,49 @@ package tomo
|
|||||||
|
|
||||||
import "sync"
|
import "sync"
|
||||||
import "image"
|
import "image"
|
||||||
import "errors"
|
|
||||||
import "git.tebibyte.media/tomo/tomo/canvas"
|
import "git.tebibyte.media/tomo/tomo/canvas"
|
||||||
|
|
||||||
var backendLock sync.Mutex
|
// TODO this really sucks. It might be a good idea to have Do be the entry point
|
||||||
var backend Backend
|
// 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
|
var stopping bool
|
||||||
// event loop in that order. This function blocks until Stop is called, or the
|
var stoppingLock sync.Mutex
|
||||||
// backend experiences a fatal error.
|
func isStopping () bool {
|
||||||
func Run (callback func ()) error {
|
stoppingLock.Lock()
|
||||||
loadPlugins()
|
defer stoppingLock.Unlock()
|
||||||
|
return stopping
|
||||||
if backend != nil {
|
}
|
||||||
return errors.New("there is already a backend running")
|
func setStopping (is bool) {
|
||||||
}
|
stoppingLock.Lock()
|
||||||
|
defer stoppingLock.Unlock()
|
||||||
back, err := Initialize()
|
stopping = is
|
||||||
if err != nil { return err }
|
|
||||||
|
|
||||||
backendLock.Lock()
|
|
||||||
backend = back
|
|
||||||
backendLock.Unlock()
|
|
||||||
|
|
||||||
callback()
|
|
||||||
return backend.Run()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertBackend () {
|
// Stop stops the currently running Backend.
|
||||||
if backend == nil { panic("nil backend") }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop stops the backend, unblocking run. Run may be called again after calling
|
|
||||||
// Stop.
|
|
||||||
func Stop () {
|
func Stop () {
|
||||||
assertBackend()
|
if isStopping() { return }
|
||||||
backend.Stop()
|
setStopping(true)
|
||||||
|
|
||||||
backendLock.Lock()
|
backendLock.Lock()
|
||||||
|
defer backendLock.Unlock()
|
||||||
|
if backend == nil { return }
|
||||||
|
backend.Stop()
|
||||||
backend = nil
|
backend = nil
|
||||||
backendLock.Unlock()
|
setStopping(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do performs a callback function in the event loop thread as soon as possible.
|
// Do performs a callback function in the event loop thread as soon as possible.
|
||||||
func Do (callback func ()) {
|
func Do (callback func ()) {
|
||||||
backendLock.Lock()
|
backendLock.Lock()
|
||||||
|
defer backendLock.Unlock()
|
||||||
if backend != nil { backend.Do(callback) }
|
if backend != nil { backend.Do(callback) }
|
||||||
backendLock.Unlock()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewWindow creates and returns a window within the specified bounds on screen.
|
// NewWindow creates and returns a Window within the specified bounds on screen.
|
||||||
func NewWindow (bounds image.Rectangle) (MainWindow, error) {
|
func NewWindow (kind WindowKind, bounds image.Rectangle) (Window, error) {
|
||||||
assertBackend()
|
assertBackend()
|
||||||
return backend.NewWindow(bounds)
|
return backend.NewWindow(kind, bounds)
|
||||||
}
|
|
||||||
|
|
||||||
// NewPlainWindow is like NewWindow, but it creates an undecorated window that
|
|
||||||
// does not appear in window lists. It is intended for creating things like
|
|
||||||
// docks, panels, etc.
|
|
||||||
func NewPlainWindow (bounds image.Rectangle) (MainWindow, error) {
|
|
||||||
assertBackend()
|
|
||||||
return backend.NewPlainWindow(bounds)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBox creates and returns a basic Box.
|
// NewBox creates and returns a basic Box.
|
||||||
@ -89,9 +71,16 @@ func NewContainerBox () ContainerBox {
|
|||||||
return backend.NewContainerBox()
|
return backend.NewContainerBox()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewTexture creates a new texture from an image. When no longer in use, it
|
// NewTexture creates a new canvas.Texture from an image. When no longer in use,
|
||||||
// must be freed using Close().
|
// it must be freed using Close().
|
||||||
func NewTexture (source image.Image) canvas.TextureCloser {
|
func NewTexture (source image.Image) canvas.TextureCloser {
|
||||||
assertBackend()
|
assertBackend()
|
||||||
return backend.NewTexture(source)
|
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