1-- 2-- a pulldown terminal that lives outside the normal window manager 3-- down/upside is that input handling and other features behave differently 4-- 5-- 6-- MISSING: 7-- mouse/scroll-lock control, mouse injection, cut/paste(?), option to swap 8-- in other windows 9-- 10 11local dstate = { 12 dir_x = 0, 13 dir_y = 1, 14 pos = "top", 15 ofs = 0, 16 width = 0.5, 17 height = 0.3, 18 shadow_color = {0xff, 0xff, 0xff} 19}; 20 21gconfig_register("dt_width", 0.5); 22gconfig_register("dt_height", 0.4); 23gconfig_register("dt_opa", 0.8); 24gconfig_register("dt_shadow", 0); 25gconfig_register("dt_shadow_ofs_x", 0); 26gconfig_register("dt_shadow_ofs_y", 0); 27gconfig_register("dt_shadow_opa", 0.3); 28 29-- synch main font and fallback font with the registry 30local function set_font() 31 local tbl = {gconfig_get("term_font")}; 32 local fbf = gconfig_get("font_fb"); 33 if (not resource(tbl[1], SYS_FONT_RESOURCE)) then 34 return; 35 end 36 37 if (resource(fbf, SYS_FONT_RESOURCE)) then 38 tbl[2] = fbf; 39 end 40 41 if (valid_vid(dstate.term, TYPE_FRAMESERVER)) then 42 target_fonthint(dstate.term, tbl, gconfig_get("term_font_sz") * FONT_PT_SZ, -1); 43 end 44end 45 46local function set_sz() 47 if (valid_vid(dstate.term, TYPE_FRAMESERVER)) then 48 target_fonthint(dstate.term, 49 gconfig_get("term_font_sz") * FONT_PT_SZ, -1); 50 end 51end 52 53local function set_hint() 54 if (valid_vid(dstate.term, TYPE_FRAMESERVER)) then 55 target_fonthint(dstate.term, -1, gconfig_get("term_font_hint")); 56 end 57end 58 59local function update_shadow() 60 if (not valid_vid(dstate.term)) then 61 return; 62 end 63 64 if (valid_vid(dstate.shadow_vid)) then 65 delete_image(dstate.shadow_vid); 66 end 67 68 if (gconfig_get("dt_shadow") < 1) then 69 return; 70 end 71 72 local props = image_surface_resolve(dstate.term); 73 local shadow_sz = gconfig_get("dt_shadow"); 74 dstate.shadow_vid = fill_surface(32, 32, unpack(dstate.shadow_color)); 75 link_image(dstate.shadow_vid, dstate.term); 76 blend_image(dstate.shadow_vid, gconfig_get("dt_shadow_opa")); 77 order_image(dstate.shadow_vid, 65531); 78 resize_image(dstate.shadow_vid, props.width + shadow_sz, props.height + shadow_sz); 79 move_image(dstate.shadow_vid, 80 gconfig_get("dt_shadow_ofs_x"), gconfig_get("dt_shadow_ofs_y")); 81end 82 83gconfig_listen("term_font", "pdterm", set_font); 84gconfig_listen("term_font_sz", "pdterm_sz", set_sz); 85gconfig_listen("term_font_hint", "pdterm_hint", set_hint); 86 87-- it's so cheap so just rebuild etc. on everything 88gconfig_listen("dt_shadow", "pdterm_shadow", update_shadow); 89gconfig_listen("dt_shadow_opa", "pdterm_shadow_opa", update_shadow); 90gconfig_listen("dt_shadow_ofs_x", "pdterm_shadow_ofs_x", update_shadow); 91gconfig_listen("dt_shadow_ofs_y", "pdterm_shadow_ofs_y", update_shadow); 92 93local function update_size() 94 if (valid_vid(dstate.term, TYPE_FRAMESERVER)) then 95 local disp = active_display(); 96 local neww = disp.width * gconfig_get("dt_width"); 97 local newh = disp.height * gconfig_get("dt_height"); 98 target_displayhint(dstate.term, neww, newh, 0, active_display().disptbl); 99 update_shadow(); 100 end 101end 102 103local dterm_cfg = { 104{ 105 label = "Width", 106 name = "width", 107 kind = "value", 108 hint = "(100 * val)%", 109 description = "Change the display relative window width", 110 validator = gen_valid_float(0.1, 1.0), 111 initial = function() return gconfig_get("dt_width"); end, 112 handler = function(ctx, val) 113 gconfig_set("dt_weight", tonumber(val)); 114 update_size(); 115 end 116}, 117{ 118 label = "Height", 119 name = "height", 120 kind = "value", 121 hint = "(100 * val)%", 122 description = "Change the display relative window height", 123 validator = gen_valid_float(0.1, 1.0), 124 initial = function() return gconfig_get("dt_width"); end, 125 handler = function(ctx, val) 126 gconfig_set("dt_height", tonumber(val)); 127 update_size(); 128 end 129}, 130{ 131 label = "Background Alpha", 132 name = "alpha", 133 kind = "value", 134 hint = "(0..1, 1=opaque)", 135 description = "Change the terminal background opacity", 136 validator = gen_valid_float(0.0, 1.0), 137 initial = function() return gconfig_get("dt_opa"); end, 138 handler = function(ctx, val) 139 gconfig_set("dt_opa", tonumber(val)); 140 if (valid_vid(dstate.term, TYPE_FRAMESERVER)) then 141 target_graphmode(dstate.term, gconfig_get("dt_opa")); 142 end 143 end 144}, 145{ 146 name = "shadow", 147 label = "Shadow Size (px)", 148 kind = "value", 149 hint = "(0..100, 0 = disabled)", 150 description = "Set the size of the window hard-shadow box", 151 validator = gen_valid_float(0.0, 100.0), 152 initial = function() return gconfig_get("dt_shadow"); end, 153 handler = function(ctx, val) 154 gconfig_set("dt_shadow", tonumber(val)); 155 gconfig_set("dt_shadow_ofs_x", -0.5 * val); 156 gconfig_set("dt_shadow_ofs_y", -0.5 * val); 157 end 158}, 159{ 160 name = "shadow_opa", 161 label = "Shadow Opacity", 162 kind = "value", 163 description = "Set the opacity of the window hard-shadow", 164 hint = "(0..1, 1=opaque)", 165 validator = gen_valid_float(0.0, 1.0), 166 initial = function() return gconfig_get("dt_shadow_opa"); end, 167 handler = function(ctx, val) 168 gconfig_set("dt_shadow_opa", tonumber(val)); 169 end 170}, 171{ 172 name = "shadow_ofset_x", 173 label = "Shadow Ofset (X)", 174 kind = "value", 175 hint = "-50..50", 176 description = "Set the window pixel- X offset for the shadow", 177 validator = gen_valid_float(-50, 50), 178 initial = function() return gconfig_get("dt_shadow_ofs_x"); end, 179 handler = function(ctx, val) 180 gconfig_set("dt_shadow_ofs_x", tonumber(val)); 181 end 182}, 183{ 184 name = "shadow_ofset_y", 185 label = "Shadow Ofset (Y)", 186 kind = "value", 187 hint = "-50..50", 188 description = "Set the window pixel- Y offset for the shadow", 189 validator = gen_valid_float(-50, 50), 190 initial = function() return gconfig_get("dt_shadow_ofs_y"); end, 191 handler = function(ctx, val) 192 gconfig_set("dt_shadow_ofs_y", tonumber(val)); 193 end 194} 195}; 196 197local atype = extevh_archetype("terminal"); 198 199local function drop() 200 reset_image_transform(dstate.term); 201 local props = image_surface_properties(dstate.term); 202 nudge_image(dstate.term, 203 -1 * dstate.dir_x * props.width, 204 -1 * dstate.dir_y * props.height, 205 gconfig_get("animation") 206 ); 207 blend_image(dstate.term, 0.0, gconfig_get("animation")); 208 dstate.active = false; 209 dispatch_toggle(false); 210 mouse_lockto(unpack(dstate.lock)); 211 212 target_displayhint(dstate.term, 0, 0, 213 bit.bor(TD_HINT_UNFOCUSED, TD_HINT_INVISIBLE)); 214end 215 216-- we intercept symbol- handling so our trigger path can be re-used 217-- but forward the rest to the new window 218local function ldisp(sym, iotbl, path) 219 if (not sym and not iotbl) then 220 drop(); 221 return; 222 end 223 224-- label translation 225 if (iotbl.label and atype.labels[iotbl.label]) then 226 iotbl.label = atype.labels[iotbl.label]; 227 end 228 229-- don't consume mouse here as we want to translate to the specific surface 230 if (iotbl.mouse) then 231 return false, sym, iotbl, path; 232 end 233 234 target_input(dstate.term, iotbl); 235 236-- run through the terminal archetype label binding 237 return true, sym, iotbl, path; 238end 239 240-- different rules apply to input and event response compared to normal windows 241-- we actually trust the terminal to be compliant here as it is launched 242-- authoritatively 243local function termh(source, status) 244 if (status.kind == "resized") then 245 resize_image(source, status.width, status.height); 246 update_shadow(); 247 elseif (status.kind == "terminated") then 248 if (dstate.active) then 249 drop(); 250 end 251 delete_image(source); 252 dstate.term = nil; 253 dstate.disp.lock_override = false; 254 end 255end 256 257local function dterm() 258-- if we're already toggled, then disable 259 if (dstate.active) then 260 drop(); 261 return; 262 end 263 264-- if we got a terminal running, prefer that 265-- or spawn a new one.. create anchor, attach, order, ... 266 local disp = active_display(); 267 local neww = disp.width * gconfig_get("dt_width"); 268 local newh = disp.height * gconfig_get("dt_height"); 269 270-- the spawn_terminal() function from global/open takes care of initial 271-- font setup, it's only for dynamic changes the rest is needed 272 if (not valid_vid(dstate.term)) then 273 local targ = terminal_build_argenv(); 274 dstate.term = launch_avfeed(targ, "terminal", 275 function(source, status) 276 if (status.kind == "preroll") then 277 update_size(); 278 set_font(); 279 target_graphmode(dstate.term, gconfig_get("dt_opa")); 280 target_updatehandler(source, termh); 281 end 282 end 283 ); 284 if (not valid_vid(dstate.term)) then 285 return; 286 end 287 dstate.disp = disp; 288 dstate.disp.lock_override = true; 289 end 290 291-- reattach to different output on switch or resize 292 if (dstate.disp ~= active_display()) then 293 dstate.disp.lock_override = false; 294 dstate.disp = active_display(); 295 dstate.disp.lock_override = true; 296 target_displayhint(dstate.term, neww, newh, 0, dstate.disp.disptbl); 297 rendertarget_attach(active_display(true), dstate.term, RENDERTARGET_DETACH); 298 update_size(); 299 end 300 301-- put us in the "special" overlay order range 302 order_image(dstate.term, 65532); 303 304-- center at hidden state non-dominant axis, account for user- padding 305-- do this the safe "all options rather than math" way due to the possible 306-- switching of user- config between spawns 307 if (dstate.dir_x ~= 0) then 308 neww = neww + dstate.ofs; 309 if (dstate.dir_x > 0) then 310 move_image(dstate.term, -neww, 0.5 * (disp.height - newh)); 311 nudge_image(dstate.term, neww, 0, gconfig_get("animation")); 312 else 313 move_image(dstate.term, disp.width, 0.5 * (disp.height - newh)); 314 nudge_image(dstate.term, -neww, 0, gconfig_get("animation")); 315 end 316 elseif (dstate.dir_y ~= 0) then 317 newh = newh + dstate.ofs; 318-- top 319 if (dstate.dir_y > 0) then 320 move_image(dstate.term, 0.5 * (disp.width - neww), -newh); 321 nudge_image(dstate.term, 0, newh, gconfig_get("animation")); 322 -- bottom 323 else 324 move_image(dstate.term, 0.5 * (disp.width - neww), disp.height); 325 nudge_image(dstate.term, 0, -newh, gconfig_get("animation")); 326 end 327 end 328 329 blend_image(dstate.term, 1.0, gconfig_get("animation")); 330 dstate.active = true; 331 dispatch_toggle(ldisp); 332 333-- since we don't have clipboard etc. mapping available we just toggle 334-- mouse input mode 335 target_input(dstate.term, { 336 kind = "digital", label = "MOUSE_FORWARD", 337 translated = true, 338 active = true, 339 devid = 8, subid = 8 340 }); 341 342 local v, f, w, s = mouse_lockto(dstate.term, 343 function(rx, ry, x, y, state, ind, act) 344 local props = image_surface_properties(dstate.term); 345 x = x - props.x; 346 y = y - props.y; 347 if (ind) then 348 target_input(dstate.term, { 349 kind = "digital", mouse = true, 350 active = act, devid = 0, subid = ind 351 }); 352 else 353 local iotbl = { 354 kind = "analog", mouse = true, 355 relative = false, devid = 0, subid = 0, 356 samples = {x} 357 }; 358 target_input(dstate.term, iotbl); 359 iotbl.subid = 1; 360 iotbl.samples[1] = y; 361 target_input(dstate.term, iotbl); 362 end 363 end, nil 364 ); 365 dstate.lock = {v, f, w, s}; 366 target_displayhint(dstate.term, 0, 0, 0); 367end 368 369menus_register("global", "tools", 370{ 371 name = "dterm", 372 label = "Drop-down Terminal", 373 kind = "action", 374 description = "A locked-input 'quake-style' terminal window", 375 handler = dterm 376}); 377 378menus_register("global", "settings/tools", 379{ 380 name = "dterm", 381 label = "Dropdown Terminal", 382 kind = "action", 383 submenu = true, 384 description = "Change how the dropdown terminal behaves", 385 handler = dterm_cfg 386}); 387