Concepts
========

This page covers the architecture and conventions behind a VIStk project. For a
hands-on walkthrough, start with :doc:`quickstart`.

Project Structure
-----------------

A VIStk project has the following folder layout:

.. code-block:: text

   MyProject/
   ├── .VIS/
   │   ├── project.json        <- project registry (screens, versions, metadata)
   │   ├── Host.py             <- Host entry point (generated by VIS new; not user-editable)
   │   ├── Templates/          <- screen and element templates used by the CLI
   │   └── project.spec        <- PyInstaller spec file (generated on release)
   ├── Screens/
   │   └── <screen>/           <- UI element files, prefixed f_ (e.g. f_header.py)
   ├── modules/
   │   └── <screen>/           <- logic files, prefixed m_ (e.g. m_header.py)
   ├── Icons/                  <- .ico (Windows) or .xbm (Linux) icon files
   ├── Images/                 <- image assets used by VIMG
   ├── <screen>.py             <- main script for each screen
   └── dist/                   <- compiled binaries (created on release)

``project.json`` is the source of truth for the project. It stores screen names, script
paths, icons, descriptions, version numbers, and release configuration. It is managed
automatically by the CLI and by ``VINFO``/``Project`` --- do not edit it by hand.

App Lifecycle
-------------

VIStk supports two runtime models: **standalone** (original) and **Host-based** (tabbed).

Standalone mode
~~~~~~~~~~~~~~~

Each screen is its own Python process. Switching screens replaces the current process via
``os.execl``. This is the original VIStk behaviour and still works for any screen where
``tabbed`` is ``false``.

.. code-block:: python

   from VIStk.Objects import Root

   root = Root()
   root.screenTitle("MyScreen")
   root.WindowGeometry.setGeometry(width=800, height=600, align="center")

   # ... build your UI ...

   if __name__ == "__main__":
       while root.Active:
           root.update()

Host mode
~~~~~~~~~

The ``Host`` is a persistent process that owns the Tk root window. **It starts hidden in
the system tray by default** --- no window appears on launch. The user clicks the tray icon
to restore the window, which also opens the project's default screen. The Host never closes
unless the user selects **Quit** from the tray menu or code calls ``host.quit_host()``.
Screens marked ``tabbed: true`` in ``project.json`` open as ``Frame``-based tabs inside
the Host window. Standalone screens are spawned as subprocesses by the Host.

.. code-block:: python

   from VIStk.Objects import Host

   host = Host()
   host.screenTitle("MyApp")
   host.WindowGeometry.setGeometry(width=1200, height=800, align="center")

   # Open a tabbed screen (tab appears inside the Host window)
   host.open("Dashboard")

   # Open a standalone screen (spawned as a subprocess)
   host.open("Settings")

   while host.Active:
       host.tick_fps()
       host.update()

Screen navigation from anywhere in the app:

.. code-block:: python

   # Routes through Host if running, otherwise os.execl
   root.Project.open("WorkOrders")

**Key rules:**

- Do not call ``root.mainloop()`` --- this bypasses the ``while`` loop and prevents process
  switching.
- Do not call ``root.destroy()`` to quit --- set ``root.Active = False`` or call
  ``host.quit_host()``.
- Screen scripts must include ``if __name__ == "__main__":`` around the startup code so
  they can be imported as modules by the Host without executing the top-level loop.
- Use ``Project.open()`` instead of ``Project.load()`` when a Host may be running --- it
  routes correctly in both modes.

Screen Module Pattern
---------------------

Every tabbed screen must follow this pattern. Violating it will crash the Host on import:

.. code-block:: python

   # Module-level: only imports and pure data (no widget creation, no Tk calls)

   def loop():                  pass  # called every frame (standalone only)
   def configure_menu(menubar): pass  # contribute to HostMenu when tab is active
   def on_focused():            pass  # tab gained focus
   def on_unfocused():          pass  # tab lost focus

   def setup(parent):
       # ALL widget creation goes here
       from tkinter import ttk
       frame = ttk.Frame(parent)
       frame.place(relx=0, rely=0, relwidth=1, relheight=1)

   if __name__ == "__main__":
       from Screens.root import root, frame
       setup(frame)
       while root.Active:
           root.update()

