add dashboard framework: responsive grid layout with modular cell system and fullscreen entry
This commit is contained in:
parent
f140d558b7
commit
5aabc1f7ef
6 changed files with 234 additions and 14 deletions
|
|
@ -27,15 +27,16 @@ res://
|
||||||
│ ├── icons/
|
│ ├── icons/
|
||||||
│ └── textures/
|
│ └── textures/
|
||||||
├── autoload/ # Singleton/autoload scripts
|
├── autoload/ # Singleton/autoload scripts
|
||||||
├── panels/ # Individual status panels
|
├── panels/ # Individual status panels (modules)
|
||||||
│ ├── cpu/
|
│ ├── cpu/
|
||||||
│ ├── memory/
|
│ ├── memory/
|
||||||
│ ├── network/
|
│ ├── network/
|
||||||
│ └── disk/
|
│ └── disk/
|
||||||
|
├── scenes/ # Root scenes (dashboard, etc.)
|
||||||
├── scripts/ # Shared utility scripts
|
├── scripts/ # Shared utility scripts
|
||||||
├── themes/ # Theme definitions and style resources
|
├── themes/ # Theme definitions and style resources
|
||||||
├── shaders/ # Custom shader materials
|
├── shaders/ # Custom shader materials
|
||||||
└── main.tscn # Root scene
|
└── main.tscn # Entry point (loads dashboard)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Git Workflow
|
### Git Workflow
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,33 @@
|
||||||
; V Panel — Godot Engine project configuration
|
; Engine configuration file.
|
||||||
; https://godotengine.org
|
; It's best edited using the editor UI and not directly,
|
||||||
|
; since the parameters that go here are not all obvious.
|
||||||
|
;
|
||||||
|
; Format:
|
||||||
|
; [section] ; section goes between []
|
||||||
|
; param=value ; assign values to parameters
|
||||||
|
|
||||||
config_version=5
|
config_version=5
|
||||||
|
|
||||||
[application]
|
[application]
|
||||||
|
|
||||||
config/name="V Panel"
|
config/name="V Panel"
|
||||||
config/description="A fancy real-time status monitor built with Godot Engine."
|
config/description="A fancy real-time status monitor built with Godot Engine."
|
||||||
config/version="0.1.0"
|
config/version="0.1.0"
|
||||||
|
run/main_scene="res://scenes/dashboard.tscn"
|
||||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||||
run/main_scene="res://main.tscn"
|
|
||||||
config/icon="res://assets/icons/icon.svg"
|
config/icon="res://assets/icons/icon.svg"
|
||||||
|
|
||||||
|
[autoload]
|
||||||
|
|
||||||
|
ConfigManager="*res://autoload/config_manager.gd"
|
||||||
|
|
||||||
[display]
|
[display]
|
||||||
|
|
||||||
window/size/viewport_width=1280
|
window/size/viewport_width=1280
|
||||||
window/size/viewport_height=800
|
window/size/viewport_height=800
|
||||||
window/size/mode=0
|
|
||||||
window/size/always_on_top=true
|
window/size/always_on_top=true
|
||||||
window/dpi/allow_hidpi=true
|
|
||||||
window/stretch/mode="viewport"
|
window/stretch/mode="viewport"
|
||||||
window/stretch/aspect="keep"
|
|
||||||
|
|
||||||
[rendering]
|
[rendering]
|
||||||
renderer/rendering_method="forward_plus"
|
|
||||||
renderer/rendering_method.mobile="forward_plus"
|
renderer/rendering_method.mobile="forward_plus"
|
||||||
|
|
||||||
[input]
|
|
||||||
|
|
||||||
[autoload]
|
|
||||||
ConfigManager="*res://autoload/config_manager.gd"
|
|
||||||
|
|
|
||||||
8
scenes/dashboard.gd
Normal file
8
scenes/dashboard.gd
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
extends Control
|
||||||
|
|
||||||
|
|
||||||
|
@onready var grid: DashboardGrid = %DashboardGrid
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
get_window().mode = Window.MODE_FULLSCREEN
|
||||||
19
scenes/dashboard.tscn
Normal file
19
scenes/dashboard.tscn
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
[gd_scene format=3 uid="uid://c3gq43o1aqy0b"]
|
||||||
|
|
||||||
|
[ext_resource type="Script" path="res://scenes/dashboard.gd" id="1"]
|
||||||
|
[ext_resource type="Script" path="res://scripts/dashboard_grid.gd" id="2"]
|
||||||
|
[ext_resource type="Theme" path="res://themes/default_theme.tres" id="3"]
|
||||||
|
|
||||||
|
[node name="Dashboard" type="Control"]
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
theme = ExtResource("3")
|
||||||
|
script = ExtResource("1")
|
||||||
|
|
||||||
|
[node name="DashboardGrid" type="Control" parent="."]
|
||||||
|
unique_name_in_owner = true
|
||||||
|
anchors_preset = 15
|
||||||
|
anchor_right = 1.0
|
||||||
|
anchor_bottom = 1.0
|
||||||
|
script = ExtResource("2")
|
||||||
157
scripts/dashboard_grid.gd
Normal file
157
scripts/dashboard_grid.gd
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
extends Control
|
||||||
|
class_name DashboardGrid
|
||||||
|
|
||||||
|
|
||||||
|
signal module_placed(module: Control, col: int, row: int)
|
||||||
|
signal module_removed(module: Control)
|
||||||
|
|
||||||
|
@export var cell_min_size: Vector2 = Vector2(300, 240)
|
||||||
|
@export var cell_spacing: float = 8.0
|
||||||
|
@export var margin: float = 16.0
|
||||||
|
|
||||||
|
var columns: int = 0
|
||||||
|
var rows: int = 0
|
||||||
|
|
||||||
|
var _cells: Array = [] # 2D: _cells[row][col] -> PanelContainer
|
||||||
|
var _module_positions: Dictionary = {} # "col,row" -> Control
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
resized.connect(_rebuild_grid)
|
||||||
|
_rebuild_grid()
|
||||||
|
|
||||||
|
|
||||||
|
func place_module(module: Control, col: int, row: int) -> void:
|
||||||
|
if not _is_valid_cell(col, row):
|
||||||
|
return
|
||||||
|
var cell: PanelContainer = _cells[row][col]
|
||||||
|
_set_cell_child(cell, module)
|
||||||
|
_module_positions["%d,%d" % [col, row]] = module
|
||||||
|
module_placed.emit(module, col, row)
|
||||||
|
|
||||||
|
|
||||||
|
func remove_module(col: int, row: int) -> void:
|
||||||
|
if not _is_valid_cell(col, row):
|
||||||
|
return
|
||||||
|
var cell: PanelContainer = _cells[row][col]
|
||||||
|
var key := "%d,%d" % [col, row]
|
||||||
|
for child in cell.get_children():
|
||||||
|
cell.remove_child(child)
|
||||||
|
if is_instance_valid(child):
|
||||||
|
module_removed.emit(child)
|
||||||
|
child.queue_free()
|
||||||
|
_module_positions.erase(key)
|
||||||
|
|
||||||
|
|
||||||
|
func get_grid_size() -> Vector2i:
|
||||||
|
return Vector2i(columns, rows)
|
||||||
|
|
||||||
|
|
||||||
|
func _is_valid_cell(col: int, row: int) -> bool:
|
||||||
|
return row >= 0 and row < _cells.size() and col >= 0 and col < _cells[row].size()
|
||||||
|
|
||||||
|
|
||||||
|
func _set_cell_child(cell: PanelContainer, child: Control) -> void:
|
||||||
|
for existing in cell.get_children():
|
||||||
|
cell.remove_child(existing)
|
||||||
|
if is_instance_valid(existing):
|
||||||
|
existing.queue_free()
|
||||||
|
cell.add_child(child)
|
||||||
|
|
||||||
|
|
||||||
|
func _rebuild_grid() -> void:
|
||||||
|
var avail := size - Vector2(margin * 2.0, margin * 2.0)
|
||||||
|
if avail.x < cell_min_size.x or avail.y < cell_min_size.y:
|
||||||
|
_teardown_cells()
|
||||||
|
return
|
||||||
|
|
||||||
|
var new_cols := maxi(1, int(avail.x / (cell_min_size.x + cell_spacing)))
|
||||||
|
var new_rows := maxi(1, int(avail.y / (cell_min_size.y + cell_spacing)))
|
||||||
|
|
||||||
|
# Bail out if grid dimensions haven't changed
|
||||||
|
if new_cols == columns and new_rows == rows and _cells.size() > 0:
|
||||||
|
_layout_cells()
|
||||||
|
return
|
||||||
|
|
||||||
|
columns = new_cols
|
||||||
|
rows = new_rows
|
||||||
|
|
||||||
|
_save_modules()
|
||||||
|
_teardown_cells()
|
||||||
|
_build_cells()
|
||||||
|
_restore_modules()
|
||||||
|
_layout_cells()
|
||||||
|
|
||||||
|
|
||||||
|
func _build_cells() -> void:
|
||||||
|
_cells.clear()
|
||||||
|
for row in range(rows):
|
||||||
|
_cells.append([])
|
||||||
|
for col in range(columns):
|
||||||
|
var cell := PanelContainer.new()
|
||||||
|
cell.name = "Cell_%d_%d" % [col, row]
|
||||||
|
cell.mouse_filter = Control.MOUSE_FILTER_PASS
|
||||||
|
add_child(cell)
|
||||||
|
_cells[row].append(cell)
|
||||||
|
|
||||||
|
|
||||||
|
func _teardown_cells() -> void:
|
||||||
|
for child in get_children():
|
||||||
|
remove_child(child)
|
||||||
|
if is_instance_valid(child):
|
||||||
|
child.queue_free()
|
||||||
|
_cells.clear()
|
||||||
|
|
||||||
|
|
||||||
|
func _layout_cells() -> void:
|
||||||
|
if rows == 0 or columns == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
var inner_w := size.x - margin * 2.0 - cell_spacing
|
||||||
|
var inner_h := size.y - margin * 2.0 - cell_spacing
|
||||||
|
var gap_x := cell_spacing * (columns - 1)
|
||||||
|
var gap_y := cell_spacing * (rows - 1)
|
||||||
|
var cell_w := (inner_w - gap_x) / columns
|
||||||
|
var cell_h := (inner_h - gap_y) / rows
|
||||||
|
|
||||||
|
for row in range(rows):
|
||||||
|
for col in range(columns):
|
||||||
|
var cell: PanelContainer = _cells[row][col]
|
||||||
|
if not is_instance_valid(cell):
|
||||||
|
continue
|
||||||
|
var x := margin + cell_spacing + col * (cell_w + cell_spacing)
|
||||||
|
var y := margin + cell_spacing + row * (cell_h + cell_spacing)
|
||||||
|
cell.set_position(Vector2(x, y))
|
||||||
|
cell.set_size(Vector2(cell_w, cell_h))
|
||||||
|
|
||||||
|
|
||||||
|
func _save_modules() -> void:
|
||||||
|
_module_positions.clear()
|
||||||
|
for row in range(_cells.size()):
|
||||||
|
for col in range(_cells[row].size()):
|
||||||
|
var cell: PanelContainer = _cells[row][col]
|
||||||
|
if cell.get_child_count() > 0:
|
||||||
|
var mod := cell.get_child(0)
|
||||||
|
cell.remove_child(mod)
|
||||||
|
_module_positions["%d,%d" % [col, row]] = mod
|
||||||
|
|
||||||
|
|
||||||
|
func _restore_modules() -> void:
|
||||||
|
for key in _module_positions:
|
||||||
|
var parts := key.split(",")
|
||||||
|
var col := int(parts[0])
|
||||||
|
var row := int(parts[1])
|
||||||
|
var module: Control = _module_positions[key]
|
||||||
|
|
||||||
|
if _is_valid_cell(col, row):
|
||||||
|
_set_cell_child(_cells[row][col], module)
|
||||||
|
else:
|
||||||
|
# Place in the last available cell if original position no longer exists
|
||||||
|
if _cells.size() > 0 and _cells[0].size() > 0:
|
||||||
|
var last_row := _cells.size() - 1
|
||||||
|
var last_col := _cells[last_row].size() - 1
|
||||||
|
_set_cell_child(_cells[last_row][last_col], module)
|
||||||
|
else:
|
||||||
|
module.queue_free()
|
||||||
|
|
||||||
|
_module_positions.clear()
|
||||||
32
scripts/module_base.gd
Normal file
32
scripts/module_base.gd
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
extends PanelBase
|
||||||
|
class_name ModuleBase
|
||||||
|
|
||||||
|
|
||||||
|
signal module_ready
|
||||||
|
|
||||||
|
@export var module_title: String = "Module"
|
||||||
|
|
||||||
|
var _initialized: bool = false
|
||||||
|
|
||||||
|
|
||||||
|
func _ready() -> void:
|
||||||
|
if not _initialized:
|
||||||
|
_initialize()
|
||||||
|
|
||||||
|
|
||||||
|
func initialize() -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func refresh(data: Dictionary) -> void:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
func get_module_title() -> String:
|
||||||
|
return module_title
|
||||||
|
|
||||||
|
|
||||||
|
func _initialize() -> void:
|
||||||
|
_initialized = true
|
||||||
|
initialize()
|
||||||
|
module_ready.emit()
|
||||||
Loading…
Add table
Add a link
Reference in a new issue