0.4.0 — Host and Tabbed Screens#
Released
The 0.4.0 release introduced the Host-based tabbed application model, IPC communication, and drag-to-reorder/detach/merge tab interactions.
Host Object#
Host— persistentRootsubclass; hides to system tray on window close; never destroysRegisters itself in the Windows startup registry on first run (
_register_startup)Always the parent process and sole owner of the Tk root window
Closing the window hides to tray;
VIS stopor tray Quit fully shuts it downThread-safe cross-thread call queue (
queue.SimpleQueue) polled by_poll_main_queue; pystray and IPC threads never call Tkinter directly_HOST_INSTANCEmodule-level singleton;Project.open()checks it to route navigation
TabManager and TabBar#
TabManager—Framesubclass that owns the tab strip and content areaTabBar— row of clickable tabs; flat buttons with configurable background colours; active/inactive/hover states; close button per tab; vertical separator between tabsTab buttons show the screen icon (16x16 PIL image) to the left of the screen name
Full hover behaviour: hovering the tab name changes both name and close button together; hovering close alone changes only the close button to IndianRed
IPC#
send_to_host(project_title, message)— sends any message to a running Host via localhost TCPHost writes its port to
%TEMP%/<ProjectTitle>_vis_host.porton startup; removed on quitMessages: screen name (open),
__VIS_QUIT__(shut down),__VIS_CLOSE__:<name>(close one screen)
Screen Hooks#
setup(parent)— called with the tab Frame; all widget creation must be insideconfigure_menu(menubar)— called when tab activates; items cleared on deactivationon_activate()/on_deactivate()— lifecycle hooks on tab focus change
Note
on_activate / on_deactivate were renamed to on_focused / on_unfocused
later in this release cycle. See Hook rename below.
Screen Template#
Hook stubs placed before
setup()sostitch()cannot overwrite themWidget creation sections (
#%Screen Grid,#%Screen Elements) placed insidesetup(parent)to avoid import side-effectsStandalone entry point uses
if __name__ == "__main__":guard_replace_sectionregex fixed for adjacent#%markers
VIS Commands#
VIS stop— sends__VIS_QUIT__to a running Host via IPCVIS <ProjectName>— starts Host if not running, sends default screen via IPCVIS <ProjectName> <ScreenName>— starts Host, sends screen name; no longer falls back toos.execlVIS new— prompts for default screen name after project creation
Project Creation#
Project name defaults to the current folder name
Name validated against reserved VIS commands
Host.pygenerated into.VIS/Host.pyinstead of the project rootdefault_screenstored underdefaults.default_screeninproject.json
Tab Drag-to-Reorder#
Tabs can be dragged left or right to reorder
8-pixel motion threshold distinguishes a drag from a click
Click action suppressed when a drag occurred in the same press
Tab Drag-to-Detach#
Releasing a dragged tab outside all registered
TabBarinstances firesTabBar.on_drag_detachHost closes the tab from the main
TabManagerand opens it in a newDetachedWindow
Tab Drag-to-Merge#
All live
TabBarinstances register in_TABBAR_REGISTRYDuring drag motion, cursor is checked against all registered bars
On release over a different
TabBar,on_drag_mergefiresTab is closed in source manager and re-opened in the receiving manager
DetachedWindow#
Wraps a
Toplevel+TabManagerfor popped-out or drag-detached tabsClosing the window runs
on_unfocusedon all tabs before destroying themHost._do_quit()closes allDetachedWindowinstances before tearing down main windowContains its own
HostMenu,InfoRow, and window iconSized to match Host window; positioned so cursor lands on the tab button at the same drag offset
Drag Ghost Window#
Semi-transparent
overrideredirect(True)ghostToplevelfollows cursor during dragReplicates the tab label and icon at 75% opacity
Thin coloured vertical insertion indicator shows where the tab will land
Dragged tab is dimmed while ghost is live; restored on release
InfoRow Widget#
Left: active screen name and version
Centre: project copyright string (auto-prepends year and copyright symbol)
Right: app version and live FPS counter
Layout Constraint Enforcement#
Layout.apply(widget, row, col, ...)places with absolute pixel coordinates and re-places on every parent<Configure>event, enforcingminsize/maxsize
Screen Lifecycle Additions#
Screen.close()— sends__VIS_CLOSE__via IPCProject.set_default_screen(name)— persists toproject.jsonnewScreenprompts whether the new screen should be tabbed
Per-Screen Characteristic Info#
TabManager.set_tab_info(name, text_or_var)— set a characteristic string; acceptsstrortk.StringVarTitle format:
"project: screen --- info"; tab label:"screen --- info"StringVar traces removed automatically on tab close
Multiple Instances of the Same Screen#
Opening an already-open screen creates a tab with
(2),(3)suffixbase_namemaps display name back to the screen registry entry
Hook Rename#
on_activate()/on_deactivate()renamed toon_focused()/on_unfocused()Hooks now looked up in
modules/<screen>/m_<screen>.pyfirst; screen script as fallback
Dependencies Added#
pystray— cross-platform system tray support
Bug Fixes#
TabBar._btn_clickdrag suppression now works correctlyLayout.rowSize/colSizeno longer mutate the caller’s listTab insertion positions corrected for
_reorder_to_idxGhost cursor alignment preserved via
_drag_btn_offset_x/yEmpty
TabBarshows 28px drop-zone strip with hover highlight