add plugin system with plugin/layout managers
- plugin_manager: scans res://plugins/*/plugin.cfg, loads tile definitions - plugin_tile: new base class for plugin tiles, extends ModuleBase - layout_manager: save/restore tile grid positions per named layout - migrate existing cpu/memory/testing tiles into system_monitor plugin - add module_resized signal to DashboardGrid for auto-save - dashboard reads from PluginManager + LayoutManager instead of hardcoded paths - project.godot registers PluginManager and LayoutManager autoloads - AGENTS.md updated with new structure and conventions
This commit is contained in:
parent
63af41ea61
commit
f43676e46c
19 changed files with 528 additions and 39 deletions
0
plugins/.gitkeep
Normal file
0
plugins/.gitkeep
Normal file
41
plugins/system_monitor/plugin.cfg
Normal file
41
plugins/system_monitor/plugin.cfg
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[plugin]
|
||||
name="System Monitor"
|
||||
version="1.0.0"
|
||||
description="Monitors system resources: CPU and memory usage"
|
||||
author="Fifthdread"
|
||||
|
||||
[tiles]
|
||||
count=3
|
||||
|
||||
[tile_0]
|
||||
id="cpu"
|
||||
name="CPU Monitor"
|
||||
scene="res://plugins/system_monitor/tiles/cpu/cpu_tile.tscn"
|
||||
min_w=1
|
||||
min_h=1
|
||||
max_w=4
|
||||
max_h=4
|
||||
default_w=1
|
||||
default_h=1
|
||||
|
||||
[tile_1]
|
||||
id="memory"
|
||||
name="Memory Monitor"
|
||||
scene="res://plugins/system_monitor/tiles/memory/memory_tile.tscn"
|
||||
min_w=1
|
||||
min_h=1
|
||||
max_w=4
|
||||
max_h=4
|
||||
default_w=1
|
||||
default_h=1
|
||||
|
||||
[tile_2]
|
||||
id="testing"
|
||||
name="Testing"
|
||||
scene="res://plugins/system_monitor/tiles/testing/testing_tile.tscn"
|
||||
min_w=1
|
||||
min_h=1
|
||||
max_w=4
|
||||
max_h=4
|
||||
default_w=1
|
||||
default_h=1
|
||||
51
plugins/system_monitor/tiles/cpu/cpu_collector.gd
Normal file
51
plugins/system_monitor/tiles/cpu/cpu_collector.gd
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
extends RefCounted
|
||||
class_name CpuCollector
|
||||
|
||||
|
||||
var _previous_total: int = 0
|
||||
var _previous_idle: int = 0
|
||||
var _has_previous: bool = false
|
||||
|
||||
|
||||
func collect() -> float:
|
||||
var stats: PackedStringArray = _read_stats()
|
||||
if stats.is_empty():
|
||||
return 0.0
|
||||
|
||||
var total: int = 0
|
||||
var idle: int = 0
|
||||
|
||||
for i in range(1, stats.size()):
|
||||
var val: int = stats[i].to_int()
|
||||
total += val
|
||||
if i == 4: # idle is the 4th token (index 4)
|
||||
idle = val
|
||||
|
||||
if not _has_previous:
|
||||
_previous_total = total
|
||||
_previous_idle = idle
|
||||
_has_previous = true
|
||||
return 0.0
|
||||
|
||||
var total_delta: int = total - _previous_total
|
||||
var idle_delta: int = idle - _previous_idle
|
||||
|
||||
_previous_total = total
|
||||
_previous_idle = idle
|
||||
|
||||
if total_delta == 0:
|
||||
return 0.0
|
||||
|
||||
return 100.0 * (1.0 - float(idle_delta) / float(total_delta))
|
||||
|
||||
|
||||
func _read_stats() -> PackedStringArray:
|
||||
var file := FileAccess.open("/proc/stat", FileAccess.READ)
|
||||
if file == null:
|
||||
return PackedStringArray()
|
||||
|
||||
var line: String = file.get_line()
|
||||
if not line.begins_with("cpu "):
|
||||
return PackedStringArray()
|
||||
|
||||
return line.split(" ", false)
|
||||
69
plugins/system_monitor/tiles/cpu/cpu_tile.gd
Normal file
69
plugins/system_monitor/tiles/cpu/cpu_tile.gd
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
extends PluginTile
|
||||
|
||||
|
||||
@onready var title_label: Label = %Title
|
||||
@onready var label: Label = %Label
|
||||
@onready var vial_fill: ColorRect = %VialFill
|
||||
|
||||
var _collector: CpuCollector
|
||||
var _displayed_fill: float = 0.0
|
||||
var _fill_tween: Tween
|
||||
|
||||
|
||||
func initialize() -> void:
|
||||
module_title = "CPU"
|
||||
_collector = CpuCollector.new()
|
||||
_setup_shader()
|
||||
_style()
|
||||
|
||||
|
||||
func refresh(data: Dictionary) -> void:
|
||||
var usage: float = _collector.collect()
|
||||
if usage < 0.0:
|
||||
return
|
||||
|
||||
var pct: int = roundi(usage)
|
||||
label.text = "%d%%" % pct
|
||||
|
||||
# Smoothly tween the vial fill
|
||||
var target: float = usage / 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.4).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.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
|
||||
|
||||
|
||||
func _style() -> void:
|
||||
# Transparent root panel — the VialFill shader provides the background
|
||||
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))
|
||||
41
plugins/system_monitor/tiles/cpu/cpu_tile.tscn
Normal file
41
plugins/system_monitor/tiles/cpu/cpu_tile.tscn
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[gd_scene format=3 uid="uid://bq3bs2hb4r7fb"]
|
||||
|
||||
[ext_resource type="Script" path="res://plugins/system_monitor/tiles/cpu/cpu_tile.gd" id="1"]
|
||||
[ext_resource type="Shader" path="res://shaders/vial_fill.gdshader" id="2"]
|
||||
|
||||
[node name="CpuTile" 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 = "CPU"
|
||||
|
||||
[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
|
||||
36
plugins/system_monitor/tiles/memory/memory_collector.gd
Normal file
36
plugins/system_monitor/tiles/memory/memory_collector.gd
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
extends RefCounted
|
||||
class_name MemoryCollector
|
||||
|
||||
|
||||
func collect() -> float:
|
||||
var meminfo := _read_meminfo()
|
||||
if meminfo.is_empty():
|
||||
return 0.0
|
||||
|
||||
var total: int = meminfo.get("MemTotal", 0)
|
||||
var available: int = meminfo.get("MemAvailable", 0)
|
||||
|
||||
if total == 0:
|
||||
return 0.0
|
||||
|
||||
return 100.0 * (1.0 - float(available) / float(total))
|
||||
|
||||
|
||||
func _read_meminfo() -> Dictionary:
|
||||
var file := FileAccess.open("/proc/meminfo", FileAccess.READ)
|
||||
if file == null:
|
||||
return {}
|
||||
|
||||
var result: Dictionary = {}
|
||||
while not file.eof_reached():
|
||||
var line: String = file.get_line().strip_edges()
|
||||
if line.is_empty():
|
||||
continue
|
||||
var parts := line.split(":", false)
|
||||
if parts.size() < 2:
|
||||
continue
|
||||
var key := parts[0].strip_edges()
|
||||
var val_str := parts[1].strip_edges().split(" ", false)[0]
|
||||
result[key] = val_str.to_int()
|
||||
|
||||
return result
|
||||
69
plugins/system_monitor/tiles/memory/memory_tile.gd
Normal file
69
plugins/system_monitor/tiles/memory/memory_tile.gd
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
extends PluginTile
|
||||
|
||||
|
||||
@onready var title_label: Label = %Title
|
||||
@onready var label: Label = %Label
|
||||
@onready var vial_fill: ColorRect = %VialFill
|
||||
|
||||
var _collector: MemoryCollector
|
||||
var _displayed_fill: float = 0.0
|
||||
var _fill_tween: Tween
|
||||
|
||||
|
||||
func initialize() -> void:
|
||||
module_title = "Memory"
|
||||
_collector = MemoryCollector.new()
|
||||
_setup_shader()
|
||||
_style()
|
||||
|
||||
|
||||
func refresh(data: Dictionary) -> void:
|
||||
var usage: float = _collector.collect()
|
||||
if usage < 0.0:
|
||||
return
|
||||
|
||||
var pct: int = roundi(usage)
|
||||
label.text = "%d%%" % pct
|
||||
|
||||
# Smoothly tween the vial fill
|
||||
var target: float = usage / 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.4).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.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
|
||||
|
||||
|
||||
func _style() -> void:
|
||||
# Transparent root panel — the VialFill shader provides the background
|
||||
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))
|
||||
41
plugins/system_monitor/tiles/memory/memory_tile.tscn
Normal file
41
plugins/system_monitor/tiles/memory/memory_tile.tscn
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[gd_scene format=3 uid="uid://d2d4uqrd2hh3d"]
|
||||
|
||||
[ext_resource type="Script" path="res://plugins/system_monitor/tiles/memory/memory_tile.gd" id="1"]
|
||||
[ext_resource type="Shader" path="res://shaders/vial_fill.gdshader" id="2"]
|
||||
|
||||
[node name="MemoryTile" 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 = "Memory"
|
||||
|
||||
[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
|
||||
64
plugins/system_monitor/tiles/testing/testing_tile.gd
Normal file
64
plugins/system_monitor/tiles/testing/testing_tile.gd
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
extends PluginTile
|
||||
|
||||
|
||||
@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))
|
||||
41
plugins/system_monitor/tiles/testing/testing_tile.tscn
Normal file
41
plugins/system_monitor/tiles/testing/testing_tile.tscn
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
[gd_scene format=3]
|
||||
|
||||
[ext_resource type="Script" path="res://plugins/system_monitor/tiles/testing/testing_tile.gd" id="1"]
|
||||
[ext_resource type="Shader" path="res://shaders/vial_fill.gdshader" id="2"]
|
||||
|
||||
[node name="TestingTile" 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue