Support resize of tiling windows

This commit is contained in:
Vidar Hokstad 2024-01-14 21:09:00 +00:00
parent 3c518cfc20
commit 3b7ccb6f4a
5 changed files with 142 additions and 79 deletions

View file

@ -5,6 +5,7 @@ class Desktop
def initialize(wm, id, name) = (@wm, @id, @name = wm, id, name)
def active? = (@wm.current_desktop_id == self.id)
def children = @wm.windows.values.find_all{_1.desktop==self}
def mapped_regular_children = children.find_all{_1.mapped && !_1.special?}
def show = children.each(&:show)
def hide = children.each(&:hide)
def inspect = "<Desktop id=#{id} window=#{@window}>"

20
geom.rb
View file

@ -8,16 +8,26 @@ def gap(geom,g)
geom
end
def split_geom(geom, dir, node, gap)
def split_geom(geom, dir, node, gap, ratio)
geom = geom.dup
case dir
when :lr
geom.width = (geom.width-gap)/2
geom.x += geom.width+gap if node == 1
lw = (geom.width-gap)*ratio
if node == 0
geom.width = lw
else
geom.width = (geom.width-gap)*(1.0-ratio)
geom.x += lw + gap
end
return geom
when :tb
geom.height = (geom.height-gap)/2
geom.y += geom.height+gap if node == 1
th = (geom.height-gap)*ratio
if node == 0
geom.height = th
else
geom.height = (geom.height-gap)*(1.0-ratio)
geom.y += th + gap
end
return geom
else raise "Invalid direction"
end

13
node.rb
View file

@ -1,14 +1,16 @@
class Node
attr_accessor :split, :nodes, :dir, :parent
attr_accessor :ratio, :nodes, :dir, :parent
attr_reader :geom
def inspect = "<Node @nodes=#{@nodes.inspect}, @dir=#{@dir.inspect} @split=#{@split.inspect} @parent=#{@parent.object_id}>"
def inspect = "<Node #{object_id} @nodes=#{@nodes.inspect}, @dir=#{@dir.inspect} @ratio=#{@ratio.inspect} @parent=#{@parent.object_id}>"
def initialize(nodes=[], parent: nil, dir: nil)
@nodes = Array(nodes.dup)
@nodes.each{|n| n.parent = self }
@parent = parent
@split = 0.5
@ratio = 0.5
@dir = dir
@geom = nil # *current* geometry, subject to change at all time
end
# FIXME: Restate children, placements, find in terms of
@ -47,6 +49,7 @@ class Node
@dir ||= dir
dir = @dir
nextdir = {lr: :tb, tb: :lr}[dir]
@geom = geom.dup
case @nodes.length
when 0
when 1
@ -57,8 +60,8 @@ class Node
end
@nodes[0].layout(g, dir, level+1)
when 2
@nodes[0].layout(split_geom(geom, dir, 0,gap), gap, nextdir, level+1)
@nodes[1].layout(split_geom(geom, dir, 1,gap), gap, nextdir, level+1)
@nodes[0].layout(split_geom(geom, dir, 0,gap, @ratio), gap, nextdir, level+1)
@nodes[1].layout(split_geom(geom, dir, 1,gap, @ratio), gap, nextdir, level+1)
else
STDERR.puts "WARNING: Too many nodes"
end

View file

