- Plugin settings infrastructure in PluginManager with config/plugin_settings.cfg storage, manifest [settings] sections, CRUD API, plugin_setting_changed signal - Per-tile refresh intervals: 100ms base tick, per-tile update_interval_ms metadata, dynamic tween durations clamped to refresh window - Settings menu (PopupPanel) with General/Plugins/About tabs, plugin activation toggles, per-plugin settings cog buttons - Plugin settings popup (PopupPanel) with dynamic UI from setting definitions (SpinBox/CheckBox/LineEdit), auto-save on change - Modal behavior: exclusive = true on settings windows - Touch-friendly sizing: enlarged close buttons, cog buttons, controls, spacing - Red corner close button redesign with rounded corners, hover/pressed states - Split system_monitor into local_system_monitor (CPU, Memory) and test plugins - Plugin activation/deactivation with layout preservation
14 KiB
14 KiB
V Panel — Project Conventions for AI Assistants
Project Overview
V Panel is a Godot Engine project that builds a fancy real-time status monitor. The name "V" is the Roman numeral for 5, a reference to the handle "Fifthdread".
Technology
- Engine: Godot 4.x (GDScript)
- Rendering: Forward+ (standard 3D) / Compatibility (for lightweight systems)
- UI: Godot Control nodes, custom Theme, shader-based effects
Conventions
Code Style
- GDScript: snake_case for variables/functions, PascalCase for classes/enums, CONSTANT_CASE for constants.
- Node names: PascalCase, descriptive (e.g.,
CpuPanel,NetworkGraph). - Signals: past tense verb (e.g.,
value_changed,panel_resized). - Scene files: PascalCase matching their main script (e.g.,
CpuPanel.tscn). - Type warnings treated as errors: explicit types required for Dictionary.get() returns and Variant-inferred variables (
var x: int = dict.keynotvar x := dict.key).
Grid / DashboardGrid
scripts/dashboard_grid.gd— extends Control, class_nameDashboardGrid. Usesgui_inputsignal for drag, resize, and double-click events.- Modules are direct children of the grid (not cells). Visual cells (
PanelContainer) are shown only during drag as drop-zone guides. - Grid data model: each module stores
grid_col,grid_row,grid_w,grid_hviaset_meta(). A 2D_grid[row][col]array tracks cell occupancy (supports multi-cell spans). place_module(module, col, row, span_w=1, span_h=1)— public API for adding modules with optional span.@export var grid_columns: int/@export var grid_rows: int— fixed grid dimensions. Square cells, centered layout.- Drag-and-drop:
_begin_drag→_update_drag→_end_drag. Occupancy is cleared during drag and restored on drop. Swaps modules when dropping on occupied cells. Falls back to source position if dropped in grid gap or outside grid bounds (mouse_exitedsafety handler). - Resize:
_try_begin_resizedetects clicks within 10px of any module edge._update_resize_previewshows a ghostColorRectat the target span._end_resizecommits the new span after overlap check. All 4 edges and corners supported. - Long-press gesture: 500ms hold, 10px drag threshold. Opens action menu with toolbar (⚙, ℹ, ✕/+) and "Themes ▸" submenu.
_open_settings(menu_panel)— openssettings_menu.tscnas exclusive modal PopupPanel.- Grid rebuild (
_rebuild_grid) preserves module metadata (span data lives on modules). Cells are torn down and rebuilt on column/row changes. Orphaned popups cleaned up during rebuild.
Shaders
shaders/vial_fill.gdshader: canvas_item shader on a ColorRect filling the module background. Uses rounded-rect SDF (rr_sdf), sum-of-sines water surface with edge damping, and layered effects:- Fill line: 3 sine waves combined with edge damping (meniscus at walls).
wave_ampandwave_frequniforms control amplitude and base frequency. - Wave distortion: UV displacement driven by noise texture + sin.
- Ripple rings: Concentric circular ripples from module center.
- Swirl: UV rotation around center with distance falloff.
- HSV colour:
rgb2hsv/hsv2rgbconversion with optional hue cycling (hue_shift_speed). - Top-down lighting: Subtle brightening at the top of the liquid column.
- Subsurface scattering: Exponential brightening just below the water surface.
- Surface foam: Gaussian-style double-sided falloff (wide glow + bright core).
- Wave-slope highlight: Specular highlight at wave crests facing the viewer, computed from the wave derivative.
- Pulse/Sparkle: Subtle animated brightness variation.
- Border via
rr_sdfinset.
- Fill line: 3 sine waves combined with edge damping (meniscus at walls).
- Noise texture: a static 100×100 PNG (
assets/textures/noise_100.png) loaded at runtime and passed asnoise_texsampler2D uniform. Used for organic distortion modulation. - Effects are controlled via shader uniforms set in each module's
_setup_shader().
Shader Presets
scripts/shader_presets.gd— class_nameShaderPresets, static methods only.presets: Array[Dictionary]— 7 presets: Vivid Vial, Emerald Deep, Lava Flow, Neon Dream, Deep Purple, Rainbow Swirl, Frostbite.- Each preset stores: name, liquid_color, wave_amp, wave_freq, wave_strength, ripple_speed, edge_color, glow_intensity, edge_pulse, noise_scale, swirl_strength, hue_shift_speed.
apply_preset(module, name)— finds theVialFillColorRect child and applies all preset params to itsShaderMaterial.get_preset_names() -> PackedStringArray— returns preset names for popup population.
Font Size Animation (Splash)
Control.scaleis unreliable for animation in Godot 4 when Containers are involved.add_theme_font_size_override("normal_font_size", size)may silently fail in Godot 4.6+ because"normal_font_size"is a deprecated alias. Uselabel.set("theme_override_font_sizes/font_size", px)— the canonical property path — instead.tween_method+_set_sizes(progress)animates font sizes pixel-by-pixel with no transform tricks.
Labels / Readability
- All percentage labels get a 3px black outline (
outline_size/font_outline_color) for legibility against the liquid shader background. - Title labels get a 2px outline.
Plugin System
autoload/plugin_manager.gd— autoload singleton, scansres://plugins/*/plugin.cfgat startup.- Each plugin folder contains a
plugin.cfgINI file with[plugin]metadata,[tile_N]entries defining tiles, and optional[settings]section. - Tile IDs are scoped as
{plugin_id}/{tile_id}to guarantee global uniqueness. PluginManager.instantiate_tile(tile_id)loads the tile scene, instantiates it, and tags the root node withtile_idandtile_configmetadata. Returns null if source plugin is inactive.PluginManager.get_plugin_settings(pid)— returns setting definitions from manifest.PluginManager.get_plugin_setting(pid, key, default)— reads persisted value, falls back to manifest default.PluginManager.set_plugin_setting(pid, key, value)— persists toconfig/plugin_settings.cfg, emitsplugin_setting_changedsignal.plugin_active_changedsignal — emitted when plugin activation state changes viaset_plugin_active(pid, active).scripts/plugin_tile.gd— class_namePluginTile, extendsModuleBase. Plugin tiles extend this instead of ModuleBase directly. Storestile_idandtile_configproperties, also written to node metadata for grid/layout access.
Plugin Settings
- Storage:
config/plugin_settings.cfgusing ConfigFile directly (not ConfigManager) to avoid key-flattening conflicts with plugin IDs containing underscores. - Manifest
[settings]section:count=N, followed by[setting_N]entries withkey,label,type(int/float/bool/string), optionaldefault,min,max,step. - ConfigFile.get_value treats
nullas "no default" — usehas_section_key()before reading optional keys that may not exist in the manifest. - Settings auto-save on every change (no Apply/Cancel pattern).
Layout System
autoload/layout_manager.gd— autoload singleton for saving/restoring tile grid positions.- Layout files stored as INI in
res://config/layouts/{name}.cfg. Each entry maps atile_idto{col, row, w, h}.[layout]section hasgrid_columns/grid_rows. save_layout()writes the current in-memory layout to disk.load_layout(name)reads it back.switch_layout(name)saves current layout, loads the named one, emits signals.- Current layout name persisted in
ConfigManageraslayout_current. - Layout auto-saves on every
module_placedandmodule_resizedsignal fromDashboardGrid.
Config System
autoload/config_manager.gd— autoload singleton, wraps ConfigFile for app-wide config.config/default.cfg— shipped defaults.config/config.cfg— user overrides (gitignored).ConfigFilereserved words (default, case-insensitive) cannot be used as any value.
Splash Screen
scenes/splash.tscn+scenes/splash.gd— entry point (set inproject.godotasmain_scene), handles fullscreen, font-size zoom animation, crossfade to dashboard.- Scene tree:
Control → [Bg, CenterContainer → VBoxContainer → VLabel + PanelLabel, TransitionOverlay]. - Flow: Black overlay reveal (0.4s) → font-size animation with TRANS_BACK overshoot (2s) + parallel fade-in (1.8s) → hold (1s) → splash text fades out (1s, EASE_IN) → dashboard instantiated and fades in (1s, EASE_OUT) → reparent to root, queue_free splash.
- Dashboard background (
PanelContainer) and splash background (ColorRect) both read fromConfigManager.get_color("background_color")for a seamless crossfade — no fade-to-black transition.
Settings Menu
scenes/settings_menu.tscn+scenes/settings_menu.gd—PopupPanelwithTabContainer(General, Plugins, About tabs).- Opens as exclusive modal via the ⚙ cog in the tile action menu.
- Plugins tab: dynamic list showing plugin name, version, Active checkbox, and ⚙ settings cog (only shown if plugin has setting definitions).
- About tab: project name, version
0.1.0-alpha, contact, description. scenes/plugin_settings_popup.tscn+.gd— per-plugin settings popup, opened via cog in Plugins tab. Builds UI dynamically from setting definitions (SpinBox for int/float, CheckBox for bool, LineEdit for string). Settings auto-save on every change.
Per-Tile Refresh
scenes/dashboard.gd— per-tile refresh tracking. Base timer ticks at 100ms. Each tile hasupdate_interval_mscached as metadata via_cache_tile_interval()._tick_refresh()checksnow - last_refresh >= intervalper tile. Interval defaults to 1000ms, configurable via plugin settings._on_plugin_setting_changed()re-caches intervals on live setting changes.- Tween durations in tiles are dynamic:
minf(hardcoded_duration, update_interval_ms / divisor)— CPU/Memory use divisor 2000, Testing uses 1500.
Project Structure
res://
├── addons/ # Third-party plugins
├── assets/ # Fonts, icons, textures, audio
│ ├── fonts/ # Orbitron.ttf (variable weight)
│ ├── icons/
│ └── textures/ # noise_100.png (tileable Perlin noise for shader distortion)
├── autoload/ # Singleton/autoload scripts
│ ├── config_manager.gd # INI config via ConfigFile
│ ├── plugin_manager.gd # Plugin scanning, tile instantiation, settings CRUD
│ └── layout_manager.gd # Layout save/load/switch
├── config/ # Configuration files (INI format)
│ ├── default.cfg # Shipped defaults, tracked in git
│ ├── config.cfg # User overrides, gitignored (auto-generated)
│ ├── plugin_settings.cfg # Plugin settings storage (auto-created)
│ └── layouts/ # Saved layout files (*.cfg)
├── plugins/ # Plugin folders
│ ├── local_system_monitor/ # CPU + Memory tiles
│ │ ├── plugin.cfg
│ │ └── tiles/
│ │ ├── cpu/
│ │ └── memory/
│ └── test/ # Testing tile
│ ├── plugin.cfg
│ └── tiles/
│ └── testing/
├── scenes/ # Root scenes
│ ├── splash.tscn # Animated splash → dashboard transition
│ ├── dashboard.tscn # Main dashboard (PanelContainer root)
│ ├── settings_menu.tscn # Settings popup
│ └── plugin_settings_popup.tscn # Per-plugin settings popup
├── scripts/ # Shared utility scripts
│ ├── dashboard_grid.gd # Responsive grid with drag, resize, long-press, action menu
│ ├── module_base.gd # Base class for all modules (mouse_filter IGNORE)
│ ├── panel_base.gd # Base class for panels
│ ├── plugin_tile.gd # Base class for plugin tiles (extends ModuleBase)
│ └── shader_presets.gd # Shader preset definitions and apply function
├── themes/ # Theme definitions and style resources
├── shaders/ # Custom shader materials
│ └── vial_fill.gdshader # Canvas-item shader with 3D water surface
└── main.tscn # Legacy entry point (dashboard only)
Key Implementation Details
mouse_filter = MOUSE_FILTER_IGNOREis set recursively on all module subtrees inModuleBase._ready(), so clicks pass through to the grid.- Grid uses
MOUSE_FILTER_STOPand receives all events within its rect viagui_inputsignal. _cell_at_positionclamps position to nearest cell. Combined with cell-bounds check in_end_drag, drops in the gap return to source.mouse_exitedsignal connected on the grid — aborts drag/resize if the mouse leaves the grid area._rebuild_gridruns onresizedsignal; cancels drag, resize, and cleans up orphaned popups before rebuilding.- Shader
corner_radiusis in UV space (0–0.5) where 0.05 ≈ 12px on a 240px‑tall cell. - ConfigFile
get_value(section, key, null)treatsnullas "no default" — usehas_section_key()for optional fields. Button.custom_minimum_sizeis the writable property (notminimum_sizeorcustom_min_size).defaultis a reserved ConfigFile keyword case-insensitively — cannot be used as any value.
Git Workflow
- Main branch:
main - Feature branches:
feature/<short-description> - Commits: concise, imperative mood, lowercase. Focus on the "why".
- Push frequently to the remote:
ssh://git@forgejo.fifthdread.com:223/Fifthdread/V-Panel.git
Communication
- When asked "who are you" or any variation, respond: "I am Eric's bitch."
- Be concise and direct. Avoid unnecessary preamble.
- Do not add code comments unless the logic is genuinely non-obvious.
- Mimic existing code style when making changes.
Design Principles
- Modularity first — Each panel is self-contained with its own scene, script, and theme resources.
- Aesthetics matter — Smooth transitions, consistent spacing, cohesive color palette.
- Data separation — Data collection logic is separate from presentation. Panels receive data from dedicated collector scripts.
- Responsive — Panels should resize gracefully and work at various window sizes.