2023-01-09 06:03:19 +00:00
|
|
|
package x
|
|
|
|
|
|
|
|
import "image"
|
2023-03-28 00:44:39 +00:00
|
|
|
import "errors"
|
2023-01-09 06:03:19 +00:00
|
|
|
import "github.com/jezek/xgb/xproto"
|
|
|
|
import "github.com/jezek/xgbutil/ewmh"
|
|
|
|
import "github.com/jezek/xgbutil/icccm"
|
2023-03-28 00:44:39 +00:00
|
|
|
import "github.com/jezek/xgbutil/xprop"
|
2023-01-09 06:03:19 +00:00
|
|
|
import "github.com/jezek/xgbutil/xevent"
|
|
|
|
import "github.com/jezek/xgbutil/xwindow"
|
2023-04-10 22:07:49 +00:00
|
|
|
import "github.com/jezek/xgbutil/keybind"
|
|
|
|
import "github.com/jezek/xgbutil/mousebind"
|
2023-01-09 06:03:19 +00:00
|
|
|
import "github.com/jezek/xgbutil/xgraphics"
|
2023-03-31 03:19:04 +00:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo"
|
2023-03-28 00:44:39 +00:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/data"
|
2023-02-02 06:47:55 +00:00
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/input"
|
|
|
|
import "git.tebibyte.media/sashakoshka/tomo/canvas"
|
2023-03-07 17:48:29 +00:00
|
|
|
// import "runtime/debug"
|
2023-01-09 06:03:19 +00:00
|
|
|
|
2023-03-24 04:34:25 +00:00
|
|
|
type mainWindow struct { *window }
|
2023-04-10 20:22:47 +00:00
|
|
|
type menuWindow struct { *window }
|
2023-03-14 22:54:24 +00:00
|
|
|
type window struct {
|
2023-01-09 16:31:59 +00:00
|
|
|
backend *Backend
|
|
|
|
xWindow *xwindow.Window
|
|
|
|
xCanvas *xgraphics.Image
|
2023-02-02 06:47:55 +00:00
|
|
|
canvas canvas.BasicCanvas
|
2023-03-31 03:19:04 +00:00
|
|
|
child tomo.Element
|
2023-01-09 16:31:59 +00:00
|
|
|
onClose func ()
|
2023-01-09 06:03:19 +00:00
|
|
|
skipChildDrawCallback bool
|
|
|
|
|
2023-04-10 05:56:43 +00:00
|
|
|
title, application string
|
|
|
|
|
2023-03-24 05:31:40 +00:00
|
|
|
modalParent *window
|
|
|
|
hasModal bool
|
2023-04-10 20:47:03 +00:00
|
|
|
shy bool
|
2023-03-24 05:31:40 +00:00
|
|
|
|
2023-03-31 05:06:29 +00:00
|
|
|
theme tomo.Theme
|
|
|
|
config tomo.Config
|
2023-02-03 06:35:59 +00:00
|
|
|
|
2023-03-29 04:50:23 +00:00
|
|
|
selectionRequest *selectionRequest
|
2023-03-30 03:24:42 +00:00
|
|
|
selectionClaim *selectionClaim
|
2023-03-28 00:44:39 +00:00
|
|
|
|
2023-01-09 06:03:19 +00:00
|
|
|
metrics struct {
|
2023-04-10 06:36:28 +00:00
|
|
|
bounds image.Rectangle
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (backend *Backend) NewWindow (
|
2023-04-10 06:36:28 +00:00
|
|
|
bounds image.Rectangle,
|
2023-01-09 06:03:19 +00:00
|
|
|
) (
|
2023-03-31 03:19:04 +00:00
|
|
|
output tomo.MainWindow,
|
2023-01-09 06:03:19 +00:00
|
|
|
err error,
|
|
|
|
) {
|
|
|
|
if backend == nil { panic("nil backend") }
|
2023-04-10 20:22:47 +00:00
|
|
|
window, err := backend.newWindow(bounds, false)
|
2023-03-24 04:34:25 +00:00
|
|
|
output = mainWindow { window }
|
|
|
|
return output, err
|
|
|
|
}
|
2023-01-09 06:03:19 +00:00
|
|
|
|
2023-03-24 04:34:25 +00:00
|
|
|
func (backend *Backend) newWindow (
|
2023-04-10 20:22:47 +00:00
|
|
|
bounds image.Rectangle,
|
|
|
|
override bool,
|
2023-03-24 04:34:25 +00:00
|
|
|
) (
|
|
|
|
output *window,
|
|
|
|
err error,
|
|
|
|
) {
|
2023-04-10 06:58:52 +00:00
|
|
|
if bounds.Dx() == 0 { bounds.Max.X = bounds.Min.X + 8 }
|
|
|
|
if bounds.Dy() == 0 { bounds.Max.Y = bounds.Min.Y + 8 }
|
2023-04-10 06:36:28 +00:00
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
window := &window { backend: backend }
|
2023-01-09 06:03:19 +00:00
|
|
|
|
|
|
|
window.xWindow, err = xwindow.Generate(backend.connection)
|
|
|
|
if err != nil { return }
|
2023-04-10 20:22:47 +00:00
|
|
|
|
|
|
|
if override {
|
|
|
|
err = window.xWindow.CreateChecked (
|
|
|
|
backend.connection.RootWin(),
|
|
|
|
bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(),
|
|
|
|
xproto.CwOverrideRedirect, 1)
|
|
|
|
} else {
|
|
|
|
err = window.xWindow.CreateChecked (
|
|
|
|
backend.connection.RootWin(),
|
|
|
|
bounds.Min.X, bounds.Min.Y, bounds.Dx(), bounds.Dy(), 0)
|
|
|
|
}
|
2023-04-10 06:58:52 +00:00
|
|
|
if err != nil { return }
|
2023-04-10 20:22:47 +00:00
|
|
|
|
2023-01-09 06:03:19 +00:00
|
|
|
err = window.xWindow.Listen (
|
2023-01-26 07:08:07 +00:00
|
|
|
xproto.EventMaskExposure,
|
2023-01-09 06:03:19 +00:00
|
|
|
xproto.EventMaskStructureNotify,
|
2023-03-30 03:24:42 +00:00
|
|
|
xproto.EventMaskPropertyChange,
|
2023-01-09 06:03:19 +00:00
|
|
|
xproto.EventMaskPointerMotion,
|
|
|
|
xproto.EventMaskKeyPress,
|
|
|
|
xproto.EventMaskKeyRelease,
|
|
|
|
xproto.EventMaskButtonPress,
|
|
|
|
xproto.EventMaskButtonRelease)
|
|
|
|
if err != nil { return }
|
|
|
|
|
|
|
|
window.xWindow.WMGracefulClose (func (xWindow *xwindow.Window) {
|
|
|
|
window.Close()
|
|
|
|
})
|
2023-01-26 07:08:07 +00:00
|
|
|
|
|
|
|
xevent.ExposeFun(window.handleExpose).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
2023-01-09 06:03:19 +00:00
|
|
|
xevent.ConfigureNotifyFun(window.handleConfigureNotify).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
|
|
|
xevent.KeyPressFun(window.handleKeyPress).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
|
|
|
xevent.KeyReleaseFun(window.handleKeyRelease).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
|
|
|
xevent.ButtonPressFun(window.handleButtonPress).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
|
|
|
xevent.ButtonReleaseFun(window.handleButtonRelease).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
|
|
|
xevent.MotionNotifyFun(window.handleMotionNotify).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
2023-03-28 00:44:39 +00:00
|
|
|
xevent.SelectionNotifyFun(window.handleSelectionNotify).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
2023-03-30 03:24:42 +00:00
|
|
|
xevent.PropertyNotifyFun(window.handlePropertyNotify).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
|
|
|
xevent.SelectionClearFun(window.handleSelectionClear).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
2023-03-30 17:10:58 +00:00
|
|
|
xevent.SelectionRequestFun(window.handleSelectionRequest).
|
|
|
|
Connect(backend.connection, window.xWindow.Id)
|
2023-02-03 06:35:59 +00:00
|
|
|
|
|
|
|
window.SetTheme(backend.theme)
|
|
|
|
window.SetConfig(backend.config)
|
2023-01-09 06:03:19 +00:00
|
|
|
|
2023-04-10 06:36:28 +00:00
|
|
|
window.metrics.bounds = bounds
|
2023-01-09 06:03:19 +00:00
|
|
|
window.childMinimumSizeChangeCallback(8, 8)
|
|
|
|
|
|
|
|
window.reallocateCanvas()
|
|
|
|
|
|
|
|
backend.windows[window.xWindow.Id] = window
|
2023-03-24 04:34:25 +00:00
|
|
|
|
2023-01-09 06:03:19 +00:00
|
|
|
output = window
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-31 03:19:04 +00:00
|
|
|
func (window *window) NotifyMinimumSizeChange (child tomo.Element) {
|
2023-03-14 22:54:24 +00:00
|
|
|
window.childMinimumSizeChangeCallback(child.MinimumSize())
|
|
|
|
}
|
|
|
|
|
2023-03-31 07:25:46 +00:00
|
|
|
func (window *window) Window () tomo.Window {
|
|
|
|
return window
|
|
|
|
}
|
|
|
|
|
2023-03-16 03:47:13 +00:00
|
|
|
func (window *window) RequestFocus (
|
2023-03-31 03:19:04 +00:00
|
|
|
child tomo.Focusable,
|
2023-03-16 03:47:13 +00:00
|
|
|
) (
|
|
|
|
granted bool,
|
|
|
|
) {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2023-03-31 03:19:04 +00:00
|
|
|
func (window *window) RequestFocusNext (child tomo.Focusable) {
|
|
|
|
if child, ok := window.child.(tomo.Focusable); ok {
|
2023-03-16 03:47:13 +00:00
|
|
|
if !child.HandleFocus(input.KeynavDirectionForward) {
|
|
|
|
child.HandleUnfocus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 03:19:04 +00:00
|
|
|
func (window *window) RequestFocusPrevious (child tomo.Focusable) {
|
|
|
|
if child, ok := window.child.(tomo.Focusable); ok {
|
2023-03-16 03:47:13 +00:00
|
|
|
if !child.HandleFocus(input.KeynavDirectionBackward) {
|
|
|
|
child.HandleUnfocus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 03:19:04 +00:00
|
|
|
func (window *window) Adopt (child tomo.Element) {
|
2023-01-19 20:02:56 +00:00
|
|
|
// disown previous child
|
2023-01-09 06:03:19 +00:00
|
|
|
if window.child != nil {
|
2023-03-14 22:54:24 +00:00
|
|
|
window.child.SetParent(nil)
|
|
|
|
window.child.DrawTo(nil, image.Rectangle { }, nil)
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
2023-03-16 18:42:18 +00:00
|
|
|
|
2023-01-09 06:03:19 +00:00
|
|
|
if child != nil {
|
2023-03-16 18:42:18 +00:00
|
|
|
// adopt new child
|
|
|
|
window.child = child
|
|
|
|
child.SetParent(window)
|
2023-03-31 03:19:04 +00:00
|
|
|
if newChild, ok := child.(tomo.Themeable); ok {
|
2023-03-16 18:42:18 +00:00
|
|
|
newChild.SetTheme(window.theme)
|
|
|
|
}
|
2023-03-31 03:19:04 +00:00
|
|
|
if newChild, ok := child.(tomo.Configurable); ok {
|
2023-03-16 18:42:18 +00:00
|
|
|
newChild.SetConfig(window.config)
|
|
|
|
}
|
|
|
|
if child != nil {
|
|
|
|
if !window.childMinimumSizeChangeCallback(child.MinimumSize()) {
|
|
|
|
window.resizeChildToFit()
|
|
|
|
window.redrawChildEntirely()
|
|
|
|
}
|
2023-02-09 06:30:14 +00:00
|
|
|
}
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 03:19:04 +00:00
|
|
|
func (window *window) Child () (child tomo.Element) {
|
2023-01-09 06:03:19 +00:00
|
|
|
child = window.child
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) SetTitle (title string) {
|
2023-04-10 05:56:43 +00:00
|
|
|
window.title = title
|
2023-01-09 06:03:19 +00:00
|
|
|
ewmh.WmNameSet (
|
|
|
|
window.backend.connection,
|
2023-04-09 05:57:56 +00:00
|
|
|
window.xWindow.Id,
|
|
|
|
title)
|
|
|
|
icccm.WmNameSet (
|
|
|
|
window.backend.connection,
|
|
|
|
window.xWindow.Id,
|
|
|
|
title)
|
|
|
|
icccm.WmIconNameSet (
|
|
|
|
window.backend.connection,
|
2023-01-09 06:03:19 +00:00
|
|
|
window.xWindow.Id,
|
|
|
|
title)
|
|
|
|
}
|
|
|
|
|
2023-04-10 05:56:43 +00:00
|
|
|
func (window *window) SetApplicationName (name string) {
|
|
|
|
window.application = name
|
|
|
|
icccm.WmClassSet (
|
|
|
|
window.backend.connection,
|
|
|
|
window.xWindow.Id,
|
|
|
|
&icccm.WmClass {
|
|
|
|
Instance: name,
|
|
|
|
Class: name,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) SetIcon (sizes []image.Image) {
|
2023-01-09 06:03:19 +00:00
|
|
|
wmIcons := []ewmh.WmIcon { }
|
|
|
|
|
|
|
|
for _, icon := range sizes {
|
|
|
|
width := icon.Bounds().Max.X
|
|
|
|
height := icon.Bounds().Max.Y
|
|
|
|
wmIcon := ewmh.WmIcon {
|
|
|
|
Width: uint(width),
|
|
|
|
Height: uint(height),
|
|
|
|
Data: make ([]uint, width * height),
|
|
|
|
}
|
|
|
|
|
|
|
|
// manually convert image data beacuse of course we have to do
|
|
|
|
// this
|
|
|
|
index := 0
|
|
|
|
for y := 0; y < height; y ++ {
|
|
|
|
for x := 0; x < width; x ++ {
|
|
|
|
r, g, b, a := icon.At(x, y).RGBA()
|
|
|
|
r >>= 8
|
|
|
|
g >>= 8
|
|
|
|
b >>= 8
|
|
|
|
a >>= 8
|
|
|
|
wmIcon.Data[index] =
|
|
|
|
(uint(a) << 24) |
|
|
|
|
(uint(r) << 16) |
|
|
|
|
(uint(g) << 8) |
|
|
|
|
(uint(b) << 0)
|
|
|
|
index ++
|
|
|
|
}}
|
|
|
|
|
|
|
|
wmIcons = append(wmIcons, wmIcon)
|
|
|
|
}
|
|
|
|
|
|
|
|
ewmh.WmIconSet (
|
|
|
|
window.backend.connection,
|
|
|
|
window.xWindow.Id,
|
|
|
|
wmIcons)
|
|
|
|
}
|
|
|
|
|
2023-04-10 06:36:28 +00:00
|
|
|
func (window *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
|
2023-04-10 20:22:47 +00:00
|
|
|
modal, err := window.backend.newWindow (
|
|
|
|
bounds.Add(window.metrics.bounds.Min), false)
|
2023-03-24 04:34:25 +00:00
|
|
|
icccm.WmTransientForSet (
|
|
|
|
window.backend.connection,
|
|
|
|
modal.xWindow.Id,
|
|
|
|
window.xWindow.Id)
|
|
|
|
ewmh.WmStateSet (
|
|
|
|
window.backend.connection,
|
|
|
|
modal.xWindow.Id,
|
|
|
|
[]string { "_NET_WM_STATE_MODAL" })
|
2023-03-24 05:31:40 +00:00
|
|
|
modal.modalParent = window
|
|
|
|
window.hasModal = true
|
2023-04-10 05:56:43 +00:00
|
|
|
modal.inheritProperties(window)
|
2023-03-24 04:34:25 +00:00
|
|
|
return modal, err
|
|
|
|
}
|
|
|
|
|
2023-04-10 20:22:47 +00:00
|
|
|
func (window *window) NewMenu (bounds image.Rectangle) (tomo.MenuWindow, error) {
|
|
|
|
menu, err := window.backend.newWindow (
|
|
|
|
bounds.Add(window.metrics.bounds.Min), true)
|
2023-04-10 22:07:49 +00:00
|
|
|
menu.shy = true
|
2023-04-10 20:22:47 +00:00
|
|
|
icccm.WmTransientForSet (
|
|
|
|
window.backend.connection,
|
|
|
|
menu.xWindow.Id,
|
|
|
|
window.xWindow.Id)
|
2023-04-10 20:47:03 +00:00
|
|
|
menu.setType("POPUP_MENU")
|
2023-04-10 20:22:47 +00:00
|
|
|
menu.inheritProperties(window)
|
|
|
|
return menuWindow { window: menu }, err
|
|
|
|
}
|
|
|
|
|
2023-04-10 06:36:28 +00:00
|
|
|
func (window mainWindow) NewPanel (bounds image.Rectangle) (tomo.Window, error) {
|
2023-04-10 20:22:47 +00:00
|
|
|
panel, err := window.backend.newWindow (
|
|
|
|
bounds.Add(window.metrics.bounds.Min), false)
|
2023-03-24 21:38:21 +00:00
|
|
|
if err != nil { return nil, err }
|
|
|
|
panel.setClientLeader(window.window)
|
2023-03-25 02:49:53 +00:00
|
|
|
window.setClientLeader(window.window)
|
|
|
|
icccm.WmTransientForSet (
|
|
|
|
window.backend.connection,
|
|
|
|
panel.xWindow.Id,
|
|
|
|
window.xWindow.Id)
|
2023-03-24 21:38:21 +00:00
|
|
|
panel.setType("UTILITY")
|
2023-04-10 05:56:43 +00:00
|
|
|
panel.inheritProperties(window.window)
|
2023-03-24 21:38:21 +00:00
|
|
|
return panel, err
|
|
|
|
}
|
|
|
|
|
2023-04-10 20:22:47 +00:00
|
|
|
func (window menuWindow) Pin () {
|
2023-04-10 20:47:03 +00:00
|
|
|
// TODO take off override redirect
|
|
|
|
// TODO turn off shy
|
|
|
|
// TODO set window type to MENU
|
2023-04-10 22:07:49 +00:00
|
|
|
// TODO iungrab keyboard and mouse
|
|
|
|
}
|
|
|
|
|
|
|
|
func (window *window) grabInput () {
|
|
|
|
keybind.GrabKeyboard(window.backend.connection, window.xWindow.Id)
|
|
|
|
mousebind.GrabPointer (
|
|
|
|
window.backend.connection,
|
|
|
|
window.xWindow.Id,
|
|
|
|
window.backend.connection.RootWin(), 0)
|
|
|
|
window.xWindow.Focus()
|
|
|
|
}
|
|
|
|
|
|
|
|
func (window *window) ungrabInput () {
|
|
|
|
keybind.UngrabKeyboard(window.backend.connection)
|
|
|
|
mousebind.UngrabPointer(window.backend.connection)
|
2023-04-10 20:22:47 +00:00
|
|
|
}
|
|
|
|
|
2023-04-10 05:56:43 +00:00
|
|
|
func (window *window) inheritProperties (parent *window) {
|
|
|
|
window.SetApplicationName(parent.application)
|
|
|
|
}
|
|
|
|
|
2023-03-24 21:38:21 +00:00
|
|
|
func (window *window) setType (ty string) error {
|
|
|
|
return ewmh.WmWindowTypeSet (
|
|
|
|
window.backend.connection,
|
|
|
|
window.xWindow.Id,
|
|
|
|
[]string { "_NET_WM_WINDOW_TYPE_" + ty })
|
|
|
|
}
|
|
|
|
|
|
|
|
func (window *window) setClientLeader (leader *window) error {
|
|
|
|
hints, _ := icccm.WmHintsGet(window.backend.connection, window.xWindow.Id)
|
|
|
|
if hints == nil {
|
|
|
|
hints = &icccm.Hints { }
|
|
|
|
}
|
|
|
|
hints.Flags |= icccm.HintWindowGroup
|
|
|
|
hints.WindowGroup = leader.xWindow.Id
|
|
|
|
return icccm.WmHintsSet (
|
2023-03-24 04:34:25 +00:00
|
|
|
window.backend.connection,
|
2023-03-24 21:38:21 +00:00
|
|
|
window.xWindow.Id,
|
2023-03-24 04:34:25 +00:00
|
|
|
hints)
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) Show () {
|
2023-01-09 06:03:19 +00:00
|
|
|
if window.child == nil {
|
|
|
|
window.xCanvas.For (func (x, y int) xgraphics.BGRA {
|
|
|
|
return xgraphics.BGRA { }
|
|
|
|
})
|
|
|
|
|
|
|
|
window.pushRegion(window.xCanvas.Bounds())
|
|
|
|
}
|
|
|
|
|
|
|
|
window.xWindow.Map()
|
2023-04-10 22:07:49 +00:00
|
|
|
if window.shy { window.grabInput() }
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) Hide () {
|
2023-01-09 06:03:19 +00:00
|
|
|
window.xWindow.Unmap()
|
2023-04-10 22:07:49 +00:00
|
|
|
if window.shy { window.ungrabInput() }
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
|
2023-03-28 00:44:39 +00:00
|
|
|
func (window *window) Copy (data data.Data) {
|
2023-03-30 22:05:29 +00:00
|
|
|
selectionAtom, err := xprop.Atm(window.backend.connection, clipboardName)
|
2023-03-30 17:10:58 +00:00
|
|
|
if err != nil { return }
|
|
|
|
window.selectionClaim = window.claimSelection(selectionAtom, data)
|
2023-03-28 00:44:39 +00:00
|
|
|
}
|
|
|
|
|
2023-03-29 04:50:23 +00:00
|
|
|
func (window *window) Paste (callback func (data.Data, error), accept ...data.Mime) {
|
2023-03-28 00:44:39 +00:00
|
|
|
// Follow:
|
|
|
|
// https://tronche.com/gui/x/icccm/sec-2.html#s-2.4
|
2023-03-29 04:50:23 +00:00
|
|
|
die := func (err error) { callback(nil, err) }
|
2023-03-28 00:44:39 +00:00
|
|
|
if window.selectionRequest != nil {
|
|
|
|
// TODO: add the request to a queue and take care of it when the
|
|
|
|
// current selection has completed
|
|
|
|
die(errors.New("there is already a selection request"))
|
2023-03-29 04:50:23 +00:00
|
|
|
return
|
2023-03-28 00:44:39 +00:00
|
|
|
}
|
|
|
|
|
2023-03-30 22:05:29 +00:00
|
|
|
propertyName := "TOMO_SELECTION"
|
|
|
|
selectionAtom, err := xprop.Atm(window.backend.connection, clipboardName)
|
2023-03-28 00:44:39 +00:00
|
|
|
if err != nil { die(err); return }
|
|
|
|
propertyAtom, err := xprop.Atm(window.backend.connection, propertyName)
|
|
|
|
if err != nil { die(err); return }
|
|
|
|
|
2023-03-29 04:50:23 +00:00
|
|
|
window.selectionRequest = window.newSelectionRequest (
|
|
|
|
selectionAtom, propertyAtom, callback, accept...)
|
2023-03-29 06:55:12 +00:00
|
|
|
if !window.selectionRequest.open() { window.selectionRequest = nil }
|
2023-03-28 00:44:39 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) Close () {
|
2023-01-09 06:03:19 +00:00
|
|
|
if window.onClose != nil { window.onClose() }
|
2023-03-24 05:31:40 +00:00
|
|
|
if window.modalParent != nil {
|
|
|
|
// we are a modal dialog, so unlock the parent
|
|
|
|
window.modalParent.hasModal = false
|
|
|
|
}
|
2023-03-16 18:42:18 +00:00
|
|
|
window.Hide()
|
|
|
|
window.Adopt(nil)
|
2023-02-16 22:22:33 +00:00
|
|
|
delete(window.backend.windows, window.xWindow.Id)
|
2023-01-09 06:03:19 +00:00
|
|
|
window.xWindow.Destroy()
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) OnClose (callback func ()) {
|
2023-01-09 06:03:19 +00:00
|
|
|
window.onClose = callback
|
|
|
|
}
|
|
|
|
|
2023-03-31 05:06:29 +00:00
|
|
|
func (window *window) SetTheme (theme tomo.Theme) {
|
2023-02-03 06:35:59 +00:00
|
|
|
window.theme = theme
|
2023-03-31 03:19:04 +00:00
|
|
|
if child, ok := window.child.(tomo.Themeable); ok {
|
2023-02-03 06:35:59 +00:00
|
|
|
child.SetTheme(theme)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-31 05:06:29 +00:00
|
|
|
func (window *window) SetConfig (config tomo.Config) {
|
2023-02-03 06:35:59 +00:00
|
|
|
window.config = config
|
2023-03-31 03:19:04 +00:00
|
|
|
if child, ok := window.child.(tomo.Configurable); ok {
|
2023-02-03 06:35:59 +00:00
|
|
|
child.SetConfig(config)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) reallocateCanvas () {
|
2023-04-10 06:36:28 +00:00
|
|
|
window.canvas.Reallocate (
|
|
|
|
window.metrics.bounds.Dx(),
|
|
|
|
window.metrics.bounds.Dy())
|
2023-02-01 06:47:08 +00:00
|
|
|
|
|
|
|
previousWidth, previousHeight := 0, 0
|
2023-01-09 06:03:19 +00:00
|
|
|
if window.xCanvas != nil {
|
2023-02-01 06:47:08 +00:00
|
|
|
previousWidth = window.xCanvas.Bounds().Dx()
|
|
|
|
previousHeight = window.xCanvas.Bounds().Dy()
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
2023-02-01 06:47:08 +00:00
|
|
|
|
2023-04-10 06:36:28 +00:00
|
|
|
newWidth := window.metrics.bounds.Dx()
|
|
|
|
newHeight := window.metrics.bounds.Dy()
|
2023-02-01 06:47:08 +00:00
|
|
|
larger := newWidth > previousWidth || newHeight > previousHeight
|
|
|
|
smaller := newWidth < previousWidth / 2 || newHeight < previousHeight / 2
|
2023-03-08 00:13:08 +00:00
|
|
|
|
|
|
|
allocStep := 128
|
|
|
|
|
2023-02-01 06:47:08 +00:00
|
|
|
if larger || smaller {
|
|
|
|
if window.xCanvas != nil {
|
|
|
|
window.xCanvas.Destroy()
|
|
|
|
}
|
|
|
|
window.xCanvas = xgraphics.New (
|
|
|
|
window.backend.connection,
|
|
|
|
image.Rect (
|
|
|
|
0, 0,
|
2023-03-08 00:13:08 +00:00
|
|
|
(newWidth / allocStep + 1) * allocStep,
|
|
|
|
(newHeight / allocStep + 1) * allocStep))
|
2023-02-01 06:47:08 +00:00
|
|
|
window.xCanvas.CreatePixmap()
|
|
|
|
}
|
|
|
|
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) redrawChildEntirely () {
|
|
|
|
window.paste(window.canvas.Bounds())
|
|
|
|
window.pushRegion(window.canvas.Bounds())
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) resizeChildToFit () {
|
2023-01-09 06:03:19 +00:00
|
|
|
window.skipChildDrawCallback = true
|
2023-03-14 22:54:24 +00:00
|
|
|
window.child.DrawTo (
|
|
|
|
window.canvas,
|
|
|
|
window.canvas.Bounds(),
|
|
|
|
window.childDrawCallback)
|
2023-01-09 06:03:19 +00:00
|
|
|
window.skipChildDrawCallback = false
|
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) childDrawCallback (region image.Rectangle) {
|
2023-01-09 06:03:19 +00:00
|
|
|
if window.skipChildDrawCallback { return }
|
2023-03-14 22:54:24 +00:00
|
|
|
window.paste(region)
|
|
|
|
window.pushRegion(region)
|
2023-01-26 23:01:39 +00:00
|
|
|
}
|
2023-01-09 06:03:19 +00:00
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) paste (region image.Rectangle) {
|
|
|
|
canvas := canvas.Cut(window.canvas, region)
|
2023-01-26 23:01:39 +00:00
|
|
|
data, stride := canvas.Buffer()
|
|
|
|
bounds := canvas.Bounds().Intersect(window.xCanvas.Bounds())
|
2023-03-08 00:13:08 +00:00
|
|
|
|
|
|
|
dstStride := window.xCanvas.Stride
|
|
|
|
dstData := window.xCanvas.Pix
|
|
|
|
|
2023-03-07 17:48:29 +00:00
|
|
|
// debug.PrintStack()
|
2023-01-09 06:03:19 +00:00
|
|
|
for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
|
2023-03-08 00:13:08 +00:00
|
|
|
srcYComponent := y * stride
|
|
|
|
dstYComponent := y * dstStride
|
|
|
|
for x := bounds.Min.X; x < bounds.Max.X; x ++ {
|
|
|
|
rgba := data[srcYComponent + x]
|
|
|
|
index := dstYComponent + x * 4
|
|
|
|
dstData[index + 0] = rgba.B
|
|
|
|
dstData[index + 1] = rgba.G
|
|
|
|
dstData[index + 2] = rgba.R
|
|
|
|
dstData[index + 3] = rgba.A
|
|
|
|
}
|
|
|
|
}
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) childMinimumSizeChangeCallback (width, height int) (resized bool) {
|
2023-01-09 06:03:19 +00:00
|
|
|
icccm.WmNormalHintsSet (
|
|
|
|
window.backend.connection,
|
|
|
|
window.xWindow.Id,
|
|
|
|
&icccm.NormalHints {
|
|
|
|
Flags: icccm.SizeHintPMinSize,
|
|
|
|
MinWidth: uint(width),
|
|
|
|
MinHeight: uint(height),
|
|
|
|
})
|
2023-04-10 06:36:28 +00:00
|
|
|
newWidth := window.metrics.bounds.Dx()
|
|
|
|
newHeight := window.metrics.bounds.Dy()
|
2023-01-09 06:03:19 +00:00
|
|
|
if newWidth < width { newWidth = width }
|
|
|
|
if newHeight < height { newHeight = height }
|
2023-04-10 06:36:28 +00:00
|
|
|
if newWidth != window.metrics.bounds.Dx() ||
|
|
|
|
newHeight != window.metrics.bounds.Dy() {
|
2023-01-09 06:03:19 +00:00
|
|
|
window.xWindow.Resize(newWidth, newHeight)
|
2023-02-09 06:30:14 +00:00
|
|
|
return true
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
2023-02-09 06:30:14 +00:00
|
|
|
|
|
|
|
return false
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
|
2023-03-14 22:54:24 +00:00
|
|
|
func (window *window) pushRegion (region image.Rectangle) {
|
2023-01-09 06:03:19 +00:00
|
|
|
if window.xCanvas == nil { panic("whoopsie!!!!!!!!!!!!!!") }
|
|
|
|
image, ok := window.xCanvas.SubImage(region).(*xgraphics.Image)
|
|
|
|
if ok {
|
|
|
|
image.XDraw()
|
2023-01-26 07:08:07 +00:00
|
|
|
image.XExpPaint (
|
|
|
|
window.xWindow.Id,
|
|
|
|
image.Bounds().Min.X,
|
|
|
|
image.Bounds().Min.Y)
|
2023-01-09 06:03:19 +00:00
|
|
|
}
|
|
|
|
}
|