Compare commits

..

19 Commits

Author SHA1 Message Date
8546f11471 Add config package
Closes #6
2024-09-11 01:49:25 -04:00
46f4c50381 Document the window kinds 2024-09-11 01:06:54 -04:00
b88e32fa49 Introduce the concept of window kinds 2024-09-11 00:54:45 -04:00
8f43fa310c Add new child window constructors
Closes #29
2024-09-11 00:21:57 -04:00
20c7e0fdb1 Add FuncCookie
Closes #27
2024-08-20 22:55:31 -04:00
d8a8ad7e0a Re-organize event.go 2024-08-20 22:55:20 -04:00
3feba5f811 event.Cookie is now just io.Closer
Closes #26
2024-08-20 22:50:51 -04:00
75c654d4ae Add error to Window.Close 2024-08-20 22:42:31 -04:00
da38e5411f Add TryClose event to Window
Closes #24
2024-08-20 22:41:41 -04:00
d47b525e42 Add documentation for AttrKind 2024-08-20 21:29:12 -04:00
862e08edf1 Add AttrCursor 2024-08-20 21:25:54 -04:00
47b2231acd Add list of cursor shapes
Progress on #25
2024-08-20 21:22:19 -04:00
b05e1f5d50 Fixed IconListContract being identical to IconListExpand 2024-08-18 16:16:40 -04:00
608a898be3 Fix deadlock in Stop 2024-08-16 17:40:39 -04:00
91a8ae2fa5 Add some new icons
Closes #23
2024-08-16 17:08:28 -04:00
cacfd20a8a Describe IconUnknown 2024-08-16 17:05:11 -04:00
d08fe845fc Add Bounds, InnerBounds to Window
Closes #22
2024-08-16 17:03:24 -04:00
e23a688103 Remove checkbox icons 2024-08-13 12:30:47 -04:00
f4cc47eb16 Fix meaningless panics 2024-08-13 12:17:54 -04:00
8 changed files with 179 additions and 63 deletions

View File

@@ -13,6 +13,8 @@ type Attr interface {
attr ()
}
// AttrKind enumerates all attribute kinds. There is one of these for each
// attribute type.
type AttrKind int; const (
AttrKindColor AttrKind = iota
AttrKindTexture
@@ -28,6 +30,7 @@ type AttrKind int; const (
AttrKindAlign
AttrKindOverflow
AttrKindLayout
AttrKindCursor
)
// AttrColor sets the background color of a box.
@@ -58,6 +61,8 @@ type AttrAlign struct { X, Y Align }
type AttrOverflow struct { X, Y bool }
// AttrLayout sets the layout, if the box is a ContentBox.
type AttrLayout struct { Layout }
// AttrCursor sets the mouse cursor shape.
type AttrCursor Cursor
// AColor is a convenience constructor for the color attribute.
func AColor (col color.Color) AttrColor {
@@ -115,6 +120,10 @@ func AOverflow (x, y bool) AttrOverflow {
func ALayout (layout Layout) AttrLayout {
return AttrLayout { Layout: layout }
}
// ACursor is a convenience constructor for the cursor attribute.
func ACursor (cursor Cursor) AttrCursor {
return AttrCursor(cursor)
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrColor) Equals (other Attr) bool {
@@ -233,6 +242,14 @@ func (this AttrLayout) Equals (other Attr) bool {
// two layouts cannot "reasonably" be declared equal
return false
}
// Equals returns true if both attributes can reasonably be declared equal.
func (this AttrCursor) Equals (other Attr) bool {
if other, ok := other.(AttrCursor); ok {
return this == other
} else {
return false
}
}
func (AttrColor) Kind () AttrKind { return AttrKindColor }
func (AttrTexture) Kind () AttrKind { return AttrKindTexture }
@@ -248,6 +265,7 @@ func (AttrWrap) Kind () AttrKind { return AttrKindWrap }
func (AttrAlign) Kind () AttrKind { return AttrKindAlign }
func (AttrOverflow) Kind () AttrKind { return AttrKindOverflow }
func (AttrLayout) Kind () AttrKind { return AttrKindLayout }
func (AttrCursor) Kind () AttrKind { return AttrKindCursor }
func (AttrColor) attr () { }
func (AttrTexture) attr () { }
@@ -263,3 +281,4 @@ func (AttrWrap) attr () { }
func (AttrAlign) attr () { }
func (AttrOverflow) attr () { }
func (AttrLayout) attr () { }
func (AttrCursor) attr () { }

View File

@@ -18,12 +18,7 @@ type Backend interface {
NewContainerBox () ContainerBox
// NewWindow creates a normal Window and returns it.
NewWindow (image.Rectangle) (Window, error)
// NewPlainWindow creates an undecorated window that does not appear in
// window lists and returns it. This is intended for making things like
// panels, docks, etc.
NewPlainWindow (image.Rectangle) (Window, error)
NewWindow (WindowKind, image.Rectangle) (Window, error)
// NewTexture creates a new texture from an image. The backend must
// reject any texture that was not made by it.

15
config/config.go Normal file
View File

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

35
cursor.go Normal file
View File

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

View File

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

View File

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

View File

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

46
tomo.go
View File

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