1package core
2
3// Free floats: a tile the user floated with toggle-floating (or dragged
4// out, drag.go) floats over the columns at a size and centre core owns.
5// The centre is a fraction of the usable area, so the window keeps its
6// relative place across stash, workspace and monitor moves.
7
8// freeNudge is how far a keyboard move shifts a free float, logical px.
9const freeNudge = 50
10
11// freeMinSize is the smallest client size a free float is resized to.
12const freeMinSize = 64
13
14// ToggleFloating turns the focused tile into a free float at its current
15// size, centred; a float (free or native) returns to the columns: a free
16// one to its former place, a native one as a new column. A fullscreen or
17// stashed window is left alone.
18func (w *Workspace) ToggleFloating() {
19 id, ok := w.Focused()
20 if !ok || id == w.fullscreen || w.stashIndex(id) >= 0 {
21 return
22 }
23 if i := w.floatIndex(id); i >= 0 {
24 back := w.Floats[i].back
25 w.RemoveWindow(id)
26 w.restore(id, back)
27 return
28 }
29 r, ok := w.placed(id)
30 if !ok {
31 return
32 }
33 // Its slot is released: the slot column refills without it.
34 back := w.tilePlace(id)
35 back.slot = 0
36 w.dropSlotOf(id)
37 w.RemoveWindow(id)
38 w.Floats = append(w.Floats, Float{
39 ID: id, W: max(r.W-2*w.border, 1), H: max(r.H-2*w.border, 1),
40 free: true, cx: 0.5, cy: 0.5, back: back,
41 })
42 w.floatFocus, w.stashFocus = true, false
43}
44
45// placed is the rect of window id in the current layout.
46func (w *Workspace) placed(id WindowID) (Rect, bool) {
47 for _, p := range w.Layout() {
48 if p.ID == id && !p.Hidden {
49 return p.Rect, true
50 }
51 }
52 return Rect{}, false
53}
54
55// focusedFree returns the index of the focused free float, or -1.
56func (w *Workspace) focusedFree() int {
57 id, ok := w.Focused()
58 if !ok || !w.floatFocus {
59 return -1
60 }
61 if i := w.floatIndex(id); i >= 0 && w.Floats[i].free && id != w.fullscreen {
62 return i
63 }
64 return -1
65}
66
67// freeAction runs a move or resize action on the focused free float and
68// reports whether it did: moves shift it by freeNudge, resizes change its
69// size by a share of the usable area.
70func (w *Workspace) freeAction(a Action) bool {
71 i := w.focusedFree()
72 if i < 0 {
73 return false
74 }
75 f := &w.Floats[i]
76 u := w.Usable
77 if axis, step, ok := ResizeArg(a); ok {
78 if u.W <= 0 || u.H <= 0 {
79 return true
80 }
81 // The size is the client's: the border must fit too. A width
82 // step is in cells, a cell being Usable.W/MaxColumns.
83 if axis == ResizeWidth {
84 f.W = max(min(f.W+step*u.W/max(w.MaxColumns, 1), u.W-2*w.border), freeMinSize)
85 } else {
86 f.H = max(min(f.H+u.H*step/100, u.H-2*w.border), freeMinSize)
87 }
88 w.clampFree(f)
89 return true
90 }
91 dx, dy := 0, 0
92 switch a {
93 case ActionMoveColumnLeft:
94 dx = -freeNudge
95 case ActionMoveColumnRight:
96 dx = freeNudge
97 case ActionMoveWindowUp:
98 dy = -freeNudge
99 case ActionMoveWindowDown:
100 dy = freeNudge
101 default:
102 return false
103 }
104 if u.W <= 0 || u.H <= 0 {
105 return true
106 }
107 w.moveFree(f, w.floatRect(*f), dx, dy)
108 return true
109}
110
111// moveFree puts free float f at rect r shifted by dx, dy, kept inside
112// the usable area.
113func (w *Workspace) moveFree(f *Float, r Rect, dx, dy int) {
114 u := w.Usable
115 x := min(max(r.X+dx, u.X), u.X+u.W-r.W)
116 y := min(max(r.Y+dy, u.Y), u.Y+u.H-r.H)
117 f.cx = float64(x-u.X+r.W/2) / float64(u.W)
118 f.cy = float64(y-u.Y+r.H/2) / float64(u.H)
119}
120
121// clampFree keeps the centre of f where its rect stays inside the area.
122func (w *Workspace) clampFree(f *Float) {
123 w.moveFree(f, w.floatRect(*f), 0, 0)
124}