@ -18,16 +18,13 @@ Thread.abort_on_exception = true
dpy = X11::Display.new
# FIXME: This is a workaround for a deadlock
dpy.atom(:WM_CLASS)
dpy.atom(:STRING)
#dpy.atom(:WM_CLASS)
#dpy.atom(:STRING)
$wm = WindowManager.new(dpy, num_desktops: 10)
wm = WindowManager.new(dpy, num_desktops: 10)
start = nil
attr = nil
d = TypeDispatcher.new($wm)
d = TypeDispatcher.new(wm)
d.on(:client_message) do |ev|
data = ev.data.unpack("V*")
name = dpy.get_atom_name(ev.type)
@ -36,7 +33,6 @@ d.on(:client_message) do |ev|
end
loop do
# FIXME: Select properly
ev = nil
begin
ev = dpy.next_packet
@ -47,58 +43,5 @@ loop do
end
p ev
case ev
when X11::Form::ButtonPress
if ev.child # Whichever button, we want to know more about this window
w = $wm.window(ev.child)
attr = w.get_geometry rescue nil
if attr
$wm.set_focus(w.wid)
start = ev
end
end
when X11::Form::MotionNotify # if start.button == 1 we move; if 3 we resize, all with the same request:
# TODO: If floating, do this; if tiling, find neighours and resize them too.
if ev.child != start.child
$wm.set_focus(ev.child)
end
p [:MOTION, start, attr]
if start
xdiff = ev.root_x - start.root_x;
ydiff = ev.root_y - start.root_y;
if start&.child && attr
w = $wm.window(start.child)
# FIXME: Any other types we don't want to allow moving or resizing
begin
next if w.special?
rescue # FIXME
end
p w
if start.detail == 1 # Move
w.configure(x: attr.x + xdiff, y: attr.y + ydiff)
elsif start.detail == 3 # Resize
# If left/above the centre point, we grow/shrink the window to the left/top
# otherwise to the right/bottom. Doing it to the left/top requires
# moving it at the same time.
attr.x = attr.x + (ev.event_x-attr.x < attr.width / 2 ? xdiff : 0)
attr.y = attr.y + (ev.event_y-attr.y < attr.height/ 2 ? ydiff : 0)
attr.width = attr.width + (ev.event_x-attr.x < attr.width / 2 ? -xdiff : xdiff)
attr.height = attr.height+ (ev.event_y-attr.y < attr.height / 2 ? -ydiff : ydiff)
start.root_x = ev.root_x
start.root_y = ev.root_y
w.configure(x: attr.x, y: attr.y, width: attr.width, height: attr.height)
end
end
end
when X11::Form::ButtonRelease
# Make sure we don't accidentally operate on another window.
start.child = nil if start
else
d.(ev.class, ev)
end
d.(ev.class, ev)
end

120
wm.rb
View file

