Merge branch 'main' of git.tebibyte.media:sashakoshka/tomo

This commit is contained in:
Sasha Koshka 2023-02-02 17:58:51 -05:00
commit 14d1836209
51 changed files with 593 additions and 539 deletions

View File

@ -3,11 +3,11 @@ package artist
import "math" import "math"
import "image" import "image"
import "image/color" import "image/color"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/canvas"
// FillEllipse draws a filled ellipse with the specified pattern. // FillEllipse draws a filled ellipse with the specified pattern.
func FillEllipse ( func FillEllipse (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
bounds image.Rectangle, bounds image.Rectangle,
) ( ) (
@ -36,7 +36,7 @@ func FillEllipse (
// StrokeEllipse draws the outline of an ellipse with the specified line weight // StrokeEllipse draws the outline of an ellipse with the specified line weight
// and pattern. // and pattern.
func StrokeEllipse ( func StrokeEllipse (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
weight int, weight int,
bounds image.Rectangle, bounds image.Rectangle,

View File

@ -2,14 +2,14 @@ package artist
import "image" import "image"
import "image/color" import "image/color"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/canvas"
// TODO: draw thick lines more efficiently // TODO: draw thick lines more efficiently
// Line draws a line from one point to another with the specified weight and // Line draws a line from one point to another with the specified weight and
// pattern. // pattern.
func Line ( func Line (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
weight int, weight int,
min image.Point, min image.Point,
@ -46,7 +46,7 @@ func Line (
} }
func lineLow ( func lineLow (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
weight int, weight int,
min image.Point, min image.Point,
@ -82,7 +82,7 @@ func lineLow (
} }
func lineHigh ( func lineHigh (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
weight int, weight int,
min image.Point, min image.Point,

View File

@ -1,12 +1,12 @@
package artist package artist
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/canvas"
// Paste transfers one canvas onto another, offset by the specified point. // Paste transfers one canvas onto another, offset by the specified point.
func Paste ( func Paste (
destination tomo.Canvas, destination canvas.Canvas,
source tomo.Canvas, source canvas.Canvas,
offset image.Point, offset image.Point,
) ( ) (
updatedRegion image.Rectangle, updatedRegion image.Rectangle,
@ -31,7 +31,7 @@ func Paste (
// FillRectangle draws a filled rectangle with the specified pattern. // FillRectangle draws a filled rectangle with the specified pattern.
func FillRectangle ( func FillRectangle (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
bounds image.Rectangle, bounds image.Rectangle,
) ( ) (
@ -61,7 +61,7 @@ func FillRectangle (
// StrokeRectangle draws the outline of a rectangle with the specified line // StrokeRectangle draws the outline of a rectangle with the specified line
// weight and pattern. // weight and pattern.
func StrokeRectangle ( func StrokeRectangle (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
weight int, weight int,
bounds image.Rectangle, bounds image.Rectangle,

View File

@ -6,7 +6,7 @@ import "unicode"
import "image/draw" import "image/draw"
import "golang.org/x/image/font" import "golang.org/x/image/font"
import "golang.org/x/image/math/fixed" import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/canvas"
type characterLayout struct { type characterLayout struct {
x int x int
@ -95,7 +95,7 @@ func (drawer *TextDrawer) SetAlignment (align Align) {
// Draw draws the drawer's text onto the specified canvas at the given offset. // Draw draws the drawer's text onto the specified canvas at the given offset.
func (drawer *TextDrawer) Draw ( func (drawer *TextDrawer) Draw (
destination tomo.Canvas, destination canvas.Canvas,
source Pattern, source Pattern,
offset image.Point, offset image.Point,
) ( ) (

View File

@ -1,6 +1,8 @@
package tomo package tomo
import "errors" import "errors"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/elements"
// Backend represents a connection to a display server, or something similar. // Backend represents a connection to a display server, or something similar.
// It is capable of managing an event loop, and creating windows. // It is capable of managing an event loop, and creating windows.
@ -19,13 +21,13 @@ type Backend interface {
// NewWindow creates a new window with the specified width and height, // NewWindow creates a new window with the specified width and height,
// and returns a struct representing it that fulfills the Window // and returns a struct representing it that fulfills the Window
// interface. // interface.
NewWindow (width, height int) (window Window, err error) NewWindow (width, height int) (window elements.Window, err error)
// Copy puts data into the clipboard. // Copy puts data into the clipboard.
Copy (Data) Copy (data.Data)
// Paste returns the data currently in the clipboard. // Paste returns the data currently in the clipboard.
Paste (accept []Mime) (Data) Paste (accept []data.Mime) (data.Data)
} }
// BackendFactory represents a function capable of constructing a backend // BackendFactory represents a function capable of constructing a backend

View File

@ -3,64 +3,64 @@ package x
import "unicode" import "unicode"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/keybind"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
// when making changes to this file, look at keysymdef.h and // when making changes to this file, look at keysymdef.h and
// https://tronche.com/gui/x/xlib/input/keyboard-encoding.html // https://tronche.com/gui/x/xlib/input/keyboard-encoding.html
var buttonCodeTable = map[xproto.Keysym] tomo.Key { var buttonCodeTable = map[xproto.Keysym] input.Key {
0xFFFFFF: tomo.KeyNone, 0xFFFFFF: input.KeyNone,
0xFF63: tomo.KeyInsert, 0xFF63: input.KeyInsert,
0xFF67: tomo.KeyMenu, 0xFF67: input.KeyMenu,
0xFF61: tomo.KeyPrintScreen, 0xFF61: input.KeyPrintScreen,
0xFF6B: tomo.KeyPause, 0xFF6B: input.KeyPause,
0xFFE5: tomo.KeyCapsLock, 0xFFE5: input.KeyCapsLock,
0xFF14: tomo.KeyScrollLock, 0xFF14: input.KeyScrollLock,
0xFF7F: tomo.KeyNumLock, 0xFF7F: input.KeyNumLock,
0xFF08: tomo.KeyBackspace, 0xFF08: input.KeyBackspace,
0xFF09: tomo.KeyTab, 0xFF09: input.KeyTab,
0xFE20: tomo.KeyTab, 0xFE20: input.KeyTab,
0xFF0D: tomo.KeyEnter, 0xFF0D: input.KeyEnter,
0xFF1B: tomo.KeyEscape, 0xFF1B: input.KeyEscape,
0xFF52: tomo.KeyUp, 0xFF52: input.KeyUp,
0xFF54: tomo.KeyDown, 0xFF54: input.KeyDown,
0xFF51: tomo.KeyLeft, 0xFF51: input.KeyLeft,
0xFF53: tomo.KeyRight, 0xFF53: input.KeyRight,
0xFF55: tomo.KeyPageUp, 0xFF55: input.KeyPageUp,
0xFF56: tomo.KeyPageDown, 0xFF56: input.KeyPageDown,
0xFF50: tomo.KeyHome, 0xFF50: input.KeyHome,
0xFF57: tomo.KeyEnd, 0xFF57: input.KeyEnd,
0xFFE1: tomo.KeyLeftShift, 0xFFE1: input.KeyLeftShift,
0xFFE2: tomo.KeyRightShift, 0xFFE2: input.KeyRightShift,
0xFFE3: tomo.KeyLeftControl, 0xFFE3: input.KeyLeftControl,
0xFFE4: tomo.KeyRightControl, 0xFFE4: input.KeyRightControl,
0xFFE7: tomo.KeyLeftMeta, 0xFFE7: input.KeyLeftMeta,
0xFFE8: tomo.KeyRightMeta, 0xFFE8: input.KeyRightMeta,
0xFFE9: tomo.KeyLeftAlt, 0xFFE9: input.KeyLeftAlt,
0xFFEA: tomo.KeyRightAlt, 0xFFEA: input.KeyRightAlt,
0xFFEB: tomo.KeyLeftSuper, 0xFFEB: input.KeyLeftSuper,
0xFFEC: tomo.KeyRightSuper, 0xFFEC: input.KeyRightSuper,
0xFFED: tomo.KeyLeftHyper, 0xFFED: input.KeyLeftHyper,
0xFFEE: tomo.KeyRightHyper, 0xFFEE: input.KeyRightHyper,
0xFFFF: tomo.KeyDelete, 0xFFFF: input.KeyDelete,
0xFFBE: tomo.KeyF1, 0xFFBE: input.KeyF1,
0xFFBF: tomo.KeyF2, 0xFFBF: input.KeyF2,
0xFFC0: tomo.KeyF3, 0xFFC0: input.KeyF3,
0xFFC1: tomo.KeyF4, 0xFFC1: input.KeyF4,
0xFFC2: tomo.KeyF5, 0xFFC2: input.KeyF5,
0xFFC3: tomo.KeyF6, 0xFFC3: input.KeyF6,
0xFFC4: tomo.KeyF7, 0xFFC4: input.KeyF7,
0xFFC5: tomo.KeyF8, 0xFFC5: input.KeyF8,
0xFFC6: tomo.KeyF9, 0xFFC6: input.KeyF9,
0xFFC7: tomo.KeyF10, 0xFFC7: input.KeyF10,
0xFFC8: tomo.KeyF11, 0xFFC8: input.KeyF11,
0xFFC9: tomo.KeyF12, 0xFFC9: input.KeyF12,
// TODO: send this whenever a compose key, dead key, etc is pressed, // TODO: send this whenever a compose key, dead key, etc is pressed,
// and then send the resulting character while witholding the key // and then send the resulting character while witholding the key
@ -68,46 +68,46 @@ var buttonCodeTable = map[xproto.Keysym] tomo.Key {
// concerned, a magical key with the final character was pressed and the // concerned, a magical key with the final character was pressed and the
// KeyDead key is just so that the program might provide some visual // KeyDead key is just so that the program might provide some visual
// feedback to the user while input is being waited for. // feedback to the user while input is being waited for.
0xFF20: tomo.KeyDead, 0xFF20: input.KeyDead,
} }
var keypadCodeTable = map[xproto.Keysym] tomo.Key { var keypadCodeTable = map[xproto.Keysym] input.Key {
0xff80: tomo.Key(' '), 0xff80: input.Key(' '),
0xff89: tomo.KeyTab, 0xff89: input.KeyTab,
0xff8d: tomo.KeyEnter, 0xff8d: input.KeyEnter,
0xff91: tomo.KeyF1, 0xff91: input.KeyF1,
0xff92: tomo.KeyF2, 0xff92: input.KeyF2,
0xff93: tomo.KeyF3, 0xff93: input.KeyF3,
0xff94: tomo.KeyF4, 0xff94: input.KeyF4,
0xff95: tomo.KeyHome, 0xff95: input.KeyHome,
0xff96: tomo.KeyLeft, 0xff96: input.KeyLeft,
0xff97: tomo.KeyUp, 0xff97: input.KeyUp,
0xff98: tomo.KeyRight, 0xff98: input.KeyRight,
0xff99: tomo.KeyDown, 0xff99: input.KeyDown,
0xff9a: tomo.KeyPageUp, 0xff9a: input.KeyPageUp,
0xff9b: tomo.KeyPageDown, 0xff9b: input.KeyPageDown,
0xff9c: tomo.KeyEnd, 0xff9c: input.KeyEnd,
0xff9d: tomo.KeyHome, 0xff9d: input.KeyHome,
0xff9e: tomo.KeyInsert, 0xff9e: input.KeyInsert,
0xff9f: tomo.KeyDelete, 0xff9f: input.KeyDelete,
0xffbd: tomo.Key('='), 0xffbd: input.Key('='),
0xffaa: tomo.Key('*'), 0xffaa: input.Key('*'),
0xffab: tomo.Key('+'), 0xffab: input.Key('+'),
0xffac: tomo.Key(','), 0xffac: input.Key(','),
0xffad: tomo.Key('-'), 0xffad: input.Key('-'),
0xffae: tomo.Key('.'), 0xffae: input.Key('.'),
0xffaf: tomo.Key('/'), 0xffaf: input.Key('/'),
0xffb0: tomo.Key('0'), 0xffb0: input.Key('0'),
0xffb1: tomo.Key('1'), 0xffb1: input.Key('1'),
0xffb2: tomo.Key('2'), 0xffb2: input.Key('2'),
0xffb3: tomo.Key('3'), 0xffb3: input.Key('3'),
0xffb4: tomo.Key('4'), 0xffb4: input.Key('4'),
0xffb5: tomo.Key('5'), 0xffb5: input.Key('5'),
0xffb6: tomo.Key('6'), 0xffb6: input.Key('6'),
0xffb7: tomo.Key('7'), 0xffb7: input.Key('7'),
0xffb8: tomo.Key('8'), 0xffb8: input.Key('8'),
0xffb9: tomo.Key('9'), 0xffb9: input.Key('9'),
} }
// initializeKeymapInformation grabs keyboard mapping information from the X // initializeKeymapInformation grabs keyboard mapping information from the X
@ -168,7 +168,7 @@ func (backend *Backend) keycodeToKey (
keycode xproto.Keycode, keycode xproto.Keycode,
state uint16, state uint16,
) ( ) (
button tomo.Key, button input.Key,
numberPad bool, numberPad bool,
) { ) {
// PARAGRAPH 3 // PARAGRAPH 3
@ -359,7 +359,7 @@ func (backend *Backend) keycodeToKey (
if numberPad { return } if numberPad { return }
// otherwise, use the rune // otherwise, use the rune
button = tomo.Key(selectedRune) button = input.Key(selectedRune)
return return
} }

View File

@ -1,6 +1,7 @@
package x package x
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "github.com/jezek/xgbutil" import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
@ -98,9 +99,9 @@ func (window *Window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (fo
func (window *Window) modifiersFromState ( func (window *Window) modifiersFromState (
state uint16, state uint16,
) ( ) (
modifiers tomo.Modifiers, modifiers input.Modifiers,
) { ) {
return tomo.Modifiers { return input.Modifiers {
Shift: Shift:
(state & xproto.ModMaskShift) > 0 || (state & xproto.ModMaskShift) > 0 ||
(state & window.backend.modifierMasks.shiftLock) > 0, (state & window.backend.modifierMasks.shiftLock) > 0,
@ -123,18 +124,18 @@ func (window *Window) handleKeyPress (
modifiers := window.modifiersFromState(keyEvent.State) modifiers := window.modifiersFromState(keyEvent.State)
modifiers.NumberPad = numberPad modifiers.NumberPad = numberPad
if key == tomo.KeyTab && modifiers.Alt { if key == input.KeyTab && modifiers.Alt {
if child, ok := window.child.(tomo.Focusable); ok { if child, ok := window.child.(elements.Focusable); ok {
direction := tomo.KeynavDirectionForward direction := input.KeynavDirectionForward
if modifiers.Shift { if modifiers.Shift {
direction = tomo.KeynavDirectionBackward direction = input.KeynavDirectionBackward
} }
if !child.HandleFocus(direction) { if !child.HandleFocus(direction) {
child.HandleUnfocus() child.HandleUnfocus()
} }
} }
} else if child, ok := window.child.(tomo.KeyboardTarget); ok { } else if child, ok := window.child.(elements.KeyboardTarget); ok {
child.HandleKeyDown(key, modifiers) child.HandleKeyDown(key, modifiers)
} }
} }
@ -168,7 +169,7 @@ func (window *Window) handleKeyRelease (
modifiers := window.modifiersFromState(keyEvent.State) modifiers := window.modifiersFromState(keyEvent.State)
modifiers.NumberPad = numberPad modifiers.NumberPad = numberPad
if child, ok := window.child.(tomo.KeyboardTarget); ok { if child, ok := window.child.(elements.KeyboardTarget); ok {
child.HandleKeyUp(key, modifiers) child.HandleKeyUp(key, modifiers)
} }
} }
@ -179,7 +180,7 @@ func (window *Window) handleButtonPress (
) { ) {
if window.child == nil { return } if window.child == nil { return }
if child, ok := window.child.(tomo.MouseTarget); ok { if child, ok := window.child.(elements.MouseTarget); ok {
buttonEvent := *event.ButtonPressEvent buttonEvent := *event.ButtonPressEvent
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 {
sum := scrollSum { } sum := scrollSum { }
@ -193,7 +194,7 @@ func (window *Window) handleButtonPress (
child.HandleMouseDown ( child.HandleMouseDown (
int(buttonEvent.EventX), int(buttonEvent.EventX),
int(buttonEvent.EventY), int(buttonEvent.EventY),
tomo.Button(buttonEvent.Detail)) input.Button(buttonEvent.Detail))
} }
} }
@ -205,13 +206,13 @@ func (window *Window) handleButtonRelease (
) { ) {
if window.child == nil { return } if window.child == nil { return }
if child, ok := window.child.(tomo.MouseTarget); ok { if child, ok := window.child.(elements.MouseTarget); ok {
buttonEvent := *event.ButtonReleaseEvent buttonEvent := *event.ButtonReleaseEvent
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return } if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return }
child.HandleMouseUp ( child.HandleMouseUp (
int(buttonEvent.EventX), int(buttonEvent.EventX),
int(buttonEvent.EventY), int(buttonEvent.EventY),
tomo.Button(buttonEvent.Detail)) input.Button(buttonEvent.Detail))
} }
} }
@ -221,7 +222,7 @@ func (window *Window) handleMotionNotify (
) { ) {
if window.child == nil { return } if window.child == nil { return }
if child, ok := window.child.(tomo.MouseTarget); ok { if child, ok := window.child.(elements.MouseTarget); ok {
motionEvent := window.compressMotionNotify(*event.MotionNotifyEvent) motionEvent := window.compressMotionNotify(*event.MotionNotifyEvent)
child.HandleMouseMove ( child.HandleMouseMove (
int(motionEvent.EventX), int(motionEvent.EventX),

View File

@ -7,14 +7,16 @@ import "github.com/jezek/xgbutil/icccm"
import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/xevent"
import "github.com/jezek/xgbutil/xwindow" import "github.com/jezek/xgbutil/xwindow"
import "github.com/jezek/xgbutil/xgraphics" import "github.com/jezek/xgbutil/xgraphics"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/elements"
type Window struct { type Window struct {
backend *Backend backend *Backend
xWindow *xwindow.Window xWindow *xwindow.Window
xCanvas *xgraphics.Image xCanvas *xgraphics.Image
canvas tomo.BasicCanvas canvas canvas.BasicCanvas
child tomo.Element child elements.Element
onClose func () onClose func ()
skipChildDrawCallback bool skipChildDrawCallback bool
@ -27,7 +29,7 @@ type Window struct {
func (backend *Backend) NewWindow ( func (backend *Backend) NewWindow (
width, height int, width, height int,
) ( ) (
output tomo.Window, output elements.Window,
err error, err error,
) { ) {
if backend == nil { panic("nil backend") } if backend == nil { panic("nil backend") }
@ -79,16 +81,16 @@ func (backend *Backend) NewWindow (
return return
} }
func (window *Window) Adopt (child tomo.Element) { func (window *Window) Adopt (child elements.Element) {
// disown previous child // disown previous child
if window.child != nil { if window.child != nil {
window.child.OnDamage(nil) window.child.OnDamage(nil)
window.child.OnMinimumSizeChange(nil) window.child.OnMinimumSizeChange(nil)
} }
if previousChild, ok := window.child.(tomo.Flexible); ok { if previousChild, ok := window.child.(elements.Flexible); ok {
previousChild.OnFlexibleHeightChange(nil) previousChild.OnFlexibleHeightChange(nil)
} }
if previousChild, ok := window.child.(tomo.Focusable); ok { if previousChild, ok := window.child.(elements.Focusable); ok {
previousChild.OnFocusRequest(nil) previousChild.OnFocusRequest(nil)
previousChild.OnFocusMotionRequest(nil) previousChild.OnFocusMotionRequest(nil)
if previousChild.Focused() { if previousChild.Focused() {
@ -98,10 +100,10 @@ func (window *Window) Adopt (child tomo.Element) {
// adopt new child // adopt new child
window.child = child window.child = child
if newChild, ok := child.(tomo.Flexible); ok { if newChild, ok := child.(elements.Flexible); ok {
newChild.OnFlexibleHeightChange(window.resizeChildToFit) newChild.OnFlexibleHeightChange(window.resizeChildToFit)
} }
if newChild, ok := child.(tomo.Focusable); ok { if newChild, ok := child.(elements.Focusable); ok {
newChild.OnFocusRequest(window.childSelectionRequestCallback) newChild.OnFocusRequest(window.childSelectionRequestCallback)
} }
if child != nil { if child != nil {
@ -116,7 +118,7 @@ func (window *Window) Adopt (child tomo.Element) {
} }
} }
func (window *Window) Child () (child tomo.Element) { func (window *Window) Child () (child elements.Element) {
child = window.child child = window.child
return return
} }
@ -229,7 +231,7 @@ func (window *Window) redrawChildEntirely () {
func (window *Window) resizeChildToFit () { func (window *Window) resizeChildToFit () {
window.skipChildDrawCallback = true window.skipChildDrawCallback = true
if child, ok := window.child.(tomo.Flexible); ok { if child, ok := window.child.(elements.Flexible); ok {
minimumHeight := child.FlexibleHeightFor(window.metrics.width) minimumHeight := child.FlexibleHeightFor(window.metrics.width)
minimumWidth, _ := child.MinimumSize() minimumWidth, _ := child.MinimumSize()
@ -252,12 +254,12 @@ func (window *Window) resizeChildToFit () {
window.skipChildDrawCallback = false window.skipChildDrawCallback = false
} }
func (window *Window) childDrawCallback (region tomo.Canvas) { func (window *Window) childDrawCallback (region canvas.Canvas) {
if window.skipChildDrawCallback { return } if window.skipChildDrawCallback { return }
window.pushRegion(window.paste(region)) window.pushRegion(window.paste(region))
} }
func (window *Window) paste (canvas tomo.Canvas) (updatedRegion image.Rectangle) { func (window *Window) paste (canvas canvas.Canvas) (updatedRegion image.Rectangle) {
data, stride := canvas.Buffer() data, stride := canvas.Buffer()
bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds()) bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds())
for x := bounds.Min.X; x < bounds.Max.X; x ++ { for x := bounds.Min.X; x < bounds.Max.X; x ++ {
@ -293,18 +295,18 @@ func (window *Window) childMinimumSizeChangeCallback (width, height int) {
} }
func (window *Window) childSelectionRequestCallback () (granted bool) { func (window *Window) childSelectionRequestCallback () (granted bool) {
if child, ok := window.child.(tomo.Focusable); ok { if child, ok := window.child.(elements.Focusable); ok {
child.HandleFocus(tomo.KeynavDirectionNeutral) child.HandleFocus(input.KeynavDirectionNeutral)
} }
return true return true
} }
func (window *Window) childSelectionMotionRequestCallback ( func (window *Window) childSelectionMotionRequestCallback (
direction tomo.KeynavDirection, direction input.KeynavDirection,
) ( ) (
granted bool, granted bool,
) { ) {
if child, ok := window.child.(tomo.Focusable); ok { if child, ok := window.child.(elements.Focusable); ok {
if !child.HandleFocus(direction) { if !child.HandleFocus(direction) {
child.HandleUnfocus() child.HandleUnfocus()
} }

View File

@ -1,6 +1,7 @@
package x package x
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "github.com/jezek/xgbutil" import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
@ -81,14 +82,14 @@ func (backend *Backend) Do (callback func ()) {
// Copy puts data into the clipboard. This method is not yet implemented and // Copy puts data into the clipboard. This method is not yet implemented and
// will do nothing! // will do nothing!
func (backend *Backend) Copy (data tomo.Data) { func (backend *Backend) Copy (data data.Data) {
backend.assert() backend.assert()
// TODO // TODO
} }
// Paste returns the data currently in the clipboard. This method may // Paste returns the data currently in the clipboard. This method may
// return nil. This method is not yet implemented and will do nothing! // return nil. This method is not yet implemented and will do nothing!
func (backend *Backend) Paste (accept []tomo.Mime) (data tomo.Data) { func (backend *Backend) Paste (accept []data.Mime) (data data.Data) {
backend.assert() backend.assert()
// TODO // TODO
return return

View File

@ -1,4 +1,4 @@
package tomo package canvas
import "image" import "image"
import "image/draw" import "image/draw"

22
config/config.go Normal file
View File

@ -0,0 +1,22 @@
package config
// Padding returns the amount of internal padding elements should have. An
// element's inner content (such as text) should be inset by this amount,
// in addition to the inset returned by the pattern of its background. When
// using the aforementioned inset values to calculate the element's minimum size
// or the position and alignment of its content, all parameters in the
// PatternState should be unset except for Case.
func Padding () int {
return 7
}
// Margin returns how much space should be put in between elements.
func Margin () int {
return 8
}
// HandleWidth returns how large grab handles should typically be. This is
// important for accessibility reasons.
func HandleWidth () int {
return 16
}

View File

@ -1,4 +1,4 @@
package tomo package data
import "io" import "io"

View File

@ -1,7 +1,7 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -38,10 +38,10 @@ func NewButton (text string) (element *Button) {
return return
} }
func (element *Button) HandleMouseDown (x, y int, button tomo.Button) { func (element *Button) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
if !element.Focused() { element.Focus() } if !element.Focused() { element.Focus() }
if button != tomo.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressed = true element.pressed = true
if element.core.HasImage() { if element.core.HasImage() {
element.draw() element.draw()
@ -49,8 +49,8 @@ func (element *Button) HandleMouseDown (x, y int, button tomo.Button) {
} }
} }
func (element *Button) HandleMouseUp (x, y int, button tomo.Button) { func (element *Button) HandleMouseUp (x, y int, button input.Button) {
if button != tomo.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressed = false element.pressed = false
if element.core.HasImage() { if element.core.HasImage() {
element.draw() element.draw()
@ -69,9 +69,9 @@ func (element *Button) HandleMouseUp (x, y int, button tomo.Button) {
func (element *Button) HandleMouseMove (x, y int) { } func (element *Button) HandleMouseMove (x, y int) { }
func (element *Button) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } func (element *Button) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Button) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *Button) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if !element.Enabled() { return } if !element.Enabled() { return }
if key == tomo.KeyEnter { if key == input.KeyEnter {
element.pressed = true element.pressed = true
if element.core.HasImage() { if element.core.HasImage() {
element.draw() element.draw()
@ -80,8 +80,8 @@ func (element *Button) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
} }
} }
func (element *Button) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { func (element *Button) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
if key == tomo.KeyEnter && element.pressed { if key == input.KeyEnter && element.pressed {
element.pressed = false element.pressed = false
if element.core.HasImage() { if element.core.HasImage() {
element.draw() element.draw()

View File

@ -1,7 +1,7 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -39,7 +39,7 @@ func NewCheckbox (text string, checked bool) (element *Checkbox) {
return return
} }
func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) { func (element *Checkbox) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
element.pressed = true element.pressed = true
@ -49,8 +49,8 @@ func (element *Checkbox) HandleMouseDown (x, y int, button tomo.Button) {
} }
} }
func (element *Checkbox) HandleMouseUp (x, y int, button tomo.Button) { func (element *Checkbox) HandleMouseUp (x, y int, button input.Button) {
if button != tomo.ButtonLeft || !element.pressed { return } if button != input.ButtonLeft || !element.pressed { return }
element.pressed = false element.pressed = false
within := image.Point { x, y }. within := image.Point { x, y }.
@ -71,8 +71,8 @@ func (element *Checkbox) HandleMouseUp (x, y int, button tomo.Button) {
func (element *Checkbox) HandleMouseMove (x, y int) { } func (element *Checkbox) HandleMouseMove (x, y int) { }
func (element *Checkbox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } func (element *Checkbox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Checkbox) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *Checkbox) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if key == tomo.KeyEnter { if key == input.KeyEnter {
element.pressed = true element.pressed = true
if element.core.HasImage() { if element.core.HasImage() {
element.draw() element.draw()
@ -81,8 +81,8 @@ func (element *Checkbox) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers)
} }
} }
func (element *Checkbox) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) { func (element *Checkbox) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if key == tomo.KeyEnter && element.pressed { if key == input.KeyEnter && element.pressed {
element.pressed = false element.pressed = false
element.checked = !element.checked element.checked = !element.checked
if element.core.HasImage() { if element.core.HasImage() {

View File

@ -1,9 +1,12 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var containerCase = theme.C("basic", "container") var containerCase = theme.C("basic", "container")
@ -14,21 +17,21 @@ type Container struct {
*core.Core *core.Core
core core.CoreControl core core.CoreControl
layout tomo.Layout layout layouts.Layout
children []tomo.LayoutEntry children []layouts.LayoutEntry
drags [10]tomo.MouseTarget drags [10]elements.MouseTarget
warping bool warping bool
focused bool focused bool
focusable bool focusable bool
flexible bool flexible bool
onFocusRequest func () (granted bool) onFocusRequest func () (granted bool)
onFocusMotionRequest func (tomo.KeynavDirection) (granted bool) onFocusMotionRequest func (input.KeynavDirection) (granted bool)
onFlexibleHeightChange func () onFlexibleHeightChange func ()
} }
// NewContainer creates a new container. // NewContainer creates a new container.
func NewContainer (layout tomo.Layout) (element *Container) { func NewContainer (layout layouts.Layout) (element *Container) {
element = &Container { } element = &Container { }
element.Core, element.core = core.NewCore(element.redoAll) element.Core, element.core = core.NewCore(element.redoAll)
element.SetLayout(layout) element.SetLayout(layout)
@ -36,7 +39,7 @@ func NewContainer (layout tomo.Layout) (element *Container) {
} }
// SetLayout sets the layout of this container. // SetLayout sets the layout of this container.
func (element *Container) SetLayout (layout tomo.Layout) { func (element *Container) SetLayout (layout layouts.Layout) {
element.layout = layout element.layout = layout
if element.core.HasImage() { if element.core.HasImage() {
element.redoAll() element.redoAll()
@ -47,28 +50,28 @@ func (element *Container) SetLayout (layout tomo.Layout) {
// Adopt adds a new child element to the container. If expand is set to true, // Adopt adds a new child element to the container. If expand is set to true,
// the element will expand (instead of contract to its minimum size), in // the element will expand (instead of contract to its minimum size), in
// whatever way is defined by the current layout. // whatever way is defined by the current layout.
func (element *Container) Adopt (child tomo.Element, expand bool) { func (element *Container) Adopt (child elements.Element, expand bool) {
// set event handlers // set event handlers
child.OnDamage (func (region tomo.Canvas) { child.OnDamage (func (region canvas.Canvas) {
element.core.DamageRegion(region.Bounds()) element.core.DamageRegion(region.Bounds())
}) })
child.OnMinimumSizeChange(element.updateMinimumSize) child.OnMinimumSizeChange(element.updateMinimumSize)
if child0, ok := child.(tomo.Flexible); ok { if child0, ok := child.(elements.Flexible); ok {
child0.OnFlexibleHeightChange(element.updateMinimumSize) child0.OnFlexibleHeightChange(element.updateMinimumSize)
} }
if child0, ok := child.(tomo.Focusable); ok { if child0, ok := child.(elements.Focusable); ok {
child0.OnFocusRequest (func () (granted bool) { child0.OnFocusRequest (func () (granted bool) {
return element.childFocusRequestCallback(child0) return element.childFocusRequestCallback(child0)
}) })
child0.OnFocusMotionRequest ( child0.OnFocusMotionRequest (
func (direction tomo.KeynavDirection) (granted bool) { func (direction input.KeynavDirection) (granted bool) {
if element.onFocusMotionRequest == nil { return } if element.onFocusMotionRequest == nil { return }
return element.onFocusMotionRequest(direction) return element.onFocusMotionRequest(direction)
}) })
} }
// add child // add child
element.children = append (element.children, tomo.LayoutEntry { element.children = append (element.children, layouts.LayoutEntry {
Element: child, Element: child,
Expand: expand, Expand: expand,
}) })
@ -106,7 +109,7 @@ func (element *Container) Warp (callback func ()) {
// Disown removes the given child from the container if it is contained within // Disown removes the given child from the container if it is contained within
// it. // it.
func (element *Container) Disown (child tomo.Element) { func (element *Container) Disown (child elements.Element) {
for index, entry := range element.children { for index, entry := range element.children {
if entry.Element == child { if entry.Element == child {
element.clearChildEventHandlers(entry.Element) element.clearChildEventHandlers(entry.Element)
@ -125,18 +128,18 @@ func (element *Container) Disown (child tomo.Element) {
} }
} }
func (element *Container) clearChildEventHandlers (child tomo.Element) { func (element *Container) clearChildEventHandlers (child elements.Element) {
child.DrawTo(nil) child.DrawTo(nil)
child.OnDamage(nil) child.OnDamage(nil)
child.OnMinimumSizeChange(nil) child.OnMinimumSizeChange(nil)
if child0, ok := child.(tomo.Focusable); ok { if child0, ok := child.(elements.Focusable); ok {
child0.OnFocusRequest(nil) child0.OnFocusRequest(nil)
child0.OnFocusMotionRequest(nil) child0.OnFocusMotionRequest(nil)
if child0.Focused() { if child0.Focused() {
child0.HandleUnfocus() child0.HandleUnfocus()
} }
} }
if child0, ok := child.(tomo.Flexible); ok { if child0, ok := child.(elements.Flexible); ok {
child0.OnFlexibleHeightChange(nil) child0.OnFlexibleHeightChange(nil)
} }
} }
@ -154,8 +157,8 @@ func (element *Container) DisownAll () {
} }
// Children returns a slice containing this element's children. // Children returns a slice containing this element's children.
func (element *Container) Children () (children []tomo.Element) { func (element *Container) Children () (children []elements.Element) {
children = make([]tomo.Element, len(element.children)) children = make([]elements.Element, len(element.children))
for index, entry := range element.children { for index, entry := range element.children {
children[index] = entry.Element children[index] = entry.Element
} }
@ -169,14 +172,14 @@ func (element *Container) CountChildren () (count int) {
// Child returns the child at the specified index. If the index is out of // Child returns the child at the specified index. If the index is out of
// bounds, this method will return nil. // bounds, this method will return nil.
func (element *Container) Child (index int) (child tomo.Element) { func (element *Container) Child (index int) (child elements.Element) {
if index < 0 || index > len(element.children) { return } if index < 0 || index > len(element.children) { return }
return element.children[index].Element return element.children[index].Element
} }
// ChildAt returns the child that contains the specified x and y coordinates. If // ChildAt returns the child that contains the specified x and y coordinates. If
// there are no children at the coordinates, this method will return nil. // there are no children at the coordinates, this method will return nil.
func (element *Container) ChildAt (point image.Point) (child tomo.Element) { func (element *Container) ChildAt (point image.Point) (child elements.Element) {
for _, entry := range element.children { for _, entry := range element.children {
if point.In(entry.Bounds) { if point.In(entry.Bounds) {
child = entry.Element child = entry.Element
@ -185,7 +188,7 @@ func (element *Container) ChildAt (point image.Point) (child tomo.Element) {
return return
} }
func (element *Container) childPosition (child tomo.Element) (position image.Point) { func (element *Container) childPosition (child elements.Element) (position image.Point) {
for _, entry := range element.children { for _, entry := range element.children {
if entry.Element == child { if entry.Element == child {
position = entry.Bounds.Min position = entry.Bounds.Min
@ -209,18 +212,18 @@ func (element *Container) redoAll () {
// cut our canvas up and give peices to child elements // cut our canvas up and give peices to child elements
for _, entry := range element.children { for _, entry := range element.children {
entry.DrawTo(tomo.Cut(element, entry.Bounds)) entry.DrawTo(canvas.Cut(element, entry.Bounds))
} }
} }
func (element *Container) HandleMouseDown (x, y int, button tomo.Button) { func (element *Container) HandleMouseDown (x, y int, button input.Button) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) child, handlesMouse := element.ChildAt(image.Pt(x, y)).(elements.MouseTarget)
if !handlesMouse { return } if !handlesMouse { return }
element.drags[button] = child element.drags[button] = child
child.HandleMouseDown(x, y, button) child.HandleMouseDown(x, y, button)
} }
func (element *Container) HandleMouseUp (x, y int, button tomo.Button) { func (element *Container) HandleMouseUp (x, y int, button input.Button) {
child := element.drags[button] child := element.drags[button]
if child == nil { return } if child == nil { return }
element.drags[button] = nil element.drags[button] = nil
@ -235,14 +238,14 @@ func (element *Container) HandleMouseMove (x, y int) {
} }
func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) { func (element *Container) HandleMouseScroll (x, y int, deltaX, deltaY float64) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget) child, handlesMouse := element.ChildAt(image.Pt(x, y)).(elements.MouseTarget)
if !handlesMouse { return } if !handlesMouse { return }
child.HandleMouseScroll(x, y, deltaX, deltaY) child.HandleMouseScroll(x, y, deltaX, deltaY)
} }
func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *Container) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
element.forFocused (func (child tomo.Focusable) bool { element.forFocused (func (child elements.Focusable) bool {
child0, handlesKeyboard := child.(tomo.KeyboardTarget) child0, handlesKeyboard := child.(elements.KeyboardTarget)
if handlesKeyboard { if handlesKeyboard {
child0.HandleKeyDown(key, modifiers) child0.HandleKeyDown(key, modifiers)
} }
@ -250,9 +253,9 @@ func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers)
}) })
} }
func (element *Container) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) { func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
element.forFocused (func (child tomo.Focusable) bool { element.forFocused (func (child elements.Focusable) bool {
child0, handlesKeyboard := child.(tomo.KeyboardTarget) child0, handlesKeyboard := child.(elements.KeyboardTarget)
if handlesKeyboard { if handlesKeyboard {
child0.HandleKeyUp(key, modifiers) child0.HandleKeyUp(key, modifiers)
} }
@ -261,7 +264,9 @@ func (element *Container) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
} }
func (element *Container) FlexibleHeightFor (width int) (height int) { func (element *Container) FlexibleHeightFor (width int) (height int) {
return element.layout.FlexibleHeightFor(element.children, width) return element.layout.FlexibleHeightFor (
element.children,
theme.Margin(), width)
} }
func (element *Container) OnFlexibleHeightChange (callback func ()) { func (element *Container) OnFlexibleHeightChange (callback func ()) {
@ -278,7 +283,7 @@ func (element *Container) Focus () {
} }
} }
func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool) { func (element *Container) HandleFocus (direction input.KeynavDirection) (ok bool) {
if !element.focusable { return false } if !element.focusable { return false }
direction = direction.Canon() direction = direction.Canon()
@ -288,12 +293,12 @@ func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool)
// the first or last focusable element depending on the // the first or last focusable element depending on the
// direction. // direction.
switch direction { switch direction {
case tomo.KeynavDirectionNeutral, tomo.KeynavDirectionForward: case input.KeynavDirectionNeutral, input.KeynavDirectionForward:
// if we recieve a neutral or forward direction, focus // if we recieve a neutral or forward direction, focus
// the first focusable element. // the first focusable element.
return element.focusFirstFocusableElement(direction) return element.focusFirstFocusableElement(direction)
case tomo.KeynavDirectionBackward: case input.KeynavDirectionBackward:
// if we recieve a backward direction, focus the last // if we recieve a backward direction, focus the last
// focusable element. // focusable element.
return element.focusLastFocusableElement(direction) return element.focusLastFocusableElement(direction)
@ -302,7 +307,7 @@ func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool)
// an element is currently focused, so we need to move the // an element is currently focused, so we need to move the
// focus in the specified direction // focus in the specified direction
firstFocusedChild := firstFocusedChild :=
element.children[firstFocused].Element.(tomo.Focusable) element.children[firstFocused].Element.(elements.Focusable)
// before we move the focus, the currently focused child // before we move the focus, the currently focused child
// may also be able to move its focus. if the child is able // may also be able to move its focus. if the child is able
@ -319,7 +324,7 @@ func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool)
child, focusable := child, focusable :=
element.children[index]. element.children[index].
Element.(tomo.Focusable) Element.(elements.Focusable)
if focusable && child.HandleFocus(direction) { if focusable && child.HandleFocus(direction) {
// we have found one, so we now actually move // we have found one, so we now actually move
// the focus. // the focus.
@ -334,11 +339,11 @@ func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool)
} }
func (element *Container) focusFirstFocusableElement ( func (element *Container) focusFirstFocusableElement (
direction tomo.KeynavDirection, direction input.KeynavDirection,
) ( ) (
ok bool, ok bool,
) { ) {
element.forFocusable (func (child tomo.Focusable) bool { element.forFocusable (func (child elements.Focusable) bool {
if child.HandleFocus(direction) { if child.HandleFocus(direction) {
element.focused = true element.focused = true
ok = true ok = true
@ -350,11 +355,11 @@ func (element *Container) focusFirstFocusableElement (
} }
func (element *Container) focusLastFocusableElement ( func (element *Container) focusLastFocusableElement (
direction tomo.KeynavDirection, direction input.KeynavDirection,
) ( ) (
ok bool, ok bool,
) { ) {
element.forFocusableBackward (func (child tomo.Focusable) bool { element.forFocusableBackward (func (child elements.Focusable) bool {
if child.HandleFocus(direction) { if child.HandleFocus(direction) {
element.focused = true element.focused = true
ok = true ok = true
@ -367,7 +372,7 @@ func (element *Container) focusLastFocusableElement (
func (element *Container) HandleUnfocus () { func (element *Container) HandleUnfocus () {
element.focused = false element.focused = false
element.forFocused (func (child tomo.Focusable) bool { element.forFocused (func (child elements.Focusable) bool {
child.HandleUnfocus() child.HandleUnfocus()
return true return true
}) })
@ -378,41 +383,41 @@ func (element *Container) OnFocusRequest (callback func () (granted bool)) {
} }
func (element *Container) OnFocusMotionRequest ( func (element *Container) OnFocusMotionRequest (
callback func (direction tomo.KeynavDirection) (granted bool), callback func (direction input.KeynavDirection) (granted bool),
) { ) {
element.onFocusMotionRequest = callback element.onFocusMotionRequest = callback
} }
func (element *Container) forFocused (callback func (child tomo.Focusable) bool) { func (element *Container) forFocused (callback func (child elements.Focusable) bool) {
for _, entry := range element.children { for _, entry := range element.children {
child, focusable := entry.Element.(tomo.Focusable) child, focusable := entry.Element.(elements.Focusable)
if focusable && child.Focused() { if focusable && child.Focused() {
if !callback(child) { break } if !callback(child) { break }
} }
} }
} }
func (element *Container) forFocusable (callback func (child tomo.Focusable) bool) { func (element *Container) forFocusable (callback func (child elements.Focusable) bool) {
for _, entry := range element.children { for _, entry := range element.children {
child, focusable := entry.Element.(tomo.Focusable) child, focusable := entry.Element.(elements.Focusable)
if focusable { if focusable {
if !callback(child) { break } if !callback(child) { break }
} }
} }
} }
func (element *Container) forFlexible (callback func (child tomo.Flexible) bool) { func (element *Container) forFlexible (callback func (child elements.Flexible) bool) {
for _, entry := range element.children { for _, entry := range element.children {
child, flexible := entry.Element.(tomo.Flexible) child, flexible := entry.Element.(elements.Flexible)
if flexible { if flexible {
if !callback(child) { break } if !callback(child) { break }
} }
} }
} }
func (element *Container) forFocusableBackward (callback func (child tomo.Focusable) bool) { func (element *Container) forFocusableBackward (callback func (child elements.Focusable) bool) {
for index := len(element.children) - 1; index >= 0; index -- { for index := len(element.children) - 1; index >= 0; index -- {
child, focusable := element.children[index].Element.(tomo.Focusable) child, focusable := element.children[index].Element.(elements.Focusable)
if focusable { if focusable {
if !callback(child) { break } if !callback(child) { break }
} }
@ -421,7 +426,7 @@ func (element *Container) forFocusableBackward (callback func (child tomo.Focusa
func (element *Container) firstFocused () (index int) { func (element *Container) firstFocused () (index int) {
for currentIndex, entry := range element.children { for currentIndex, entry := range element.children {
child, focusable := entry.Element.(tomo.Focusable) child, focusable := entry.Element.(elements.Focusable)
if focusable && child.Focused() { if focusable && child.Focused() {
return currentIndex return currentIndex
} }
@ -431,12 +436,12 @@ func (element *Container) firstFocused () (index int) {
func (element *Container) reflectChildProperties () { func (element *Container) reflectChildProperties () {
element.focusable = false element.focusable = false
element.forFocusable (func (tomo.Focusable) bool { element.forFocusable (func (elements.Focusable) bool {
element.focusable = true element.focusable = true
return false return false
}) })
element.flexible = false element.flexible = false
element.forFlexible (func (tomo.Flexible) bool { element.forFlexible (func (elements.Flexible) bool {
element.flexible = true element.flexible = true
return false return false
}) })
@ -446,16 +451,16 @@ func (element *Container) reflectChildProperties () {
} }
func (element *Container) childFocusRequestCallback ( func (element *Container) childFocusRequestCallback (
child tomo.Focusable, child elements.Focusable,
) ( ) (
granted bool, granted bool,
) { ) {
if element.onFocusRequest != nil && element.onFocusRequest() { if element.onFocusRequest != nil && element.onFocusRequest() {
element.forFocused (func (child tomo.Focusable) bool { element.forFocused (func (child elements.Focusable) bool {
child.HandleUnfocus() child.HandleUnfocus()
return true return true
}) })
child.HandleFocus(tomo.KeynavDirectionNeutral) child.HandleFocus(input.KeynavDirectionNeutral)
return true return true
} else { } else {
return false return false
@ -463,13 +468,16 @@ func (element *Container) childFocusRequestCallback (
} }
func (element *Container) updateMinimumSize () { func (element *Container) updateMinimumSize () {
width, height := element.layout.MinimumSize(element.children) width, height := element.layout.MinimumSize (
element.children, theme.Margin())
if element.flexible { if element.flexible {
height = element.layout.FlexibleHeightFor(element.children, width) height = element.layout.FlexibleHeightFor (
element.children, theme.Margin(), width)
} }
element.core.SetMinimumSize(width, height) element.core.SetMinimumSize(width, height)
} }
func (element *Container) recalculate () { func (element *Container) recalculate () {
element.layout.Arrange(element.children, element.Bounds()) element.layout.Arrange (
element.children, theme.Margin(), element.Bounds())
} }

View File

@ -1,4 +1,4 @@
package basic package basicElements
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"

View File

@ -1,9 +1,10 @@
package basic package basicElements
import "fmt" import "fmt"
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -73,10 +74,10 @@ func (element *List) Collapse (width, height int) {
element.updateMinimumSize() element.updateMinimumSize()
} }
func (element *List) HandleMouseDown (x, y int, button tomo.Button) { func (element *List) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
if !element.Focused() { element.Focus() } if !element.Focused() { element.Focus() }
if button != tomo.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressed = true element.pressed = true
if element.selectUnderMouse(x, y) && element.core.HasImage() { if element.selectUnderMouse(x, y) && element.core.HasImage() {
element.draw() element.draw()
@ -84,8 +85,8 @@ func (element *List) HandleMouseDown (x, y int, button tomo.Button) {
} }
} }
func (element *List) HandleMouseUp (x, y int, button tomo.Button) { func (element *List) HandleMouseUp (x, y int, button input.Button) {
if button != tomo.ButtonLeft { return } if button != input.ButtonLeft { return }
element.pressed = false element.pressed = false
} }
@ -100,18 +101,18 @@ func (element *List) HandleMouseMove (x, y int) {
func (element *List) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } func (element *List) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *List) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *List) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if !element.Enabled() { return } if !element.Enabled() { return }
altered := false altered := false
switch key { switch key {
case tomo.KeyLeft, tomo.KeyUp: case input.KeyLeft, input.KeyUp:
altered = element.changeSelectionBy(-1) altered = element.changeSelectionBy(-1)
case tomo.KeyRight, tomo.KeyDown: case input.KeyRight, input.KeyDown:
altered = element.changeSelectionBy(1) altered = element.changeSelectionBy(1)
case tomo.KeyEscape: case input.KeyEscape:
altered = element.selectEntry(-1) altered = element.selectEntry(-1)
} }
@ -121,7 +122,7 @@ func (element *List) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
} }
} }
func (element *List) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { } func (element *List) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
// ScrollContentBounds returns the full content size of the element. // ScrollContentBounds returns the full content size of the element.
func (element *List) ScrollContentBounds () (bounds image.Rectangle) { func (element *List) ScrollContentBounds () (bounds image.Rectangle) {
@ -383,7 +384,7 @@ func (element *List) draw () {
bounds.Min.X, bounds.Min.X,
bounds.Min.Y - element.scroll, bounds.Min.Y - element.scroll,
} }
innerCanvas := tomo.Cut(element, bounds) innerCanvas := canvas.Cut(element, bounds)
for index, entry := range element.entries { for index, entry := range element.entries {
entryPosition := dot entryPosition := dot
dot.Y += entry.Bounds().Dy() dot.Y += entry.Bounds().Dy()

View File

@ -1,8 +1,8 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
var listEntryCase = theme.C("basic", "listEntry") var listEntryCase = theme.C("basic", "listEntry")
@ -53,7 +53,7 @@ func (entry *ListEntry) updateBounds () {
} }
func (entry *ListEntry) Draw ( func (entry *ListEntry) Draw (
destination tomo.Canvas, destination canvas.Canvas,
offset image.Point, offset image.Point,
focused bool, focused bool,
on bool, on bool,

View File

@ -1,4 +1,4 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"

View File

@ -1,9 +1,11 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var scrollContainerCase = theme.C("basic", "scrollContainer") var scrollContainerCase = theme.C("basic", "scrollContainer")
@ -17,7 +19,7 @@ type ScrollContainer struct {
core core.CoreControl core core.CoreControl
focused bool focused bool
child tomo.Scrollable child elements.Scrollable
childWidth, childHeight int childWidth, childHeight int
horizontal struct { horizontal struct {
@ -41,7 +43,7 @@ type ScrollContainer struct {
} }
onFocusRequest func () (granted bool) onFocusRequest func () (granted bool)
onFocusMotionRequest func (tomo.KeynavDirection) (granted bool) onFocusMotionRequest func (input.KeynavDirection) (granted bool)
} }
// NewScrollContainer creates a new scroll container with the specified scroll // NewScrollContainer creates a new scroll container with the specified scroll
@ -64,7 +66,7 @@ func (element *ScrollContainer) handleResize () {
// Adopt adds a scrollable element to the scroll container. The container can // Adopt adds a scrollable element to the scroll container. The container can
// only contain one scrollable element at a time, and when a new one is adopted // only contain one scrollable element at a time, and when a new one is adopted
// it replaces the last one. // it replaces the last one.
func (element *ScrollContainer) Adopt (child tomo.Scrollable) { func (element *ScrollContainer) Adopt (child elements.Scrollable) {
// disown previous child if it exists // disown previous child if it exists
if element.child != nil { if element.child != nil {
element.clearChildEventHandlers(child) element.clearChildEventHandlers(child)
@ -76,7 +78,7 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
child.OnDamage(element.childDamageCallback) child.OnDamage(element.childDamageCallback)
child.OnMinimumSizeChange(element.updateMinimumSize) child.OnMinimumSizeChange(element.updateMinimumSize)
child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback) child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
if newChild, ok := child.(tomo.Focusable); ok { if newChild, ok := child.(elements.Focusable); ok {
newChild.OnFocusRequest ( newChild.OnFocusRequest (
element.childFocusRequestCallback) element.childFocusRequestCallback)
newChild.OnFocusMotionRequest ( newChild.OnFocusMotionRequest (
@ -96,19 +98,19 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
} }
} }
func (element *ScrollContainer) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if child, ok := element.child.(tomo.KeyboardTarget); ok { if child, ok := element.child.(elements.KeyboardTarget); ok {
child.HandleKeyDown(key, modifiers) child.HandleKeyDown(key, modifiers)
} }
} }
func (element *ScrollContainer) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) { func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if child, ok := element.child.(tomo.KeyboardTarget); ok { if child, ok := element.child.(elements.KeyboardTarget); ok {
child.HandleKeyUp(key, modifiers) child.HandleKeyUp(key, modifiers)
} }
} }
func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) { func (element *ScrollContainer) HandleMouseDown (x, y int, button input.Button) {
point := image.Pt(x, y) point := image.Pt(x, y)
if point.In(element.horizontal.bar) { if point.In(element.horizontal.bar) {
element.horizontal.dragging = true element.horizontal.dragging = true
@ -140,12 +142,12 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) {
element.scrollChildBy(0, -16) element.scrollChildBy(0, -16)
} }
} else if child, ok := element.child.(tomo.MouseTarget); ok { } else if child, ok := element.child.(elements.MouseTarget); ok {
child.HandleMouseDown(x, y, button) child.HandleMouseDown(x, y, button)
} }
} }
func (element *ScrollContainer) HandleMouseUp (x, y int, button tomo.Button) { func (element *ScrollContainer) HandleMouseUp (x, y int, button input.Button) {
if element.horizontal.dragging { if element.horizontal.dragging {
element.horizontal.dragging = false element.horizontal.dragging = false
element.drawHorizontalBar() element.drawHorizontalBar()
@ -156,7 +158,7 @@ func (element *ScrollContainer) HandleMouseUp (x, y int, button tomo.Button) {
element.drawVerticalBar() element.drawVerticalBar()
element.core.DamageRegion(element.vertical.bar) element.core.DamageRegion(element.vertical.bar)
} else if child, ok := element.child.(tomo.MouseTarget); ok { } else if child, ok := element.child.(elements.MouseTarget); ok {
child.HandleMouseUp(x, y, button) child.HandleMouseUp(x, y, button)
} }
} }
@ -168,7 +170,7 @@ func (element *ScrollContainer) HandleMouseMove (x, y int) {
} else if element.vertical.dragging { } else if element.vertical.dragging {
element.dragVerticalBar(image.Pt(x, y)) element.dragVerticalBar(image.Pt(x, y))
} else if child, ok := element.child.(tomo.MouseTarget); ok { } else if child, ok := element.child.(elements.MouseTarget); ok {
child.HandleMouseMove(x, y) child.HandleMouseMove(x, y)
} }
} }
@ -199,11 +201,11 @@ func (element *ScrollContainer) Focus () {
} }
func (element *ScrollContainer) HandleFocus ( func (element *ScrollContainer) HandleFocus (
direction tomo.KeynavDirection, direction input.KeynavDirection,
) ( ) (
accepted bool, accepted bool,
) { ) {
if child, ok := element.child.(tomo.Focusable); ok { if child, ok := element.child.(elements.Focusable); ok {
element.focused = true element.focused = true
return child.HandleFocus(direction) return child.HandleFocus(direction)
} else { } else {
@ -213,7 +215,7 @@ func (element *ScrollContainer) HandleFocus (
} }
func (element *ScrollContainer) HandleUnfocus () { func (element *ScrollContainer) HandleUnfocus () {
if child, ok := element.child.(tomo.Focusable); ok { if child, ok := element.child.(elements.Focusable); ok {
child.HandleUnfocus() child.HandleUnfocus()
} }
element.focused = false element.focused = false
@ -224,20 +226,20 @@ func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool))
} }
func (element *ScrollContainer) OnFocusMotionRequest ( func (element *ScrollContainer) OnFocusMotionRequest (
callback func (direction tomo.KeynavDirection) (granted bool), callback func (direction input.KeynavDirection) (granted bool),
) { ) {
element.onFocusMotionRequest = callback element.onFocusMotionRequest = callback
} }
func (element *ScrollContainer) childDamageCallback (region tomo.Canvas) { func (element *ScrollContainer) childDamageCallback (region canvas.Canvas) {
element.core.DamageRegion(artist.Paste(element, region, image.Point { })) element.core.DamageRegion(artist.Paste(element, region, image.Point { }))
} }
func (element *ScrollContainer) childFocusRequestCallback () (granted bool) { func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
child, ok := element.child.(tomo.Focusable) child, ok := element.child.(elements.Focusable)
if !ok { return false } if !ok { return false }
if element.onFocusRequest != nil && element.onFocusRequest() { if element.onFocusRequest != nil && element.onFocusRequest() {
child.HandleFocus(tomo.KeynavDirectionNeutral) child.HandleFocus(input.KeynavDirectionNeutral)
return true return true
} else { } else {
return false return false
@ -245,7 +247,7 @@ func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
} }
func (element *ScrollContainer) childFocusMotionRequestCallback ( func (element *ScrollContainer) childFocusMotionRequestCallback (
direction tomo.KeynavDirection, direction input.KeynavDirection,
) ( ) (
granted bool, granted bool,
) { ) {
@ -253,19 +255,19 @@ func (element *ScrollContainer) childFocusMotionRequestCallback (
return element.onFocusMotionRequest(direction) return element.onFocusMotionRequest(direction)
} }
func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) { func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
child.DrawTo(nil) child.DrawTo(nil)
child.OnDamage(nil) child.OnDamage(nil)
child.OnMinimumSizeChange(nil) child.OnMinimumSizeChange(nil)
child.OnScrollBoundsChange(nil) child.OnScrollBoundsChange(nil)
if child0, ok := child.(tomo.Focusable); ok { if child0, ok := child.(elements.Focusable); ok {
child0.OnFocusRequest(nil) child0.OnFocusRequest(nil)
child0.OnFocusMotionRequest(nil) child0.OnFocusMotionRequest(nil)
if child0.Focused() { if child0.Focused() {
child0.HandleUnfocus() child0.HandleUnfocus()
} }
} }
if child0, ok := child.(tomo.Flexible); ok { if child0, ok := child.(elements.Flexible); ok {
child0.OnFlexibleHeightChange(nil) child0.OnFlexibleHeightChange(nil)
} }
} }
@ -275,7 +277,7 @@ func (element *ScrollContainer) resizeChildToFit () {
0, 0, 0, 0,
element.childWidth, element.childWidth,
element.childHeight).Add(element.Bounds().Min) element.childHeight).Add(element.Bounds().Min)
element.child.DrawTo(tomo.Cut(element, childBounds)) element.child.DrawTo(canvas.Cut(element, childBounds))
} }
func (element *ScrollContainer) recalculate () { func (element *ScrollContainer) recalculate () {

View File

@ -1,4 +1,4 @@
package basic package basicElements
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"

View File

@ -1,7 +1,7 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -41,7 +41,7 @@ func NewSwitch (text string, on bool) (element *Switch) {
return return
} }
func (element *Switch) HandleMouseDown (x, y int, button tomo.Button) { func (element *Switch) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
element.Focus() element.Focus()
element.pressed = true element.pressed = true
@ -51,8 +51,8 @@ func (element *Switch) HandleMouseDown (x, y int, button tomo.Button) {
} }
} }
func (element *Switch) HandleMouseUp (x, y int, button tomo.Button) { func (element *Switch) HandleMouseUp (x, y int, button input.Button) {
if button != tomo.ButtonLeft || !element.pressed { return } if button != input.ButtonLeft || !element.pressed { return }
element.pressed = false element.pressed = false
within := image.Point { x, y }. within := image.Point { x, y }.
@ -73,8 +73,8 @@ func (element *Switch) HandleMouseUp (x, y int, button tomo.Button) {
func (element *Switch) HandleMouseMove (x, y int) { } func (element *Switch) HandleMouseMove (x, y int) { }
func (element *Switch) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } func (element *Switch) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Switch) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) { func (element *Switch) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if key == tomo.KeyEnter { if key == input.KeyEnter {
element.pressed = true element.pressed = true
if element.core.HasImage() { if element.core.HasImage() {
element.draw() element.draw()
@ -83,8 +83,8 @@ func (element *Switch) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
} }
} }
func (element *Switch) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) { func (element *Switch) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if key == tomo.KeyEnter && element.pressed { if key == input.KeyEnter && element.pressed {
element.pressed = false element.pressed = false
element.checked = !element.checked element.checked = !element.checked
if element.core.HasImage() { if element.core.HasImage() {

View File

@ -1,7 +1,7 @@
package basic package basicElements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/textmanip" import "git.tebibyte.media/sashakoshka/tomo/textmanip"
@ -24,7 +24,7 @@ type TextBox struct {
placeholderDrawer artist.TextDrawer placeholderDrawer artist.TextDrawer
valueDrawer artist.TextDrawer valueDrawer artist.TextDrawer
onKeyDown func (key tomo.Key, modifiers tomo.Modifiers) (handled bool) onKeyDown func (key input.Key, modifiers input.Modifiers) (handled bool)
onChange func () onChange func ()
onScrollBoundsChange func () onScrollBoundsChange func ()
} }
@ -59,16 +59,16 @@ func (element *TextBox) handleResize () {
} }
} }
func (element *TextBox) HandleMouseDown (x, y int, button tomo.Button) { func (element *TextBox) HandleMouseDown (x, y int, button input.Button) {
if !element.Enabled() { return } if !element.Enabled() { return }
if !element.Focused() { element.Focus() } if !element.Focused() { element.Focus() }
} }
func (element *TextBox) HandleMouseUp (x, y int, button tomo.Button) { } func (element *TextBox) HandleMouseUp (x, y int, button input.Button) { }
func (element *TextBox) HandleMouseMove (x, y int) { } func (element *TextBox) HandleMouseMove (x, y int) { }
func (element *TextBox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { } func (element *TextBox) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) { func (element *TextBox) HandleKeyDown(key input.Key, modifiers input.Modifiers) {
if element.onKeyDown != nil && element.onKeyDown(key, modifiers) { if element.onKeyDown != nil && element.onKeyDown(key, modifiers) {
return return
} }
@ -77,7 +77,7 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
altered := true altered := true
textChanged := false textChanged := false
switch { switch {
case key == tomo.KeyBackspace: case key == input.KeyBackspace:
if len(element.text) < 1 { break } if len(element.text) < 1 { break }
element.text, element.cursor = textmanip.Backspace ( element.text, element.cursor = textmanip.Backspace (
element.text, element.text,
@ -85,7 +85,7 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
modifiers.Control) modifiers.Control)
textChanged = true textChanged = true
case key == tomo.KeyDelete: case key == input.KeyDelete:
if len(element.text) < 1 { break } if len(element.text) < 1 { break }
element.text, element.cursor = textmanip.Delete ( element.text, element.cursor = textmanip.Delete (
element.text, element.text,
@ -93,13 +93,13 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
modifiers.Control) modifiers.Control)
textChanged = true textChanged = true
case key == tomo.KeyLeft: case key == input.KeyLeft:
element.cursor = textmanip.MoveLeft ( element.cursor = textmanip.MoveLeft (
element.text, element.text,
element.cursor, element.cursor,
modifiers.Control) modifiers.Control)
case key == tomo.KeyRight: case key == input.KeyRight:
element.cursor = textmanip.MoveRight ( element.cursor = textmanip.MoveRight (
element.text, element.text,
element.cursor, element.cursor,
@ -136,7 +136,7 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
} }
} }
func (element *TextBox) HandleKeyUp(key tomo.Key, modifiers tomo.Modifiers) { } func (element *TextBox) HandleKeyUp(key input.Key, modifiers input.Modifiers) { }
func (element *TextBox) SetPlaceholder (placeholder string) { func (element *TextBox) SetPlaceholder (placeholder string) {
if element.placeholder == placeholder { return } if element.placeholder == placeholder { return }
@ -177,7 +177,7 @@ func (element *TextBox) Filled () (filled bool) {
} }
func (element *TextBox) OnKeyDown ( func (element *TextBox) OnKeyDown (
callback func (key tomo.Key, modifiers tomo.Modifiers) (handled bool), callback func (key input.Key, modifiers input.Modifiers) (handled bool),
) { ) {
element.onKeyDown = callback element.onKeyDown = callback
} }

View File

@ -2,12 +2,12 @@ package core
import "image" import "image"
import "image/color" import "image/color"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/canvas"
// Core is a struct that implements some core functionality common to most // Core is a struct that implements some core functionality common to most
// widgets. It is meant to be embedded directly into a struct. // widgets. It is meant to be embedded directly into a struct.
type Core struct { type Core struct {
canvas tomo.Canvas canvas canvas.Canvas
metrics struct { metrics struct {
minimumWidth int minimumWidth int
@ -16,7 +16,7 @@ type Core struct {
drawSizeChange func () drawSizeChange func ()
onMinimumSizeChange func () onMinimumSizeChange func ()
onDamage func (region tomo.Canvas) onDamage func (region canvas.Canvas)
} }
// NewCore creates a new element core and its corresponding control. // NewCore creates a new element core and its corresponding control.
@ -49,7 +49,7 @@ func (core *Core) Set (x, y int, c color.Color) () {
core.canvas.Set(x, y, c) core.canvas.Set(x, y, c)
} }
// Buffer fulfills the tomo.Canvas interface. // Buffer fulfills the canvas.Canvas interface.
func (core *Core) Buffer () (data []color.RGBA, stride int) { func (core *Core) Buffer () (data []color.RGBA, stride int) {
if core.canvas == nil { return } if core.canvas == nil { return }
return core.canvas.Buffer() return core.canvas.Buffer()
@ -63,7 +63,7 @@ func (core *Core) MinimumSize () (width, height int) {
// DrawTo fulfills the tomo.Element interface. This should not need to be // DrawTo fulfills the tomo.Element interface. This should not need to be
// overridden. // overridden.
func (core *Core) DrawTo (canvas tomo.Canvas) { func (core *Core) DrawTo (canvas canvas.Canvas) {
core.canvas = canvas core.canvas = canvas
if core.drawSizeChange != nil { if core.drawSizeChange != nil {
core.drawSizeChange() core.drawSizeChange()
@ -72,7 +72,7 @@ func (core *Core) DrawTo (canvas tomo.Canvas) {
// OnDamage fulfils the tomo.Element interface. This should not need to be // OnDamage fulfils the tomo.Element interface. This should not need to be
// overridden. // overridden.
func (core *Core) OnDamage (callback func (region tomo.Canvas)) { func (core *Core) OnDamage (callback func (region canvas.Canvas)) {
core.onDamage = callback core.onDamage = callback
} }
@ -100,7 +100,7 @@ func (control CoreControl) HasImage () (has bool) {
// does not need to be called when responding to a resize event. // does not need to be called when responding to a resize event.
func (control CoreControl) DamageRegion (bounds image.Rectangle) { func (control CoreControl) DamageRegion (bounds image.Rectangle) {
if control.core.onDamage != nil { if control.core.onDamage != nil {
control.core.onDamage(tomo.Cut(control.core, bounds)) control.core.onDamage(canvas.Cut(control.core, bounds))
} }
} }

View File

@ -1,6 +1,6 @@
package core package core
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
// FocusableCore is a struct that can be embedded into objects to make them // FocusableCore is a struct that can be embedded into objects to make them
// focusable, giving them the default keynav behavior. // focusable, giving them the default keynav behavior.
@ -9,7 +9,7 @@ type FocusableCore struct {
enabled bool enabled bool
drawFocusChange func () drawFocusChange func ()
onFocusRequest func () (granted bool) onFocusRequest func () (granted bool)
onFocusMotionRequest func(tomo.KeynavDirection) (granted bool) onFocusMotionRequest func(input.KeynavDirection) (granted bool)
} }
// NewFocusableCore creates a new focusability core and its corresponding // NewFocusableCore creates a new focusability core and its corresponding
@ -46,13 +46,13 @@ func (core *FocusableCore) Focus () {
// HandleFocus causes this element to mark itself as focused, if it can // HandleFocus causes this element to mark itself as focused, if it can
// currently be. Otherwise, it will return false and do nothing. // currently be. Otherwise, it will return false and do nothing.
func (core *FocusableCore) HandleFocus ( func (core *FocusableCore) HandleFocus (
direction tomo.KeynavDirection, direction input.KeynavDirection,
) ( ) (
accepted bool, accepted bool,
) { ) {
direction = direction.Canon() direction = direction.Canon()
if !core.enabled { return false } if !core.enabled { return false }
if core.focused && direction != tomo.KeynavDirectionNeutral { if core.focused && direction != input.KeynavDirectionNeutral {
return false return false
} }
@ -80,7 +80,7 @@ func (core *FocusableCore) OnFocusRequest (callback func () (granted bool)) {
// should return true if the request was granted, and false if it was // should return true if the request was granted, and false if it was
// not. // not.
func (core *FocusableCore) OnFocusMotionRequest ( func (core *FocusableCore) OnFocusMotionRequest (
callback func (direction tomo.KeynavDirection) (granted bool), callback func (direction input.KeynavDirection) (granted bool),
) { ) {
core.onFocusMotionRequest = callback core.onFocusMotionRequest = callback
} }

View File

@ -1,13 +1,15 @@
package tomo package elements
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
// Element represents a basic on-screen object. // Element represents a basic on-screen object.
type Element interface { type Element interface {
// Element must implement the Canvas interface. Elements should start // Element must implement the Canvas interface. Elements should start
// out with a completely blank buffer, and only allocate memory and draw // out with a completely blank buffer, and only allocate memory and draw
// on it for the first time when sent an EventResize event. // on it for the first time when sent an EventResize event.
Canvas canvas.Canvas
// MinimumSize specifies the minimum amount of pixels this element's // MinimumSize specifies the minimum amount of pixels this element's
// width and height may be set to. If the element is given a resize // width and height may be set to. If the element is given a resize
@ -18,37 +20,17 @@ type Element interface {
// DrawTo sets this element's canvas. This should only be called by the // DrawTo sets this element's canvas. This should only be called by the
// parent element. This is typically a region of the parent element's // parent element. This is typically a region of the parent element's
// canvas. // canvas.
DrawTo (canvas Canvas) DrawTo (canvas canvas.Canvas)
// OnDamage sets a function to be called when an area of the element is // OnDamage sets a function to be called when an area of the element is
// drawn on and should be pushed to the screen. // drawn on and should be pushed to the screen.
OnDamage (callback func (region Canvas)) OnDamage (callback func (region canvas.Canvas))
// OnMinimumSizeChange sets a function to be called when the element's // OnMinimumSizeChange sets a function to be called when the element's
// minimum size is changed. // minimum size is changed.
OnMinimumSizeChange (callback func ()) OnMinimumSizeChange (callback func ())
} }
// KeynavDirection represents a keyboard navigation direction.
type KeynavDirection int
const (
KeynavDirectionNeutral KeynavDirection = 0
KeynavDirectionBackward KeynavDirection = -1
KeynavDirectionForward KeynavDirection = 1
)
// Canon returns a well-formed direction.
func (direction KeynavDirection) Canon () (canon KeynavDirection) {
if direction > 0 {
return KeynavDirectionForward
} else if direction == 0 {
return KeynavDirectionNeutral
} else {
return KeynavDirectionBackward
}
}
// Focusable represents an element that has keyboard navigation support. This // Focusable represents an element that has keyboard navigation support. This
// includes inputs, buttons, sliders, etc. as well as any elements that have // includes inputs, buttons, sliders, etc. as well as any elements that have
// children (so keyboard navigation events can be propagated downward). // children (so keyboard navigation events can be propagated downward).
@ -67,7 +49,7 @@ type Focusable interface {
// selectable children in the given direction, it should return false // selectable children in the given direction, it should return false
// and do nothing. Otherwise, it should select itself and any children // and do nothing. Otherwise, it should select itself and any children
// (if applicable) and return true. // (if applicable) and return true.
HandleFocus (direction KeynavDirection) (accepted bool) HandleFocus (direction input.KeynavDirection) (accepted bool)
// HandleDeselection causes this element to mark itself and all of its // HandleDeselection causes this element to mark itself and all of its
// children as unfocused. // children as unfocused.
@ -83,7 +65,7 @@ type Focusable interface {
// front of it, depending on the specified direction. Parent elements // front of it, depending on the specified direction. Parent elements
// should return true if the request was granted, and false if it was // should return true if the request was granted, and false if it was
// not. // not.
OnFocusMotionRequest (func (direction KeynavDirection) (granted bool)) OnFocusMotionRequest (func (direction input.KeynavDirection) (granted bool))
} }
// KeyboardTarget represents an element that can receive keyboard input. // KeyboardTarget represents an element that can receive keyboard input.
@ -95,11 +77,11 @@ type KeyboardTarget interface {
// every key down event is guaranteed to be paired with exactly one key // every key down event is guaranteed to be paired with exactly one key
// up event. This is the reason a list of modifier keys held down at the // up event. This is the reason a list of modifier keys held down at the
// time of the key press is given. // time of the key press is given.
HandleKeyDown (key Key, modifiers Modifiers) HandleKeyDown (key input.Key, modifiers input.Modifiers)
// HandleKeyUp is called when a key is released while this element has // HandleKeyUp is called when a key is released while this element has
// keyboard focus. // keyboard focus.
HandleKeyUp (key Key, modifiers Modifiers) HandleKeyUp (key input.Key, modifiers input.Modifiers)
} }
// MouseTarget represents an element that can receive mouse events. // MouseTarget represents an element that can receive mouse events.
@ -111,11 +93,11 @@ type MouseTarget interface {
// HandleMouseDown is called when a mouse button is pressed down on this // HandleMouseDown is called when a mouse button is pressed down on this
// element. // element.
HandleMouseDown (x, y int, button Button) HandleMouseDown (x, y int, button input.Button)
// HandleMouseUp is called when a mouse button is released that was // HandleMouseUp is called when a mouse button is released that was
// originally pressed down on this element. // originally pressed down on this element.
HandleMouseUp (x, y int, button Button) HandleMouseUp (x, y int, button input.Button)
// HandleMouseMove is called when the mouse is moved over this element, // HandleMouseMove is called when the mouse is moved over this element,
// or the mouse is moving while being held down and originally pressed // or the mouse is moving while being held down and originally pressed

View File

@ -2,7 +2,7 @@ package testing
import "image" import "image"
import "image/color" import "image/color"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core" import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -44,12 +44,12 @@ func (element *Mouse) draw () {
bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1))) bounds.Min.Add(image.Pt(bounds.Dx() - 2, 1)))
} }
func (element *Mouse) HandleMouseDown (x, y int, button tomo.Button) { func (element *Mouse) HandleMouseDown (x, y int, button input.Button) {
element.drawing = true element.drawing = true
element.lastMousePos = image.Pt(x, y) element.lastMousePos = image.Pt(x, y)
} }
func (element *Mouse) HandleMouseUp (x, y int, button tomo.Button) { func (element *Mouse) HandleMouseUp (x, y int, button input.Button) {
element.drawing = false element.drawing = false
mousePos := image.Pt(x, y) mousePos := image.Pt(x, y)
element.core.DamageRegion (artist.Line ( element.core.DamageRegion (artist.Line (

View File

@ -1,4 +1,4 @@
package tomo package elements
import "image" import "image"

View File

@ -11,7 +11,7 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("example button") window.SetTitle("example button")
button := basic.NewButton("hello tomo!") button := basicElements.NewButton("hello tomo!")
button.OnClick (func () { button.OnClick (func () {
// when we set the button's text to something longer, the window // when we set the button's text to something longer, the window
// will automatically resize to accomodate it. // will automatically resize to accomodate it.

View File

@ -2,7 +2,7 @@ package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -14,22 +14,22 @@ func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Checkboxes") window.SetTitle("Checkboxes")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt (basic.NewLabel ( container.Adopt (basicElements.NewLabel (
"We advise you to not read thPlease listen to me. I am " + "We advise you to not read thPlease listen to me. I am " +
"trapped inside the example code. This is the only way for " + "trapped inside the example code. This is the only way for " +
"me to communicate.", true), true) "me to communicate.", true), true)
container.Adopt(basic.NewSpacer(true), false) container.Adopt(basicElements.NewSpacer(true), false)
container.Adopt(basic.NewCheckbox("Oh god", false), false) container.Adopt(basicElements.NewCheckbox("Oh god", false), false)
container.Adopt(basic.NewCheckbox("Can you hear them", true), false) container.Adopt(basicElements.NewCheckbox("Can you hear them", true), false)
container.Adopt(basic.NewCheckbox("They are in the walls", false), false) container.Adopt(basicElements.NewCheckbox("They are in the walls", false), false)
container.Adopt(basic.NewCheckbox("They are coming for us", false), false) container.Adopt(basicElements.NewCheckbox("They are coming for us", false), false)
disabledCheckbox := basic.NewCheckbox("We are but their helpless prey", false) disabledCheckbox := basicElements.NewCheckbox("We are but their helpless prey", false)
disabledCheckbox.SetEnabled(false) disabledCheckbox.SetEnabled(false)
container.Adopt(disabledCheckbox, false) container.Adopt(disabledCheckbox, false)
vsync := basic.NewCheckbox("Enable vsync", false) vsync := basicElements.NewCheckbox("Enable vsync", false)
vsync.OnToggle (func () { vsync.OnToggle (func () {
if vsync.Value() { if vsync.Value() {
popups.NewDialog ( popups.NewDialog (
@ -39,7 +39,7 @@ func run () {
} }
}) })
container.Adopt(vsync, false) container.Adopt(vsync, false)
button := basic.NewButton("What") button := basicElements.NewButton("What")
button.OnClick(tomo.Stop) button.OnClick(tomo.Stop)
container.Adopt(button, false) container.Adopt(button, false)
button.Focus() button.Focus()

View File

@ -1,7 +1,7 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -13,14 +13,14 @@ func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("dialog") window.SetTitle("dialog")
container := basic.NewContainer(layouts.Dialog { true, true }) container := basicElements.NewContainer(basicLayouts.Dialog { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt(basic.NewLabel("you will explode", true), true) container.Adopt(basicElements.NewLabel("you will explode", true), true)
cancel := basic.NewButton("Cancel") cancel := basicElements.NewButton("Cancel")
cancel.SetEnabled(false) cancel.SetEnabled(false)
container.Adopt(cancel, false) container.Adopt(cancel, false)
okButton := basic.NewButton("OK") okButton := basicElements.NewButton("OK")
container.Adopt(okButton, false) container.Adopt(okButton, false)
okButton.Focus() okButton.Focus()

View File

@ -2,7 +2,7 @@ package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/flow" import "git.tebibyte.media/sashakoshka/tomo/flow"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -13,21 +13,21 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("adventure") window.SetTitle("adventure")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
var world flow.Flow var world flow.Flow
world.Transition = container.DisownAll world.Transition = container.DisownAll
world.Stages = map [string] func () { world.Stages = map [string] func () {
"start": func () { "start": func () {
label := basic.NewLabel ( label := basicElements.NewLabel (
"you are standing next to a river.", true) "you are standing next to a river.", true)
button0 := basic.NewButton("go in the river") button0 := basicElements.NewButton("go in the river")
button0.OnClick(world.SwitchFunc("wet")) button0.OnClick(world.SwitchFunc("wet"))
button1 := basic.NewButton("walk along the river") button1 := basicElements.NewButton("walk along the river")
button1.OnClick(world.SwitchFunc("house")) button1.OnClick(world.SwitchFunc("house"))
button2 := basic.NewButton("turn around") button2 := basicElements.NewButton("turn around")
button2.OnClick(world.SwitchFunc("bear")) button2.OnClick(world.SwitchFunc("bear"))
container.Warp ( func () { container.Warp ( func () {
@ -39,13 +39,13 @@ func run () {
}) })
}, },
"wet": func () { "wet": func () {
label := basic.NewLabel ( label := basicElements.NewLabel (
"you get completely soaked.\n" + "you get completely soaked.\n" +
"you die of hypothermia.", true) "you die of hypothermia.", true)
button0 := basic.NewButton("try again") button0 := basicElements.NewButton("try again")
button0.OnClick(world.SwitchFunc("start")) button0.OnClick(world.SwitchFunc("start"))
button1 := basic.NewButton("exit") button1 := basicElements.NewButton("exit")
button1.OnClick(tomo.Stop) button1.OnClick(tomo.Stop)
container.Warp (func () { container.Warp (func () {
@ -56,13 +56,13 @@ func run () {
}) })
}, },
"house": func () { "house": func () {
label := basic.NewLabel ( label := basicElements.NewLabel (
"you are standing in front of a delapidated " + "you are standing in front of a delapidated " +
"house.", true) "house.", true)
button1 := basic.NewButton("go inside") button1 := basicElements.NewButton("go inside")
button1.OnClick(world.SwitchFunc("inside")) button1.OnClick(world.SwitchFunc("inside"))
button0 := basic.NewButton("turn back") button0 := basicElements.NewButton("turn back")
button0.OnClick(world.SwitchFunc("start")) button0.OnClick(world.SwitchFunc("start"))
container.Warp (func () { container.Warp (func () {
@ -73,14 +73,14 @@ func run () {
}) })
}, },
"inside": func () { "inside": func () {
label := basic.NewLabel ( label := basicElements.NewLabel (
"you are standing inside of the house.\n" + "you are standing inside of the house.\n" +
"it is dark, but rays of light stream " + "it is dark, but rays of light stream " +
"through the window.\n" + "through the window.\n" +
"there is nothing particularly interesting " + "there is nothing particularly interesting " +
"here.", true) "here.", true)
button0 := basic.NewButton("go back outside") button0 := basicElements.NewButton("go back outside")
button0.OnClick(world.SwitchFunc("house")) button0.OnClick(world.SwitchFunc("house"))
container.Warp (func () { container.Warp (func () {
@ -90,13 +90,13 @@ func run () {
}) })
}, },
"bear": func () { "bear": func () {
label := basic.NewLabel ( label := basicElements.NewLabel (
"you come face to face with a bear.\n" + "you come face to face with a bear.\n" +
"it eats you (it was hungry).", true) "it eats you (it was hungry).", true)
button0 := basic.NewButton("try again") button0 := basicElements.NewButton("try again")
button0.OnClick(world.SwitchFunc("start")) button0.OnClick(world.SwitchFunc("start"))
button1 := basic.NewButton("exit") button1 := basicElements.NewButton("exit")
button1.OnClick(tomo.Stop) button1.OnClick(tomo.Stop)
container.Warp (func () { container.Warp (func () {

View File

@ -3,7 +3,7 @@ package main
import "os" import "os"
import "time" import "time"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/fun" import "git.tebibyte.media/sashakoshka/tomo/elements/fun"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -16,12 +16,12 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("clock") window.SetTitle("clock")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
clock := fun.NewAnalogClock(time.Now()) clock := fun.NewAnalogClock(time.Now())
container.Adopt(clock, true) container.Adopt(clock, true)
label := basic.NewLabel(formatTime(), false) label := basicElements.NewLabel(formatTime(), false)
container.Adopt(label, false) container.Adopt(label, false)
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)
@ -33,7 +33,7 @@ func formatTime () (timeString string) {
return time.Now().Format("2006-01-02 15:04:05") return time.Now().Format("2006-01-02 15:04:05")
} }
func tick (label *basic.Label, clock *fun.AnalogClock) { func tick (label *basicElements.Label, clock *fun.AnalogClock) {
for { for {
tomo.Do (func () { tomo.Do (func () {
label.SetText(formatTime()) label.SetText(formatTime())

View File

@ -1,7 +1,7 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -13,12 +13,12 @@ func run () {
window, _ := tomo.NewWindow(360, 2) window, _ := tomo.NewWindow(360, 2)
window.SetTitle("horizontal stack") window.SetTitle("horizontal stack")
container := basic.NewContainer(layouts.Horizontal { true, true }) container := basicElements.NewContainer(basicLayouts.Horizontal { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt(basic.NewLabel("this is sample text", true), true) container.Adopt(basicElements.NewLabel("this is sample text", true), true)
container.Adopt(basic.NewLabel("this is sample text", true), true) container.Adopt(basicElements.NewLabel("this is sample text", true), true)
container.Adopt(basic.NewLabel("this is sample text", true), true) container.Adopt(basicElements.NewLabel("this is sample text", true), true)
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)
window.Show() window.Show()

View File

@ -2,7 +2,7 @@ package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -13,14 +13,14 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Enter Details") window.SetTitle("Enter Details")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
// create inputs // create inputs
firstName := basic.NewTextBox("First name", "") firstName := basicElements.NewTextBox("First name", "")
lastName := basic.NewTextBox("Last name", "") lastName := basicElements.NewTextBox("Last name", "")
fingerLength := basic.NewTextBox("Length of fingers", "") fingerLength := basicElements.NewTextBox("Length of fingers", "")
button := basic.NewButton("Ok") button := basicElements.NewButton("Ok")
button.SetEnabled(false) button.SetEnabled(false)
button.OnClick (func () { button.OnClick (func () {
@ -45,11 +45,11 @@ func run () {
fingerLength.OnChange(check) fingerLength.OnChange(check)
// add elements to container // add elements to container
container.Adopt(basic.NewLabel("Choose your words carefully.", false), true) container.Adopt(basicElements.NewLabel("Choose your words carefully.", false), true)
container.Adopt(firstName, false) container.Adopt(firstName, false)
container.Adopt(lastName, false) container.Adopt(lastName, false)
container.Adopt(fingerLength, false) container.Adopt(fingerLength, false)
container.Adopt(basic.NewSpacer(true), false) container.Adopt(basicElements.NewSpacer(true), false)
container.Adopt(button, false) container.Adopt(button, false)
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)

View File

@ -11,7 +11,7 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(480, 2) window, _ := tomo.NewWindow(480, 2)
window.SetTitle("example label") window.SetTitle("example label")
window.Adopt(basic.NewLabel(text, true)) window.Adopt(basicElements.NewLabel(text, true))
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)
window.Show() window.Show()
} }

View File

@ -2,7 +2,8 @@ package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/testing" import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -15,11 +16,11 @@ func run () {
window, _ := tomo.NewWindow(300, 2) window, _ := tomo.NewWindow(300, 2)
window.SetTitle("List Sidebar") window.SetTitle("List Sidebar")
container := basic.NewContainer(layouts.Horizontal { true, true }) container := basicElements.NewContainer(basicLayouts.Horizontal { true, true })
window.Adopt(container) window.Adopt(container)
var currentPage tomo.Element var currentPage elements.Element
turnPage := func (newPage tomo.Element) { turnPage := func (newPage elements.Element) {
container.Warp (func () { container.Warp (func () {
if currentPage != nil { if currentPage != nil {
container.Disown(currentPage) container.Disown(currentPage)
@ -29,27 +30,27 @@ func run () {
}) })
} }
intro := basic.NewLabel ( intro := basicElements.NewLabel (
"The List element can be easily used as a sidebar. " + "The List element can be easily used as a sidebar. " +
"Click on entries to flip pages!", true) "Click on entries to flip pages!", true)
button := basic.NewButton("I do nothing!") button := basicElements.NewButton("I do nothing!")
button.OnClick (func () { button.OnClick (func () {
popups.NewDialog(popups.DialogKindInfo, "", "Sike!") popups.NewDialog(popups.DialogKindInfo, "", "Sike!")
}) })
mouse := testing.NewMouse() mouse := testing.NewMouse()
input := basic.NewTextBox("Write some text", "") input := basicElements.NewTextBox("Write some text", "")
form := basic.NewContainer(layouts.Vertical { true, false}) form := basicElements.NewContainer(basicLayouts.Vertical { true, false})
form.Adopt(basic.NewLabel("I have:", false), false) form.Adopt(basicElements.NewLabel("I have:", false), false)
form.Adopt(basic.NewSpacer(true), false) form.Adopt(basicElements.NewSpacer(true), false)
form.Adopt(basic.NewCheckbox("Skin", true), false) form.Adopt(basicElements.NewCheckbox("Skin", true), false)
form.Adopt(basic.NewCheckbox("Blood", false), false) form.Adopt(basicElements.NewCheckbox("Blood", false), false)
form.Adopt(basic.NewCheckbox("Bone", false), false) form.Adopt(basicElements.NewCheckbox("Bone", false), false)
list := basic.NewList ( list := basicElements.NewList (
basic.NewListEntry("button", func () { turnPage(button) }), basicElements.NewListEntry("button", func () { turnPage(button) }),
basic.NewListEntry("mouse", func () { turnPage(mouse) }), basicElements.NewListEntry("mouse", func () { turnPage(mouse) }),
basic.NewListEntry("input", func () { turnPage(input) }), basicElements.NewListEntry("input", func () { turnPage(input) }),
basic.NewListEntry("form", func () { turnPage(form) })) basicElements.NewListEntry("form", func () { turnPage(form) }))
list.OnNoEntrySelected(func () { turnPage (intro) }) list.OnNoEntrySelected(func () { turnPage (intro) })
list.Collapse(96, 0) list.Collapse(96, 0)

View File

@ -2,7 +2,7 @@ package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -14,12 +14,12 @@ func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Dialog Boxes") window.SetTitle("Dialog Boxes")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt(basic.NewLabel("Try out different dialogs:", false), true) container.Adopt(basicElements.NewLabel("Try out different dialogs:", false), true)
infoButton := basic.NewButton("popups.DialogKindInfo") infoButton := basicElements.NewButton("popups.DialogKindInfo")
infoButton.OnClick (func () { infoButton.OnClick (func () {
popups.NewDialog ( popups.NewDialog (
popups.DialogKindInfo, popups.DialogKindInfo,
@ -29,7 +29,7 @@ func run () {
container.Adopt(infoButton, false) container.Adopt(infoButton, false)
infoButton.Focus() infoButton.Focus()
questionButton := basic.NewButton("popups.DialogKindQuestion") questionButton := basicElements.NewButton("popups.DialogKindQuestion")
questionButton.OnClick (func () { questionButton.OnClick (func () {
popups.NewDialog ( popups.NewDialog (
popups.DialogKindQuestion, popups.DialogKindQuestion,
@ -41,7 +41,7 @@ func run () {
}) })
container.Adopt(questionButton, false) container.Adopt(questionButton, false)
warningButton := basic.NewButton("popups.DialogKindWarning") warningButton := basicElements.NewButton("popups.DialogKindWarning")
warningButton.OnClick (func () { warningButton.OnClick (func () {
popups.NewDialog ( popups.NewDialog (
popups.DialogKindQuestion, popups.DialogKindQuestion,
@ -50,7 +50,7 @@ func run () {
}) })
container.Adopt(warningButton, false) container.Adopt(warningButton, false)
errorButton := basic.NewButton("popups.DialogKindError") errorButton := basicElements.NewButton("popups.DialogKindError")
errorButton.OnClick (func () { errorButton.OnClick (func () {
popups.NewDialog ( popups.NewDialog (
popups.DialogKindQuestion, popups.DialogKindQuestion,
@ -59,7 +59,7 @@ func run () {
}) })
container.Adopt(errorButton, false) container.Adopt(errorButton, false)
cancelButton := basic.NewButton("No thank you.") cancelButton := basicElements.NewButton("No thank you.")
cancelButton.OnClick(tomo.Stop) cancelButton.OnClick(tomo.Stop)
container.Adopt(cancelButton, false) container.Adopt(cancelButton, false)

View File

@ -3,7 +3,7 @@ package main
import "time" import "time"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/popups" import "git.tebibyte.media/sashakoshka/tomo/popups"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -14,14 +14,14 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Approaching") window.SetTitle("Approaching")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt (basic.NewLabel ( container.Adopt (basicElements.NewLabel (
"Rapidly approaching your location...", false), false) "Rapidly approaching your location...", false), false)
bar := basic.NewProgressBar(0) bar := basicElements.NewProgressBar(0)
container.Adopt(bar, false) container.Adopt(bar, false)
button := basic.NewButton("Stop") button := basicElements.NewButton("Stop")
button.SetEnabled(false) button.SetEnabled(false)
container.Adopt(button, false) container.Adopt(button, false)
@ -30,7 +30,7 @@ func run () {
go fill(bar) go fill(bar)
} }
func fill (bar *basic.ProgressBar) { func fill (bar *basicElements.ProgressBar) {
for progress := 0.0; progress < 1.0; progress += 0.01 { for progress := 0.0; progress < 1.0; progress += 0.01 {
time.Sleep(time.Second / 24) time.Sleep(time.Second / 24)
tomo.Do (func () { tomo.Do (func () {

View File

@ -1,7 +1,7 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -12,13 +12,13 @@ func main () {
func run () { func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Scroll") window.SetTitle("Scroll")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt(basic.NewLabel("look at this non sense", false), false) container.Adopt(basicElements.NewLabel("look at this non sense", false), false)
textBox := basic.NewTextBox("", "sample text sample text") textBox := basicElements.NewTextBox("", "sample text sample text")
scrollContainer := basic.NewScrollContainer(true, false) scrollContainer := basicElements.NewScrollContainer(true, false)
scrollContainer.Adopt(textBox) scrollContainer.Adopt(textBox)
container.Adopt(scrollContainer, true) container.Adopt(scrollContainer, true)

View File

@ -1,7 +1,7 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -13,14 +13,14 @@ func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Spaced Out") window.SetTitle("Spaced Out")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt (basic.NewLabel("This is at the top", false), false) container.Adopt (basicElements.NewLabel("This is at the top", false), false)
container.Adopt (basic.NewSpacer(true), false) container.Adopt (basicElements.NewSpacer(true), false)
container.Adopt (basic.NewLabel("This is in the middle", false), false) container.Adopt (basicElements.NewLabel("This is in the middle", false), false)
container.Adopt (basic.NewSpacer(false), true) container.Adopt (basicElements.NewSpacer(false), true)
container.Adopt (basic.NewLabel("This is at the bottom", false), false) container.Adopt (basicElements.NewLabel("This is at the bottom", false), false)
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)
window.Show() window.Show()

View File

@ -1,7 +1,7 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -13,12 +13,12 @@ func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Switches") window.SetTitle("Switches")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt(basic.NewSwitch("hahahah", false), false) container.Adopt(basicElements.NewSwitch("hahahah", false), false)
container.Adopt(basic.NewSwitch("hehehehheheh", false), false) container.Adopt(basicElements.NewSwitch("hehehehheheh", false), false)
container.Adopt(basic.NewSwitch("you can flick da swicth", false), false) container.Adopt(basicElements.NewSwitch("you can flick da swicth", false), false)
window.OnClose(tomo.Stop) window.OnClose(tomo.Stop)
window.Show() window.Show()

View File

@ -1,7 +1,7 @@
package main package main
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/testing" import "git.tebibyte.media/sashakoshka/tomo/elements/testing"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x" import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -14,15 +14,15 @@ func run () {
window, _ := tomo.NewWindow(2, 2) window, _ := tomo.NewWindow(2, 2)
window.SetTitle("vertical stack") window.SetTitle("vertical stack")
container := basic.NewContainer(layouts.Vertical { true, true }) container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container) window.Adopt(container)
label := basic.NewLabel("it is a label hehe", true) label := basicElements.NewLabel("it is a label hehe", true)
button := basic.NewButton("drawing pad") button := basicElements.NewButton("drawing pad")
okButton := basic.NewButton("OK") okButton := basicElements.NewButton("OK")
button.OnClick (func () { button.OnClick (func () {
container.DisownAll() container.DisownAll()
container.Adopt(basic.NewLabel("Draw here:", false), false) container.Adopt(basicElements.NewLabel("Draw here:", false), false)
container.Adopt(testing.NewMouse(), true) container.Adopt(testing.NewMouse(), true)
container.Adopt(okButton, false) container.Adopt(okButton, false)
okButton.Focus() okButton.Focus()

View File

@ -1,4 +1,4 @@
package tomo package input
import "unicode" import "unicode"
@ -110,3 +110,22 @@ type Modifiers struct {
NumberPad bool NumberPad bool
} }
// KeynavDirection represents a keyboard navigation direction.
type KeynavDirection int
const (
KeynavDirectionNeutral KeynavDirection = 0
KeynavDirectionBackward KeynavDirection = -1
KeynavDirectionForward KeynavDirection = 1
)
// Canon returns a well-formed direction.
func (direction KeynavDirection) Canon () (canon KeynavDirection) {
if direction > 0 {
return KeynavDirectionForward
} else if direction == 0 {
return KeynavDirectionNeutral
} else {
return KeynavDirectionBackward
}
}

View File

@ -1,8 +1,8 @@
package layouts package basicLayouts
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/elements"
// Dialog arranges elements in the form of a dialog box. The first element is // Dialog arranges elements in the form of a dialog box. The first element is
// positioned above as the main focus of the dialog, and is set to expand // positioned above as the main focus of the dialog, and is set to expand
@ -19,13 +19,18 @@ type Dialog struct {
} }
// Arrange arranges a list of entries into a dialog. // Arrange arranges a list of entries into a dialog.
func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { func (layout Dialog) Arrange (
if layout.Pad { bounds = bounds.Inset(theme.Margin()) } entries []layouts.LayoutEntry,
margin int,
bounds image.Rectangle,
) {
if layout.Pad { bounds = bounds.Inset(margin) }
controlRowWidth, controlRowHeight := 0, 0 controlRowWidth, controlRowHeight := 0, 0
if len(entries) > 1 { if len(entries) > 1 {
controlRowWidth, controlRowWidth,
controlRowHeight = layout.minimumSizeOfControlRow(entries[1:]) controlRowHeight = layout.minimumSizeOfControlRow (
entries[1:], margin)
} }
if len(entries) > 0 { if len(entries) > 0 {
@ -33,7 +38,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle
main.Bounds.Min = bounds.Min main.Bounds.Min = bounds.Min
mainHeight := bounds.Dy() - controlRowHeight mainHeight := bounds.Dy() - controlRowHeight
if layout.Gap { if layout.Gap {
mainHeight -= theme.Margin() mainHeight -= margin
} }
main.Bounds.Max = main.Bounds.Min.Add(image.Pt(bounds.Dx(), mainHeight)) main.Bounds.Max = main.Bounds.Min.Add(image.Pt(bounds.Dx(), mainHeight))
entries[0] = main entries[0] = main
@ -53,7 +58,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle
freeSpace -= entryMinWidth freeSpace -= entryMinWidth
} }
if index > 0 && layout.Gap { if index > 0 && layout.Gap {
freeSpace -= theme.Margin() freeSpace -= margin
} }
} }
expandingElementWidth := 0 expandingElementWidth := 0
@ -69,7 +74,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle
// set the size and position of each element in the control row // set the size and position of each element in the control row
for index, entry := range entries[1:] { for index, entry := range entries[1:] {
if index > 0 && layout.Gap { dot.X += theme.Margin() } if index > 0 && layout.Gap { dot.X += margin }
entry.Bounds.Min = dot entry.Bounds.Min = dot
entryWidth := 0 entryWidth := 0
@ -95,7 +100,8 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle
// MinimumSize returns the minimum width and height that will be needed to // MinimumSize returns the minimum width and height that will be needed to
// arrange the given list of entries. // arrange the given list of entries.
func (layout Dialog) MinimumSize ( func (layout Dialog) MinimumSize (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
) ( ) (
width, height int, width, height int,
) { ) {
@ -106,9 +112,10 @@ func (layout Dialog) MinimumSize (
} }
if len(entries) > 1 { if len(entries) > 1 {
if layout.Gap { height += theme.Margin() } if layout.Gap { height += margin }
additionalWidth, additionalWidth,
additionalHeight := layout.minimumSizeOfControlRow(entries[1:]) additionalHeight := layout.minimumSizeOfControlRow (
entries[1:], margin)
height += additionalHeight height += additionalHeight
if additionalWidth > width { if additionalWidth > width {
width = additionalWidth width = additionalWidth
@ -116,8 +123,8 @@ func (layout Dialog) MinimumSize (
} }
if layout.Pad { if layout.Pad {
width += theme.Margin() * 2 width += margin * 2
height += theme.Margin() * 2 height += margin * 2
} }
return return
} }
@ -125,18 +132,19 @@ func (layout Dialog) MinimumSize (
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the // FlexibleHeightFor Returns the minimum height the layout needs to lay out the
// specified elements at the given width, taking into account flexible elements. // specified elements at the given width, taking into account flexible elements.
func (layout Dialog) FlexibleHeightFor ( func (layout Dialog) FlexibleHeightFor (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
width int, width int,
) ( ) (
height int, height int,
) { ) {
if layout.Pad { if layout.Pad {
width -= theme.Margin() * 2 width -= margin * 2
} }
if len(entries) > 0 { if len(entries) > 0 {
mainChildHeight := 0 mainChildHeight := 0
if child, flexible := entries[0].Element.(tomo.Flexible); flexible { if child, flexible := entries[0].Element.(elements.Flexible); flexible {
mainChildHeight = child.FlexibleHeightFor(width) mainChildHeight = child.FlexibleHeightFor(width)
} else { } else {
_, mainChildHeight = entries[0].MinimumSize() _, mainChildHeight = entries[0].MinimumSize()
@ -145,13 +153,14 @@ func (layout Dialog) FlexibleHeightFor (
} }
if len(entries) > 1 { if len(entries) > 1 {
if layout.Gap { height += theme.Margin() } if layout.Gap { height += margin }
_, additionalHeight := layout.minimumSizeOfControlRow(entries[1:]) _, additionalHeight := layout.minimumSizeOfControlRow (
entries[1:], margin)
height += additionalHeight height += additionalHeight
} }
if layout.Pad { if layout.Pad {
height += theme.Margin() * 2 height += margin * 2
} }
return return
} }
@ -159,7 +168,8 @@ func (layout Dialog) FlexibleHeightFor (
// TODO: possibly flatten this method to account for flexible elements within // TODO: possibly flatten this method to account for flexible elements within
// the control row. // the control row.
func (layout Dialog) minimumSizeOfControlRow ( func (layout Dialog) minimumSizeOfControlRow (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
) ( ) (
width, height int, width, height int,
) { ) {
@ -170,7 +180,7 @@ func (layout Dialog) minimumSizeOfControlRow (
} }
width += entryWidth width += entryWidth
if layout.Gap && index > 0 { if layout.Gap && index > 0 {
width += theme.Margin() width += margin
} }
} }
return return

View File

@ -1,8 +1,8 @@
package layouts package basicLayouts
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/elements"
// Horizontal arranges elements horizontally. Elements at the start of the entry // Horizontal arranges elements horizontally. Elements at the start of the entry
// list will be positioned on the left, and elements at the end of the entry // list will be positioned on the left, and elements at the end of the entry
@ -17,16 +17,21 @@ type Horizontal struct {
} }
// Arrange arranges a list of entries horizontally. // Arrange arranges a list of entries horizontally.
func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { func (layout Horizontal) Arrange (
if layout.Pad { bounds = bounds.Inset(theme.Margin()) } entries []layouts.LayoutEntry,
margin int,
bounds image.Rectangle,
) {
if layout.Pad { bounds = bounds.Inset(margin) }
// get width of expanding elements // get width of expanding elements
expandingElementWidth := layout.expandingElementWidth(entries, bounds.Dx()) expandingElementWidth := layout.expandingElementWidth (
entries, margin, bounds.Dx())
// set the size and position of each element // set the size and position of each element
dot := bounds.Min dot := bounds.Min
for index, entry := range entries { for index, entry := range entries {
if index > 0 && layout.Gap { dot.X += theme.Margin() } if index > 0 && layout.Gap { dot.X += margin }
entry.Bounds.Min = dot entry.Bounds.Min = dot
entryWidth := 0 entryWidth := 0
@ -45,7 +50,8 @@ func (layout Horizontal) Arrange (entries []tomo.LayoutEntry, bounds image.Recta
// MinimumSize returns the minimum width and height that will be needed to // MinimumSize returns the minimum width and height that will be needed to
// arrange the given list of entries. // arrange the given list of entries.
func (layout Horizontal) MinimumSize ( func (layout Horizontal) MinimumSize (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
) ( ) (
width, height int, width, height int,
) { ) {
@ -56,13 +62,13 @@ func (layout Horizontal) MinimumSize (
} }
width += entryWidth width += entryWidth
if layout.Gap && index > 0 { if layout.Gap && index > 0 {
width += theme.Margin() width += margin
} }
} }
if layout.Pad { if layout.Pad {
width += theme.Margin() * 2 width += margin * 2
height += theme.Margin() * 2 height += margin * 2
} }
return return
} }
@ -70,21 +76,22 @@ func (layout Horizontal) MinimumSize (
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the // FlexibleHeightFor Returns the minimum height the layout needs to lay out the
// specified elements at the given width, taking into account flexible elements. // specified elements at the given width, taking into account flexible elements.
func (layout Horizontal) FlexibleHeightFor ( func (layout Horizontal) FlexibleHeightFor (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
width int, width int,
) ( ) (
height int, height int,
) { ) {
if layout.Pad { if layout.Pad { width -= margin * 2 }
width -= theme.Margin() * 2
}
// get width of expanding elements // get width of expanding elements
expandingElementWidth := layout.expandingElementWidth(entries, width) expandingElementWidth := layout.expandingElementWidth (
entries, margin, width)
x, y := 0, 0 x, y := 0, 0
if layout.Pad { if layout.Pad {
x += theme.Margin() x += margin
y += theme.Margin() y += margin
} }
// set the size and position of each element // set the size and position of each element
@ -93,23 +100,24 @@ func (layout Horizontal) FlexibleHeightFor (
if entry.Expand { if entry.Expand {
entryWidth = expandingElementWidth entryWidth = expandingElementWidth
} }
if child, flexible := entry.Element.(tomo.Flexible); flexible { if child, flexible := entry.Element.(elements.Flexible); flexible {
entryHeight = child.FlexibleHeightFor(entryWidth) entryHeight = child.FlexibleHeightFor(entryWidth)
} }
if entryHeight > height { height = entryHeight } if entryHeight > height { height = entryHeight }
x += entryWidth x += entryWidth
if index > 0 && layout.Gap { x += theme.Margin() } if index > 0 && layout.Gap { x += margin }
} }
if layout.Pad { if layout.Pad {
height += theme.Margin() * 2 height += margin * 2
} }
return return
} }
func (layout Horizontal) expandingElementWidth ( func (layout Horizontal) expandingElementWidth (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
freeSpace int, freeSpace int,
) ( ) (
width int, width int,
@ -126,7 +134,7 @@ func (layout Horizontal) expandingElementWidth (
freeSpace -= entryMinWidth freeSpace -= entryMinWidth
} }
if index > 0 && layout.Gap { if index > 0 && layout.Gap {
freeSpace -= theme.Margin() freeSpace -= margin
} }
} }

View File

@ -1,8 +1,8 @@
package layouts package basicLayouts
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/theme" import "git.tebibyte.media/sashakoshka/tomo/elements"
// Vertical arranges elements vertically. Elements at the start of the entry // Vertical arranges elements vertically. Elements at the start of the entry
// list will be positioned at the top, and elements at the end of the entry list // list will be positioned at the top, and elements at the end of the entry list
@ -17,8 +17,12 @@ type Vertical struct {
} }
// Arrange arranges a list of entries vertically. // Arrange arranges a list of entries vertically.
func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) { func (layout Vertical) Arrange (
if layout.Pad { bounds = bounds.Inset(theme.Margin()) } entries []layouts.LayoutEntry,
margin int,
bounds image.Rectangle,
) {
if layout.Pad { bounds = bounds.Inset(margin) }
// count the number of expanding elements and the amount of free space // count the number of expanding elements and the amount of free space
// for them to collectively occupy, while gathering minimum heights. // for them to collectively occupy, while gathering minimum heights.
@ -28,7 +32,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang
for index, entry := range entries { for index, entry := range entries {
var entryMinHeight int var entryMinHeight int
if child, flexible := entry.Element.(tomo.Flexible); flexible { if child, flexible := entry.Element.(elements.Flexible); flexible {
entryMinHeight = child.FlexibleHeightFor(bounds.Dx()) entryMinHeight = child.FlexibleHeightFor(bounds.Dx())
} else { } else {
_, entryMinHeight = entry.MinimumSize() _, entryMinHeight = entry.MinimumSize()
@ -41,7 +45,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang
freeSpace -= entryMinHeight freeSpace -= entryMinHeight
} }
if index > 0 && layout.Gap { if index > 0 && layout.Gap {
freeSpace -= theme.Margin() freeSpace -= margin
} }
} }
@ -53,7 +57,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang
// set the size and position of each element // set the size and position of each element
dot := bounds.Min dot := bounds.Min
for index, entry := range entries { for index, entry := range entries {
if index > 0 && layout.Gap { dot.Y += theme.Margin() } if index > 0 && layout.Gap { dot.Y += margin }
entry.Bounds.Min = dot entry.Bounds.Min = dot
entryHeight := 0 entryHeight := 0
@ -72,7 +76,8 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang
// MinimumSize returns the minimum width and height that will be needed to // MinimumSize returns the minimum width and height that will be needed to
// arrange the given list of entries. // arrange the given list of entries.
func (layout Vertical) MinimumSize ( func (layout Vertical) MinimumSize (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
) ( ) (
width, height int, width, height int,
) { ) {
@ -83,13 +88,13 @@ func (layout Vertical) MinimumSize (
} }
height += entryHeight height += entryHeight
if layout.Gap && index > 0 { if layout.Gap && index > 0 {
height += theme.Margin() height += margin
} }
} }
if layout.Pad { if layout.Pad {
width += theme.Margin() * 2 width += margin * 2
height += theme.Margin() * 2 height += margin * 2
} }
return return
} }
@ -97,18 +102,19 @@ func (layout Vertical) MinimumSize (
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the // FlexibleHeightFor Returns the minimum height the layout needs to lay out the
// specified elements at the given width, taking into account flexible elements. // specified elements at the given width, taking into account flexible elements.
func (layout Vertical) FlexibleHeightFor ( func (layout Vertical) FlexibleHeightFor (
entries []tomo.LayoutEntry, entries []layouts.LayoutEntry,
margin int,
width int, width int,
) ( ) (
height int, height int,
) { ) {
if layout.Pad { if layout.Pad {
width -= theme.Margin() * 2 width -= margin * 2
height += theme.Margin() * 2 height += margin * 2
} }
for index, entry := range entries { for index, entry := range entries {
child, flexible := entry.Element.(tomo.Flexible) child, flexible := entry.Element.(elements.Flexible)
if flexible { if flexible {
height += child.FlexibleHeightFor(width) height += child.FlexibleHeightFor(width)
} else { } else {
@ -117,7 +123,7 @@ func (layout Vertical) FlexibleHeightFor (
} }
if layout.Gap && index > 0 { if layout.Gap && index > 0 {
height += theme.Margin() height += margin
} }
} }
return return

View File

@ -1,11 +1,12 @@
package tomo package layouts
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/elements"
// LayoutEntry associates an element with layout and positioning information so // LayoutEntry associates an element with layout and positioning information so
// it can be arranged by a Layout. // it can be arranged by a Layout.
type LayoutEntry struct { type LayoutEntry struct {
Element elements.Element
Bounds image.Rectangle Bounds image.Rectangle
Expand bool Expand bool
} }
@ -17,14 +18,20 @@ type Layout interface {
// and changes the position of the entiries in the slice so that they // and changes the position of the entiries in the slice so that they
// are properly laid out. The given width and height should not be less // are properly laid out. The given width and height should not be less
// than what is returned by MinimumSize. // than what is returned by MinimumSize.
Arrange (entries []LayoutEntry, bounds image.Rectangle) Arrange (entries []LayoutEntry, margin int, bounds image.Rectangle)
// MinimumSize returns the minimum width and height that the layout // MinimumSize returns the minimum width and height that the layout
// needs to properly arrange the given slice of layout entries. // needs to properly arrange the given slice of layout entries.
MinimumSize (entries []LayoutEntry) (width, height int) MinimumSize (entries []LayoutEntry, margin int) (width, height int)
// FlexibleHeightFor Returns the minimum height the layout needs to lay // FlexibleHeightFor Returns the minimum height the layout needs to lay
// out the specified elements at the given width, taking into account // out the specified elements at the given width, taking into account
// flexible elements. // flexible elements.
FlexibleHeightFor (entries []LayoutEntry, squeeze int) (height int) FlexibleHeightFor (
entries []LayoutEntry,
margin int,
squeeze int,
) (
height int,
)
} }

View File

@ -1,7 +1,8 @@
package popups package popups
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/layouts" import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/layouts/basic"
import "git.tebibyte.media/sashakoshka/tomo/elements/basic" import "git.tebibyte.media/sashakoshka/tomo/elements/basic"
// DialogKind defines the semantic role of a dialog window. // DialogKind defines the semantic role of a dialog window.
@ -30,24 +31,24 @@ func NewDialog (
title, message string, title, message string,
buttons ...Button, buttons ...Button,
) ( ) (
window tomo.Window, window elements.Window,
) { ) {
window, _ = tomo.NewWindow(2, 2) window, _ = tomo.NewWindow(2, 2)
window.SetTitle(title) window.SetTitle(title)
container := basic.NewContainer(layouts.Dialog { true, true }) container := basicElements.NewContainer(basicLayouts.Dialog { true, true })
window.Adopt(container) window.Adopt(container)
container.Adopt(basic.NewLabel(message, false), true) container.Adopt(basicElements.NewLabel(message, false), true)
if len(buttons) == 0 { if len(buttons) == 0 {
button := basic.NewButton("OK") button := basicElements.NewButton("OK")
button.OnClick(window.Close) button.OnClick(window.Close)
container.Adopt(button, false) container.Adopt(button, false)
button.Focus() button.Focus()
} else { } else {
var button *basic.Button var button *basicElements.Button
for _, buttonDescriptor := range buttons { for _, buttonDescriptor := range buttons {
button = basic.NewButton(buttonDescriptor.Name) button = basicElements.NewButton(buttonDescriptor.Name)
button.SetEnabled(buttonDescriptor.OnPress != nil) button.SetEnabled(buttonDescriptor.OnPress != nil)
button.OnClick (func () { button.OnClick (func () {
buttonDescriptor.OnPress() buttonDescriptor.OnPress()

View File

@ -111,24 +111,3 @@ func FontFaceItalic () font.Face {
func FontFaceBoldItalic () font.Face { func FontFaceBoldItalic () font.Face {
return defaultfont.FaceBoldItalic return defaultfont.FaceBoldItalic
} }
// Padding returns the amount of internal padding elements should have. An
// element's inner content (such as text) should be inset by this amount,
// in addition to the inset returned by the pattern of its background. When
// using the aforementioned inset values to calculate the element's minimum size
// or the position and alignment of its content, all parameters in the
// PatternState should be unset except for Case.
func Padding () int {
return 7
}
// Margin returns how much space should be put in between elements.
func Margin () int {
return 8
}
// HandleWidth returns how large grab handles should typically be. This is
// important for accessibility reasons.
func HandleWidth () int {
return 16
}

View File

@ -1,6 +1,8 @@
package tomo package tomo
import "errors" import "errors"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/elements"
var backend Backend var backend Backend
@ -32,7 +34,7 @@ func Do (callback func ()) {
// Window. If the window could not be created, an error is returned explaining // Window. If the window could not be created, an error is returned explaining
// why. If this function is called without a running backend, an error is // why. If this function is called without a running backend, an error is
// returned as well. // returned as well.
func NewWindow (width, height int) (window Window, err error) { func NewWindow (width, height int) (window elements.Window, err error) {
if backend == nil { if backend == nil {
err = errors.New("no backend is running.") err = errors.New("no backend is running.")
return return
@ -41,14 +43,14 @@ func NewWindow (width, height int) (window Window, err error) {
} }
// Copy puts data into the clipboard. // Copy puts data into the clipboard.
func Copy (data Data) { func Copy (data data.Data) {
if backend == nil { panic("no backend is running") } if backend == nil { panic("no backend is running") }
backend.Copy(data) backend.Copy(data)
} }
// Paste returns the data currently in the clipboard. This method may // Paste returns the data currently in the clipboard. This method may
// return nil. // return nil.
func Paste (accept []Mime) (Data) { func Paste (accept []data.Mime) (data.Data) {
if backend == nil { panic("no backend is running") } if backend == nil { panic("no backend is running") }
return backend.Paste(accept) return backend.Paste(accept)
} }