1-- returning true means we took responsibility for attaching to parent 2local swap_focus; 3 4local function save_wnd(wnd) 5 wnd.old = { 6 autocrop = wnd.autocrop, 7 shader = image_shader(wnd.canvas), 8 scalemode = wnd.scalemode, 9 showtbar = wnd.show_titlebar 10 }; 11end 12 13local function restore_wnd(wnd) 14 if (not wnd.old) then 15 return; 16 end 17 18 if (wnd.old.shader > 0) then 19 image_shader(wnd.canvas, wnd.old.shader); 20 else 21 image_shader(wnd.canvas, "DEFAULT"); 22 end 23 24 wnd.scalemode = wnd.old.scalemode; 25 wnd.autocrop = wnd.old.autocrop; 26 wnd.show_titlebar = wnd.old.showtbar; 27 wnd.displayhint_block_wh = false; 28 blend_image(wnd.canvas, wnd.canvas_opa and wnd.canvas_opa or 1.0); 29 wnd:set_title(); 30end 31 32-- "center" means normal behavior and the active shader for the window 33local function center_imgcfg(wnd) 34 show_image(wnd.anchor); 35 if (wnd.space and not wnd.space.layouter) then 36 print("dangling layouter", debug.traceback()); 37 end 38 39 if (wnd.space and not wnd.space.layouter.scaled) then 40 return; 41 end 42 43 wnd.displayhint_block_wh = true; 44 if (not wnd.old) then 45 save_wnd(wnd); 46 end 47 48 restore_wnd(wnd); 49end 50 51-- "side" means stretch, optional blend and a normal shader 52local function side_imgcfg(wnd, btime) 53 local vid = wnd.canvas; 54-- should the blend be animated, we add it as a timer so any unknown 55-- reset-image-transforms don't get in the way 56 if (btime) then 57 timer_add_periodic("layblend", 1, true, 58 function() 59 if (valid_vid(vid)) then 60 blend_image(vid, gconfig_get("autolay_sideopa"), btime); 61 end 62 end, true); 63 else 64 blend_image(wnd.canvas, gconfig_get("autolay_sideopa")); 65 end 66 67 if (wnd.space and not wnd.space.layouter.scaled) then 68 return; 69 end 70 71-- first cache original values so we can restore when reassigning 72 if (not wnd.old) then 73 save_wnd(wnd); 74 end 75 wnd.scalemode = "stretch"; 76 wnd.autocrop = false; 77 wnd.displayhint_block_wh = true; 78 79 image_set_txcos_default(wnd.canvas, wnd.origo_ll); 80 if (gconfig_get("autolay_shader")) then 81 shader_setup(wnd.canvas, "simple", gconfig_get("autolay_sideshdr")); 82 end 83 84-- swap to side, maybe disable titlebar 85 wnd:set_titlebar(gconfig_get("autolay_sidetbar")); 86end 87 88local function sel_h(wnd, mouse) 89 if (mouse and not dispatch_locked()) then 90 if (gconfig_get("autolay_selswap")) then 91 swap_focus(wnd); 92 end 93 end 94end 95 96-- trigger if window baseclass changes 97local function reg_h(wnd) 98 if (not wnd.space) then 99 return; 100 end 101 102 save_wnd(wnd); 103 if (wnd.space.children[2] == wnd) then 104 center_imgcfg(wnd); 105 else 106 side_imgcfg(wnd); 107 end 108end 109 110local function center_setup(space) 111 local lin = space:linearize(); 112-- nothing in focus region? then use selected window 113 local focus = space.children[2]; 114 if (space.children[1]) then 115 if (space.children[1].center_focus) then 116 focus = space.children[1]; 117 end 118 end 119 120 if (not focus) then 121 focus = space.selected; 122 end 123 124 if (focus) then 125 center_imgcfg(focus); 126 end 127 128-- nothing to do 129 if (#lin <= 1) then 130 return; 131 end 132 focus.center_focus = true; 133 134-- new "fair" window division of the remaining windows 135 local left = {}; 136 local right = {}; 137 local ctr = 1; 138 for i=1,#lin do 139 lin[i]:add_handler("register", reg_h); 140 lin[i]:add_handler("select", sel_h); 141 lin[i].vweight = 1.0; 142 if (lin[i] ~= focus) then 143 lin[i].center_focus = false; 144 side_imgcfg(lin[i]); 145 146 if (ctr % 2 == 1) then 147 table.insert(left, lin[i]); 148 else 149 table.insert(right, lin[i]); 150 end 151 ctr = ctr + 1; 152 end 153 end 154 155-- edge condition, ignore due to lost still left in structure can get us here 156 if (0 == #left and 0 == #right) then 157 return; 158 end 159 160 local ccount = right[1] and 3 or 2; 161 local mw = ccount * gconfig_get("autolay_centerw"); 162 local rw = (ccount - mw) / (ccount - 1); 163-- set horizontal weight to be 10 80 10 or 10 [80 yet expand to 90] 164 space.children = {left[1], focus, right[1]}; 165 focus.children = {}; 166 focus.weight = mw; 167 focus.parent = space; 168 169 left[1].parent = space; 170 left[1].weight = rw; 171 left[1].children = {}; 172 173 if (right[1]) then 174 right[1].weight = rw; 175 right[1].parent = space; 176 right[1].children = {}; 177 end 178 179 for i=2,#left do 180 left[i].parent = left[i-1]; 181 left[i].parent.children[1] = left[i]; 182 left[i].children = {}; 183 end 184 185 for i=2,#right do 186 right[i].parent = right[i-1]; 187 right[i].parent.children[1] = right[i]; 188 right[i].children = {}; 189 end 190 191 space.children[2]:select(); 192end 193 194-- all the edge cases to make sure center area is priority-focus and not 195-- running into multiple :select or :deselect calls on the same object 196-- breaking state 197local function center_focus(space) 198 local dst = space.children[2] and space.children[2] or space.children[1]; 199 if (not dst) then 200 return; 201 end 202 203 space.layouter.cw = dst.max_w; 204 space.layouter.ch = dst.max_h; 205 206 if (space.selected == dst) then 207 return; 208 end 209 210 if (space.selected) then 211 space.selected:deselect(); 212 end 213end 214 215-- find out how many levels of children a specific node has, needed to calc 216-- fair vertical weight when doing column- relayouting 217local function get_depth(node) 218 local depth = 0; 219 local last = node; 220 221 while node do 222 last = node; 223 depth = depth + 1; 224 node = node.children[1]; 225 end 226 227 return depth, last; 228end 229 230-- return true? then we take responsibility for marking selected and insertion 231local function center_added(space, wnd) 232 if (not wnd.old) then 233 save_wnd(wnd); 234 end 235 236 if (#space.children ~= 3) then 237 table.insert(space.children, wnd); 238 wnd.parent = space; 239 center_setup(space); 240 space:resize(); 241 center_focus(space); 242 return true; 243 end 244 245-- find the least used column 246 local ld = get_depth(space.children[1]); 247 local rd = get_depth(space.children[3]); 248 local dst; 249 local ind = ld < rd and 1 or 3; 250 251 local dst = space.children[ind]; 252 253-- go to the deepest slot 254 while true do 255 if (not dst.children[1]) then 256 break; 257 else 258 dst = dst.children[1]; 259 end 260 end 261 262-- insert there 263 dst.children[1] = wnd; 264 wnd.parent = dst; 265 266-- make sure sizes are fair 267 side_imgcfg(wnd); 268 269 hijack = true; 270 wnd:add_handler("register", reg_h); 271 wnd:add_handler("select", sel_h); 272 center_focus(space); 273 space:resize(); 274 return true; 275end 276 277local function center_lost(space, wnd, destroy) 278-- priority is balance the first entry 279 if (#space.children ~= 3) then 280 center_setup(space); 281 space:resize(); 282 center_focus(space); 283 return true; 284 end 285 286 287-- rebalance columns if the number of nodes in each gets unevenly distributed 288 local ld, ln = get_depth(space.children[1]); 289 local rd, rn = get_depth(space.children[3]); 290 291 if (ld > rd + 1) then 292 ln.parent.children[1] = nil; 293 rn.children[1] = ln; 294 ln.parent = rn; 295 elseif (rd > ld + 1) then 296 rn.parent.children[1] = nil; 297 ln.children[1] = rn; 298 rn.parent = ln; 299 end 300 301 if (not destroy) then 302 restore_wnd(wnd); 303 wnd:drop_handler("register", reg_h); 304 wnd:drop_handler("select", sel_h); 305 end 306 307 local mw = (3 - 3 * gconfig_get("autolay_centerw")) / 2; 308 309-- ugly edge condition, if we migrate or destroy a child to the first 310-- or last node, it can be promoted to first level with no event for us to 311-- latch on to, therefore force- reset the weights. 312 space.children[1].weight = mw; 313 space.children[3].weight = mw; 314 315 center_focus(space); 316 space:resize(); 317 return true; 318end 319 320local function center_resize(space, lin, evblock, wnd, cb) 321 322-- this layouter can only deal with tiling mode 323 if (space.mode ~= "tile") then 324 if (lin) then 325 for i,v in ipairs(lin) do 326 restore_wnd(v); 327 end 328 end 329 return false; 330 end 331 332 if (evblock) then 333-- always forward the dimensions of the center space, block if this 334-- corresponds to last known "column" size as a catch all for some async. races 335 if (space.children[2]) then 336 local mw = space.children[2].max_w; 337 local mh = space.children[2].max_h; 338 local dw = space.children[2].width - space.children[2].effective_w; 339 local dh = space.children[2].height - space.children[2].effective_h; 340 341 if (not space.layouter.scaled or (not space.layouter.cw or 342 (mw == space.layouter.cw and mh == space.layouter.ch))) then 343 cb(mw, mh, mw - dw, mh - dh); 344 end 345 end 346 return true; 347 end 348 if (not active_display().selected and 349 active_display():active_space() == space) then 350 if (space.children[2]) then 351 space.children[2]:select(); 352 elseif (space.children[1]) then 353 space.children[1]:select(); 354 end 355 end 356-- just forward to the next layer 357end 358 359swap_focus = function(sel) 360 local sp = active_display():active_space(); 361 local sw = active_display().selected; 362 local dw = sp.children[2]; 363 if (not sp or #sp.children < 2 or not sw or sw.space ~= sp) then 364 return; 365 end 366 367 local cw = dw.max_w; 368 local ch = dw.max_h; 369 local cx = dw.x; 370 local cy = dw.y; 371 dw.center_focus = false; 372 373-- swap and maipulate allowed / perceived dimensions as the swap function 374-- does not do that, otherwise we risk one additional space- resize 375 if (sw ~= dw) then 376 local rw = sw.max_w; 377 local rh = sw.max_h; 378 379 sw.last = dw; 380 sw.center_focus = true; 381 sw.max_w = cw; sw.max_h = ch; 382 sw:swap(dw, false, true); 383 center_imgcfg(sw); 384 sw:resize(cw, ch); 385 dw.x = sw.x; dw.y = sw.y; 386 dw.max_w = rw; dw.max_h = rh; 387 sw.x = cx; sw.y = cy; 388 side_imgcfg(dw, gconfig_get("wnd_animation")); 389 390-- mask the event propagation if we're running in scaled- mode 391 dw:resize(rw, rh, false, true); 392 dw:reposition(); 393 394-- "swap-in", use [last] reference for the window to swap 395 elseif (dw.last and dw.last.swap) then 396 local newc = dw.last; 397 newc.last = dw; 398 newc.center_focus = true; 399 dw.max_w = newc.max_w; 400 dw.max_h = newc.max_h; 401 newc.max_w = cw; 402 newc.max_h = ch; 403 dw:swap(newc, false, true); 404 center_imgcfg(newc); 405 newc:resize(cw, ch); 406 dw.x = newc.x; dw.y = newc.y; 407 newc.x = cx; newc.y = cy; 408 side_imgcfg(dw, gconfig_get("wnd_animation")); 409 dw:resize(dw.max_w, dw.max_h, false, true); 410 end 411 412 if (sel) then 413 sp.children[2]:select(); 414 end 415 416 sp:resize(true); 417end 418 419local function center_free(space) 420 local lst = space:linearize(); 421 for k,v in ipairs(lst) do 422 restore_wnd(v); 423 v.weight = 1.0; 424 v.vweight = 1.0; 425 v:drop_handler("register", reg_h); 426 v:drop_handler("select", sel_h); 427 end 428 429 space.layouter = nil; 430 space:resize(); 431end 432 433-- book layouter is a horizontal/flat layout where a specific ration is 434-- designated to the 'main' area (children[1]) and the rest will be 'evenly' 435-- divided across the remaining area. Instad of scaling, we crop by 436-- manipulating texture coordinates. The switch operation works like turning a 437-- page. All pages are resized to fit the main area, so there are no resize 438-- cascades. 439local function book_resize(space, lin, evblock, wnd, cb) 440end 441 442local function book_added(space, wnd) 443end 444 445local function book_lost(space, wnd, destroy) 446end 447 448local function book_free(space) 449 450end 451 452-- ONLY REGISTRATION / MENU SETUP BOILERPLATE BELOW -- 453 454local function copy(intbl) 455 local res = {}; 456 for k,v in pairs(intbl) do res[k] = v; end 457 return res; 458end 459 460local book_layouter = { 461 resize = book_resize, 462 added = book_added, 463 lost = book_lost, 464 cleanup = book_free, 465 block_grow = true, 466 block_merge = true, 467 block_collapse = true, 468 block_swap = true 469}; 470 471local centerscale_layouter = { 472 resize = center_resize, 473 added = center_added, 474 lost = center_lost, 475 cleanup = center_free, 476 477-- control all "normal" operations 478 block_grow = true, 479 block_merge = true, 480 block_collapse = true, 481 block_swap = true, 482 scaled = true, 483 block_rzevent = true 484}; 485 486local center_layouter = { 487 resize = center_resize, 488 added = center_added, 489 lost = center_lost, 490 cleanup = center_free, 491 492-- control all "normal" operations 493 block_grow = true, 494 block_merge = true, 495 block_collapse = true, 496 block_swap = true, 497 scaled = false, 498 block_rzevent = false 499}; 500 501local function set_layouter(space, layouter) 502 space.layouter = copy(layouter); 503 center_setup(space); 504 space:resize(); 505 center_focus(space); 506end 507 508local layouters = { 509{ 510 name = "center", 511 label = "Center Focus", 512 kind = "action", 513 handler = function() 514 set_layouter(active_display():active_space(), center_layouter); 515 end, 516}, 517{ 518 name = "center_scale", 519 label = "Center Focus (Force-Scale)", 520 kind = "action", 521 handler = function() 522 set_layouter(active_display():active_space(), centerscale_layouter); 523 end 524}, 525{ 526 name = "book", 527 label = "Book", 528 kind = "action", 529 eval = function() 530 return false; 531 end, 532 handler = function() 533 set_layouter(active_display():active_space(), book_layouter); 534 end 535}, 536{ 537 name = "none", 538 label = "Default", 539 kind = "action", 540 eval = function() 541 local d = active_display():active_space(); 542 return d.layouter ~= nil; 543 end, 544 handler = function() 545 local space = active_display():active_space(); 546 space.layouter.cleanup(space); 547 end 548} 549} 550 551-- recurse down the side columns and make their properties reflect side-cfg 552local function update_space(space) 553 for i,v in ipairs({1, 3}) do 554 local a = space.children[v]; 555 while (a) do 556 side_imgcfg(a); 557 a = a.children[1]; 558 end 559 end 560end 561 562-- add new config-db keys 563gconfig_register("autolay_sideopa", 0.5); 564gconfig_register("autolay_selswap", true); 565gconfig_register("autolay_centerw", 0.8); 566gconfig_register("autolay_sidetbar", true); 567gconfig_register("autolay_sideshdr", "noalpha"); 568gconfig_register("autolay_shader", false); 569 570local function reconfig() 571 for tiler in all_tilers_iter() do 572-- for all workspaces that uses this layouter, rebuild the layout to 573-- reflect changes in weights, titlebar, etc. 574 for i=1, 10 do 575 if (tiler.spaces[i] and tiler.spaces[i].layouter and 576 tiler.spaces[i].layouter.scaled and 577 tiler.spaces[i].layouter.resize == center_resize) then 578 update_space(tiler.spaces[i]); 579 end 580 end 581 end 582end 583 584-- and triggers for config change 585gconfig_listen("autolay_sideshdr", "autolayshdr", reconfig); 586gconfig_listen("autolay_sidetbar", "autolaytb", reconfig); 587gconfig_listen("autolay_sideopa", "autolayh", reconfig); 588 589-- and menu entries 590local laycfg = { 591{ 592 name = "sideopa", 593 label = "Side-Opacity(Scaled)", 594 kind = "value", 595 description = "Set the opacity that will be applied to side columns windows", 596 initial = function() return gconfig_get("autolay_sideopa"); end, 597 validator = gen_valid_float(0.0, 1.0), 598 handler = function(ctx, val) 599 gconfig_set("autolay_sideopa", tonumber(val)); 600 end 601}, 602{ 603 name = "selswap", 604 label = "Mouse-SelectSwap", 605 kind = "value", 606 description = "Change swap-on-click behavior", 607 set = {LBL_YES, LBL_NO}, 608 initial = function() 609 return gconfig_get("autolay_selswap") and LBL_YES or LBL_NO; 610 end, 611 handler = function(ctx, val) 612 gconfig_set("autolay_selswap", val == LBL_YES); 613 end 614}, 615{ 616 name = "centersz", 617 label = "Center Weight", 618 kind = "value", 619 description = "Control the display- relative center area allocation weight", 620 initial = function() 621 return gconfig_get("autolay_centerw"); 622 end, 623 hint = "0.5 .. 0.9", 624 validator = gen_valid_float(0.5, 0.9), 625 handler = function(ctx, val) 626 gconfig_set("autolay_centerw", tonumber(val)); 627 end 628}, 629{ 630 name = "sidetbar", 631 label = "Side Titlebar", 632 kind = "value", 633 description = "Control if the side columns windows should retain their titlebar or not", 634 set = {LBL_YES, LBL_NO}, 635 initial = function() 636 return gconfig_get("autolay_sidetbar") and LBL_YES or LBL_NO; 637 end, 638 handler = function(ctx, val) 639 gconfig_set("autolay_sidetbar", val == LBL_YES); 640 end 641}, 642{ 643 name = "sideshader", 644 label = "Side Shader", 645 kind = "value", 646 description = "Control if the side columns will get special processing or not", 647 set = {LBL_YES, LBL_NO}, 648 initial = function() 649 return gconfig_get("autolay_shader") and LBL_YES or LBL_NO; 650 end, 651 handler = function(ctx, val) 652 gconfig_set("autolay_shader", val == LBL_YES); 653 end 654}, 655{ 656 name = "sideshader_value", 657 label = "Side Shader-Select", 658 description = "Change the shader that will be used on entries in the side columns", 659 initial = function() 660 return gconfig_get("autolay_sideshdr"); 661 end, 662 eval = function() 663 return gconfig_get("autolay_shader"); 664 end, 665 kind = "value", 666 set = function() return shader_list({"effect", "simple"}); end, 667 handler = function(ctx, val) 668 local key, dom = shader_getkey(val, {"effect", "simple"}); 669 if (key ~= nil) then 670 gconfig_set("autolay_sideshdr", key); 671 end 672 end 673} 674}; 675 676menus_register("global", "settings/tools", 677{ 678 name = "autolayouts", 679 label = "Auto Layouting", 680 submenu = true, 681 description = "Change the look and feel of the tiling autolayouter", 682 kind = "action", 683 handler = laycfg 684}); 685 686menus_register("global", "tools", 687{ 688 name = "autolayouts", 689 label = "Auto Layouting", 690 submenu = true, 691 description = "Heuristics for automatic management of tile workspaces", 692 kind = "action", 693 handler = layouters 694}); 695 696menus_register("target", "window/swap", 697{ 698 name = "swap", 699 label = "Swap(Focus)", 700 kind = "action", 701 description = "Swap the window with the focus area", 702 eval = function() 703 return active_display().selected and 704 active_display().selected.space.layouter; 705 end, 706 handler = function() swap_focus(); end 707}); 708 709menus_register("target", "window/swap", 710{ 711 name = "swap_sel", 712 label = "Swap-Select(Focus)", 713 kind = "action", 714 description = "Swap the window with the focus area, and select it", 715 eval = function() 716 return active_display().selected and 717 active_display().selected.space.layouter; 718 end, 719 handler = function() 720 swap_focus(true); 721 end 722}); 723