The ``if __name__ == "__main__":`` guard is required for tabbed screens. When the Host
imports the module to call ``setup()``, the guard prevents the standalone loop from
running.

**Hook lookup priority:** If ``modules/<screen>/m_<screen>.py`` exists, the Host checks
it first for ``on_focused``, ``on_unfocused``, and ``configure_menu``. The screen script
is used as a fallback.

The f_element Pattern
---------------------

Elements are modular UI sections within a screen. Each element has a ``build(parent)``
function that receives the parent frame and places widgets into it.

.. code-block:: python

   # Screens/MyScreen/f_header.py

   widget_ref = None  # module global -- set by build(), read by loop()

   def build(parent):
       global f_elem, widget_ref
       f_elem = ttk.Frame(parent)
       f_elem.place(parent.Layout.cell(row, col))  # 0-indexed
       widget_ref = ttk.Label(f_elem, text="Header")
       widget_ref.pack()

The ``#%`` Marker System
------------------------

VIStk templates use ``#%`` comment markers as structural anchors. The ``stitch`` command
uses these to locate and rewrite import blocks in screen scripts.

.. warning::

   Do not delete or rename ``#%`` comment lines. They are not standard comments --- they are
   structural anchors that VIStk searches for by text pattern.

The two critical blocks:

.. code-block:: python

   #%Screen Elements
   from Screens.myscreen.f_header import *
   from Screens.myscreen.f_body import *
   #%Screen Grid

   #%Screen Modules
   from modules.myscreen.m_header import *
   from modules.myscreen.m_body import *
   #%Handle Arguments

``stitch`` replaces everything between ``#%Screen Elements`` and ``#%Screen Grid`` with
fresh imports from ``Screens/<screen>/f_*.py``, and everything between
``#%Screen Modules`` and ``#%Handle Arguments`` with fresh imports from
``modules/<screen>/m_*.py``.

configure_menu Pattern
----------------------

Screens contribute menu items to the Host's menu bar via the ``configure_menu`` hook.
Items are added when the tab activates and automatically cleared when it deactivates.

.. code-block:: python

   def configure_menu(menubar):
       menubar.set_screen_items([
           {"label": "Export PDF", "command": export_pdf},
           {"label": "Print",      "command": print_fn},
       ], label="Work Orders")

       menubar.set_screen_items([
           {"label": "About", "command": show_about},
       ], label="Help")

Call ``set_screen_items`` multiple times to contribute multiple cascades.

Project-wide items are set once in ``Host.py``:

.. code-block:: python

   host = Host()
   host.HostMenu.set_project_items([
       {"label": "New",  "command": new_fn},
       {"separator": True},
       {"label": "Exit", "command": host.quit_host},
   ], label="File")

project.json
------------

``project.json`` is managed by the CLI and ``VINFO``. Its structure:

.. code-block:: json

   {
       "metadata": {
           "title": "MyApp",
           "company": "My Company",
           "copyright": "My Company",
           "version": "0.1.0",
           "d_icon": "app",
           "dist_location": "dist",
           "host_script": "Host.py"
       },
       "defaults": {
           "default_screen": "Home"
       },
       "Screens": {
           "Home": {
               "script": "Home.py",
               "release": true,
               "icon": "home",
               "desc": "Main dashboard",
               "tabbed": true,
               "single_instance": false,
               "version": "0.1.0",
               "current": null
           }
       }
   }

Per-screen fields:

.. list-table::
   :header-rows: 1
   :widths: 20 15 65

   * - Field
     - Type
     - Description
   * - ``script``
     - string
     - Python script filename in the project root
   * - ``release``
     - bool
     - Whether this screen is compiled to its own binary
   * - ``icon``
     - string/null
     - Icon name (without extension) from ``Icons/``
   * - ``desc``
     - string
     - Screen description
   * - ``tabbed``
     - bool
     - ``true`` = Host tab, ``false`` = standalone subprocess
   * - ``single_instance``
     - bool
     - Prevent duplicate tabs when ``true``
   * - ``version``
     - string
     - Screen-specific version (``major.minor.patch``)
   * - ``current``
     - string/null
     - Free-form current-state string
