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`` --- persistent ``Root`` subclass; hides to system tray on window close; never destroys - Registers 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 stop`` or tray Quit fully shuts it down - Thread-safe cross-thread call queue (``queue.SimpleQueue``) polled by ``_poll_main_queue``; pystray and IPC threads never call Tkinter directly - ``_HOST_INSTANCE`` module-level singleton; ``Project.open()`` checks it to route navigation TabManager and TabBar --------------------- - ``TabManager`` --- ``Frame`` subclass that owns the tab strip and content area - ``TabBar`` --- row of clickable tabs; flat buttons with configurable background colours; active/inactive/hover states; close button per tab; vertical separator between tabs - Tab 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 Screen Navigation ----------------- - ``host.open(screen)`` --- unified navigation; tabbed screens open as Frame tabs; standalone screens open as ``Toplevel`` windows - ``TabManager.open_tab`` / ``TabManager.close_tab`` --- full tab lifecycle with ``setup()``, ``on_activate()``, ``on_deactivate()`` hooks - ``__VIS_CLOSE__:`` IPC message --- a screen can ask the Host to close itself IPC --- - ``send_to_host(project_title, message)`` --- sends any message to a running Host via localhost TCP - Host writes its port to ``%TEMP%/_vis_host.port`` on startup; removed on quit - Messages: screen name (open), ``__VIS_QUIT__`` (shut down), ``__VIS_CLOSE__:`` (close one screen) Screen Hooks ------------ - ``setup(parent)`` --- called with the tab Frame; all widget creation must be inside - ``configure_menu(menubar)`` --- called when tab activates; items cleared on deactivation - ``on_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()`` so ``stitch()`` cannot overwrite them - Widget creation sections (``#%Screen Grid``, ``#%Screen Elements``) placed inside ``setup(parent)`` to avoid import side-effects - Standalone entry point uses ``if __name__ == "__main__":`` guard - ``_replace_section`` regex fixed for adjacent ``#%`` markers VIS Commands ------------ - ``VIS stop`` --- sends ``__VIS_QUIT__`` to a running Host via IPC - ``VIS `` --- starts Host if not running, sends default screen via IPC - ``VIS `` --- starts Host, sends screen name; no longer falls back to ``os.execl`` - ``VIS 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.py`` generated into ``.VIS/Host.py`` instead of the project root - ``default_screen`` stored under ``defaults.default_screen`` in ``project.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 Right-Click Context Menu ----------------------------- - **Open in new window**, **Force refresh**, and **Close** - Open in new window creates a new ``DetachedWindow`` - Force refresh re-imports and re-runs ``setup(parent)`` Tab Drag-to-Detach ------------------- - Releasing a dragged tab outside all registered ``TabBar`` instances fires ``TabBar.on_drag_detach`` - Host closes the tab from the main ``TabManager`` and opens it in a new ``DetachedWindow`` Tab Drag-to-Merge ------------------ - All live ``TabBar`` instances register in ``_TABBAR_REGISTRY`` - During drag motion, cursor is checked against all registered bars - On release over a different ``TabBar``, ``on_drag_merge`` fires - Tab is closed in source manager and re-opened in the receiving manager DetachedWindow -------------- - Wraps a ``Toplevel`` + ``TabManager`` for popped-out or drag-detached tabs - Closing the window runs ``on_unfocused`` on all tabs before destroying them - ``Host._do_quit()`` closes all ``DetachedWindow`` instances before tearing down main window - Contains its own ``HostMenu``, ``InfoRow``, and window icon - Sized to match Host window; positioned so cursor lands on the tab button at the same drag offset Drag Ghost Window ----------------- - Semi-transparent ``overrideredirect(True)`` ghost ``Toplevel`` follows cursor during drag - Replicates 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 ```` event, enforcing ``minsize``/``maxsize`` Screen Lifecycle Additions -------------------------- - ``Screen.close()`` --- sends ``__VIS_CLOSE__`` via IPC - ``Project.set_default_screen(name)`` --- persists to ``project.json`` - ``newScreen`` prompts whether the new screen should be tabbed Per-Screen Characteristic Info ------------------------------ - ``TabManager.set_tab_info(name, text_or_var)`` --- set a characteristic string; accepts ``str`` or ``tk.StringVar`` - Title 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)`` suffix - ``base_name`` maps display name back to the screen registry entry Hook Rename ----------- - ``on_activate()`` / ``on_deactivate()`` renamed to ``on_focused()`` / ``on_unfocused()`` - Hooks now looked up in ``modules//m_.py`` first; screen script as fallback Dependencies Added ------------------ - ``pystray`` --- cross-platform system tray support Bug Fixes --------- - ``TabBar._btn_click`` drag suppression now works correctly - ``Layout.rowSize`` / ``colSize`` no longer mutate the caller's list - Tab insertion positions corrected for ``_reorder_to_idx`` - Ghost cursor alignment preserved via ``_drag_btn_offset_x/y`` - Empty ``TabBar`` shows 28px drop-zone strip with hover highlight