@ -23,6 +23,7 @@ class WindowManager
# FIXME: Config
# FIXME: Improved way of specifying pre-designed layouts.
r = desktops[1].layout.root
r.ratio = 0.5
r.nodes[0] = Leaf.new(iclass: "todo-todo")
r.nodes[1] = Node.new([
Leaf.new(iclass: "todo-done"),
@ -214,7 +215,7 @@ class WindowManager
# FIXME: Switch focus (keep focus stack per desktop)
old.hide
change_property(:_NET_CURRENT_DESKTOP, :cardinal, d)
f = current_desktop&.children&.find {|w| !w.special?}
f = current_desktop&.mapped_regular_children&.first
set_focus(f.wid) if f
end
@ -238,7 +239,7 @@ class WindowManager
# FIXME: This needs tweaks. Especially for floating windows, where
# what we really want is to e.g. treat partially overlapping windows
# so that the one closest to *overlapping* the correct border is picked
def find_closest(w, dir, from = windows.values)
def find_closest(w, dir, from)
g = w.get_geometry
case dir
@ -252,8 +253,6 @@ class WindowManager
list = []
p [:here]
from.each do |win|
next if win.special?
next if !win.mapped
p [:checking, win, dir]
g2 = win.get_geometry rescue nil
next if g2.nil?
@ -312,6 +311,114 @@ class WindowManager
p dpy.get_atom_name(ev.atom) rescue nil
end
def on_button_press(ev)
return if !ev.child
w = window(ev.child)
@attr = w.get_geometry rescue nil
if @attr
set_focus(w.wid)
@start = ev
end
end
# FIXME: This does not yet handle tiling
def on_motion_notify(ev)
# @start.button == 1 -> move
# @start.button == 3 -> resize
if ev.child != @start.child
set_focus(ev.child) rescue nil # FIXME
end
return if !@start&.child || !@attr
xdiff = ev.root_x - @start.root_x;
ydiff = ev.root_y - @start.root_y;
w = window(@start.child)
# FIXME: Any other types we don't want to allow moving or resizing
begin
return if w.special?
rescue # FIXME: Why is this here?
end
#p w
if @start.detail == 1 # Move
w.configure(x: attr.x + xdiff, y: attr.y + ydiff)
elsif @start.detail == 3 # Resize
lr = (ev.event_x-@attr.x < @attr.width / 2)
tb = (ev.event_y-@attr.y < @attr.height/ 2)
if w.floating?
# If left/above the centre point, we grow/shrink the window to the left/top
# otherwise to the right/bottom. Doing it to the left/top requires
# moving it at the same time.
@attr.x = @attr.x + (lr ? xdiff : 0)
@attr.y = @attr.y + (tb ? ydiff : 0)
@attr.width = @attr.width + (lr ? -xdiff : xdiff)
@attr.height = @attr.height+ (tb ? -ydiff : ydiff)
@start.root_x = ev.root_x
@start.root_y = ev.root_y
w.configure(x: @attr.x, y: @attr.y, width: @attr.width, height: @attr.height)
else
layout = current_desktop&.layout
if layout
prev = layout.find(w)
node = prev.parent
p :RESIZE
p [prev, node, layout.root]
while node #&& node != layout.root
p [lr, tb, node.dir, node]
if !lr.nil? && node.dir == :lr
dx = 0
if node.geom
dx = (((node.geom.width * node.ratio) + xdiff)/node.geom.width) - node.ratio
else
dx = 0.00
end
if node.nodes[0] == prev && !lr
# FIXME: This doesn't seem to matter
node.ratio += dx
@start.root_x = ev.root_x
lr = nil
elsif node.nodes[1] == prev
node.ratio += dx
@start.root_x = ev.root_x
lr = nil
end
end
if !tb.nil? && node.dir == :tb
dy = 0
if node.geom
dy = (((node.geom.height * node.ratio) + ydiff)/node.geom.height) - node.ratio
else
dy = 0.00
end
if node.nodes[0] == prev && !tb
# FIXME: This doesn't seem to matter
node.ratio += dy
@start.root_y = ev.root_y
node.ratio.clamp(0.1,0.9)
tb = nil
elsif node.nodes[1] == prev
node.ratio += dy
@start.root_y = ev.root_y
tb = nil
end
end
node.ratio = node.ratio.clamp(0.1,0.9)
prev = node
node = node.parent
end
p :RESIZE_DONE
layout.call
end
end
end
end
def on_button_release(ev)
@start.child = nil if @start
end
def on_focus_in(ev) = (set_focus(ev.event) if !focus)
def on_enter_notify(ev) = set_focus(ev.event)
def on_unmap_notify(ev) = window(ev.window)&.desktop&.update_layout
@ -366,7 +473,7 @@ class WindowManager
def on_rwm_focus(_, dir)
dir = dpy.get_atom_name(dir).downcase.to_sym
return if !@focus || @focus.special?
w = find_closest(@focus, dir, @focus.desktop.children)
w = find_closest(@focus, dir, @focus.desktop.mapped_regular_children)
set_focus(w.wid) if w
end
@ -418,7 +525,7 @@ class WindowManager
return
end
w = find_closest(@focus, dir, @focus.desktop.children.find_all{_1.mapped})
w = find_closest(@focus, dir, @focus.desktop.mapped_regular_children)
l1 = @focus.desktop&.layout&.find(@focus)
l2 = w&.desktop&.layout&.find(w)
@ -438,5 +545,4 @@ class WindowManager
set_focus(@focus.wid)
end
end
end