reorganize #17

Merged
sashakoshka merged 53 commits from reorganize into main 2023-05-03 13:42:22 -06:00
8 changed files with 98 additions and 74 deletions
Showing only changes of commit 567358bf4c - Show all commits

View File

@ -3,7 +3,7 @@ package ability
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/canvas" import "git.tebibyte.media/sashakoshka/tomo/artist"
// Layoutable represents an element that needs to perform layout calculations // Layoutable represents an element that needs to perform layout calculations
// before it can draw itself. // before it can draw itself.
@ -23,7 +23,7 @@ type Container interface {
// the specified canvas. The bounds of this canvas specify the area that // the specified canvas. The bounds of this canvas specify the area that
// is actually drawn to, while the Entity bounds specify the actual area // is actually drawn to, while the Entity bounds specify the actual area
// of the element. // of the element.
DrawBackground (canvas.Canvas) DrawBackground (artist.Canvas)
// HandleChildMinimumSizeChange is called when a child's minimum size is // HandleChildMinimumSizeChange is called when a child's minimum size is
// changed. // changed.

21
plugins/x/main.go Normal file
View File

@ -0,0 +1,21 @@
// Plugin x provides the X11 backend as a plugin.
package main
import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/plugins/x/x"
func Expects () tomo.Version {
return tomo.Version { 0, 0, 0 }
}
func Name () string {
return "X"
}
func Description () string {
return "Provides an X11 backend."
}
func NewBackend () (tomo.Backend, error) {
return x.NewBackend()
}

View File

@ -112,7 +112,7 @@ var keypadCodeTable = map[xproto.Keysym] input.Key {
// initializeKeymapInformation grabs keyboard mapping information from the X // initializeKeymapInformation grabs keyboard mapping information from the X
// server. // server.
func (backend *Backend) initializeKeymapInformation () { func (backend *backend) initializeKeymapInformation () {
keybind.Initialize(backend.connection) keybind.Initialize(backend.connection)
backend.modifierMasks.capsLock = backend.keysymToMask(0xFFE5) backend.modifierMasks.capsLock = backend.keysymToMask(0xFFE5)
backend.modifierMasks.shiftLock = backend.keysymToMask(0xFFE6) backend.modifierMasks.shiftLock = backend.keysymToMask(0xFFE6)
@ -127,7 +127,7 @@ func (backend *Backend) initializeKeymapInformation () {
// keysymToKeycode converts an X keysym to an X keycode, instead of the other // keysymToKeycode converts an X keysym to an X keycode, instead of the other
// way around. // way around.
func (backend *Backend) keysymToKeycode ( func (backend *backend) keysymToKeycode (
symbol xproto.Keysym, symbol xproto.Keysym,
) ( ) (
code xproto.Keycode, code xproto.Keycode,
@ -148,7 +148,7 @@ func (backend *Backend) keysymToKeycode (
} }
// keysymToMask returns the X modmask for a given modifier key. // keysymToMask returns the X modmask for a given modifier key.
func (backend *Backend) keysymToMask ( func (backend *backend) keysymToMask (
symbol xproto.Keysym, symbol xproto.Keysym,
) ( ) (
mask uint16, mask uint16,
@ -164,7 +164,7 @@ func (backend *Backend) keysymToMask (
// fleshed out version of some of the logic found in xgbutil/keybind/encoding.go // fleshed out version of some of the logic found in xgbutil/keybind/encoding.go
// to get a full keycode to keysym conversion, but eliminates redundant work by // to get a full keycode to keysym conversion, but eliminates redundant work by
// going straight to a tomo keycode. // going straight to a tomo keycode.
func (backend *Backend) keycodeToKey ( func (backend *backend) keycodeToKey (
keycode xproto.Keycode, keycode xproto.Keycode,
state uint16, state uint16,
) ( ) (

View File

@ -3,6 +3,7 @@ package x
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/ability"
type entity struct { type entity struct {
window *window window *window
@ -20,9 +21,9 @@ type entity struct {
isContainer bool isContainer bool
} }
func (backend *Backend) NewEntity (owner tomo.Element) tomo.Entity { func (backend *backend) NewEntity (owner tomo.Element) tomo.Entity {
entity := &entity { element: owner } entity := &entity { element: owner }
if _, ok := owner.(tomo.Container); ok { if _, ok := owner.(ability.Container); ok {
entity.isContainer = true entity.isContainer = true
entity.InvalidateLayout() entity.InvalidateLayout()
} }
@ -44,7 +45,7 @@ func (ent *entity) unlink () {
ent.parent = nil ent.parent = nil
ent.window = nil ent.window = nil
if element, ok := ent.element.(tomo.Selectable); ok { if element, ok := ent.element.(ability.Selectable); ok {
ent.selected = false ent.selected = false
element.HandleSelectionChange() element.HandleSelectionChange()
} }
@ -111,15 +112,15 @@ func (entity *entity) scrollTargetChildAt (point image.Point) *entity {
} }
} }
if _, ok := entity.element.(tomo.ScrollTarget); ok { if _, ok := entity.element.(ability.ScrollTarget); ok {
return entity return entity
} }
return nil return nil
} }
func (entity *entity) forMouseTargetContainers (callback func (tomo.MouseTargetContainer, tomo.Element)) { func (entity *entity) forMouseTargetContainers (callback func (ability.MouseTargetContainer, tomo.Element)) {
if entity.parent == nil { return } if entity.parent == nil { return }
if parent, ok := entity.parent.element.(tomo.MouseTargetContainer); ok { if parent, ok := entity.parent.element.(ability.MouseTargetContainer); ok {
callback(parent, entity.element) callback(parent, entity.element)
} }
entity.parent.forMouseTargetContainers(callback) entity.parent.forMouseTargetContainers(callback)
@ -156,18 +157,19 @@ func (entity *entity) SetMinimumSize (width, height int) {
entity.window.setMinimumSize(width, height) entity.window.setMinimumSize(width, height)
} }
} else { } else {
entity.parent.element.(tomo.Container). entity.parent.element.(ability.Container).
HandleChildMinimumSizeChange(entity.element) HandleChildMinimumSizeChange(entity.element)
} }
} }
func (entity *entity) DrawBackground (destination canvas.Canvas) { func (entity *entity) DrawBackground (destination artist.Canvas) {
if entity.parent != nil { if entity.parent != nil {
entity.parent.element.(tomo.Container).DrawBackground(destination) entity.parent.element.(ability.Container).DrawBackground(destination)
} else if entity.window != nil { } else if entity.window != nil {
entity.window.system.theme.Pattern ( entity.window.system.theme.Pattern (
tomo.PatternBackground, tomo.PatternBackground,
tomo.State { }).Draw ( tomo.State { },
tomo.C("tomo", "window")).Draw (
destination, destination,
entity.window.canvas.Bounds()) entity.window.canvas.Bounds())
} }
@ -233,7 +235,7 @@ func (entity *entity) PlaceChild (index int, bounds image.Rectangle) {
func (entity *entity) SelectChild (index int, selected bool) { func (entity *entity) SelectChild (index int, selected bool) {
child := entity.children[index] child := entity.children[index]
if element, ok := child.element.(tomo.Selectable); ok { if element, ok := child.element.(ability.Selectable); ok {
if child.selected == selected { return } if child.selected == selected { return }
child.selected = selected child.selected = selected
element.HandleSelectionChange() element.HandleSelectionChange()
@ -275,9 +277,9 @@ func (entity *entity) Selected () bool {
func (entity *entity) NotifyFlexibleHeightChange () { func (entity *entity) NotifyFlexibleHeightChange () {
if entity.parent == nil { return } if entity.parent == nil { return }
if parent, ok := entity.parent.element.(tomo.FlexibleContainer); ok { if parent, ok := entity.parent.element.(ability.FlexibleContainer); ok {
parent.HandleChildFlexibleHeightChange ( parent.HandleChildFlexibleHeightChange (
entity.element.(tomo.Flexible)) entity.element.(ability.Flexible))
} }
} }
@ -285,8 +287,8 @@ func (entity *entity) NotifyFlexibleHeightChange () {
func (entity *entity) NotifyScrollBoundsChange () { func (entity *entity) NotifyScrollBoundsChange () {
if entity.parent == nil { return } if entity.parent == nil { return }
if parent, ok := entity.parent.element.(tomo.ScrollableContainer); ok { if parent, ok := entity.parent.element.(ability.ScrollableContainer); ok {
parent.HandleChildScrollBoundsChange ( parent.HandleChildScrollBoundsChange (
entity.element.(tomo.Scrollable)) entity.element.(ability.Scrollable))
} }
} }

View File

@ -3,6 +3,7 @@ package x
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/input" import "git.tebibyte.media/sashakoshka/tomo/input"
import "git.tebibyte.media/sashakoshka/tomo/ability"
import "github.com/jezek/xgbutil" import "github.com/jezek/xgbutil"
import "github.com/jezek/xgb/xproto" import "github.com/jezek/xgb/xproto"
@ -134,7 +135,7 @@ func (window *window) handleKeyPress (
} else if key == input.KeyEscape && window.shy { } else if key == input.KeyEscape && window.shy {
window.Close() window.Close()
} else if window.focused != nil { } else if window.focused != nil {
focused, ok := window.focused.element.(tomo.KeyboardTarget) focused, ok := window.focused.element.(ability.KeyboardTarget)
if ok { focused.HandleKeyDown(key, modifiers) } if ok { focused.HandleKeyDown(key, modifiers) }
} }
} }
@ -169,7 +170,7 @@ func (window *window) handleKeyRelease (
modifiers.NumberPad = numberPad modifiers.NumberPad = numberPad
if window.focused != nil { if window.focused != nil {
focused, ok := window.focused.element.(tomo.KeyboardTarget) focused, ok := window.focused.element.(ability.KeyboardTarget)
if ok { focused.HandleKeyUp(key, modifiers) } if ok { focused.HandleKeyUp(key, modifiers) }
} }
} }
@ -191,7 +192,7 @@ func (window *window) handleButtonPress (
} else if scrolling { } else if scrolling {
underneath := window.system.scrollTargetChildAt(point) underneath := window.system.scrollTargetChildAt(point)
if underneath != nil { if underneath != nil {
if child, ok := underneath.element.(tomo.ScrollTarget); ok { if child, ok := underneath.element.(ability.ScrollTarget); ok {
sum := scrollSum { } sum := scrollSum { }
sum.add(buttonEvent.Detail, window, buttonEvent.State) sum.add(buttonEvent.Detail, window, buttonEvent.State)
window.compressScrollSum(buttonEvent, &sum) window.compressScrollSum(buttonEvent, &sum)
@ -203,12 +204,12 @@ func (window *window) handleButtonPress (
} else { } else {
underneath := window.system.childAt(point) underneath := window.system.childAt(point)
window.system.drags[buttonEvent.Detail] = underneath window.system.drags[buttonEvent.Detail] = underneath
if child, ok := underneath.element.(tomo.MouseTarget); ok { if child, ok := underneath.element.(ability.MouseTarget); ok {
child.HandleMouseDown ( child.HandleMouseDown (
point, input.Button(buttonEvent.Detail), point, input.Button(buttonEvent.Detail),
modifiers) modifiers)
} }
callback := func (container tomo.MouseTargetContainer, child tomo.Element) { callback := func (container ability.MouseTargetContainer, child tomo.Element) {
container.HandleChildMouseDown ( container.HandleChildMouseDown (
point, input.Button(buttonEvent.Detail), point, input.Button(buttonEvent.Detail),
modifiers, child) modifiers, child)
@ -229,7 +230,7 @@ func (window *window) handleButtonRelease (
dragging := window.system.drags[buttonEvent.Detail] dragging := window.system.drags[buttonEvent.Detail]
if dragging != nil { if dragging != nil {
if child, ok := dragging.element.(tomo.MouseTarget); ok { if child, ok := dragging.element.(ability.MouseTarget); ok {
child.HandleMouseUp ( child.HandleMouseUp (
image.Pt ( image.Pt (
int(buttonEvent.EventX), int(buttonEvent.EventX),
@ -237,7 +238,7 @@ func (window *window) handleButtonRelease (
input.Button(buttonEvent.Detail), input.Button(buttonEvent.Detail),
modifiers) modifiers)
} }
callback := func (container tomo.MouseTargetContainer, child tomo.Element) { callback := func (container ability.MouseTargetContainer, child tomo.Element) {
container.HandleChildMouseUp ( container.HandleChildMouseUp (
image.Pt ( image.Pt (
int(buttonEvent.EventX), int(buttonEvent.EventX),
@ -262,7 +263,7 @@ func (window *window) handleMotionNotify (
handled := false handled := false
for _, child := range window.system.drags { for _, child := range window.system.drags {
if child == nil { continue } if child == nil { continue }
if child, ok := child.element.(tomo.MotionTarget); ok { if child, ok := child.element.(ability.MotionTarget); ok {
child.HandleMotion(image.Pt(x, y)) child.HandleMotion(image.Pt(x, y))
handled = true handled = true
} }
@ -270,7 +271,7 @@ func (window *window) handleMotionNotify (
if !handled { if !handled {
child := window.system.childAt(image.Pt(x, y)) child := window.system.childAt(image.Pt(x, y))
if child, ok := child.element.(tomo.MotionTarget); ok { if child, ok := child.element.(ability.MotionTarget); ok {
child.HandleMotion(image.Pt(x, y)) child.HandleMotion(image.Pt(x, y))
} }
} }

View File

@ -3,8 +3,9 @@ package x
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo"
import "git.tebibyte.media/sashakoshka/tomo/artist" import "git.tebibyte.media/sashakoshka/tomo/artist"
import "git.tebibyte.media/sashakoshka/tomo/default/theme" import "git.tebibyte.media/sashakoshka/tomo/ability"
import "git.tebibyte.media/sashakoshka/tomo/default/config" import defaultTheme "git.tebibyte.media/sashakoshka/tomo/default/theme"
import defaultConfig "git.tebibyte.media/sashakoshka/tomo/default/config"
type entitySet map[*entity] struct { } type entitySet map[*entity] struct { }
@ -24,10 +25,10 @@ func (set entitySet) Add (entity *entity) {
type system struct { type system struct {
child *entity child *entity
focused *entity focused *entity
canvas canvas.BasicCanvas canvas artist.BasicCanvas
theme theme.Wrapped theme tomo.Theme
config config.Wrapped config tomo.Config
invalidateIgnore bool invalidateIgnore bool
drawingInvalid entitySet drawingInvalid entitySet
@ -42,21 +43,29 @@ func (system *system) initialize () {
system.drawingInvalid = make(entitySet) system.drawingInvalid = make(entitySet)
} }
func (system *system) SetTheme (theme tomo.Theme) { func (system *system) setTheme (theme tomo.Theme) {
system.theme.Theme = theme if theme == nil {
system.theme = defaultTheme.Default { }
} else {
system.theme = theme
}
system.propagate (func (entity *entity) bool { system.propagate (func (entity *entity) bool {
if child, ok := system.child.element.(tomo.Themeable); ok { if child, ok := system.child.element.(ability.Themeable); ok {
child.SetTheme(theme) child.HandleThemeChange()
} }
return true return true
}) })
} }
func (system *system) SetConfig (config tomo.Config) { func (system *system) setConfig (config tomo.Config) {
system.config.Config = config if config == nil {
system.config = defaultConfig.Default { }
} else {
system.config = config
}
system.propagate (func (entity *entity) bool { system.propagate (func (entity *entity) bool {
if child, ok := system.child.element.(tomo.Configurable); ok { if child, ok := system.child.element.(ability.Configurable); ok {
child.SetConfig(config) child.HandleConfigChange()
} }
return true return true
}) })
@ -66,10 +75,10 @@ func (system *system) focus (entity *entity) {
previous := system.focused previous := system.focused
system.focused = entity system.focused = entity
if previous != nil { if previous != nil {
previous.element.(tomo.Focusable).HandleFocusChange() previous.element.(ability.Focusable).HandleFocusChange()
} }
if entity != nil { if entity != nil {
entity.element.(tomo.Focusable).HandleFocusChange() entity.element.(ability.Focusable).HandleFocusChange()
} }
} }
@ -79,7 +88,7 @@ func (system *system) focusNext () {
system.propagateAlt (func (entity *entity) bool { system.propagateAlt (func (entity *entity) bool {
if found { if found {
// looking for the next element to select // looking for the next element to select
child, ok := entity.element.(tomo.Focusable) child, ok := entity.element.(ability.Focusable)
if ok && child.Enabled() { if ok && child.Enabled() {
// found it // found it
entity.Focus() entity.Focus()
@ -106,7 +115,7 @@ func (system *system) focusPrevious () {
return false return false
} }
child, ok := entity.element.(tomo.Focusable) child, ok := entity.element.(ability.Focusable)
if ok && child.Enabled() { behind = entity } if ok && child.Enabled() { behind = entity }
return true return true
}) })
@ -153,7 +162,7 @@ func (system *system) afterEvent () {
func (system *system) layout (entity *entity, force bool) { func (system *system) layout (entity *entity, force bool) {
if entity == nil { return } if entity == nil { return }
if entity.layoutInvalid == true || force { if entity.layoutInvalid == true || force {
if element, ok := entity.element.(tomo.Layoutable); ok { if element, ok := entity.element.(ability.Layoutable); ok {
element.Layout() element.Layout()
entity.layoutInvalid = false entity.layoutInvalid = false
force = true force = true
@ -176,7 +185,7 @@ func (system *system) draw () {
for entity := range system.drawingInvalid { for entity := range system.drawingInvalid {
if entity.clippedBounds.Empty() { continue } if entity.clippedBounds.Empty() { continue }
entity.element.Draw (canvas.Cut ( entity.element.Draw (artist.Cut (
system.canvas, system.canvas,
entity.clippedBounds)) entity.clippedBounds))
finalBounds = finalBounds.Union(entity.clippedBounds) finalBounds = finalBounds.Union(entity.clippedBounds)

View File

@ -20,7 +20,7 @@ type menuWindow struct { *window }
type window struct { type window struct {
system system
backend *Backend backend *backend
xWindow *xwindow.Window xWindow *xwindow.Window
xCanvas *xgraphics.Image xCanvas *xgraphics.Image
@ -40,7 +40,7 @@ type window struct {
onClose func () onClose func ()
} }
func (backend *Backend) NewWindow ( func (backend *backend) NewWindow (
bounds image.Rectangle, bounds image.Rectangle,
) ( ) (
output tomo.MainWindow, output tomo.MainWindow,
@ -53,7 +53,7 @@ func (backend *Backend) NewWindow (
return output, err return output, err
} }
func (backend *Backend) newWindow ( func (backend *backend) newWindow (
bounds image.Rectangle, bounds image.Rectangle,
override bool, override bool,
) ( ) (
@ -67,7 +67,6 @@ func (backend *Backend) newWindow (
window.system.initialize() window.system.initialize()
window.system.pushFunc = window.pasteAndPush window.system.pushFunc = window.pasteAndPush
window.theme.Case = tomo.C("tomo", "window")
window.xWindow, err = xwindow.Generate(backend.connection) window.xWindow, err = xwindow.Generate(backend.connection)
if err != nil { return } if err != nil { return }
@ -122,8 +121,8 @@ func (backend *Backend) newWindow (
xevent.SelectionRequestFun(window.handleSelectionRequest). xevent.SelectionRequestFun(window.handleSelectionRequest).
Connect(backend.connection, window.xWindow.Id) Connect(backend.connection, window.xWindow.Id)
window.SetTheme(backend.theme) window.setTheme(backend.theme)
window.SetConfig(backend.config) window.setConfig(backend.config)
window.metrics.bounds = bounds window.metrics.bounds = bounds
window.setMinimumSize(8, 8) window.setMinimumSize(8, 8)
@ -418,7 +417,7 @@ func (window *window) pasteAndPush (region image.Rectangle) {
} }
func (window *window) paste (region image.Rectangle) { func (window *window) paste (region image.Rectangle) {
canvas := canvas.Cut(window.canvas, region) canvas := artist.Cut(window.canvas, region)
data, stride := canvas.Buffer() data, stride := canvas.Buffer()
bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds()) bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds())

View File

@ -8,8 +8,7 @@ import "github.com/jezek/xgbutil/xevent"
import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/keybind"
import "github.com/jezek/xgbutil/mousebind" import "github.com/jezek/xgbutil/mousebind"
// Backend is an instance of an X backend. type backend struct {
type Backend struct {
connection *xgbutil.XUtil connection *xgbutil.XUtil
doChannel chan(func ()) doChannel chan(func ())
@ -36,7 +35,7 @@ type Backend struct {
// NewBackend instantiates an X backend. // NewBackend instantiates an X backend.
func NewBackend () (output tomo.Backend, err error) { func NewBackend () (output tomo.Backend, err error) {
backend := &Backend { backend := &backend {
windows: map[xproto.Window] *window { }, windows: map[xproto.Window] *window { },
doChannel: make(chan func (), 32), doChannel: make(chan func (), 32),
open: true, open: true,
@ -54,9 +53,7 @@ func NewBackend () (output tomo.Backend, err error) {
return return
} }
// Run runs the backend's event loop. This method will not exit until Stop() is func (backend *backend) Run () (err error) {
// called, or the backend experiences a fatal error.
func (backend *Backend) Run () (err error) {
backend.assert() backend.assert()
pingBefore, pingBefore,
pingAfter, pingAfter,
@ -76,8 +73,7 @@ func (backend *Backend) Run () (err error) {
} }
} }
// Stop gracefully closes the connection and stops the event loop. func (backend *backend) Stop () {
func (backend *Backend) Stop () {
backend.assert() backend.assert()
if !backend.open { return } if !backend.open { return }
backend.open = false backend.open = false
@ -93,31 +89,27 @@ func (backend *Backend) Stop () {
backend.connection.Conn().Close() backend.connection.Conn().Close()
} }
// Do executes the specified callback within the main thread as soon as func (backend *backend) Do (callback func ()) {
// possible. This function can be safely called from other threads.
func (backend *Backend) Do (callback func ()) {
backend.assert() backend.assert()
backend.doChannel <- callback backend.doChannel <- callback
} }
// SetTheme sets the theme of all open windows. func (backend *backend) SetTheme (theme tomo.Theme) {
func (backend *Backend) SetTheme (theme tomo.Theme) {
backend.assert() backend.assert()
backend.theme = theme backend.theme = theme
for _, window := range backend.windows { for _, window := range backend.windows {
window.SetTheme(theme) window.setTheme(theme)
} }
} }
// SetConfig sets the configuration of all open windows. func (backend *backend) SetConfig (config tomo.Config) {
func (backend *Backend) SetConfig (config tomo.Config) {
backend.assert() backend.assert()
backend.config = config backend.config = config
for _, window := range backend.windows { for _, window := range backend.windows {
window.SetConfig(config) window.setConfig(config)
} }
} }
func (backend *Backend) assert () { func (backend *backend) assert () {
if backend == nil { panic("nil backend") } if backend == nil { panic("nil backend") }
} }