25 Commits

Author SHA1 Message Date
caa261665f Remove obsolete TODO 2024-06-15 23:35:44 -04:00
e21b57a915 X backend properly converts image data 2024-06-15 23:33:59 -04:00
727a801243 Attempt to fix strange issue with overflowing 2024-06-14 02:30:59 -04:00
76701d4383 Fix style application part 2 2024-06-12 02:12:24 -04:00
6619987b5a Fixed style application 2024-06-12 00:39:00 -04:00
02de78c997 Window is resizable by default (lol) 2024-06-12 00:29:07 -04:00
95b1d033a9 SetResizable has been implemented 2024-06-11 23:58:19 -04:00
1951b6e408 Partially implement new stuff for X 2024-06-11 23:45:49 -04:00
c7f09c7894 Add recommended sizes and all that jazz 2024-06-11 22:45:40 -04:00
80f60b42de I lied 2024-06-11 18:35:40 -04:00
995e6fd624 Add theme setting nonsense 2024-06-11 18:12:47 -04:00
26b69d3e21 Update Tomo API 2024-06-11 17:18:30 -04:00
52a0136e60 Fixed Window.NewChild returning the parent (oops) 2024-06-07 01:47:24 -04:00
5657f85c83 Update Window.SetIcon 2024-06-07 01:12:04 -04:00
a9ac9f6600 Change scrolling speed from 16 to 32 2024-06-07 01:07:48 -04:00
6eff6887e7 Unify window and mainWindow 2024-06-07 01:07:28 -04:00
adf0ef3a89 Update Tomo API 2024-06-07 01:07:15 -04:00
07bcd119d7 X backend uses Close method instead of Destroy to free canvas 2024-06-03 20:54:45 -04:00
c51ce65c88 System no longer requires a NewCanvas method 2024-06-03 20:44:58 -04:00
0d93d73c32 Backend returns CanvasCloser 2024-06-03 20:43:05 -04:00
006921d690 Store Role in Box 2024-06-03 20:42:54 -04:00
f2bcc217a4 Update Tomo API 2024-06-03 20:42:08 -04:00
a4e067cacb HandleKeyUp causes a... key up event. As it should. 2024-06-03 02:20:37 -04:00
5a2b4cc2f5 Boxes are able to check if they are focused 2024-06-03 02:18:30 -04:00
250c3076fb Fixed another segfault. Do not write code at 4 am. 2024-06-03 02:11:47 -04:00
13 changed files with 306 additions and 89 deletions

5
go.mod
View File

