diff --git a/AGENTS.md b/AGENTS.md index 32f543b..a251bff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -18,6 +18,15 @@ V Panel is a Godot Engine project that builds a fancy real-time status monitor. - Signals: past tense verb (e.g., `value_changed`, `panel_resized`). - Scene files: PascalCase matching their main script (e.g., `CpuPanel.tscn`). +### 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 (wave distortion, ripple rings, swirl, HSV shift, top-down lighting, sparkle, foam line). +- Noise texture: a static 100×100 PNG (`assets/textures/noise_100.png`) loaded at runtime and passed as `noise_tex` sampler2D uniform. Used for organic distortion modulation. +- Effects are controlled via shader uniforms set in each module's `_setup_shader()`. + +### 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. + ### Project Structure ``` res:// @@ -31,7 +40,8 @@ res:// │ ├── cpu/ │ ├── memory/ │ ├── network/ -│ └── disk/ +│ ├── disk/ +│ └── testing/ ├── scenes/ # Root scenes (dashboard, etc.) ├── scripts/ # Shared utility scripts ├── themes/ # Theme definitions and style resources diff --git a/README.md b/README.md index 8494815..e40a62f 100644 --- a/README.md +++ b/README.md @@ -6,16 +6,24 @@ A fancy status monitor built with the Godot Engine. V stands for the Roman numer V Panel is a visually rich, real-time status monitoring dashboard built entirely in Godot. Designed to be both functional and aesthetically polished, it serves as a showcase for Godot's UI capabilities outside of gaming — custom shaders, smooth animations, reactive layouts, and system integration. -## Goals +## Features -- **Beautiful UI** — Smooth animations, custom shaders, stylish typography, and a cohesive dark theme. -- **Real-time data** — Display live system metrics (CPU, memory, disk, network, processes). -- **Modular panels** — Each metric or subsystem gets its own panel widget. Easy to add, remove, or rearrange. -- **Godot showcase** — Demonstrate what Godot can do for non-game applications. +- **Responsive grid dashboard** — Modules auto-arrange in a dynamic grid that adapts to window size. Drag-and-drop to rearrange. +- **Shader-based vial fill** — Each module uses a custom `vial_fill.gdshader` instead of progress bars. Features sum-of-sines water surface animation, edge-damped meniscus, wave distortion, ripple rings, swirl, HSV colour shifting, top-down lighting, sparkle effects, and a foam surface line. +- **Live system monitoring** — Reads CPU usage from `/proc/stat` and memory usage from `/proc/meminfo` on Linux. Extensible module system for adding new collectors. +- **Smooth animations** — All fill level transitions use tweens with cubic easing. + +## Modules + +| Module | Data Source | Description | +|--------|-------------|-------------| +| CPU | `/proc/stat` | Real-time CPU usage percentage | +| Memory | `/proc/meminfo` | Real-time memory usage percentage | +| Testing | N/A (cycles 0–100%) | Shader visual testing with stepped fill levels | ## Project Status -Early development. The framework and initial modules are being designed now. +Active development. Core framework, drag-and-drop, CPU/memory collectors, and shader-based vial fill are implemented. More system modules (disk, network) are planned. ## License diff --git a/assets/textures/noise_100.png b/assets/textures/noise_100.png new file mode 100644 index 0000000..e59457d Binary files /dev/null and b/assets/textures/noise_100.png differ diff --git a/panels/.gitkeep b/panels/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/panels/cpu/cpu_module.gd b/panels/cpu/cpu_module.gd index 3f162a5..6c06d2c 100644 --- a/panels/cpu/cpu_module.gd +++ b/panels/cpu/cpu_module.gd @@ -46,6 +46,7 @@ func _setup_shader() -> void: mat.shader = preload("res://shaders/vial_fill.gdshader") mat.set_shader_parameter("liquid_color", Color(0.2, 0.5, 0.8, 1.0)) mat.set_shader_parameter("fill", 0.0) + mat.set_shader_parameter("noise_tex", load("res://assets/textures/noise_100.png")) vial_fill.material = mat @@ -61,5 +62,9 @@ func _style() -> void: add_theme_stylebox_override("panel_focused", panel) title_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.8, 1.0)) + title_label.add_theme_constant_override("outline_size", 2) + title_label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.5)) label.add_theme_color_override("font_color", Color(0.9, 0.9, 1.0, 1.0)) label.add_theme_font_size_override("font_size", 48) + label.add_theme_constant_override("outline_size", 3) + label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.7)) diff --git a/panels/memory/memory_module.gd b/panels/memory/memory_module.gd index ab71b2b..ce56849 100644 --- a/panels/memory/memory_module.gd +++ b/panels/memory/memory_module.gd @@ -46,6 +46,7 @@ func _setup_shader() -> void: mat.shader = preload("res://shaders/vial_fill.gdshader") mat.set_shader_parameter("liquid_color", Color(0.2, 0.7, 0.4, 1.0)) mat.set_shader_parameter("fill", 0.0) + mat.set_shader_parameter("noise_tex", load("res://assets/textures/noise_100.png")) vial_fill.material = mat @@ -61,5 +62,9 @@ func _style() -> void: add_theme_stylebox_override("panel_focused", panel) title_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.8, 1.0)) + title_label.add_theme_constant_override("outline_size", 2) + title_label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.5)) label.add_theme_color_override("font_color", Color(0.9, 0.9, 1.0, 1.0)) label.add_theme_font_size_override("font_size", 48) + label.add_theme_constant_override("outline_size", 3) + label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.7)) diff --git a/panels/testing/testing_module.gd b/panels/testing/testing_module.gd new file mode 100644 index 0000000..fb923ee --- /dev/null +++ b/panels/testing/testing_module.gd @@ -0,0 +1,65 @@ +extends ModuleBase +class_name TestingModule + + +@onready var title_label: Label = %Title +@onready var label: Label = %Label +@onready var vial_fill: ColorRect = %VialFill + +var _displayed_fill: float = 0.0 +var _fill_tween: Tween +var _cycle: int = 0 +var _levels: Array[float] = [0.0, 0.25, 0.50, 0.75, 1.0] + + +func initialize() -> void: + module_title = "Testing" + _setup_shader() + _style() + + +func refresh(data: Dictionary) -> void: + _cycle = (_cycle + 1) % _levels.size() + var target: float = _levels[_cycle] + + label.text = "%d%%" % roundi(target * 100.0) + + if _fill_tween and _fill_tween.is_valid(): + _fill_tween.kill() + _fill_tween = create_tween() + _fill_tween.tween_method(_set_fill, _displayed_fill, target, 0.6).set_ease(Tween.EASE_OUT).set_trans(Tween.TRANS_CUBIC) + + +func _set_fill(value: float) -> void: + _displayed_fill = value + var mat := vial_fill.material as ShaderMaterial + if mat: + mat.set_shader_parameter("fill", value) + + +func _setup_shader() -> void: + var mat := ShaderMaterial.new() + mat.shader = preload("res://shaders/vial_fill.gdshader") + mat.set_shader_parameter("liquid_color", Color(0.6, 0.3, 0.8, 1.0)) + mat.set_shader_parameter("fill", 0.0) + mat.set_shader_parameter("noise_tex", load("res://assets/textures/noise_100.png")) + vial_fill.material = mat + + +func _style() -> void: + var panel := StyleBoxFlat.new() + panel.bg_color = Color.TRANSPARENT + panel.corner_radius_top_left = 12 + panel.corner_radius_top_right = 12 + panel.corner_radius_bottom_right = 12 + panel.corner_radius_bottom_left = 12 + add_theme_stylebox_override("panel", panel) + add_theme_stylebox_override("panel_focused", panel) + + title_label.add_theme_color_override("font_color", Color(0.7, 0.7, 0.8, 1.0)) + title_label.add_theme_constant_override("outline_size", 2) + title_label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.5)) + label.add_theme_color_override("font_color", Color(0.9, 0.9, 1.0, 1.0)) + label.add_theme_font_size_override("font_size", 48) + label.add_theme_constant_override("outline_size", 3) + label.add_theme_color_override("font_outline_color", Color(0.0, 0.0, 0.0, 0.7)) diff --git a/panels/testing/testing_module.tscn b/panels/testing/testing_module.tscn new file mode 100644 index 0000000..196574b --- /dev/null +++ b/panels/testing/testing_module.tscn @@ -0,0 +1,41 @@ +[gd_scene format=3] + +[ext_resource type="Script" path="res://panels/testing/testing_module.gd" id="1"] +[ext_resource type="Shader" path="res://shaders/vial_fill.gdshader" id="2"] + +[node name="TestingModule" type="PanelContainer"] +anchors_preset = 0 +script = ExtResource("1") + +[node name="VialFill" type="ColorRect" parent="."] +unique_name_in_owner = true +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +mouse_filter = 2 + +[node name="MarginContainer" type="MarginContainer" parent="."] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +theme_constant_overrides/margin_left = 12 +theme_constant_overrides/margin_top = 12 +theme_constant_overrides/margin_right = 12 +theme_constant_overrides/margin_bottom = 12 + +[node name="VBoxContainer" type="VBoxContainer" parent="MarginContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 + +[node name="Title" type="Label" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +text = "Testing" + +[node name="Spacer" type="Control" parent="MarginContainer/VBoxContainer"] +size_flags_vertical = 3 + +[node name="Label" type="Label" parent="MarginContainer/VBoxContainer"] +unique_name_in_owner = true +horizontal_alignment = 1 +size_flags_horizontal = 4 diff --git a/scenes/dashboard.gd b/scenes/dashboard.gd index 3db6b24..bf3ea3b 100644 --- a/scenes/dashboard.gd +++ b/scenes/dashboard.gd @@ -31,6 +31,10 @@ func _add_modules() -> void: grid.place_module(mem, 1, 0) _modules.append(mem) + var test := preload("res://panels/testing/testing_module.tscn").instantiate() + grid.place_module(test, 2, 0) + _modules.append(test) + var timer := Timer.new() timer.timeout.connect(_refresh_modules) timer.autostart = true diff --git a/shaders/vial_fill.gdshader b/shaders/vial_fill.gdshader index 168bf91..2d34a89 100644 --- a/shaders/vial_fill.gdshader +++ b/shaders/vial_fill.gdshader @@ -1,17 +1,57 @@ shader_type canvas_item; +// --- Fill control --- uniform float fill : hint_range(0.0, 1.0) = 0.0; - -uniform vec4 liquid_color : source_color = vec4(0.2, 0.5, 0.8, 1.0); uniform vec4 bg_color : source_color = vec4(0.08, 0.08, 0.12, 1.0); uniform vec4 border_color : source_color = vec4(0.2, 0.2, 0.28, 1.0); - -uniform float corner_radius : hint_range(0.0, 0.5) = 0.06; +uniform float corner_radius : hint_range(0.0, 0.5) = 0.05; uniform float border_width : hint_range(0.0, 0.1) = 0.008; - uniform float wave_amp : hint_range(0.0, 0.05) = 0.012; uniform float wave_freq : hint_range(0.0, 15.0) = 4.0; +// --- Liquid base color (modulated by effects below) --- +uniform vec4 liquid_color : source_color = vec4(0.2, 0.5, 0.8, 1.0); + +// --- Animation controls --- +uniform float time_factor : hint_range(0.0, 5.0) = 1.0; +uniform float wave_strength : hint_range(0.0, 0.5) = 0.05; +uniform float ripple_speed : hint_range(0.0, 5.0) = 1.0; + +// --- Glow / edge --- +uniform vec4 edge_color : source_color = vec4(0.3, 0.6, 1.0, 1.0); +uniform float glow_intensity : hint_range(0.0, 5.0) = 1.5; +uniform float edge_pulse : hint_range(0.0, 2.0) = 1.0; + +// --- Distortion --- +uniform sampler2D noise_tex : repeat_enable; +uniform float noise_scale : hint_range(0.0, 5.0) = 1.0; +uniform float swirl_strength : hint_range(-2.0, 2.0) = 0.5; +uniform float hue_shift_speed : hint_range(0.0, 5.0) = 0.0; + + +// ---------- helpers ---------- + +vec3 rgb2hsv(vec3 c) { + vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0); + vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g)); + vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r)); + float d = q.x - min(q.w, q.y); + float e = 1e-10; + return vec3(abs((q.w - q.y) / (6.0 * d + e) + q.z), d / (q.x + e), q.x); +} + +vec3 hsv2rgb(vec3 c) { + vec3 p = abs(fract(c.xxx + vec3(0.0, 1.0 / 3.0, 2.0 / 3.0)) * 6.0 - 3.0); + return c.z * mix(vec3(1.0), clamp(p - 1.0, 0.0, 1.0), c.y); +} + +vec2 get_swirl_uv(vec2 uv, float strength) { + vec2 center = vec2(0.5); + vec2 diff = uv - center; + float dist = length(diff); + float angle = atan(diff.y, diff.x) + strength * (0.5 - dist) * sin(TIME * 0.5); + return center + vec2(cos(angle), sin(angle)) * dist; +} float rr_sdf(vec2 p, vec2 half_size, float r) { vec2 d = abs(p) - half_size + r; @@ -19,42 +59,86 @@ float rr_sdf(vec2 p, vec2 half_size, float r) { } +// ---------- main ---------- + void fragment() { vec2 half_size = vec2(0.5, 0.5); vec2 uv_centered = UV - half_size; + float t = TIME * time_factor; + // --- rounded-rect mask --- float max_r = min(corner_radius, half_size.x); - - // Outer rounded rect float outer_d = rr_sdf(uv_centered, half_size, max_r); float outer = 1.0 - smoothstep(0.0, max(0.001, fwidth(outer_d)), max(outer_d, 0.0)); - // Inner (inset by border_width) float inner_r = max(0.0, max_r - border_width); float inner_hw = max(0.0, half_size.x - border_width); float inner_hh = max(0.0, half_size.y - border_width); float inner_d = rr_sdf(uv_centered, vec2(inner_hw, inner_hh), inner_r); float inner = 1.0 - smoothstep(0.0, max(0.001, fwidth(inner_d)), max(inner_d, 0.0)); - // Liquid fill with a gentle animated wave at the surface. - // UV.y = 0 is top, UV.y = 1 is bottom. fill = 0 → empty, fill = 1 → full. - float wave = sin(UV.x * wave_freq * 6.283 + TIME * 1.5) * wave_amp; - float fill_line = 1.0 - fill + wave; + // --- liquid fill line (sum-of-sines + edge damping) --- + float wf = wave_freq * 6.283; + float w1 = sin(UV.x * wf + TIME * 5.0); + float w2 = sin(UV.x * wf * 1.7 + TIME * 7.0 + 1.3) * 0.5; + float w3 = sin(UV.x * wf * 0.6 + TIME * 2.5 + 2.9) * 0.3; + float wave_sum = (w1 + w2 + w3) / 1.8; + + // damp wave near left/right walls for meniscus effect + float edge_damp = smoothstep(0.0, 0.08, UV.x) * smoothstep(0.0, 0.08, 1.0 - UV.x); + wave_sum *= edge_damp; + + float fill_line = 1.0 - fill + wave_sum * wave_amp; float liquid = smoothstep(fill_line - 0.001, fill_line + 0.001, UV.y); liquid *= inner; - // Base background + // --- build effect colour for the liquid region --- + vec2 uv = UV; + float noise_val = texture(noise_tex, uv * noise_scale + t * 0.05).r; + + // wave distortion + uv.y += sin(uv.x * 10.0 + t) * wave_strength * noise_val; + uv.x += cos(uv.y * 10.0 + t * 0.5) * wave_strength * noise_val; + + // ripple rings + float dist_center = distance(uv, vec2(0.5)); + uv += sin(dist_center * 20.0 - t * ripple_speed) * (wave_strength * 0.5 * noise_val); + + // swirl + uv = get_swirl_uv(uv, swirl_strength); + + // sample the ColorRect texture (water image if assigned, else 1x1 white) + vec4 tex = texture(TEXTURE, uv); + + // hue-shift the base liquid colour, modulated by the texture + vec3 base_rgb = tex.rgb * liquid_color.rgb; + vec3 hsv = rgb2hsv(base_rgb); + hsv.x = fract(hsv.x + t * 0.1 * hue_shift_speed); + hsv.y = clamp(hsv.y + 0.2 * noise_val, 0.0, 1.0); + vec3 effect_rgb = hsv2rgb(hsv); + + // top-down lighting — subtle brightening at the very top + float top_light = smoothstep(0.0, 0.3, 1.0 - UV.y); + effect_rgb *= 1.0 + top_light * 0.2; + + // overall pulse + float pulse = 0.8 + 0.2 * sin(t); + effect_rgb *= (glow_intensity * pulse); + + // tiny sparkle + effect_rgb += 0.03 * vec3(sin(t * 3.0), cos(t * 2.0), noise_val * 0.1); + + // --- composite --- vec3 col = bg_color.rgb; + col = mix(col, effect_rgb, liquid); - // Liquid with subtle depth gradient - vec3 liq = mix(liquid_color.rgb, liquid_color.rgb * 1.35, 1.0 - UV.y); - col = mix(col, liq, liquid); + // surface foam line (wider glow + bright core) + float foam_glow = smoothstep(fill_line - 0.02, fill_line, UV.y) - smoothstep(fill_line, fill_line + 0.004, UV.y); + col += foam_glow * vec3(0.5, 0.65, 0.9) * 0.6 * liquid; + float foam_core = smoothstep(fill_line - 0.006, fill_line, UV.y) - smoothstep(fill_line, fill_line + 0.001, UV.y); + col += foam_core * vec3(0.7, 0.85, 1.0) * liquid; - // Surface highlight line at the top of the liquid column - float surface = smoothstep(fill_line - 0.008, fill_line, UV.y) - smoothstep(fill_line, fill_line + 0.002, UV.y); - col += surface * vec3(0.3, 0.4, 0.5) * liquid; - - // Border + // border col = mix(col, border_color.rgb, outer - inner); COLOR = vec4(col, outer);