From 567358bf4ca7867ec159dab81df8890466927893 Mon Sep 17 00:00:00 2001 From: Sasha Koshka Date: Sun, 30 Apr 2023 14:16:14 -0400 Subject: [PATCH] Made the X backend into a plugin --- ability/element.go | 4 ++-- plugins/x/main.go | 21 ++++++++++++++++++ plugins/x/x/encoding.go | 8 +++---- plugins/x/x/entity.go | 32 +++++++++++++++------------- plugins/x/x/event.go | 19 +++++++++-------- plugins/x/x/system.go | 47 ++++++++++++++++++++++++----------------- plugins/x/x/window.go | 13 ++++++------ plugins/x/x/x.go | 28 +++++++++--------------- 8 files changed, 98 insertions(+), 74 deletions(-) create mode 100644 plugins/x/main.go diff --git a/ability/element.go b/ability/element.go index 467de1b..1a7782b 100644 --- a/ability/element.go +++ b/ability/element.go @@ -3,7 +3,7 @@ package ability import "image" 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/artist" // Layoutable represents an element that needs to perform layout calculations // before it can draw itself. @@ -23,7 +23,7 @@ type Container interface { // the specified canvas. The bounds of this canvas specify the area that // is actually drawn to, while the Entity bounds specify the actual area // of the element. - DrawBackground (canvas.Canvas) + DrawBackground (artist.Canvas) // HandleChildMinimumSizeChange is called when a child's minimum size is // changed. diff --git a/plugins/x/main.go b/plugins/x/main.go new file mode 100644 index 0000000..bce6b24 --- /dev/null +++ b/plugins/x/main.go @@ -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() +} diff --git a/plugins/x/x/encoding.go b/plugins/x/x/encoding.go index cf5a404..296163f 100644 --- a/plugins/x/x/encoding.go +++ b/plugins/x/x/encoding.go @@ -112,7 +112,7 @@ var keypadCodeTable = map[xproto.Keysym] input.Key { // initializeKeymapInformation grabs keyboard mapping information from the X // server. -func (backend *Backend) initializeKeymapInformation () { +func (backend *backend) initializeKeymapInformation () { keybind.Initialize(backend.connection) backend.modifierMasks.capsLock = backend.keysymToMask(0xFFE5) 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 // way around. -func (backend *Backend) keysymToKeycode ( +func (backend *backend) keysymToKeycode ( symbol xproto.Keysym, ) ( code xproto.Keycode, @@ -148,7 +148,7 @@ func (backend *Backend) keysymToKeycode ( } // keysymToMask returns the X modmask for a given modifier key. -func (backend *Backend) keysymToMask ( +func (backend *backend) keysymToMask ( symbol xproto.Keysym, ) ( mask uint16, @@ -164,7 +164,7 @@ func (backend *Backend) keysymToMask ( // 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 // going straight to a tomo keycode. -func (backend *Backend) keycodeToKey ( +func (backend *backend) keycodeToKey ( keycode xproto.Keycode, state uint16, ) ( diff --git a/plugins/x/x/entity.go b/plugins/x/x/entity.go index f15794f..b630191 100644 --- a/plugins/x/x/entity.go +++ b/plugins/x/x/entity.go @@ -3,6 +3,7 @@ package x import "image" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/artist" +import "git.tebibyte.media/sashakoshka/tomo/ability" type entity struct { window *window @@ -20,9 +21,9 @@ type entity struct { isContainer bool } -func (backend *Backend) NewEntity (owner tomo.Element) tomo.Entity { +func (backend *backend) NewEntity (owner tomo.Element) tomo.Entity { entity := &entity { element: owner } - if _, ok := owner.(tomo.Container); ok { + if _, ok := owner.(ability.Container); ok { entity.isContainer = true entity.InvalidateLayout() } @@ -44,7 +45,7 @@ func (ent *entity) unlink () { ent.parent = nil ent.window = nil - if element, ok := ent.element.(tomo.Selectable); ok { + if element, ok := ent.element.(ability.Selectable); ok { ent.selected = false 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 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 parent, ok := entity.parent.element.(tomo.MouseTargetContainer); ok { + if parent, ok := entity.parent.element.(ability.MouseTargetContainer); ok { callback(parent, entity.element) } entity.parent.forMouseTargetContainers(callback) @@ -156,18 +157,19 @@ func (entity *entity) SetMinimumSize (width, height int) { entity.window.setMinimumSize(width, height) } } else { - entity.parent.element.(tomo.Container). + entity.parent.element.(ability.Container). HandleChildMinimumSizeChange(entity.element) } } -func (entity *entity) DrawBackground (destination canvas.Canvas) { +func (entity *entity) DrawBackground (destination artist.Canvas) { if entity.parent != nil { - entity.parent.element.(tomo.Container).DrawBackground(destination) + entity.parent.element.(ability.Container).DrawBackground(destination) } else if entity.window != nil { entity.window.system.theme.Pattern ( tomo.PatternBackground, - tomo.State { }).Draw ( + tomo.State { }, + tomo.C("tomo", "window")).Draw ( destination, 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) { 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 } child.selected = selected element.HandleSelectionChange() @@ -275,9 +277,9 @@ func (entity *entity) Selected () bool { func (entity *entity) NotifyFlexibleHeightChange () { 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 ( - entity.element.(tomo.Flexible)) + entity.element.(ability.Flexible)) } } @@ -285,8 +287,8 @@ func (entity *entity) NotifyFlexibleHeightChange () { func (entity *entity) NotifyScrollBoundsChange () { 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 ( - entity.element.(tomo.Scrollable)) + entity.element.(ability.Scrollable)) } } diff --git a/plugins/x/x/event.go b/plugins/x/x/event.go index b195d1a..182a607 100644 --- a/plugins/x/x/event.go +++ b/plugins/x/x/event.go @@ -3,6 +3,7 @@ package x import "image" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/input" +import "git.tebibyte.media/sashakoshka/tomo/ability" import "github.com/jezek/xgbutil" import "github.com/jezek/xgb/xproto" @@ -134,7 +135,7 @@ func (window *window) handleKeyPress ( } else if key == input.KeyEscape && window.shy { window.Close() } 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) } } } @@ -169,7 +170,7 @@ func (window *window) handleKeyRelease ( modifiers.NumberPad = numberPad if window.focused != nil { - focused, ok := window.focused.element.(tomo.KeyboardTarget) + focused, ok := window.focused.element.(ability.KeyboardTarget) if ok { focused.HandleKeyUp(key, modifiers) } } } @@ -191,7 +192,7 @@ func (window *window) handleButtonPress ( } else if scrolling { underneath := window.system.scrollTargetChildAt(point) if underneath != nil { - if child, ok := underneath.element.(tomo.ScrollTarget); ok { + if child, ok := underneath.element.(ability.ScrollTarget); ok { sum := scrollSum { } sum.add(buttonEvent.Detail, window, buttonEvent.State) window.compressScrollSum(buttonEvent, &sum) @@ -203,12 +204,12 @@ func (window *window) handleButtonPress ( } else { underneath := window.system.childAt(point) window.system.drags[buttonEvent.Detail] = underneath - if child, ok := underneath.element.(tomo.MouseTarget); ok { + if child, ok := underneath.element.(ability.MouseTarget); ok { child.HandleMouseDown ( point, input.Button(buttonEvent.Detail), modifiers) } - callback := func (container tomo.MouseTargetContainer, child tomo.Element) { + callback := func (container ability.MouseTargetContainer, child tomo.Element) { container.HandleChildMouseDown ( point, input.Button(buttonEvent.Detail), modifiers, child) @@ -229,7 +230,7 @@ func (window *window) handleButtonRelease ( dragging := window.system.drags[buttonEvent.Detail] if dragging != nil { - if child, ok := dragging.element.(tomo.MouseTarget); ok { + if child, ok := dragging.element.(ability.MouseTarget); ok { child.HandleMouseUp ( image.Pt ( int(buttonEvent.EventX), @@ -237,7 +238,7 @@ func (window *window) handleButtonRelease ( input.Button(buttonEvent.Detail), modifiers) } - callback := func (container tomo.MouseTargetContainer, child tomo.Element) { + callback := func (container ability.MouseTargetContainer, child tomo.Element) { container.HandleChildMouseUp ( image.Pt ( int(buttonEvent.EventX), @@ -262,7 +263,7 @@ func (window *window) handleMotionNotify ( handled := false for _, child := range window.system.drags { 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)) handled = true } @@ -270,7 +271,7 @@ func (window *window) handleMotionNotify ( if !handled { 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)) } } diff --git a/plugins/x/x/system.go b/plugins/x/x/system.go index 6522108..cf77e5c 100644 --- a/plugins/x/x/system.go +++ b/plugins/x/x/system.go @@ -3,8 +3,9 @@ package x import "image" import "git.tebibyte.media/sashakoshka/tomo" import "git.tebibyte.media/sashakoshka/tomo/artist" -import "git.tebibyte.media/sashakoshka/tomo/default/theme" -import "git.tebibyte.media/sashakoshka/tomo/default/config" +import "git.tebibyte.media/sashakoshka/tomo/ability" +import defaultTheme "git.tebibyte.media/sashakoshka/tomo/default/theme" +import defaultConfig "git.tebibyte.media/sashakoshka/tomo/default/config" type entitySet map[*entity] struct { } @@ -24,10 +25,10 @@ func (set entitySet) Add (entity *entity) { type system struct { child *entity focused *entity - canvas canvas.BasicCanvas + canvas artist.BasicCanvas - theme theme.Wrapped - config config.Wrapped + theme tomo.Theme + config tomo.Config invalidateIgnore bool drawingInvalid entitySet @@ -42,21 +43,29 @@ func (system *system) initialize () { system.drawingInvalid = make(entitySet) } -func (system *system) SetTheme (theme tomo.Theme) { - system.theme.Theme = theme +func (system *system) setTheme (theme tomo.Theme) { + if theme == nil { + system.theme = defaultTheme.Default { } + } else { + system.theme = theme + } system.propagate (func (entity *entity) bool { - if child, ok := system.child.element.(tomo.Themeable); ok { - child.SetTheme(theme) + if child, ok := system.child.element.(ability.Themeable); ok { + child.HandleThemeChange() } return true }) } -func (system *system) SetConfig (config tomo.Config) { - system.config.Config = config +func (system *system) setConfig (config tomo.Config) { + if config == nil { + system.config = defaultConfig.Default { } + } else { + system.config = config + } system.propagate (func (entity *entity) bool { - if child, ok := system.child.element.(tomo.Configurable); ok { - child.SetConfig(config) + if child, ok := system.child.element.(ability.Configurable); ok { + child.HandleConfigChange() } return true }) @@ -66,10 +75,10 @@ func (system *system) focus (entity *entity) { previous := system.focused system.focused = entity if previous != nil { - previous.element.(tomo.Focusable).HandleFocusChange() + previous.element.(ability.Focusable).HandleFocusChange() } 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 { if found { // looking for the next element to select - child, ok := entity.element.(tomo.Focusable) + child, ok := entity.element.(ability.Focusable) if ok && child.Enabled() { // found it entity.Focus() @@ -106,7 +115,7 @@ func (system *system) focusPrevious () { return false } - child, ok := entity.element.(tomo.Focusable) + child, ok := entity.element.(ability.Focusable) if ok && child.Enabled() { behind = entity } return true }) @@ -153,7 +162,7 @@ func (system *system) afterEvent () { func (system *system) layout (entity *entity, force bool) { if entity == nil { return } if entity.layoutInvalid == true || force { - if element, ok := entity.element.(tomo.Layoutable); ok { + if element, ok := entity.element.(ability.Layoutable); ok { element.Layout() entity.layoutInvalid = false force = true @@ -176,7 +185,7 @@ func (system *system) draw () { for entity := range system.drawingInvalid { if entity.clippedBounds.Empty() { continue } - entity.element.Draw (canvas.Cut ( + entity.element.Draw (artist.Cut ( system.canvas, entity.clippedBounds)) finalBounds = finalBounds.Union(entity.clippedBounds) diff --git a/plugins/x/x/window.go b/plugins/x/x/window.go index 0ca9271..85b20a1 100644 --- a/plugins/x/x/window.go +++ b/plugins/x/x/window.go @@ -20,7 +20,7 @@ type menuWindow struct { *window } type window struct { system - backend *Backend + backend *backend xWindow *xwindow.Window xCanvas *xgraphics.Image @@ -40,7 +40,7 @@ type window struct { onClose func () } -func (backend *Backend) NewWindow ( +func (backend *backend) NewWindow ( bounds image.Rectangle, ) ( output tomo.MainWindow, @@ -53,7 +53,7 @@ func (backend *Backend) NewWindow ( return output, err } -func (backend *Backend) newWindow ( +func (backend *backend) newWindow ( bounds image.Rectangle, override bool, ) ( @@ -67,7 +67,6 @@ func (backend *Backend) newWindow ( window.system.initialize() window.system.pushFunc = window.pasteAndPush - window.theme.Case = tomo.C("tomo", "window") window.xWindow, err = xwindow.Generate(backend.connection) if err != nil { return } @@ -122,8 +121,8 @@ func (backend *Backend) newWindow ( xevent.SelectionRequestFun(window.handleSelectionRequest). Connect(backend.connection, window.xWindow.Id) - window.SetTheme(backend.theme) - window.SetConfig(backend.config) + window.setTheme(backend.theme) + window.setConfig(backend.config) window.metrics.bounds = bounds window.setMinimumSize(8, 8) @@ -418,7 +417,7 @@ func (window *window) pasteAndPush (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() bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds()) diff --git a/plugins/x/x/x.go b/plugins/x/x/x.go index ee77e00..a063129 100644 --- a/plugins/x/x/x.go +++ b/plugins/x/x/x.go @@ -8,8 +8,7 @@ import "github.com/jezek/xgbutil/xevent" import "github.com/jezek/xgbutil/keybind" import "github.com/jezek/xgbutil/mousebind" -// Backend is an instance of an X backend. -type Backend struct { +type backend struct { connection *xgbutil.XUtil doChannel chan(func ()) @@ -36,7 +35,7 @@ type Backend struct { // NewBackend instantiates an X backend. func NewBackend () (output tomo.Backend, err error) { - backend := &Backend { + backend := &backend { windows: map[xproto.Window] *window { }, doChannel: make(chan func (), 32), open: true, @@ -54,9 +53,7 @@ func NewBackend () (output tomo.Backend, err error) { return } -// Run runs the backend's event loop. This method will not exit until Stop() is -// called, or the backend experiences a fatal error. -func (backend *Backend) Run () (err error) { +func (backend *backend) Run () (err error) { backend.assert() pingBefore, 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() if !backend.open { return } backend.open = false @@ -93,31 +89,27 @@ func (backend *Backend) Stop () { backend.connection.Conn().Close() } -// Do executes the specified callback within the main thread as soon as -// possible. This function can be safely called from other threads. -func (backend *Backend) Do (callback func ()) { +func (backend *backend) Do (callback func ()) { backend.assert() 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.theme = theme 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.config = config 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") } }