@@ -3,11 +3,8 @@ module git.tebibyte.media/tomo/backend
go 1.20 go 1.20
require ( require (
git.tebibyte.media/tomo/tomo v0.34.0 git.tebibyte.media/tomo/tomo v0.38.0
git.tebibyte.media/tomo/typeset v0.7.1 git.tebibyte.media/tomo/typeset v0.7.1
)
require (
git.tebibyte.media/tomo/xgbkb v1.0.1 git.tebibyte.media/tomo/xgbkb v1.0.1
github.com/jezek/xgb v1.1.1 github.com/jezek/xgb v1.1.1
github.com/jezek/xgbutil v0.0.0-20231116234834-47f30c120111 github.com/jezek/xgbutil v0.0.0-20231116234834-47f30c120111

4
go.sum
View File

@@ -1,6 +1,6 @@
git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q= git.tebibyte.media/sashakoshka/xgbkb v1.0.0/go.mod h1:pNcE6TRO93vHd6q42SdwLSTTj25L0Yzggz7yLe0JV6Q=
git.tebibyte.media/tomo/tomo v0.34.0 h1:r5yJPks9rtzdDI2RyAUdqa1qb6BebG0QFe2cTmcFi+0= git.tebibyte.media/tomo/tomo v0.38.0 h1:K5TP67RxnszudeNfmGZiU5cFTRjFueXiI3NCsgw+05U=
git.tebibyte.media/tomo/tomo v0.34.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps= git.tebibyte.media/tomo/tomo v0.38.0/go.mod h1:C9EzepS9wjkTJjnZaPBh22YvVPyA4hbBAJVU20Rdmps=
git.tebibyte.media/tomo/typeset v0.7.1 h1:aZrsHwCG5ZB4f5CruRFsxLv5ezJUCFUFsQJJso2sXQ8= git.tebibyte.media/tomo/typeset v0.7.1 h1:aZrsHwCG5ZB4f5CruRFsxLv5ezJUCFUFsQJJso2sXQ8=
git.tebibyte.media/tomo/typeset v0.7.1/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g= git.tebibyte.media/tomo/typeset v0.7.1/go.mod h1:PwDpSdBF3l/EzoIsa2ME7QffVVajnTHZN6l3MHEGe1g=
git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE= git.tebibyte.media/tomo/xgbkb v1.0.1 h1:b3HDUopjdQp1MZrb5Vpil4bOtk3NnNXtfQW27Blw2kE=

View File

@@ -19,6 +19,11 @@ type box struct {
parent parent parent parent
outer anyBox outer anyBox
role tomo.Role
styleCookie event.Cookie
lastStyleNonce int
lastIconsNonce int
bounds image.Rectangle bounds image.Rectangle
minSize image.Point minSize image.Point
userMinSize image.Point userMinSize image.Point
@@ -42,19 +47,21 @@ type box struct {
drawer canvas.Drawer drawer canvas.Drawer
on struct { on struct {
focusEnter event.FuncBroadcaster focusEnter event.FuncBroadcaster
focusLeave event.FuncBroadcaster focusLeave event.FuncBroadcaster
dndEnter event.FuncBroadcaster dndEnter event.FuncBroadcaster
dndLeave event.FuncBroadcaster dndLeave event.FuncBroadcaster
dndDrop event.Broadcaster[func (data.Data)] dndDrop event.Broadcaster[func (data.Data)]
mouseEnter event.FuncBroadcaster mouseEnter event.FuncBroadcaster
mouseLeave event.FuncBroadcaster mouseLeave event.FuncBroadcaster
mouseMove event.FuncBroadcaster mouseMove event.FuncBroadcaster
mouseDown event.Broadcaster[func (input.Button)] mouseDown event.Broadcaster[func (input.Button)]
mouseUp event.Broadcaster[func (input.Button)] mouseUp event.Broadcaster[func (input.Button)]
scroll event.Broadcaster[func (float64, float64)] scroll event.Broadcaster[func (float64, float64)]
keyDown event.Broadcaster[func (input.Key, bool)] keyDown event.Broadcaster[func (input.Key, bool)]
keyUp event.Broadcaster[func (input.Key, bool)] keyUp event.Broadcaster[func (input.Key, bool)]
styleChange event.FuncBroadcaster
iconsChange event.FuncBroadcaster
} }
} }
@@ -106,6 +113,10 @@ func (this *box) MinimumSize () image.Point {
return this.minSize return this.minSize
} }
func (this *box) Role () tomo.Role {
return this.role
}
func (this *box) borderSum () tomo.Inset { func (this *box) borderSum () tomo.Inset {
sum := tomo.Inset { } sum := tomo.Inset { }
for _, border := range this.border { for _, border := range this.border {
@@ -117,6 +128,15 @@ func (this *box) borderSum () tomo.Inset {
return sum return sum
} }
func (this *box) borderAndPaddingSum () tomo.Inset {
sum := this.borderSum()
sum[0] += this.padding[0]
sum[1] += this.padding[1]
sum[2] += this.padding[2]
sum[3] += this.padding[3]
return sum
}
func (this *box) SetBounds (bounds image.Rectangle) { func (this *box) SetBounds (bounds image.Rectangle) {
if this.bounds == bounds { return } if this.bounds == bounds { return }
this.bounds = bounds this.bounds = bounds
@@ -182,6 +202,10 @@ func (this *box) SetPadding (padding tomo.Inset) {
this.invalidateMinimum() this.invalidateMinimum()
} }
func (this *box) SetRole (role tomo.Role) {
this.role = role
}
func (this *box) SetDNDData (dat data.Data) { func (this *box) SetDNDData (dat data.Data) {
this.dndData = dat this.dndData = dat
} }
@@ -191,7 +215,7 @@ func (this *box) SetDNDAccept (types ...data.Mime) {
} }
func (this *box) SetFocused (focused bool) { func (this *box) SetFocused (focused bool) {
hierarchy := this.parent.getHierarchy() hierarchy := this.getHierarchy()
if hierarchy == nil { if hierarchy == nil {
focusedCopy := focused focusedCopy := focused
this.focusQueued = &focusedCopy this.focusQueued = &focusedCopy
@@ -217,7 +241,7 @@ func (this *box) SetFocusable (focusable bool) {
func (this *box) Focused () bool { func (this *box) Focused () bool {
hierarchy := this.getHierarchy() hierarchy := this.getHierarchy()
if hierarchy == nil { return false } if hierarchy == nil { return false }
return hierarchy.isFocused(this) return hierarchy.isFocused(this.outer)
} }
func (this *box) Modifiers () input.Modifiers { func (this *box) Modifiers () input.Modifiers {
@@ -272,6 +296,12 @@ func (this *box) OnKeyDown (callback func(key input.Key, numberPad bool)) event.
func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie { func (this *box) OnKeyUp (callback func(key input.Key, numberPad bool)) event.Cookie {
return this.on.keyUp.Connect(callback) return this.on.keyUp.Connect(callback)
} }
func (this *box) OnStyleChange (callback func()) event.Cookie {
return this.on.styleChange.Connect(callback)
}
func (this *box) OnIconsChange (callback func()) event.Cookie {
return this.on.iconsChange.Connect(callback)
}
func (this *box) handleFocusEnter () { func (this *box) handleFocusEnter () {
this.on.focusEnter.Broadcast() this.on.focusEnter.Broadcast()
} }
@@ -441,7 +471,7 @@ func (this *box) doLayout () {
// laycnt ++ // laycnt ++
this.innerClippingBounds = this.borderSum().Apply(this.bounds) this.innerClippingBounds = this.borderSum().Apply(this.bounds)
this.loseCanvas() this.outer.recursiveLoseCanvas()
} }
func (this *box) setParent (parent parent) { func (this *box) setParent (parent parent) {
@@ -449,6 +479,7 @@ func (this *box) setParent (parent parent) {
this.SetFocused(false) this.SetFocused(false)
} }
this.parent = parent this.parent = parent
this.outer.recursiveReApply()
} }
func (this *box) getParent () parent { func (this *box) getParent () parent {
@@ -471,7 +502,7 @@ func (this *box) recursiveRedo () {
this.doDraw() this.doDraw()
} }
func (this *box) loseCanvas () { func (this *box) recursiveLoseCanvas () {
this.canvas.InvalidateTo(nil) this.canvas.InvalidateTo(nil)
} }
@@ -496,6 +527,36 @@ func (this *box) invalidateMinimum () {
} }
} }
func (this *box) recursiveReApply () {
if this.getHierarchy() == nil { return }
// re-apply styling, icons *if needed*
// style
hierarchyStyleNonce := this.getStyleNonce()
if this.lastStyleNonce != hierarchyStyleNonce {
// remove old style
this.lastStyleNonce = hierarchyStyleNonce
if this.styleCookie != nil {
this.styleCookie.Close()
this.styleCookie = nil
}
// apply new one
if style := this.getStyle(); style != nil {
this.styleCookie = style.Apply(this.outer)
}
this.on.styleChange.Broadcast()
}
// icons
hierarchyIconsNonce := this.getIconsNonce()
if this.lastIconsNonce != hierarchyIconsNonce {
this.lastIconsNonce = hierarchyIconsNonce
this.on.iconsChange.Broadcast()
}
}
func (this *box) canBeFocused () bool { func (this *box) canBeFocused () bool {
return this.focusable return this.focusable
} }
@@ -529,7 +590,23 @@ func (this *box) getWindow () tomo.Window {
return hierarchy.getWindow() return hierarchy.getWindow()
} }
func (this *box) getStyle () tomo.Style {
hierarchy := this.getHierarchy()
if hierarchy == nil { return nil }
return hierarchy.getStyle()
}
func (this *box) getHierarchy () *Hierarchy { func (this *box) getHierarchy () *Hierarchy {
if this.parent == nil { return nil } if this.parent == nil { return nil }
return this.parent.getHierarchy() return this.parent.getHierarchy()
} }
func (this *box) getStyleNonce () int {
// should panic if not in the tree
return this.getHierarchy().getStyleNonce()
}
func (this *box) getIconsNonce () int {
// should panic if not in the tree
return this.getHierarchy().getIconsNonce()
}

View File

@@ -73,6 +73,24 @@ func (this *containerBox) ScrollTo (point image.Point) {
this.invalidateLayout() this.invalidateLayout()
} }
func (this *containerBox) RecommendedHeight (width int) int {
if this.layout == nil || this.vOverflow {
return this.MinimumSize().Y
} else {
return this.layout.RecommendedHeight(this.layoutHints(), this.children, width) +
this.borderAndPaddingSum().Vertical()
}
}
func (this *containerBox) RecommendedWidth (height int) int {
if this.layout == nil || this.hOverflow {
return this.MinimumSize().X
} else {
return this.layout.RecommendedWidth(this.layoutHints(), this.children, height) +
this.borderAndPaddingSum().Horizontal()
}
}
func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie { func (this *containerBox) OnContentBoundsChange (callback func()) event.Cookie {
return this.on.contentBoundsChange.Connect(callback) return this.on.contentBoundsChange.Connect(callback)
} }
@@ -335,6 +353,20 @@ func (this *containerBox) recursiveRedo () {
} }
} }
func (this *containerBox) recursiveLoseCanvas () {
this.box.recursiveLoseCanvas()
for _, child := range this.children {
child.(anyBox).recursiveLoseCanvas()
}
}
func (this *containerBox) recursiveReApply () {
this.box.recursiveReApply()
for _, child := range this.children {
child.(anyBox).recursiveReApply()
}
}
func (this *containerBox) boxUnder (point image.Point, category eventCategory) anyBox { func (this *containerBox) boxUnder (point image.Point, category eventCategory) anyBox {
if !point.In(this.bounds) { return nil } if !point.In(this.bounds) { return nil }

View File

@@ -36,7 +36,7 @@ func (this *Hierarchy) HandleKeyDown (key input.Key, numberPad bool) {
// be called *before* HandleKeyUp. // be called *before* HandleKeyUp.
func (this *Hierarchy) HandleKeyUp (key input.Key, numberPad bool) { func (this *Hierarchy) HandleKeyUp (key input.Key, numberPad bool) {
if target := this.keyboardTarget(); target != nil { if target := this.keyboardTarget(); target != nil {
target.handleKeyDown(key, numberPad) target.handleKeyUp(key, numberPad)
} }
} }

View File

@@ -56,6 +56,7 @@ func (this *System) NewHierarchy (link WindowLink) *Hierarchy {
needLayout: make(util.Set[anyBox]), needLayout: make(util.Set[anyBox]),
needDraw: make(util.Set[anyBox]), needDraw: make(util.Set[anyBox]),
} }
this.hierarchies.Add(hierarchy)
return hierarchy return hierarchy
} }
@@ -85,7 +86,7 @@ func (this *Hierarchy) Empty () bool {
// draw to. The Hierarchy will use the canvas.Canvas's bounds to lay itself out. // draw to. The Hierarchy will use the canvas.Canvas's bounds to lay itself out.
func (this *Hierarchy) SetCanvas (can canvas.Canvas) { func (this *Hierarchy) SetCanvas (can canvas.Canvas) {
this.canvas = can this.canvas = can
if this.root != nil { this.root.loseCanvas() } if this.root != nil { this.root.recursiveLoseCanvas() }
this.needRedo = true this.needRedo = true
} }
@@ -135,6 +136,20 @@ func (this *Hierarchy) AfterEvent () {
} }
} }
// Close closes the Hierarchy. This should be called when the Window that
// contains it has been closed.
func (this *Hierarchy) Close () {
this.system.removeHierarchy(this)
}
func (this *Hierarchy) setStyle () {
if this.root != nil { this.root.recursiveReApply() }
}
func (this *Hierarchy) setIcons () {
if this.root != nil { this.root.recursiveReApply() }
}
func (this *Hierarchy) getHierarchy () *Hierarchy { func (this *Hierarchy) getHierarchy () *Hierarchy {
return this return this
} }
@@ -143,6 +158,18 @@ func (this *Hierarchy) getWindow () tomo.Window {
return this.link.GetWindow() return this.link.GetWindow()
} }
func (this *Hierarchy) getStyle () tomo.Style {
return this.system.style
}
func (this *Hierarchy) getStyleNonce () int {
return this.system.styleNonce
}
func (this *Hierarchy) getIconsNonce () int {
return this.system.iconsNonce
}
func (this *Hierarchy) getCanvas () canvas.Canvas { func (this *Hierarchy) getCanvas () canvas.Canvas {
return this.canvas return this.canvas
} }

