tile.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411
  1. import {Position} from './position.js';
  2. import Meta from 'gi://Meta';
  3. import {Direction, TileWindowManager} from './tileWindowManager.js';
  4. import GLib from 'gi://GLib';
  5. export var Orientation;
  6. (function (Orientation) {
  7. Orientation[Orientation['Vertical'] = 1] = 'Vertical';
  8. Orientation[Orientation['Horizontal'] = 2] = 'Horizontal';
  9. Orientation[Orientation['None'] = 3] = 'None';
  10. })(Orientation || (Orientation = {}));
  11. export var TileState;
  12. (function (TileState) {
  13. TileState[TileState['DEFAULT'] = 0] = 'DEFAULT';
  14. TileState[TileState['MINIMIZED'] = 1] = 'MINIMIZED';
  15. TileState[TileState['MAXIMIZED'] = 2] = 'MAXIMIZED';
  16. TileState[TileState['ALONE_MAXIMIZED'] = 3] = 'ALONE_MAXIMIZED';
  17. })(TileState || (TileState = {}));
  18. export class Tile {
  19. id;
  20. _leaf;
  21. _parent;
  22. _child1;
  23. _child2;
  24. _position;
  25. _window;
  26. _state;
  27. _orientation;
  28. _updateSource;
  29. _workspace;
  30. _nr_tiles;
  31. _monitor;
  32. _adjacents;
  33. static padding = 0;
  34. static id_count = 0;
  35. static fullscreen;
  36. constructor() {
  37. this.id = Tile.id_count++;
  38. this._position = new Position();
  39. this._window = null;
  40. this._state = TileState.DEFAULT;
  41. this._leaf = true;
  42. this._parent = null;
  43. this._child1 = null;
  44. this._child2 = null;
  45. this._orientation = Orientation.None;
  46. this._nr_tiles = 0;
  47. this._workspace = 0;
  48. this._updateSource = null;
  49. // west / east / north / south
  50. this._adjacents = [false, false, false, false];
  51. }
  52. /** Tile factory
  53. *
  54. * @param {Meta.Window} window
  55. * @param {Position} position
  56. * @param {number} monitor
  57. * @param {Tile | null} parent
  58. * @returns
  59. */
  60. static createTileLeaf(window, position, monitor, parent = null) {
  61. let tile = new Tile();
  62. tile._window = window;
  63. tile._position = position;
  64. tile._monitor = monitor;
  65. tile._parent = parent;
  66. tile._leaf = true;
  67. tile._nr_tiles = 1;
  68. tile._workspace = 0;
  69. tile.findAdjacents();
  70. return tile;
  71. }
  72. decrementTiles() {
  73. this._nr_tiles--;
  74. if (this._parent)
  75. this._parent.decrementTiles();
  76. }
  77. addWindowOnBlock(window) {
  78. if (this.leaf) {
  79. if (!this._window)
  80. throw new Error('A leaf must have a window');
  81. let newPositions = this._position.split();
  82. let c1 = Tile.createTileLeaf(this._window, newPositions[0], this.monitor, this);
  83. c1._state = this._state;
  84. this._window.tile = c1;
  85. let c2 = Tile.createTileLeaf(window, newPositions[1], this.monitor, this);
  86. window.tile = c2;
  87. this._window = null;
  88. this._leaf = false;
  89. this._child1 = c1;
  90. this._child2 = c2;
  91. this._nr_tiles++;
  92. this._orientation = this._position.width > this._position.height
  93. ? Orientation.Horizontal : Orientation.Vertical;
  94. c1.findAdjacents();
  95. c2.findAdjacents();
  96. } else if (this._child1 && this._child2) {
  97. if (this._child1?._nr_tiles > this._child2?._nr_tiles)
  98. this._child2.addWindowOnBlock(window);
  99. else
  100. this._child1.addWindowOnBlock(window);
  101. this._nr_tiles++;
  102. } else {
  103. throw new Error('Unexpected state to add window');
  104. }
  105. }
  106. removeTile() {
  107. if (this._window == null || this._child1 || this._child2 || !this._leaf)
  108. throw new Error('Invalid remove state');
  109. let parent = this._parent;
  110. if (!parent)
  111. return null;
  112. if (parent._child1 === this && parent._child2) {
  113. parent._leaf = parent._child2._leaf;
  114. parent._orientation = parent._child2._orientation;
  115. parent._window = parent._child2._window;
  116. parent._child1 = parent._child2._child1;
  117. parent._child2 = parent._child2._child2;
  118. } else if (parent._child2 === this && parent._child1) {
  119. parent._leaf = parent._child1._leaf;
  120. parent._orientation = parent._child1._orientation;
  121. parent._window = parent._child1._window;
  122. parent._child2 = parent._child1._child2;
  123. parent._child1 = parent._child1._child1;
  124. } else {
  125. return this;
  126. }
  127. if (parent._child1)
  128. parent._child1._parent = parent;
  129. if (parent._child2)
  130. parent._child2._parent = parent;
  131. if (parent._window)
  132. parent._window.tile = parent;
  133. parent.resize(parent._position);
  134. parent.decrementTiles();
  135. return parent;
  136. }
  137. /** Update tile position and its children size
  138. *
  139. * @param {Position} position
  140. */
  141. resize(position) {
  142. this._position = position;
  143. if (!this._window) {
  144. let newPositions = this._position.split(this._orientation);
  145. this._child1?.resize(newPositions[this._child1.position.index]);
  146. this._child2?.resize(newPositions[this._child2.position.index]);
  147. }
  148. }
  149. forEach(fn) {
  150. fn(this);
  151. this._child1?.forEach(fn);
  152. this._child2?.forEach(fn);
  153. }
  154. update() {
  155. if (this._window) {
  156. if (!this._window.isAlive)
  157. return;
  158. if (this._state === TileState.MAXIMIZED) {
  159. this._window?._originalMaximize(Meta.MaximizeFlags.BOTH);
  160. return;
  161. } else if (this._state === TileState.MINIMIZED) {
  162. this._window?._originalMinimize();
  163. return;
  164. }
  165. if (this._window.minimized)
  166. this._window.unminimize();
  167. if (this._position.proportion === 1) {
  168. this.state = TileState.ALONE_MAXIMIZED;
  169. if (this._window.maximized_horizontally || this._window.maximized_vertically)
  170. this._window.unmaximize(Meta.MaximizeFlags.BOTH);
  171. const workspc = this._window.get_workspace();
  172. const area = workspc?.get_work_area_for_monitor(this._monitor ? this._monitor : 0);
  173. if (!area)
  174. return;
  175. this._position.x = 0;
  176. this._position.y = 0;
  177. this._position.width = area.width;
  178. this._position.height = area.height;
  179. this._window?.move_resize_frame(true, area.x + this._position.x + (this._adjacents[0] ? Tile.padding / 2 : Tile.padding), area.y + this._position.y + (this._adjacents[2] ? Tile.padding / 2 : Tile.padding), this._position.width - (this._adjacents[1] ? Tile.padding * 1.5 : Tile.padding * 2), this._position.height - (this._adjacents[3] ? Tile.padding * 1.5 : Tile.padding * 2));
  180. if (this._updateSource !== null)
  181. GLib.Source.remove(this._updateSource);
  182. this._updateSource = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
  183. this._window?.move_frame(true, area.x + this._position.x + (this._adjacents[0] ? Tile.padding / 2 : Tile.padding), area.y + this._position.y + (this._adjacents[2] ? Tile.padding / 2 : Tile.padding));
  184. this._updateSource = null;
  185. return GLib.SOURCE_REMOVE;
  186. });
  187. } else {
  188. this.state = TileState.DEFAULT;
  189. if (this._window.maximized_horizontally || this._window.maximized_vertically)
  190. this._window.unmaximize(Meta.MaximizeFlags.BOTH);
  191. const workspc = this._window.get_workspace();
  192. const area = workspc?.get_work_area_for_monitor(this._monitor ? this._monitor : 0);
  193. if (!area)
  194. return;
  195. this._window?.move_resize_frame(true, area.x + this._position.x + (this._adjacents[0] ? Tile.padding / 2 : Tile.padding), area.y + this._position.y + (this._adjacents[2] ? Tile.padding / 2 : Tile.padding), this._position.width - (this._adjacents[1] ? Tile.padding * 1.5 : Tile.padding * 2), this._position.height - (this._adjacents[3] ? Tile.padding * 1.5 : Tile.padding * 2));
  196. if (this._updateSource !== null)
  197. GLib.Source.remove(this._updateSource);
  198. this._updateSource = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
  199. this._window?.move_frame(true, area.x + this._position.x + (this._adjacents[0] ? Tile.padding / 2 : Tile.padding), area.y + this._position.y + (this._adjacents[2] ? Tile.padding / 2 : Tile.padding));
  200. this._updateSource = null;
  201. return GLib.SOURCE_REMOVE;
  202. });
  203. }
  204. } else {
  205. this._child1?.update();
  206. this._child2?.update();
  207. }
  208. }
  209. /** Leaf to root research
  210. *
  211. * @param {(el : Tile) => boolean} fn
  212. * @returns
  213. */
  214. findParent(fn) {
  215. if (fn(this)) {
  216. if (!this._parent)
  217. return this;
  218. let res = this._parent.findParent(fn);
  219. if (res)
  220. return res;
  221. else
  222. return this;
  223. } else {
  224. return null;
  225. }
  226. }
  227. /** Classic recursive traversal on tree
  228. *
  229. * @param {(el : Tile) => boolean} fn
  230. * @returns
  231. */
  232. find(fn) {
  233. if (fn(this)) {
  234. return this;
  235. } else {
  236. let r1 = this.child1?.find(fn);
  237. if (r1)
  238. return r1;
  239. let r2 = this.child2?.find(fn);
  240. if (r2)
  241. return r2;
  242. return null;
  243. }
  244. }
  245. getSibling() {
  246. if (this === this?._parent?._child1)
  247. return this?._parent?._child2;
  248. else if (this === this?._parent?._child2)
  249. return this?._parent?._child1;
  250. else
  251. return null;
  252. }
  253. contains(window) {
  254. if (this._window?.get_id() === window.get_id())
  255. return true;
  256. return !!(this.child1?.contains(window) || this.child2?.contains(window));
  257. }
  258. addToChild() {
  259. if (this._child1 && this._child2) {
  260. this._child1.parent = this;
  261. this._child2.parent = this;
  262. this._child1.addToChild();
  263. this._child2.addToChild();
  264. }
  265. }
  266. static fromObject(obj, parent = null) {
  267. let tile = new Tile();
  268. if (obj._child1 && obj._child2)
  269. tile.setChild(Tile.fromObject(obj._child1, tile), Tile.fromObject(obj._child2, tile));
  270. tile.position = Position.fromObject(obj._position);
  271. tile.state = obj._state;
  272. tile.leaf = obj._leaf;
  273. tile.orientation = obj._orientation;
  274. tile.monitor = obj._monitor;
  275. tile.nr_tiles = obj._nr_tiles;
  276. tile.window = obj._window;
  277. tile.adjacents = obj._adjacents;
  278. tile.workspace = obj._workspace;
  279. if (parent)
  280. tile.parent = parent;
  281. if (tile.window)
  282. tile.window.tile = tile;
  283. return tile;
  284. }
  285. findAdjacents() {
  286. let _monitor = TileWindowManager.getMonitors()[this.monitor];
  287. let w = _monitor.closestTile(this, Direction.West);
  288. let e = _monitor.closestTile(this, Direction.East);
  289. let n = _monitor.closestTile(this, Direction.North);
  290. let s = _monitor.closestTile(this, Direction.South);
  291. this._adjacents = [w !== null, e !== null, n !== null, s !== null];
  292. }
  293. setChild(child1, child2) {
  294. this._child1 = child1;
  295. this._child2 = child2;
  296. }
  297. destroy() {
  298. if (this._updateSource !== null)
  299. GLib.Source.remove(this._updateSource);
  300. this._updateSource = null;
  301. this._window = null;
  302. }
  303. /** *********************/
  304. /* GETTERS AND SETTERS */
  305. set orientation(o) {
  306. this._orientation = o;
  307. }
  308. get orientation() {
  309. return this._orientation;
  310. }
  311. set parent(p) {
  312. this._parent = p;
  313. }
  314. get parent() {
  315. return this._parent;
  316. }
  317. get nr_tiles() {
  318. return this._nr_tiles;
  319. }
  320. set nr_tiles(n) {
  321. this._nr_tiles = n;
  322. }
  323. get child1() {
  324. return this._child1;
  325. }
  326. get child2() {
  327. return this._child2;
  328. }
  329. set position(pos) {
  330. this._position = pos;
  331. }
  332. get position() {
  333. return this._position;
  334. }
  335. set window(w) {
  336. this._window = w;
  337. }
  338. get window() {
  339. return this._window;
  340. }
  341. set state(value) {
  342. this._state = value;
  343. }
  344. get state() {
  345. return this._state;
  346. }
  347. set monitor(m) {
  348. this._monitor = m;
  349. }
  350. get monitor() {
  351. return this._monitor ? this._monitor : 0;
  352. }
  353. get adjacents() {
  354. return this._adjacents;
  355. }
  356. set adjacents(adj) {
  357. this._adjacents = adj;
  358. }
  359. set workspace(w) {
  360. this._workspace = w;
  361. }
  362. get workspace() {
  363. return this._workspace;
  364. }
  365. get leaf() {
  366. return this._leaf;
  367. }
  368. set leaf(b) {
  369. this._leaf = b;
  370. }
  371. }