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 "image"
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.
func FillEllipse (
destination tomo.Canvas,
destination canvas.Canvas,
source Pattern,
bounds image.Rectangle,
) (
@ -36,7 +36,7 @@ func FillEllipse (
// StrokeEllipse draws the outline of an ellipse with the specified line weight
// and pattern.
func StrokeEllipse (
destination tomo.Canvas,
destination canvas.Canvas,
source Pattern,
weight int,
bounds image.Rectangle,

View File

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

View File

@ -1,12 +1,12 @@
package artist
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.
func Paste (
destination tomo.Canvas,
source tomo.Canvas,
destination canvas.Canvas,
source canvas.Canvas,
offset image.Point,
) (
updatedRegion image.Rectangle,
@ -31,7 +31,7 @@ func Paste (
// FillRectangle draws a filled rectangle with the specified pattern.
func FillRectangle (
destination tomo.Canvas,
destination canvas.Canvas,
source Pattern,
bounds image.Rectangle,
) (
@ -61,7 +61,7 @@ func FillRectangle (
// StrokeRectangle draws the outline of a rectangle with the specified line
// weight and pattern.
func StrokeRectangle (
destination tomo.Canvas,
destination canvas.Canvas,
source Pattern,
weight int,
bounds image.Rectangle,

View File

@ -6,7 +6,7 @@ import "unicode"
import "image/draw"
import "golang.org/x/image/font"
import "golang.org/x/image/math/fixed"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
type characterLayout struct {
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.
func (drawer *TextDrawer) Draw (
destination tomo.Canvas,
destination canvas.Canvas,
source Pattern,
offset image.Point,
) (

View File

@ -1,6 +1,8 @@
package tomo
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.
// 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,
// and returns a struct representing it that fulfills the Window
// 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 (Data)
Copy (data.Data)
// 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

View File

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

View File

@ -1,6 +1,7 @@
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/xgb/xproto"
@ -98,9 +99,9 @@ func (window *Window) exposeEventFollows (event xproto.ConfigureNotifyEvent) (fo
func (window *Window) modifiersFromState (
state uint16,
) (
modifiers tomo.Modifiers,
modifiers input.Modifiers,
) {
return tomo.Modifiers {
return input.Modifiers {
Shift:
(state & xproto.ModMaskShift) > 0 ||
(state & window.backend.modifierMasks.shiftLock) > 0,
@ -123,18 +124,18 @@ func (window *Window) handleKeyPress (
modifiers := window.modifiersFromState(keyEvent.State)
modifiers.NumberPad = numberPad
if key == tomo.KeyTab && modifiers.Alt {
if child, ok := window.child.(tomo.Focusable); ok {
direction := tomo.KeynavDirectionForward
if key == input.KeyTab && modifiers.Alt {
if child, ok := window.child.(elements.Focusable); ok {
direction := input.KeynavDirectionForward
if modifiers.Shift {
direction = tomo.KeynavDirectionBackward
direction = input.KeynavDirectionBackward
}
if !child.HandleFocus(direction) {
child.HandleUnfocus()
}
}
} else if child, ok := window.child.(tomo.KeyboardTarget); ok {
} else if child, ok := window.child.(elements.KeyboardTarget); ok {
child.HandleKeyDown(key, modifiers)
}
}
@ -168,7 +169,7 @@ func (window *Window) handleKeyRelease (
modifiers := window.modifiersFromState(keyEvent.State)
modifiers.NumberPad = numberPad
if child, ok := window.child.(tomo.KeyboardTarget); ok {
if child, ok := window.child.(elements.KeyboardTarget); ok {
child.HandleKeyUp(key, modifiers)
}
}
@ -179,7 +180,7 @@ func (window *Window) handleButtonPress (
) {
if window.child == nil { return }
if child, ok := window.child.(tomo.MouseTarget); ok {
if child, ok := window.child.(elements.MouseTarget); ok {
buttonEvent := *event.ButtonPressEvent
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 {
sum := scrollSum { }
@ -193,7 +194,7 @@ func (window *Window) handleButtonPress (
child.HandleMouseDown (
int(buttonEvent.EventX),
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 child, ok := window.child.(tomo.MouseTarget); ok {
if child, ok := window.child.(elements.MouseTarget); ok {
buttonEvent := *event.ButtonReleaseEvent
if buttonEvent.Detail >= 4 && buttonEvent.Detail <= 7 { return }
child.HandleMouseUp (
int(buttonEvent.EventX),
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 child, ok := window.child.(tomo.MouseTarget); ok {
if child, ok := window.child.(elements.MouseTarget); ok {
motionEvent := window.compressMotionNotify(*event.MotionNotifyEvent)
child.HandleMouseMove (
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/xwindow"
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 {
backend *Backend
xWindow *xwindow.Window
xCanvas *xgraphics.Image
canvas tomo.BasicCanvas
child tomo.Element
canvas canvas.BasicCanvas
child elements.Element
onClose func ()
skipChildDrawCallback bool
@ -27,7 +29,7 @@ type Window struct {
func (backend *Backend) NewWindow (
width, height int,
) (
output tomo.Window,
output elements.Window,
err error,
) {
if backend == nil { panic("nil backend") }
@ -79,16 +81,16 @@ func (backend *Backend) NewWindow (
return
}
func (window *Window) Adopt (child tomo.Element) {
func (window *Window) Adopt (child elements.Element) {
// disown previous child
if window.child != nil {
window.child.OnDamage(nil)
window.child.OnMinimumSizeChange(nil)
}
if previousChild, ok := window.child.(tomo.Flexible); ok {
if previousChild, ok := window.child.(elements.Flexible); ok {
previousChild.OnFlexibleHeightChange(nil)
}
if previousChild, ok := window.child.(tomo.Focusable); ok {
if previousChild, ok := window.child.(elements.Focusable); ok {
previousChild.OnFocusRequest(nil)
previousChild.OnFocusMotionRequest(nil)
if previousChild.Focused() {
@ -98,10 +100,10 @@ func (window *Window) Adopt (child tomo.Element) {
// adopt new child
window.child = child
if newChild, ok := child.(tomo.Flexible); ok {
if newChild, ok := child.(elements.Flexible); ok {
newChild.OnFlexibleHeightChange(window.resizeChildToFit)
}
if newChild, ok := child.(tomo.Focusable); ok {
if newChild, ok := child.(elements.Focusable); ok {
newChild.OnFocusRequest(window.childSelectionRequestCallback)
}
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
return
}
@ -229,7 +231,7 @@ func (window *Window) redrawChildEntirely () {
func (window *Window) resizeChildToFit () {
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)
minimumWidth, _ := child.MinimumSize()
@ -252,12 +254,12 @@ func (window *Window) resizeChildToFit () {
window.skipChildDrawCallback = false
}
func (window *Window) childDrawCallback (region tomo.Canvas) {
func (window *Window) childDrawCallback (region canvas.Canvas) {
if window.skipChildDrawCallback { return }
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()
bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds())
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) {
if child, ok := window.child.(tomo.Focusable); ok {
child.HandleFocus(tomo.KeynavDirectionNeutral)
if child, ok := window.child.(elements.Focusable); ok {
child.HandleFocus(input.KeynavDirectionNeutral)
}
return true
}
func (window *Window) childSelectionMotionRequestCallback (
direction tomo.KeynavDirection,
direction input.KeynavDirection,
) (
granted bool,
) {
if child, ok := window.child.(tomo.Focusable); ok {
if child, ok := window.child.(elements.Focusable); ok {
if !child.HandleFocus(direction) {
child.HandleUnfocus()
}

View File

@ -1,6 +1,7 @@
package x
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "github.com/jezek/xgbutil"
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
// will do nothing!
func (backend *Backend) Copy (data tomo.Data) {
func (backend *Backend) Copy (data data.Data) {
backend.assert()
// TODO
}
// Paste returns the data currently in the clipboard. This method may
// 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()
// TODO
return

View File

@ -1,4 +1,4 @@
package tomo
package canvas
import "image"
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"

View File

@ -1,7 +1,7 @@
package basic
package basicElements
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/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -38,10 +38,10 @@ func NewButton (text string) (element *Button) {
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.Focused() { element.Focus() }
if button != tomo.ButtonLeft { return }
if button != input.ButtonLeft { return }
element.pressed = true
if element.core.HasImage() {
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) {
if button != tomo.ButtonLeft { return }
func (element *Button) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft { return }
element.pressed = false
if element.core.HasImage() {
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) 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 key == tomo.KeyEnter {
if key == input.KeyEnter {
element.pressed = true
if element.core.HasImage() {
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) {
if key == tomo.KeyEnter && element.pressed {
func (element *Button) HandleKeyUp(key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed {
element.pressed = false
if element.core.HasImage() {
element.draw()

View File

@ -1,7 +1,7 @@
package basic
package basicElements
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/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -39,7 +39,7 @@ func NewCheckbox (text string, checked bool) (element *Checkbox) {
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 }
element.Focus()
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) {
if button != tomo.ButtonLeft || !element.pressed { return }
func (element *Checkbox) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft || !element.pressed { return }
element.pressed = false
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) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Checkbox) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
if key == tomo.KeyEnter {
func (element *Checkbox) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter {
element.pressed = true
if element.core.HasImage() {
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) {
if key == tomo.KeyEnter && element.pressed {
func (element *Checkbox) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed {
element.pressed = false
element.checked = !element.checked
if element.core.HasImage() {

View File

@ -1,9 +1,12 @@
package basic
package basicElements
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/canvas"
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"
var containerCase = theme.C("basic", "container")
@ -14,21 +17,21 @@ type Container struct {
*core.Core
core core.CoreControl
layout tomo.Layout
children []tomo.LayoutEntry
drags [10]tomo.MouseTarget
layout layouts.Layout
children []layouts.LayoutEntry
drags [10]elements.MouseTarget
warping bool
focused bool
focusable bool
flexible bool
onFocusRequest func () (granted bool)
onFocusMotionRequest func (tomo.KeynavDirection) (granted bool)
onFocusMotionRequest func (input.KeynavDirection) (granted bool)
onFlexibleHeightChange func ()
}
// NewContainer creates a new container.
func NewContainer (layout tomo.Layout) (element *Container) {
func NewContainer (layout layouts.Layout) (element *Container) {
element = &Container { }
element.Core, element.core = core.NewCore(element.redoAll)
element.SetLayout(layout)
@ -36,7 +39,7 @@ func NewContainer (layout tomo.Layout) (element *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
if element.core.HasImage() {
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,
// the element will expand (instead of contract to its minimum size), in
// 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
child.OnDamage (func (region tomo.Canvas) {
child.OnDamage (func (region canvas.Canvas) {
element.core.DamageRegion(region.Bounds())
})
child.OnMinimumSizeChange(element.updateMinimumSize)
if child0, ok := child.(tomo.Flexible); ok {
if child0, ok := child.(elements.Flexible); ok {
child0.OnFlexibleHeightChange(element.updateMinimumSize)
}
if child0, ok := child.(tomo.Focusable); ok {
if child0, ok := child.(elements.Focusable); ok {
child0.OnFocusRequest (func () (granted bool) {
return element.childFocusRequestCallback(child0)
})
child0.OnFocusMotionRequest (
func (direction tomo.KeynavDirection) (granted bool) {
func (direction input.KeynavDirection) (granted bool) {
if element.onFocusMotionRequest == nil { return }
return element.onFocusMotionRequest(direction)
})
}
// add child
element.children = append (element.children, tomo.LayoutEntry {
element.children = append (element.children, layouts.LayoutEntry {
Element: child,
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
// it.
func (element *Container) Disown (child tomo.Element) {
func (element *Container) Disown (child elements.Element) {
for index, entry := range element.children {
if entry.Element == child {
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.OnDamage(nil)
child.OnMinimumSizeChange(nil)
if child0, ok := child.(tomo.Focusable); ok {
if child0, ok := child.(elements.Focusable); ok {
child0.OnFocusRequest(nil)
child0.OnFocusMotionRequest(nil)
if child0.Focused() {
child0.HandleUnfocus()
}
}
if child0, ok := child.(tomo.Flexible); ok {
if child0, ok := child.(elements.Flexible); ok {
child0.OnFlexibleHeightChange(nil)
}
}
@ -154,8 +157,8 @@ func (element *Container) DisownAll () {
}
// Children returns a slice containing this element's children.
func (element *Container) Children () (children []tomo.Element) {
children = make([]tomo.Element, len(element.children))
func (element *Container) Children () (children []elements.Element) {
children = make([]elements.Element, len(element.children))
for index, entry := range element.children {
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
// 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 }
return element.children[index].Element
}
// 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.
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 {
if point.In(entry.Bounds) {
child = entry.Element
@ -185,7 +188,7 @@ func (element *Container) ChildAt (point image.Point) (child tomo.Element) {
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 {
if entry.Element == child {
position = entry.Bounds.Min
@ -209,18 +212,18 @@ func (element *Container) redoAll () {
// cut our canvas up and give peices to child elements
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) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
func (element *Container) HandleMouseDown (x, y int, button input.Button) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(elements.MouseTarget)
if !handlesMouse { return }
element.drags[button] = child
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]
if child == nil { return }
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) {
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(tomo.MouseTarget)
child, handlesMouse := element.ChildAt(image.Pt(x, y)).(elements.MouseTarget)
if !handlesMouse { return }
child.HandleMouseScroll(x, y, deltaX, deltaY)
}
func (element *Container) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
element.forFocused (func (child tomo.Focusable) bool {
child0, handlesKeyboard := child.(tomo.KeyboardTarget)
func (element *Container) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
element.forFocused (func (child elements.Focusable) bool {
child0, handlesKeyboard := child.(elements.KeyboardTarget)
if handlesKeyboard {
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) {
element.forFocused (func (child tomo.Focusable) bool {
child0, handlesKeyboard := child.(tomo.KeyboardTarget)
func (element *Container) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
element.forFocused (func (child elements.Focusable) bool {
child0, handlesKeyboard := child.(elements.KeyboardTarget)
if handlesKeyboard {
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) {
return element.layout.FlexibleHeightFor(element.children, width)
return element.layout.FlexibleHeightFor (
element.children,
theme.Margin(), width)
}
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 }
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
// direction.
switch direction {
case tomo.KeynavDirectionNeutral, tomo.KeynavDirectionForward:
case input.KeynavDirectionNeutral, input.KeynavDirectionForward:
// if we recieve a neutral or forward direction, focus
// the first focusable element.
return element.focusFirstFocusableElement(direction)
case tomo.KeynavDirectionBackward:
case input.KeynavDirectionBackward:
// if we recieve a backward direction, focus the last
// focusable element.
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
// focus in the specified direction
firstFocusedChild :=
element.children[firstFocused].Element.(tomo.Focusable)
element.children[firstFocused].Element.(elements.Focusable)
// before we move the focus, the currently focused child
// 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 :=
element.children[index].
Element.(tomo.Focusable)
Element.(elements.Focusable)
if focusable && child.HandleFocus(direction) {
// we have found one, so we now actually move
// the focus.
@ -334,11 +339,11 @@ func (element *Container) HandleFocus (direction tomo.KeynavDirection) (ok bool)
}
func (element *Container) focusFirstFocusableElement (
direction tomo.KeynavDirection,
direction input.KeynavDirection,
) (
ok bool,
) {
element.forFocusable (func (child tomo.Focusable) bool {
element.forFocusable (func (child elements.Focusable) bool {
if child.HandleFocus(direction) {
element.focused = true
ok = true
@ -350,11 +355,11 @@ func (element *Container) focusFirstFocusableElement (
}
func (element *Container) focusLastFocusableElement (
direction tomo.KeynavDirection,
direction input.KeynavDirection,
) (
ok bool,
) {
element.forFocusableBackward (func (child tomo.Focusable) bool {
element.forFocusableBackward (func (child elements.Focusable) bool {
if child.HandleFocus(direction) {
element.focused = true
ok = true
@ -367,7 +372,7 @@ func (element *Container) focusLastFocusableElement (
func (element *Container) HandleUnfocus () {
element.focused = false
element.forFocused (func (child tomo.Focusable) bool {
element.forFocused (func (child elements.Focusable) bool {
child.HandleUnfocus()
return true
})
@ -378,41 +383,41 @@ func (element *Container) OnFocusRequest (callback func () (granted bool)) {
}
func (element *Container) OnFocusMotionRequest (
callback func (direction tomo.KeynavDirection) (granted bool),
callback func (direction input.KeynavDirection) (granted bool),
) {
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 {
child, focusable := entry.Element.(tomo.Focusable)
child, focusable := entry.Element.(elements.Focusable)
if focusable && child.Focused() {
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 {
child, focusable := entry.Element.(tomo.Focusable)
child, focusable := entry.Element.(elements.Focusable)
if focusable {
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 {
child, flexible := entry.Element.(tomo.Flexible)
child, flexible := entry.Element.(elements.Flexible)
if flexible {
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 -- {
child, focusable := element.children[index].Element.(tomo.Focusable)
child, focusable := element.children[index].Element.(elements.Focusable)
if focusable {
if !callback(child) { break }
}
@ -421,7 +426,7 @@ func (element *Container) forFocusableBackward (callback func (child tomo.Focusa
func (element *Container) firstFocused () (index int) {
for currentIndex, entry := range element.children {
child, focusable := entry.Element.(tomo.Focusable)
child, focusable := entry.Element.(elements.Focusable)
if focusable && child.Focused() {
return currentIndex
}
@ -431,12 +436,12 @@ func (element *Container) firstFocused () (index int) {
func (element *Container) reflectChildProperties () {
element.focusable = false
element.forFocusable (func (tomo.Focusable) bool {
element.forFocusable (func (elements.Focusable) bool {
element.focusable = true
return false
})
element.flexible = false
element.forFlexible (func (tomo.Flexible) bool {
element.forFlexible (func (elements.Flexible) bool {
element.flexible = true
return false
})
@ -446,16 +451,16 @@ func (element *Container) reflectChildProperties () {
}
func (element *Container) childFocusRequestCallback (
child tomo.Focusable,
child elements.Focusable,
) (
granted bool,
) {
if element.onFocusRequest != nil && element.onFocusRequest() {
element.forFocused (func (child tomo.Focusable) bool {
element.forFocused (func (child elements.Focusable) bool {
child.HandleUnfocus()
return true
})
child.HandleFocus(tomo.KeynavDirectionNeutral)
child.HandleFocus(input.KeynavDirectionNeutral)
return true
} else {
return false
@ -463,13 +468,16 @@ func (element *Container) childFocusRequestCallback (
}
func (element *Container) updateMinimumSize () {
width, height := element.layout.MinimumSize(element.children)
width, height := element.layout.MinimumSize (
element.children, theme.Margin())
if element.flexible {
height = element.layout.FlexibleHeightFor(element.children, width)
height = element.layout.FlexibleHeightFor (
element.children, theme.Margin(), width)
}
element.core.SetMinimumSize(width, height)
}
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/artist"

View File

@ -1,9 +1,10 @@
package basic
package basicElements
import "fmt"
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/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -73,10 +74,10 @@ func (element *List) Collapse (width, height int) {
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.Focused() { element.Focus() }
if button != tomo.ButtonLeft { return }
if button != input.ButtonLeft { return }
element.pressed = true
if element.selectUnderMouse(x, y) && element.core.HasImage() {
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) {
if button != tomo.ButtonLeft { return }
func (element *List) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft { return }
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) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
func (element *List) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if !element.Enabled() { return }
altered := false
switch key {
case tomo.KeyLeft, tomo.KeyUp:
case input.KeyLeft, input.KeyUp:
altered = element.changeSelectionBy(-1)
case tomo.KeyRight, tomo.KeyDown:
case input.KeyRight, input.KeyDown:
altered = element.changeSelectionBy(1)
case tomo.KeyEscape:
case input.KeyEscape:
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.
func (element *List) ScrollContentBounds () (bounds image.Rectangle) {
@ -383,7 +384,7 @@ func (element *List) draw () {
bounds.Min.X,
bounds.Min.Y - element.scroll,
}
innerCanvas := tomo.Cut(element, bounds)
innerCanvas := canvas.Cut(element, bounds)
for index, entry := range element.entries {
entryPosition := dot
dot.Y += entry.Bounds().Dy()

View File

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

View File

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

View File

@ -1,9 +1,11 @@
package basic
package basicElements
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/canvas"
import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
var scrollContainerCase = theme.C("basic", "scrollContainer")
@ -17,7 +19,7 @@ type ScrollContainer struct {
core core.CoreControl
focused bool
child tomo.Scrollable
child elements.Scrollable
childWidth, childHeight int
horizontal struct {
@ -41,7 +43,7 @@ type ScrollContainer struct {
}
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
@ -64,7 +66,7 @@ func (element *ScrollContainer) handleResize () {
// 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
// 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
if element.child != nil {
element.clearChildEventHandlers(child)
@ -76,7 +78,7 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
child.OnDamage(element.childDamageCallback)
child.OnMinimumSizeChange(element.updateMinimumSize)
child.OnScrollBoundsChange(element.childScrollBoundsChangeCallback)
if newChild, ok := child.(tomo.Focusable); ok {
if newChild, ok := child.(elements.Focusable); ok {
newChild.OnFocusRequest (
element.childFocusRequestCallback)
newChild.OnFocusMotionRequest (
@ -96,19 +98,19 @@ func (element *ScrollContainer) Adopt (child tomo.Scrollable) {
}
}
func (element *ScrollContainer) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
if child, ok := element.child.(tomo.KeyboardTarget); ok {
func (element *ScrollContainer) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if child, ok := element.child.(elements.KeyboardTarget); ok {
child.HandleKeyDown(key, modifiers)
}
}
func (element *ScrollContainer) HandleKeyUp (key tomo.Key, modifiers tomo.Modifiers) {
if child, ok := element.child.(tomo.KeyboardTarget); ok {
func (element *ScrollContainer) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if child, ok := element.child.(elements.KeyboardTarget); ok {
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)
if point.In(element.horizontal.bar) {
element.horizontal.dragging = true
@ -140,12 +142,12 @@ func (element *ScrollContainer) HandleMouseDown (x, y int, button tomo.Button) {
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)
}
}
func (element *ScrollContainer) HandleMouseUp (x, y int, button tomo.Button) {
func (element *ScrollContainer) HandleMouseUp (x, y int, button input.Button) {
if element.horizontal.dragging {
element.horizontal.dragging = false
element.drawHorizontalBar()
@ -156,7 +158,7 @@ func (element *ScrollContainer) HandleMouseUp (x, y int, button tomo.Button) {
element.drawVerticalBar()
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)
}
}
@ -168,7 +170,7 @@ func (element *ScrollContainer) HandleMouseMove (x, y int) {
} else if element.vertical.dragging {
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)
}
}
@ -199,11 +201,11 @@ func (element *ScrollContainer) Focus () {
}
func (element *ScrollContainer) HandleFocus (
direction tomo.KeynavDirection,
direction input.KeynavDirection,
) (
accepted bool,
) {
if child, ok := element.child.(tomo.Focusable); ok {
if child, ok := element.child.(elements.Focusable); ok {
element.focused = true
return child.HandleFocus(direction)
} else {
@ -213,7 +215,7 @@ func (element *ScrollContainer) HandleFocus (
}
func (element *ScrollContainer) HandleUnfocus () {
if child, ok := element.child.(tomo.Focusable); ok {
if child, ok := element.child.(elements.Focusable); ok {
child.HandleUnfocus()
}
element.focused = false
@ -224,20 +226,20 @@ func (element *ScrollContainer) OnFocusRequest (callback func () (granted bool))
}
func (element *ScrollContainer) OnFocusMotionRequest (
callback func (direction tomo.KeynavDirection) (granted bool),
callback func (direction input.KeynavDirection) (granted bool),
) {
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 { }))
}
func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
child, ok := element.child.(tomo.Focusable)
child, ok := element.child.(elements.Focusable)
if !ok { return false }
if element.onFocusRequest != nil && element.onFocusRequest() {
child.HandleFocus(tomo.KeynavDirectionNeutral)
child.HandleFocus(input.KeynavDirectionNeutral)
return true
} else {
return false
@ -245,7 +247,7 @@ func (element *ScrollContainer) childFocusRequestCallback () (granted bool) {
}
func (element *ScrollContainer) childFocusMotionRequestCallback (
direction tomo.KeynavDirection,
direction input.KeynavDirection,
) (
granted bool,
) {
@ -253,19 +255,19 @@ func (element *ScrollContainer) childFocusMotionRequestCallback (
return element.onFocusMotionRequest(direction)
}
func (element *ScrollContainer) clearChildEventHandlers (child tomo.Scrollable) {
func (element *ScrollContainer) clearChildEventHandlers (child elements.Scrollable) {
child.DrawTo(nil)
child.OnDamage(nil)
child.OnMinimumSizeChange(nil)
child.OnScrollBoundsChange(nil)
if child0, ok := child.(tomo.Focusable); ok {
if child0, ok := child.(elements.Focusable); ok {
child0.OnFocusRequest(nil)
child0.OnFocusMotionRequest(nil)
if child0.Focused() {
child0.HandleUnfocus()
}
}
if child0, ok := child.(tomo.Flexible); ok {
if child0, ok := child.(elements.Flexible); ok {
child0.OnFlexibleHeightChange(nil)
}
}
@ -275,7 +277,7 @@ func (element *ScrollContainer) resizeChildToFit () {
0, 0,
element.childWidth,
element.childHeight).Add(element.Bounds().Min)
element.child.DrawTo(tomo.Cut(element, childBounds))
element.child.DrawTo(canvas.Cut(element, childBounds))
}
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/artist"

View File

@ -1,7 +1,7 @@
package basic
package basicElements
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/artist"
import "git.tebibyte.media/sashakoshka/tomo/elements/core"
@ -41,7 +41,7 @@ func NewSwitch (text string, on bool) (element *Switch) {
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 }
element.Focus()
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) {
if button != tomo.ButtonLeft || !element.pressed { return }
func (element *Switch) HandleMouseUp (x, y int, button input.Button) {
if button != input.ButtonLeft || !element.pressed { return }
element.pressed = false
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) HandleMouseScroll (x, y int, deltaX, deltaY float64) { }
func (element *Switch) HandleKeyDown (key tomo.Key, modifiers tomo.Modifiers) {
if key == tomo.KeyEnter {
func (element *Switch) HandleKeyDown (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter {
element.pressed = true
if element.core.HasImage() {
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) {
if key == tomo.KeyEnter && element.pressed {
func (element *Switch) HandleKeyUp (key input.Key, modifiers input.Modifiers) {
if key == input.KeyEnter && element.pressed {
element.pressed = false
element.checked = !element.checked
if element.core.HasImage() {

View File

@ -1,7 +1,7 @@
package basic
package basicElements
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/artist"
import "git.tebibyte.media/sashakoshka/tomo/textmanip"
@ -24,7 +24,7 @@ type TextBox struct {
placeholderDrawer 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 ()
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.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) 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) {
return
}
@ -77,7 +77,7 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
altered := true
textChanged := false
switch {
case key == tomo.KeyBackspace:
case key == input.KeyBackspace:
if len(element.text) < 1 { break }
element.text, element.cursor = textmanip.Backspace (
element.text,
@ -85,7 +85,7 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
modifiers.Control)
textChanged = true
case key == tomo.KeyDelete:
case key == input.KeyDelete:
if len(element.text) < 1 { break }
element.text, element.cursor = textmanip.Delete (
element.text,
@ -93,13 +93,13 @@ func (element *TextBox) HandleKeyDown(key tomo.Key, modifiers tomo.Modifiers) {
modifiers.Control)
textChanged = true
case key == tomo.KeyLeft:
case key == input.KeyLeft:
element.cursor = textmanip.MoveLeft (
element.text,
element.cursor,
modifiers.Control)
case key == tomo.KeyRight:
case key == input.KeyRight:
element.cursor = textmanip.MoveRight (
element.text,
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) {
if element.placeholder == placeholder { return }
@ -177,7 +177,7 @@ func (element *TextBox) Filled () (filled bool) {
}
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
}

View File

@ -2,12 +2,12 @@ package core
import "image"
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
// widgets. It is meant to be embedded directly into a struct.
type Core struct {
canvas tomo.Canvas
canvas canvas.Canvas
metrics struct {
minimumWidth int
@ -16,7 +16,7 @@ type Core struct {
drawSizeChange func ()
onMinimumSizeChange func ()
onDamage func (region tomo.Canvas)
onDamage func (region canvas.Canvas)
}
// 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)
}
// Buffer fulfills the tomo.Canvas interface.
// Buffer fulfills the canvas.Canvas interface.
func (core *Core) Buffer () (data []color.RGBA, stride int) {
if core.canvas == nil { return }
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
// overridden.
func (core *Core) DrawTo (canvas tomo.Canvas) {
func (core *Core) DrawTo (canvas canvas.Canvas) {
core.canvas = canvas
if core.drawSizeChange != nil {
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
// overridden.
func (core *Core) OnDamage (callback func (region tomo.Canvas)) {
func (core *Core) OnDamage (callback func (region canvas.Canvas)) {
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.
func (control CoreControl) DamageRegion (bounds image.Rectangle) {
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
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
// focusable, giving them the default keynav behavior.
@ -9,7 +9,7 @@ type FocusableCore struct {
enabled bool
drawFocusChange func ()
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
@ -46,13 +46,13 @@ func (core *FocusableCore) Focus () {
// HandleFocus causes this element to mark itself as focused, if it can
// currently be. Otherwise, it will return false and do nothing.
func (core *FocusableCore) HandleFocus (
direction tomo.KeynavDirection,
direction input.KeynavDirection,
) (
accepted bool,
) {
direction = direction.Canon()
if !core.enabled { return false }
if core.focused && direction != tomo.KeynavDirectionNeutral {
if core.focused && direction != input.KeynavDirectionNeutral {
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
// not.
func (core *FocusableCore) OnFocusMotionRequest (
callback func (direction tomo.KeynavDirection) (granted bool),
callback func (direction input.KeynavDirection) (granted bool),
) {
core.onFocusMotionRequest = callback
}

View File

@ -1,13 +1,15 @@
package tomo
package elements
import "image"
import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas"
// Element represents a basic on-screen object.
type Element interface {
// Element must implement the Canvas interface. Elements should start
// out with a completely blank buffer, and only allocate memory and draw
// on it for the first time when sent an EventResize event.
Canvas
canvas.Canvas
// MinimumSize specifies the minimum amount of pixels this element's
// 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
// parent element. This is typically a region of the parent element's
// canvas.
DrawTo (canvas Canvas)
DrawTo (canvas canvas.Canvas)
// OnDamage sets a function to be called when an area of the element is
// 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
// minimum size is changed.
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
// includes inputs, buttons, sliders, etc. as well as any elements that have
// 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
// and do nothing. Otherwise, it should select itself and any children
// (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
// children as unfocused.
@ -83,7 +65,7 @@ type Focusable interface {
// front of it, depending on the specified direction. Parent elements
// should return true if the request was granted, and false if it was
// not.
OnFocusMotionRequest (func (direction KeynavDirection) (granted bool))
OnFocusMotionRequest (func (direction input.KeynavDirection) (granted bool))
}
// 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
// up event. This is the reason a list of modifier keys held down at the
// 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
// keyboard focus.
HandleKeyUp (key Key, modifiers Modifiers)
HandleKeyUp (key input.Key, modifiers input.Modifiers)
}
// 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
// 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
// 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,
// or the mouse is moving while being held down and originally pressed

View File

@ -2,7 +2,7 @@ package testing
import "image"
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/artist"
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)))
}
func (element *Mouse) HandleMouseDown (x, y int, button tomo.Button) {
func (element *Mouse) HandleMouseDown (x, y int, button input.Button) {
element.drawing = true
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
mousePos := image.Pt(x, y)
element.core.DamageRegion (artist.Line (

View File

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

View File

@ -11,7 +11,7 @@ func main () {
func run () {
window, _ := tomo.NewWindow(2, 2)
window.SetTitle("example button")
button := basic.NewButton("hello tomo!")
button := basicElements.NewButton("hello tomo!")
button.OnClick (func () {
// when we set the button's text to something longer, the window
// 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/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/backends/x"
@ -14,22 +14,22 @@ func run () {
window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Checkboxes")
container := basic.NewContainer(layouts.Vertical { true, true })
container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container)
container.Adopt (basic.NewLabel (
container.Adopt (basicElements.NewLabel (
"We advise you to not read thPlease listen to me. I am " +
"trapped inside the example code. This is the only way for " +
"me to communicate.", true), true)
container.Adopt(basic.NewSpacer(true), false)
container.Adopt(basic.NewCheckbox("Oh god", false), false)
container.Adopt(basic.NewCheckbox("Can you hear them", true), false)
container.Adopt(basic.NewCheckbox("They are in the walls", false), false)
container.Adopt(basic.NewCheckbox("They are coming for us", false), false)
disabledCheckbox := basic.NewCheckbox("We are but their helpless prey", false)
container.Adopt(basicElements.NewSpacer(true), false)
container.Adopt(basicElements.NewCheckbox("Oh god", false), false)
container.Adopt(basicElements.NewCheckbox("Can you hear them", true), false)
container.Adopt(basicElements.NewCheckbox("They are in the walls", false), false)
container.Adopt(basicElements.NewCheckbox("They are coming for us", false), false)
disabledCheckbox := basicElements.NewCheckbox("We are but their helpless prey", false)
disabledCheckbox.SetEnabled(false)
container.Adopt(disabledCheckbox, false)
vsync := basic.NewCheckbox("Enable vsync", false)
vsync := basicElements.NewCheckbox("Enable vsync", false)
vsync.OnToggle (func () {
if vsync.Value() {
popups.NewDialog (
@ -39,7 +39,7 @@ func run () {
}
})
container.Adopt(vsync, false)
button := basic.NewButton("What")
button := basicElements.NewButton("What")
button.OnClick(tomo.Stop)
container.Adopt(button, false)
button.Focus()

View File

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

View File

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

View File

@ -3,7 +3,7 @@ package main
import "os"
import "time"
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/basic"
import _ "git.tebibyte.media/sashakoshka/tomo/backends/x"
@ -16,12 +16,12 @@ func main () {
func run () {
window, _ := tomo.NewWindow(2, 2)
window.SetTitle("clock")
container := basic.NewContainer(layouts.Vertical { true, true })
container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
window.Adopt(container)
clock := fun.NewAnalogClock(time.Now())
container.Adopt(clock, true)
label := basic.NewLabel(formatTime(), false)
label := basicElements.NewLabel(formatTime(), false)
container.Adopt(label, false)
window.OnClose(tomo.Stop)
@ -33,7 +33,7 @@ func formatTime () (timeString string) {
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 {
tomo.Do (func () {
label.SetText(formatTime())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
package main
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/backends/x"
@ -12,13 +12,13 @@ func main () {
func run () {
window, _ := tomo.NewWindow(2, 2)
window.SetTitle("Scroll")
container := basic.NewContainer(layouts.Vertical { true, true })
container := basicElements.NewContainer(basicLayouts.Vertical { true, true })
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")
scrollContainer := basic.NewScrollContainer(true, false)
textBox := basicElements.NewTextBox("", "sample text sample text")
scrollContainer := basicElements.NewScrollContainer(true, false)
scrollContainer.Adopt(textBox)
container.Adopt(scrollContainer, true)

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package tomo
package input
import "unicode"
@ -110,3 +110,22 @@ type Modifiers struct {
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 "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/elements"
// 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
@ -19,13 +19,18 @@ type Dialog struct {
}
// Arrange arranges a list of entries into a dialog.
func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
func (layout Dialog) Arrange (
entries []layouts.LayoutEntry,
margin int,
bounds image.Rectangle,
) {
if layout.Pad { bounds = bounds.Inset(margin) }
controlRowWidth, controlRowHeight := 0, 0
if len(entries) > 1 {
controlRowWidth,
controlRowHeight = layout.minimumSizeOfControlRow(entries[1:])
controlRowHeight = layout.minimumSizeOfControlRow (
entries[1:], margin)
}
if len(entries) > 0 {
@ -33,7 +38,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle
main.Bounds.Min = bounds.Min
mainHeight := bounds.Dy() - controlRowHeight
if layout.Gap {
mainHeight -= theme.Margin()
mainHeight -= margin
}
main.Bounds.Max = main.Bounds.Min.Add(image.Pt(bounds.Dx(), mainHeight))
entries[0] = main
@ -53,7 +58,7 @@ func (layout Dialog) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle
freeSpace -= entryMinWidth
}
if index > 0 && layout.Gap {
freeSpace -= theme.Margin()
freeSpace -= margin
}
}
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
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
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
// arrange the given list of entries.
func (layout Dialog) MinimumSize (
entries []tomo.LayoutEntry,
entries []layouts.LayoutEntry,
margin int,
) (
width, height int,
) {
@ -106,9 +112,10 @@ func (layout Dialog) MinimumSize (
}
if len(entries) > 1 {
if layout.Gap { height += theme.Margin() }
if layout.Gap { height += margin }
additionalWidth,
additionalHeight := layout.minimumSizeOfControlRow(entries[1:])
additionalHeight := layout.minimumSizeOfControlRow (
entries[1:], margin)
height += additionalHeight
if additionalWidth > width {
width = additionalWidth
@ -116,8 +123,8 @@ func (layout Dialog) MinimumSize (
}
if layout.Pad {
width += theme.Margin() * 2
height += theme.Margin() * 2
width += margin * 2
height += margin * 2
}
return
}
@ -125,18 +132,19 @@ func (layout Dialog) MinimumSize (
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
// specified elements at the given width, taking into account flexible elements.
func (layout Dialog) FlexibleHeightFor (
entries []tomo.LayoutEntry,
entries []layouts.LayoutEntry,
margin int,
width int,
) (
height int,
) {
if layout.Pad {
width -= theme.Margin() * 2
width -= margin * 2
}
if len(entries) > 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)
} else {
_, mainChildHeight = entries[0].MinimumSize()
@ -145,13 +153,14 @@ func (layout Dialog) FlexibleHeightFor (
}
if len(entries) > 1 {
if layout.Gap { height += theme.Margin() }
_, additionalHeight := layout.minimumSizeOfControlRow(entries[1:])
if layout.Gap { height += margin }
_, additionalHeight := layout.minimumSizeOfControlRow (
entries[1:], margin)
height += additionalHeight
}
if layout.Pad {
height += theme.Margin() * 2
height += margin * 2
}
return
}
@ -159,7 +168,8 @@ func (layout Dialog) FlexibleHeightFor (
// TODO: possibly flatten this method to account for flexible elements within
// the control row.
func (layout Dialog) minimumSizeOfControlRow (
entries []tomo.LayoutEntry,
entries []layouts.LayoutEntry,
margin int,
) (
width, height int,
) {
@ -170,7 +180,7 @@ func (layout Dialog) minimumSizeOfControlRow (
}
width += entryWidth
if layout.Gap && index > 0 {
width += theme.Margin()
width += margin
}
}
return

View File

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

View File

@ -1,8 +1,8 @@
package layouts
package basicLayouts
import "image"
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/theme"
import "git.tebibyte.media/sashakoshka/tomo/layouts"
import "git.tebibyte.media/sashakoshka/tomo/elements"
// 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
@ -17,8 +17,12 @@ type Vertical struct {
}
// Arrange arranges a list of entries vertically.
func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectangle) {
if layout.Pad { bounds = bounds.Inset(theme.Margin()) }
func (layout Vertical) Arrange (
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
// 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 {
var entryMinHeight int
if child, flexible := entry.Element.(tomo.Flexible); flexible {
if child, flexible := entry.Element.(elements.Flexible); flexible {
entryMinHeight = child.FlexibleHeightFor(bounds.Dx())
} else {
_, entryMinHeight = entry.MinimumSize()
@ -41,7 +45,7 @@ func (layout Vertical) Arrange (entries []tomo.LayoutEntry, bounds image.Rectang
freeSpace -= entryMinHeight
}
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
dot := bounds.Min
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
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
// arrange the given list of entries.
func (layout Vertical) MinimumSize (
entries []tomo.LayoutEntry,
entries []layouts.LayoutEntry,
margin int,
) (
width, height int,
) {
@ -83,13 +88,13 @@ func (layout Vertical) MinimumSize (
}
height += entryHeight
if layout.Gap && index > 0 {
height += theme.Margin()
height += margin
}
}
if layout.Pad {
width += theme.Margin() * 2
height += theme.Margin() * 2
width += margin * 2
height += margin * 2
}
return
}
@ -97,18 +102,19 @@ func (layout Vertical) MinimumSize (
// FlexibleHeightFor Returns the minimum height the layout needs to lay out the
// specified elements at the given width, taking into account flexible elements.
func (layout Vertical) FlexibleHeightFor (
entries []tomo.LayoutEntry,
entries []layouts.LayoutEntry,
margin int,
width int,
) (
height int,
) {
if layout.Pad {
width -= theme.Margin() * 2
height += theme.Margin() * 2
width -= margin * 2
height += margin * 2
}
for index, entry := range entries {
child, flexible := entry.Element.(tomo.Flexible)
child, flexible := entry.Element.(elements.Flexible)
if flexible {
height += child.FlexibleHeightFor(width)
} else {
@ -117,7 +123,7 @@ func (layout Vertical) FlexibleHeightFor (
}
if layout.Gap && index > 0 {
height += theme.Margin()
height += margin
}
}
return

View File

@ -1,11 +1,12 @@
package tomo
package layouts
import "image"
import "git.tebibyte.media/sashakoshka/tomo/elements"
// LayoutEntry associates an element with layout and positioning information so
// it can be arranged by a Layout.
type LayoutEntry struct {
Element
elements.Element
Bounds image.Rectangle
Expand bool
}
@ -17,14 +18,20 @@ type Layout interface {
// 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
// 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
// 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
// out the specified elements at the given width, taking into account
// 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
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"
// DialogKind defines the semantic role of a dialog window.
@ -30,24 +31,24 @@ func NewDialog (
title, message string,
buttons ...Button,
) (
window tomo.Window,
window elements.Window,
) {
window, _ = tomo.NewWindow(2, 2)
window.SetTitle(title)
container := basic.NewContainer(layouts.Dialog { true, true })
container := basicElements.NewContainer(basicLayouts.Dialog { true, true })
window.Adopt(container)
container.Adopt(basic.NewLabel(message, false), true)
container.Adopt(basicElements.NewLabel(message, false), true)
if len(buttons) == 0 {
button := basic.NewButton("OK")
button := basicElements.NewButton("OK")
button.OnClick(window.Close)
container.Adopt(button, false)
button.Focus()
} else {
var button *basic.Button
var button *basicElements.Button
for _, buttonDescriptor := range buttons {
button = basic.NewButton(buttonDescriptor.Name)
button = basicElements.NewButton(buttonDescriptor.Name)
button.SetEnabled(buttonDescriptor.OnPress != nil)
button.OnClick (func () {
buttonDescriptor.OnPress()

View File

@ -111,24 +111,3 @@ func FontFaceItalic () font.Face {
func FontFaceBoldItalic () font.Face {
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
import "errors"
import "git.tebibyte.media/sashakoshka/tomo/data"
import "git.tebibyte.media/sashakoshka/tomo/elements"
var backend Backend
@ -32,7 +34,7 @@ func Do (callback func ()) {
// 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
// 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 {
err = errors.New("no backend is running.")
return
@ -41,14 +43,14 @@ func NewWindow (width, height int) (window Window, err error) {
}
// Copy puts data into the clipboard.
func Copy (data Data) {
func Copy (data data.Data) {
if backend == nil { panic("no backend is running") }
backend.Copy(data)
}
// Paste returns the data currently in the clipboard. This method may
// return nil.
func Paste (accept []Mime) (Data) {
func Paste (accept []data.Mime) (data.Data) {
if backend == nil { panic("no backend is running") }
return backend.Paste(accept)
}