View File

@@ -51,13 +51,17 @@ type anyBox interface {
// flushActionQueue performs any queued actions, like invalidating the // flushActionQueue performs any queued actions, like invalidating the
// minimum size or grabbing input focus. // minimum size or grabbing input focus.
flushActionQueue () flushActionQueue ()
// recursiveRedo recursively recalculates the minimum size, layout, and // recursiveRedo recursively recalculates the minimum size, layout, and
// re-paints this anyBox and all of its children. // re-paints this anyBox and all of its children.
recursiveRedo () recursiveRedo ()
// loseCanvas causes this anyBox and its children (if applicable) to // loseCanvas causes this anyBox and its children (if applicable) to
// lose their canvases and re-cut them as needed. // lose their canvases and re-cut them as needed.
loseCanvas () recursiveLoseCanvas ()
// recursiveReAppply causes this anyBox and its children (if applicable)
// to check whether they have an outdated style or icon set, and if so,
// update it and trigger the appropriate event broadcasters.
recursiveReApply ()
// contentMinimum returns the minimum dimensions of this box's content // contentMinimum returns the minimum dimensions of this box's content
contentMinimum () image.Point contentMinimum () image.Point
@@ -81,19 +85,19 @@ type anyBox interface {
propagate (func (anyBox) bool) bool propagate (func (anyBox) bool) bool
propagateAlt (func (anyBox) bool) bool propagateAlt (func (anyBox) bool) bool
handleFocusEnter () handleFocusEnter ()
handleFocusLeave () handleFocusLeave ()
// handleDndEnter () // handleDndEnter ()
// handleDndLeave () // handleDndLeave ()
// handleDndDrop (data.Data) // handleDndDrop (data.Data)
handleMouseEnter () handleMouseEnter ()
handleMouseLeave () handleMouseLeave ()
handleMouseMove () handleMouseMove ()
handleMouseDown (input.Button) handleMouseDown (input.Button)
handleMouseUp (input.Button) handleMouseUp (input.Button)
handleScroll (float64, float64) handleScroll (float64, float64)
handleKeyDown (input.Key, bool) handleKeyDown (input.Key, bool)
handleKeyUp (input.Key, bool) handleKeyUp (input.Key, bool)
} }
func assertAnyBox (unknown tomo.Box) anyBox { func assertAnyBox (unknown tomo.Box) anyBox {

View File

@@ -2,12 +2,20 @@ package system
import "io" import "io"
import "image" import "image"
import "git.tebibyte.media/tomo/tomo"
import "git.tebibyte.media/tomo/tomo/canvas" import "git.tebibyte.media/tomo/tomo/canvas"
import "git.tebibyte.media/tomo/backend/internal/util"
// System is coupled to a tomo.Backend implementation, and manages Hierarchies // System is coupled to a tomo.Backend implementation, and manages Hierarchies
// and Boxes. // and Boxes.
type System struct { type System struct {
link BackendLink link BackendLink
style tomo.Style
styleNonce int
iconsNonce int
hierarchies util.Set[*Hierarchy]
} }
// BackendLink allows the System to call up into the tomo.Backend implementation // BackendLink allows the System to call up into the tomo.Backend implementation
@@ -15,8 +23,6 @@ type System struct {
type BackendLink interface { type BackendLink interface {
// NewTexture creates a new texture from an image. // NewTexture creates a new texture from an image.
NewTexture (image.Image) canvas.TextureCloser NewTexture (image.Image) canvas.TextureCloser
// NewCanvas creates a new blank canvas with the specified bounds.
NewCanvas (image.Rectangle) canvas.Canvas
// NewSurface creates a new surface with the specified bounds. // NewSurface creates a new surface with the specified bounds.
NewSurface (image.Rectangle) (SurfaceLink, error) NewSurface (image.Rectangle) (SurfaceLink, error)
} }
@@ -32,6 +38,29 @@ type SurfaceLink interface {
// New creates a new System. // New creates a new System.
func New (link BackendLink) *System { func New (link BackendLink) *System {
return &System { return &System {
link: link, link: link,
hierarchies: make(util.Set[*Hierarchy]),
} }
} }
// SetStyle sets the tomo.Style that is applied to objects, and notifies them
// that the style has changed.
func (this *System) SetStyle (style tomo.Style) {
this.style = style
this.styleNonce ++
for hierarchy := range this.hierarchies {
hierarchy.setStyle()
}
}
// SetIcons notifies objects that the icons have changed.
func (this *System) SetIcons (icons tomo.Icons) {
this.iconsNonce ++
for hierarchy := range this.hierarchies {
hierarchy.setIcons()
}
}
func (this *System) removeHierarchy (hierarchy *Hierarchy) {
delete(this.hierarchies, hierarchy)
}

View File

@@ -65,6 +65,15 @@ func (this *textBox) ScrollTo (point image.Point) {
this.invalidateLayout() this.invalidateLayout()
} }
func (this *textBox) RecommendedHeight (width int) int {
return this.drawer.ReccomendedHeightFor(width) + this.borderAndPaddingSum().Vertical()
}
func (this *textBox) RecommendedWidth (height int) int {
// TODO maybe not the best idea?
return this.MinimumSize().X
}
func (this *textBox) OnContentBoundsChange (callback func()) event.Cookie { func (this *textBox) OnContentBoundsChange (callback func()) event.Cookie {
return this.on.contentBoundsChange.Connect(callback) return this.on.contentBoundsChange.Connect(callback)
} }

View File

@@ -31,10 +31,6 @@ func (this *backendLink) NewTexture (source image.Image) canvas.TextureCloser {
return this.backend.NewTexture(source) return this.backend.NewTexture(source)
} }
func (this *backendLink) NewCanvas (bounds image.Rectangle) canvas.Canvas {
return this.backend.NewCanvas(bounds)
}
func (this *backendLink) NewSurface (bounds image.Rectangle) (system.SurfaceLink, error) { func (this *backendLink) NewSurface (bounds image.Rectangle) (system.SurfaceLink, error) {
// TODO // TODO
return nil, errors.New("x: not implemented") return nil, errors.New("x: not implemented")
@@ -126,10 +122,18 @@ func (this *Backend) NewTexture (source image.Image) canvas.TextureCloser {
return xcanvas.NewTextureFrom(source) return xcanvas.NewTextureFrom(source)
} }
func (this *Backend) NewCanvas (bounds image.Rectangle) canvas.Canvas { func (this *Backend) NewCanvas (bounds image.Rectangle) canvas.CanvasCloser {
return xcanvas.NewCanvas(this.x, bounds) return xcanvas.NewCanvas(this.x, bounds)
} }
func (this *Backend) SetStyle (style tomo.Style) {
this.system.SetStyle(style)
}
func (this *Backend) SetIcons (icons tomo.Icons) {
this.system.SetIcons(icons)
}
func (this *Backend) assert () { func (this *Backend) assert () {
if this == nil { panic("x: nil backend") } if this == nil { panic("x: nil backend") }
} }

View File

@@ -49,9 +49,10 @@ func (this *Canvas) Push (window xproto.Window) {
} }
// Close frees this canvas from the X server. // Close frees this canvas from the X server.
func (this *Canvas) Close () { func (this *Canvas) Close () error {
this.assert() this.assert()
this.Image.Destroy() this.Image.Destroy()
return nil
} }
func (this *Canvas) assert () { func (this *Canvas) assert () {

View File

@@ -13,7 +13,7 @@ type scrollSum struct {
} }
// TODO: this needs to be configurable, we need a config api // TODO: this needs to be configurable, we need a config api
const scrollDistance = 16 const scrollDistance = 32
func (sum *scrollSum) add (button xproto.Button, window *window, state uint16) { func (sum *scrollSum) add (button xproto.Button, window *window, state uint16) {
if xgbkb.StateToModifiers(state).Shift { if xgbkb.StateToModifiers(state).Shift {

View File

@@ -18,7 +18,6 @@ import "github.com/jezek/xgbutil/keybind"
import "github.com/jezek/xgbutil/mousebind" import "github.com/jezek/xgbutil/mousebind"
import "github.com/jezek/xgbutil/xgraphics" import "github.com/jezek/xgbutil/xgraphics"
type mainWindow struct { *window }
type window struct { type window struct {
backend *Backend backend *Backend
hierarchy *system.Hierarchy hierarchy *system.Hierarchy
@@ -28,10 +27,13 @@ type window struct {
title string title string
leader *window
modalParent *window modalParent *window
hasModal bool hasModal bool
shy bool shy bool
visible bool visible bool
resizeX bool
resizeY bool
metrics struct { metrics struct {
bounds image.Rectangle bounds image.Rectangle
@@ -63,28 +65,24 @@ func (this *windowLink) NotifyMinimumSizeChange () {
func (this *Backend) NewWindow ( func (this *Backend) NewWindow (
bounds image.Rectangle, bounds image.Rectangle,
) ( ) (
output tomo.MainWindow, output tomo.Window,
err error, err error,
) { ) {
this.assert() this.assert()
window, err := this.newWindow(bounds, false) return this.newWindow(bounds, false)
output = mainWindow { window: window }
return output, err
} }
func (this *Backend) NewPlainWindow ( func (this *Backend) NewPlainWindow (
bounds image.Rectangle, bounds image.Rectangle,
) ( ) (
output tomo.MainWindow, output tomo.Window,
err error, err error,
) { ) {
this.assert() this.assert()
window, err := this.newWindow(bounds, false) window, err := this.newWindow(bounds, false)
window.setType("dock") window.setType("dock")
output = mainWindow { window: window } return window, err
return output, err
} }
func (this *Backend) newWindow ( func (this *Backend) newWindow (
@@ -100,6 +98,9 @@ func (this *Backend) newWindow (
window := &window { backend: this } window := &window { backend: this }
link := &windowLink { window: window } link := &windowLink { window: window }
window.hierarchy = this.system.NewHierarchy(link) window.hierarchy = this.system.NewHierarchy(link)
window.leader = window
window.resizeX = true
window.resizeY = true
window.xWindow, err = xwindow.Generate(this.x) window.xWindow, err = xwindow.Generate(this.x)
if err != nil { return } if err != nil { return }
@@ -178,12 +179,16 @@ func (this *window) SetTitle (title string) {
icccm.WmIconNameSet (this.backend.x, this.xWindow.Id, title) icccm.WmIconNameSet (this.backend.x, this.xWindow.Id, title)
} }
func (this *window) SetIcon (sizes ...image.Image) { func (this *window) SetIcon (sizes ...canvas.Texture) {
wmIcons := []ewmh.WmIcon { } wmIcons := []ewmh.WmIcon { }
for _, icon := range sizes { for _, icon := range sizes {
width := icon.Bounds().Max.X icon, ok := icon.(*xcanvas.Texture)
height := icon.Bounds().Max.Y if !ok { continue }
bounds := icon.Bounds()
width := bounds.Dx()
height := bounds.Dy()
wmIcon := ewmh.WmIcon { wmIcon := ewmh.WmIcon {
Width: uint(width), Width: uint(width),
Height: uint(height), Height: uint(height),
@@ -193,18 +198,14 @@ func (this *window) SetIcon (sizes ...image.Image) {
// manually convert image data beacuse of course we have to do // manually convert image data beacuse of course we have to do
// this // this
index := 0 index := 0
for y := 0; y < height; y ++ { for y := bounds.Min.Y; y < bounds.Max.Y; y ++ {
for x := 0; x < width; x ++ { for x := bounds.Min.X; x < bounds.Max.X; x ++ {
r, g, b, a := icon.At(x, y).RGBA() pixel := icon.BGRAAt(x, y)
r >>= 8
g >>= 8
b >>= 8
a >>= 8
wmIcon.Data[index] = wmIcon.Data[index] =
(uint(a) << 24) | (uint(pixel.A) << 24) |
(uint(r) << 16) | (uint(pixel.R) << 16) |
(uint(g) << 8) | (uint(pixel.G) << 8) |
(uint(b) << 0) (uint(pixel.B) << 0)
index ++ index ++
}} }}
@@ -217,6 +218,20 @@ func (this *window) SetIcon (sizes ...image.Image) {
wmIcons) wmIcons)
} }
func (this *window) SetResizable (x, y bool) {
if this.resizeX == x && this.resizeY == y { return }
this.resizeX = x
this.resizeY = y
this.doMinimumSize()
}
func (this *window) SetBounds (bounds image.Rectangle) {
this.xWindow.WMMoveResize (
bounds.Min.X, bounds.Min.Y,
bounds.Min.X + bounds.Dx(),
bounds.Min.Y + bounds.Dy())
}
func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) { func (this *window) NewMenu (bounds image.Rectangle) (tomo.Window, error) {
menu, err := this.backend.newWindow ( menu, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), true) bounds.Add(this.metrics.bounds.Min), true)
@@ -247,19 +262,24 @@ func (this *window) NewModal (bounds image.Rectangle) (tomo.Window, error) {
return modal, err return modal, err
} }
func (this mainWindow) NewChild (bounds image.Rectangle) (tomo.Window, error) { func (this *window) NewChild (bounds image.Rectangle) (tomo.Window, error) {
leader := this.leader
child, err := this.backend.newWindow ( child, err := this.backend.newWindow (
bounds.Add(this.metrics.bounds.Min), false) bounds.Add(this.metrics.bounds.Min), false)
child.leader = leader
if err != nil { return nil, err } if err != nil { return nil, err }
child.setClientLeader(this.window)
this.setClientLeader(this.window) child.setClientLeader(leader)
leader.setClientLeader(leader)
icccm.WmTransientForSet ( icccm.WmTransientForSet (
this.backend.x, this.backend.x,
this.xWindow.Id, child.xWindow.Id,
this.xWindow.Id) leader.xWindow.Id)
this.setType("UTILITY") child.setType("UTILITY")
// this.inheritProperties(this.window) // child.inheritProperties(leader.window)
return this, err return child, err
} }
func (this *window) Widget () (tomo.Window, error) { func (this *window) Widget () (tomo.Window, error) {
@@ -306,6 +326,7 @@ func (this *window) Close () {
this.SetRoot(nil) this.SetRoot(nil)
delete(this.backend.windows, this.xWindow.Id) delete(this.backend.windows, this.xWindow.Id)
this.xWindow.Destroy() this.xWindow.Destroy()
this.hierarchy.Close()
} }
func (this *window) OnClose (callback func ()) event.Cookie { func (this *window) OnClose (callback func ()) event.Cookie {
@@ -361,7 +382,7 @@ func (this *window) reallocateCanvas () {
if larger || smaller { if larger || smaller {
if this.xCanvas != nil { if this.xCanvas != nil {
this.xCanvas.Destroy() this.xCanvas.Close()
} }
this.xCanvas = xcanvas.NewCanvasFrom(xgraphics.New ( this.xCanvas = xcanvas.NewCanvasFrom(xgraphics.New (
this.backend.x, this.backend.x,
@@ -404,14 +425,30 @@ func (this *window) doMinimumSize () {
if size.X < 8 { size.X = 8 } if size.X < 8 { size.X = 8 }
if size.Y < 8 { size.Y = 8 } if size.Y < 8 { size.Y = 8 }
hints := icccm.NormalHints {
Flags: icccm.SizeHintPMinSize,
MinWidth: uint(size.X),
MinHeight: uint(size.Y),
// now you can tell your friends that the max size of a Tomo
// window under X when one of the dimensions is constrained is
// 99999999999
MaxWidth: uint(99999999999),
MaxHeight: uint(99999999999),
}
if !this.resizeX {
hints.Flags |= icccm.SizeHintPMaxSize
hints.MaxWidth = uint(size.X)
}
if !this.resizeY {
hints.Flags |= icccm.SizeHintPMaxSize
hints.MaxHeight = uint(size.Y)
}
icccm.WmNormalHintsSet ( icccm.WmNormalHintsSet (
this.backend.x, this.backend.x,
this.xWindow.Id, this.xWindow.Id,
&icccm.NormalHints { &hints)
Flags: icccm.SizeHintPMinSize,
MinWidth: uint(size.X),
MinHeight: uint(size.Y),
})
newWidth := this.metrics.bounds.Dx() newWidth := this.metrics.bounds.Dx()
newHeight := this.metrics.bounds.Dy() newHeight := this.metrics.bounds.Dy()
if newWidth < size.X { newWidth = size.X } if newWidth < size.X { newWidth = size.X }