Added support for relative window positioning

This commit is contained in:
Sasha Koshka 2023-04-10 02:36:28 -04:00
parent 8abb45e77a
commit 6db5901247
5 changed files with 59 additions and 38 deletions

View File

@ -1,5 +1,6 @@
package tomo package tomo
import "image"
import "errors" import "errors"
// Backend represents a connection to a display server, or something similar. // Backend represents a connection to a display server, or something similar.
@ -7,7 +8,7 @@ import "errors"
type Backend interface { type Backend interface {
// Run runs the backend's event loop. It must block until the backend // Run runs the backend's event loop. It must block until the backend
// experiences a fatal error, or Stop() is called. // experiences a fatal error, or Stop() is called.
Run () (err error) Run () error
// Stop stops the backend's event loop. // Stop stops the backend's event loop.
Stop () Stop ()
@ -16,10 +17,10 @@ type Backend interface {
// possible. This method must be safe to call from other threads. // possible. This method must be safe to call from other threads.
Do (callback func ()) Do (callback func ())
// NewWindow creates a new window with the specified width and height, // NewWindow creates a new window within the specified bounding
// and returns a struct representing it that fulfills the MainWindow // rectangle. The position on screen may be overridden by the backend or
// interface. // operating system.
NewWindow (width, height int) (window MainWindow, err error) NewWindow (bounds image.Rectangle) (MainWindow, error)
// SetTheme sets the theme of all open windows. // SetTheme sets the theme of all open windows.
SetTheme (Theme) SetTheme (Theme)

View File

@ -52,6 +52,12 @@ func (window *window) handleExpose (
window.pushRegion(region) window.pushRegion(region)
} }
func (window *window) updateBounds (x, y int16, width, height uint16) {
window.metrics.bounds =
image.Rect(0, 0, int(width), int(height)).
Add(image.Pt(int(x), int(y)))
}
func (window *window) handleConfigureNotify ( func (window *window) handleConfigureNotify (
connection *xgbutil.XUtil, connection *xgbutil.XUtil,
event xevent.ConfigureNotifyEvent, event xevent.ConfigureNotifyEvent,
@ -63,15 +69,18 @@ func (window *window) handleConfigureNotify (
newWidth := int(configureEvent.Width) newWidth := int(configureEvent.Width)
newHeight := int(configureEvent.Height) newHeight := int(configureEvent.Height)
sizeChanged := sizeChanged :=
window.metrics.width != newWidth || window.metrics.bounds.Dx() != newWidth ||
window.metrics.height != newHeight window.metrics.bounds.Dy() != newHeight
window.metrics.width = newWidth window.updateBounds (
window.metrics.height = newHeight configureEvent.X, configureEvent.Y,
configureEvent.Width, configureEvent.Height)
if sizeChanged { if sizeChanged {
configureEvent = window.compressConfigureNotify(configureEvent) configureEvent = window.compressConfigureNotify(configureEvent)
window.metrics.width = int(configureEvent.Width) window.updateBounds (
window.metrics.height = int(configureEvent.Height) configureEvent.X, configureEvent.Y,
configureEvent.Width, configureEvent.Height)
window.reallocateCanvas() window.reallocateCanvas()
window.resizeChildToFit() window.resizeChildToFit()

View File

@ -37,36 +37,37 @@ type window struct {
selectionClaim *selectionClaim selectionClaim *selectionClaim
metrics struct { metrics struct {
width int bounds image.Rectangle
height int
} }
} }
func (backend *Backend) NewWindow ( func (backend *Backend) NewWindow (
width, height int, bounds image.Rectangle,
) ( ) (
output tomo.MainWindow, output tomo.MainWindow,
err error, err error,
) { ) {
if backend == nil { panic("nil backend") } if backend == nil { panic("nil backend") }
window, err := backend.newWindow(width, height) window, err := backend.newWindow(bounds)
output = mainWindow { window } output = mainWindow { window }
return output, err return output, err
} }
func (backend *Backend) newWindow ( func (backend *Backend) newWindow (
width, height int, bounds image.Rectangle,
) ( ) (
output *window, output *window,
err error, err error,
) { ) {
// TODO: take position flag into account
window := &window { backend: backend } window := &window { backend: backend }
window.xWindow, err = xwindow.Generate(backend.connection) window.xWindow, err = xwindow.Generate(backend.connection)
if err != nil { return } if err != nil { return }
window.xWindow.Create ( window.xWindow.Create (
backend.connection.RootWin(), backend.connection.RootWin(),
0, 0, width, height, 0) bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(), 0)
err = window.xWindow.Listen ( err = window.xWindow.Listen (
xproto.EventMaskExposure, xproto.EventMaskExposure,
xproto.EventMaskStructureNotify, xproto.EventMaskStructureNotify,
@ -108,8 +109,7 @@ func (backend *Backend) newWindow (
window.SetTheme(backend.theme) window.SetTheme(backend.theme)
window.SetConfig(backend.config) window.SetConfig(backend.config)
window.metrics.width = width window.metrics.bounds = bounds
window.metrics.height = height
window.childMinimumSizeChangeCallback(8, 8) window.childMinimumSizeChangeCallback(8, 8)
window.reallocateCanvas() window.reallocateCanvas()
@ -249,8 +249,8 @@ func (window *window) SetIcon (sizes []image.Image) {
wmIcons) wmIcons)
} }
func (window *window) NewModal (width, height int) (tomo.Window, error) { func (window *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
modal, err := window.backend.newWindow(width, height) modal, err := window.backend.newWindow(bounds.Add(window.metrics.bounds.Min))
icccm.WmTransientForSet ( icccm.WmTransientForSet (
window.backend.connection, window.backend.connection,
modal.xWindow.Id, modal.xWindow.Id,
@ -265,8 +265,8 @@ func (window *window) NewModal (width, height int) (tomo.Window, error) {
return modal, err return modal, err
} }
func (window mainWindow) NewPanel (width, height int) (tomo.Window, error) { func (window mainWindow) NewPanel (bounds image.Rectangle) (tomo.Window, error) {
panel, err := window.backend.newWindow(width, height) panel, err := window.backend.newWindow(bounds.Add(window.metrics.bounds.Min))
if err != nil { return nil, err } if err != nil { return nil, err }
panel.setClientLeader(window.window) panel.setClientLeader(window.window)
window.setClientLeader(window.window) window.setClientLeader(window.window)
@ -379,7 +379,9 @@ func (window *window) SetConfig (config tomo.Config) {
} }
func (window *window) reallocateCanvas () { func (window *window) reallocateCanvas () {
window.canvas.Reallocate(window.metrics.width, window.metrics.height) window.canvas.Reallocate (
window.metrics.bounds.Dx(),
window.metrics.bounds.Dy())
previousWidth, previousHeight := 0, 0 previousWidth, previousHeight := 0, 0
if window.xCanvas != nil { if window.xCanvas != nil {
@ -387,8 +389,8 @@ func (window *window) reallocateCanvas () {
previousHeight = window.xCanvas.Bounds().Dy() previousHeight = window.xCanvas.Bounds().Dy()
} }
newWidth := window.metrics.width newWidth := window.metrics.bounds.Dx()
newHeight := window.metrics.height newHeight := window.metrics.bounds.Dy()
larger := newWidth > previousWidth || newHeight > previousHeight larger := newWidth > previousWidth || newHeight > previousHeight
smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2 smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2
@ -461,12 +463,12 @@ func (window *window) childMinimumSizeChangeCallback (width, height int) (resize
MinWidth: uint(width), MinWidth: uint(width),
MinHeight: uint(height), MinHeight: uint(height),
}) })
newWidth := window.metrics.width newWidth := window.metrics.bounds.Dx()
newHeight := window.metrics.height newHeight := window.metrics.bounds.Dy()
if newWidth < width { newWidth = width } if newWidth < width { newWidth = width }
if newHeight < height { newHeight = height } if newHeight < height { newHeight = height }
if newWidth != window.metrics.width || if newWidth != window.metrics.bounds.Dx() ||
newHeight != window.metrics.height { newHeight != window.metrics.bounds.Dy() {
window.xWindow.Resize(newWidth, newHeight) window.xWindow.Resize(newWidth, newHeight)
return true return true
} }

View File

@ -1,5 +1,7 @@
package tomo package tomo
import "image"
var backend Backend var backend Backend
// Run initializes a backend, calls the callback function, and begins the event // Run initializes a backend, calls the callback function, and begins the event
@ -30,9 +32,9 @@ func Do (callback func ()) {
// NewWindow creates a new window using the current backend, and returns it as a // NewWindow creates a new window using the current backend, and returns it as a
// MainWindow. If the window could not be created, an error is returned // MainWindow. If the window could not be created, an error is returned
// explaining why. // explaining why.
func NewWindow (width, height int) (window MainWindow, err error) { func NewWindow (bounds image.Rectangle) (window MainWindow, err error) {
assertBackend() assertBackend()
return backend.NewWindow(width, height) return backend.NewWindow(bounds)
} }
// SetTheme sets the theme of all open windows. // SetTheme sets the theme of all open windows.

View File

@ -3,6 +3,10 @@ package tomo
import "image" import "image"
import "git.tebibyte.media/sashakoshka/tomo/data" import "git.tebibyte.media/sashakoshka/tomo/data"
// TODO: add support for the icon window because imagine if we allowed
// applications to display live updating information readouts on their icons.
// that would be baller
// Window represents a top-level container generated by the currently running // Window represents a top-level container generated by the currently running
// backend. It can contain a single element. It is hidden by default, and must // backend. It can contain a single element. It is hidden by default, and must
// be explicitly shown with the Show() method. // be explicitly shown with the Show() method.
@ -30,8 +34,10 @@ type Window interface {
// NewModal creates a new modal dialog window. The resulting window will // NewModal creates a new modal dialog window. The resulting window will
// inherit this window's application name and icon, but these can be // inherit this window's application name and icon, but these can be
// manually overridden. // manually overridden. The modal will be placed relative to the parent
NewModal (width, height int) (window Window, err error) // window, but this position may be overridden by the backend or
// operating system.
NewModal (bounds image.Rectangle) (Window, error)
// Copy puts data into the clipboard. // Copy puts data into the clipboard.
Copy (data.Data) Copy (data.Data)
@ -61,8 +67,9 @@ type MainWindow interface {
Window Window
// NewPanel creates a panel window that is semantically tied to this // NewPanel creates a panel window that is semantically tied to this
// window. This is intended to be used for utility windows, tool bars, // window, positioned relative to it. This is intended to be used for
// torn-off menus, etc. The resulting window will inherit this window's // utility windows, tool bars, torn-off menus, etc. The resulting window
// application name and icon, but these can be manually overridden. // will inherit this window's application name and icon, but these can
NewPanel (width, height int) (window Window, err error) // be manually overridden.
NewPanel (bounds image.Rectangle) (Window, error)
} }