Add per-client LED controls aligned with the REST API schema.
Each client panel gets mode-specific LED inputs (color, intensity, progress, digit, blink) and the Makefile now copies API_REST.md alongside the WebSocket docs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
84827c9782
commit
4f3435ba37
284
API_REST.md
Normal file
284
API_REST.md
Normal file
@ -0,0 +1,284 @@
|
||||
# REST API
|
||||
|
||||
`go run . -port /dev/ttyUSB0 serve` starts two HTTP servers on the same UART link:
|
||||
|
||||
| Base URL | Flag | Used by |
|
||||
|----------|------|---------|
|
||||
| `http://localhost:8080` | `-addr` (default `:8080`) | Web dashboard + automation on the UI routes |
|
||||
| `http://localhost:8081` | `-api-addr` (default `:8081`, `""` disables) | External programs; subset of routes + service info |
|
||||
|
||||
WebSocket streaming (accel/tap push): [`API_WEBSOCKET.md`](API_WEBSOCKET.md).
|
||||
|
||||
All JSON responses use `Content-Type: application/json`. On UART errors many routes return **503** with `"error"` in the body.
|
||||
|
||||
---
|
||||
|
||||
## External API (`:8081`)
|
||||
|
||||
### Service info
|
||||
|
||||
```http
|
||||
GET /
|
||||
GET /api/v1/
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "powerpod-external-api",
|
||||
"version": "1",
|
||||
"serial_port": "/dev/ttyUSB0",
|
||||
"websocket": "/ws",
|
||||
"default_interval_ms": 16,
|
||||
"min_interval_ms": 1,
|
||||
"max_interval_ms": 10000,
|
||||
"tap_display_min_ms": 2000,
|
||||
"description": "..."
|
||||
}
|
||||
```
|
||||
|
||||
### Battery
|
||||
|
||||
```http
|
||||
GET /api/battery?all_clients=true
|
||||
GET /api/battery?client_id=16
|
||||
POST /api/battery
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
POST body:
|
||||
|
||||
```json
|
||||
{"all_clients": true}
|
||||
{"client_id": 0}
|
||||
{"client_id": 16}
|
||||
```
|
||||
|
||||
Response:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"samples": [
|
||||
{
|
||||
"client_id": 16,
|
||||
"lipo1": {"valid": true, "voltage_mv": 3850, "percent": 71},
|
||||
"lipo2": {"valid": false},
|
||||
"age_ms": 1200
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Slaves push battery to the master every **30 s**; these routes read the master cache.
|
||||
|
||||
WebSocket equivalent: `get_battery` on `ws://localhost:8081/ws` (reply type `battery_status`).
|
||||
|
||||
### LED ring
|
||||
|
||||
```http
|
||||
POST /api/led-ring
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
Body:
|
||||
|
||||
```json
|
||||
{"mode":"color","client_id":16,"r":255,"g":0,"b":0,"intensity":128}
|
||||
{"mode":"digit","client_id":0,"digit":3,"r":0,"g":255,"b":0}
|
||||
{"mode":"find-me","all_clients":true,"slaves_only":true}
|
||||
```
|
||||
|
||||
| `mode` | Notes |
|
||||
|--------|--------|
|
||||
| `clear` | Turn off |
|
||||
| `color` | Full ring RGB + `intensity` |
|
||||
| `progress` | `progress` 0–100 |
|
||||
| `digit` | `digit` 0–10 |
|
||||
| `blink` | `blink_ms`, `blink_count` |
|
||||
| `find-me` | Locate pod |
|
||||
|
||||
Use `client_id` (`0` = master) or `all_clients` (+ optional `slaves_only`) for broadcast.
|
||||
|
||||
Response: `success`, `slaves_updated`, optional `error`.
|
||||
|
||||
WebSocket: `set_led_ring` with the same fields plus `"type":"set_led_ring"` → `led_ring_status`.
|
||||
|
||||
---
|
||||
|
||||
## Dashboard API (`:8080`)
|
||||
|
||||
Used by the web UI; safe for scripts that drive the same features.
|
||||
|
||||
### Live stream (host `CACHE_STATUS` poll ~16 ms)
|
||||
|
||||
```http
|
||||
GET /api/live-stream
|
||||
PUT /api/live-stream
|
||||
Content-Type: application/json
|
||||
{"enable": true}
|
||||
```
|
||||
|
||||
```json
|
||||
{"enabled": true, "success": true}
|
||||
```
|
||||
|
||||
Enables fast UART polling for dashboard accel/tap display. Per-slave accel still requires accel-stream (below).
|
||||
|
||||
### Accel stream (firmware ESP-NOW, per slave)
|
||||
|
||||
```http
|
||||
GET /api/clients/16/accel-stream
|
||||
PUT /api/clients/16/accel-stream
|
||||
Content-Type: application/json
|
||||
{"enable": true}
|
||||
```
|
||||
|
||||
```json
|
||||
{"enabled": true, "client_id": 16, "success": true}
|
||||
```
|
||||
|
||||
All slaves:
|
||||
|
||||
```http
|
||||
POST /api/accel-stream
|
||||
Content-Type: application/json
|
||||
{"write": true, "enable": true, "all_clients": true}
|
||||
```
|
||||
|
||||
Polling on the host runs only while at least one slave has streaming enabled (here or via external WebSocket / dashboard).
|
||||
|
||||
### Tap notify (firmware; does not start host tap polling)
|
||||
|
||||
```http
|
||||
GET /api/clients/16/tap-notify
|
||||
PUT /api/clients/16/tap-notify
|
||||
Content-Type: application/json
|
||||
{"single": true, "double_tap": false, "triple": false}
|
||||
```
|
||||
|
||||
```json
|
||||
{
|
||||
"client_id": 16,
|
||||
"success": true,
|
||||
"slaves_updated": 1,
|
||||
"single": true,
|
||||
"double_tap": false,
|
||||
"triple": false
|
||||
}
|
||||
```
|
||||
|
||||
All slaves:
|
||||
|
||||
```http
|
||||
POST /api/tap-notify
|
||||
Content-Type: application/json
|
||||
{"single": true, "double_tap": false, "triple": false, "all_clients": true}
|
||||
```
|
||||
|
||||
Host tap display / external `set_tap_stream` is separate.
|
||||
|
||||
### Tap snapshot (one-shot, via `CACHE_STATUS`)
|
||||
|
||||
```http
|
||||
GET /api/tap-snapshot?client_id=16
|
||||
```
|
||||
|
||||
Reads the combined cache (`CACHE_STATUS`); optional `client_id` filters pending tap events. Pending taps are consumed on read.
|
||||
|
||||
```json
|
||||
{
|
||||
"events": [
|
||||
{"client_id": 16, "kind": "single", "age_ms": 4}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Deadzone
|
||||
|
||||
```http
|
||||
GET /api/deadzone?client_id=0
|
||||
POST /api/deadzone
|
||||
Content-Type: application/json
|
||||
{"write": true, "deadzone": 128, "client_id": 0}
|
||||
```
|
||||
|
||||
With `all_clients` + `slaves_only`: push to ESP-NOW slaves only (master BMA456 unchanged).
|
||||
|
||||
```json
|
||||
{"deadzone": 128, "client_id": 0, "success": true, "slaves_updated": 2}
|
||||
```
|
||||
|
||||
### Unicast test
|
||||
|
||||
```http
|
||||
POST /api/unicast-test
|
||||
Content-Type: application/json
|
||||
{"client_id": 16, "seq": 42}
|
||||
```
|
||||
|
||||
### Find me
|
||||
|
||||
```http
|
||||
POST /api/find-me
|
||||
Content-Type: application/json
|
||||
{"client_id": 16}
|
||||
```
|
||||
|
||||
`client_id` `0` = master LED ring.
|
||||
|
||||
### Restart
|
||||
|
||||
```http
|
||||
POST /api/restart
|
||||
Content-Type: application/json
|
||||
{"client_id": 16}
|
||||
```
|
||||
|
||||
### OTA (master UART upload)
|
||||
|
||||
```http
|
||||
POST /api/ota
|
||||
Content-Type: multipart/form-data
|
||||
```
|
||||
|
||||
Form field **`firmware`**: binary image, max **2 MiB**.
|
||||
|
||||
```json
|
||||
{"success": true, "bytes_written": 123456, "target_slot": 1}
|
||||
```
|
||||
|
||||
Firmware distributes to slaves over ESP-NOW after `OTA_END`. Progress also appears on dashboard WebSocket as `ota_progress` messages.
|
||||
|
||||
CLI equivalent: `go run . -port /dev/ttyUSB0 ota build/powerpod.bin`
|
||||
|
||||
### LED ring and battery
|
||||
|
||||
Same as external API:
|
||||
|
||||
- `POST /api/led-ring`
|
||||
- `GET` / `POST` `/api/battery`
|
||||
|
||||
---
|
||||
|
||||
## Dashboard vs external
|
||||
|
||||
| Feature | Dashboard `:8080` | External `:8081` |
|
||||
|---------|-------------------|------------------|
|
||||
| Client list | Via dashboard WebSocket state / CLI `clients` | WebSocket `list_clients` |
|
||||
| Accel/tap **push stream** | WebSocket state when live-stream on | WebSocket `set_stream` / `set_tap_stream` |
|
||||
| Accel stream enable | REST `PUT .../accel-stream` | WebSocket `set_accel_stream` |
|
||||
| Tap notify | REST `PUT .../tap-notify` | WebSocket `set_tap_notify` |
|
||||
| LED / battery | REST | REST + WebSocket on `:8081` |
|
||||
|
||||
---
|
||||
|
||||
## UI mapping
|
||||
|
||||
| UI action | REST / CLI |
|
||||
|-----------|------------|
|
||||
| Nur Master deadzone | `POST /api/deadzone` `client_id: 0` or CLI `deadzone -set -client 0` |
|
||||
| Einzelner Slave | `client_id: <id>` |
|
||||
| Alle Slaves deadzone | `all_clients` + `slaves_only` on POST |
|
||||
| Unicast test | `POST /api/unicast-test` |
|
||||
| Tap notify S/D/T | `PUT /api/clients/{id}/tap-notify` |
|
||||
| Tap receive (UI) | Live stream + tap notify; see WebSocket doc for external API |
|
||||
3
Makefile
3
Makefile
@ -1,2 +1,3 @@
|
||||
get_api_desc:
|
||||
cp ../dev/esp/powerpod/goTool/docs/API_WEBSOCKET.md ./API_WEBSOCKET.md
|
||||
cp ../dev/esp/powerpod/goTool/docs/API_WEBSOCKET.md ./API_WEBSOCKET.md
|
||||
cp ../dev/esp/powerpod/goTool/docs/API_REST.md ./API_REST.md
|
||||
239
scripts/demo.gd
239
scripts/demo.gd
@ -10,6 +10,9 @@ const PANEL_BORDER_NORMAL := Color(0.22, 0.25, 0.32, 1.0)
|
||||
const PANEL_STYLE_FLASH := Color(0.32, 0.28, 0.06, 1.0)
|
||||
const PANEL_BORDER_FLASH := Color(1.0, 0.82, 0.15, 1.0)
|
||||
|
||||
const LED_MODES: PackedStringArray = ["color", "clear", "progress", "digit", "blink", "find-me"]
|
||||
const LED_MODES_COLOR_INTENSITY: PackedStringArray = ["color", "progress", "digit", "blink"]
|
||||
|
||||
@onready var status_label: Label = $UiLayer/TopBar/StatusLabel
|
||||
@onready var log_output: TextEdit = $UiLayer/MainSplit/LogPanel/LogOutput
|
||||
@onready var stream_output: Label = $UiLayer/MainSplit/LogPanel/StreamOutput
|
||||
@ -210,6 +213,122 @@ func _populate_clients(clients: Array) -> void:
|
||||
tap_row.add_child(tap_double)
|
||||
tap_row.add_child(tap_triple)
|
||||
|
||||
var led_label := Label.new()
|
||||
led_label.text = "LED Ring:"
|
||||
led_label.add_theme_font_size_override("font_size", 12)
|
||||
led_label.add_theme_color_override("font_color", Color(0.7, 0.75, 0.85))
|
||||
vbox.add_child(led_label)
|
||||
|
||||
var led_row := HBoxContainer.new()
|
||||
led_row.add_theme_constant_override("separation", 8)
|
||||
vbox.add_child(led_row)
|
||||
|
||||
var led_mode := OptionButton.new()
|
||||
for mode in LED_MODES:
|
||||
led_mode.add_item(mode)
|
||||
led_mode.disabled = not available
|
||||
led_row.add_child(led_mode)
|
||||
|
||||
var led_apply := Button.new()
|
||||
led_apply.text = "Anwenden"
|
||||
led_apply.disabled = not available
|
||||
led_apply.pressed.connect(_apply_led_for_client.bind(cid))
|
||||
led_row.add_child(led_apply)
|
||||
|
||||
var led_params := VBoxContainer.new()
|
||||
led_params.add_theme_constant_override("separation", 4)
|
||||
vbox.add_child(led_params)
|
||||
|
||||
var led_color_row := HBoxContainer.new()
|
||||
led_color_row.add_theme_constant_override("separation", 8)
|
||||
led_params.add_child(led_color_row)
|
||||
var led_color_lbl := Label.new()
|
||||
led_color_lbl.text = "Farbe:"
|
||||
led_color_row.add_child(led_color_lbl)
|
||||
var led_color := ColorPickerButton.new()
|
||||
led_color.color = Color(1.0, 0.0, 0.0)
|
||||
led_color.custom_minimum_size = Vector2(36, 28)
|
||||
led_color.disabled = not available
|
||||
led_color_row.add_child(led_color)
|
||||
|
||||
var led_intensity_row := HBoxContainer.new()
|
||||
led_intensity_row.add_theme_constant_override("separation", 8)
|
||||
led_params.add_child(led_intensity_row)
|
||||
var led_intensity_lbl := Label.new()
|
||||
led_intensity_lbl.text = "Intensität:"
|
||||
led_intensity_row.add_child(led_intensity_lbl)
|
||||
var led_intensity := SpinBox.new()
|
||||
led_intensity.min_value = 0
|
||||
led_intensity.max_value = 255
|
||||
led_intensity.value = 128
|
||||
led_intensity.custom_minimum_size = Vector2(80, 0)
|
||||
_set_spinbox_enabled(led_intensity, available)
|
||||
led_intensity_row.add_child(led_intensity)
|
||||
|
||||
var led_progress_row := HBoxContainer.new()
|
||||
led_progress_row.add_theme_constant_override("separation", 8)
|
||||
led_params.add_child(led_progress_row)
|
||||
var led_progress_lbl := Label.new()
|
||||
led_progress_lbl.text = "Fortschritt %:"
|
||||
led_progress_row.add_child(led_progress_lbl)
|
||||
var led_progress := SpinBox.new()
|
||||
led_progress.min_value = 0
|
||||
led_progress.max_value = 100
|
||||
led_progress.value = 50
|
||||
led_progress.custom_minimum_size = Vector2(80, 0)
|
||||
_set_spinbox_enabled(led_progress, available)
|
||||
led_progress_row.add_child(led_progress)
|
||||
|
||||
var led_digit_row := HBoxContainer.new()
|
||||
led_digit_row.add_theme_constant_override("separation", 8)
|
||||
led_params.add_child(led_digit_row)
|
||||
var led_digit_lbl := Label.new()
|
||||
led_digit_lbl.text = "Ziffer:"
|
||||
led_digit_row.add_child(led_digit_lbl)
|
||||
var led_digit := SpinBox.new()
|
||||
led_digit.min_value = 0
|
||||
led_digit.max_value = 10
|
||||
led_digit.value = 3
|
||||
led_digit.custom_minimum_size = Vector2(80, 0)
|
||||
_set_spinbox_enabled(led_digit, available)
|
||||
led_digit_row.add_child(led_digit)
|
||||
|
||||
var led_blink_row := HBoxContainer.new()
|
||||
led_blink_row.add_theme_constant_override("separation", 8)
|
||||
led_params.add_child(led_blink_row)
|
||||
var led_blink_ms_lbl := Label.new()
|
||||
led_blink_ms_lbl.text = "Blink ms:"
|
||||
led_blink_row.add_child(led_blink_ms_lbl)
|
||||
var led_blink_ms := SpinBox.new()
|
||||
led_blink_ms.min_value = 1
|
||||
led_blink_ms.max_value = 10000
|
||||
led_blink_ms.value = 500
|
||||
led_blink_ms.custom_minimum_size = Vector2(80, 0)
|
||||
_set_spinbox_enabled(led_blink_ms, available)
|
||||
led_blink_row.add_child(led_blink_ms)
|
||||
var led_blink_count_lbl := Label.new()
|
||||
led_blink_count_lbl.text = "Anzahl:"
|
||||
led_blink_row.add_child(led_blink_count_lbl)
|
||||
var led_blink_count := SpinBox.new()
|
||||
led_blink_count.min_value = 1
|
||||
led_blink_count.max_value = 100
|
||||
led_blink_count.value = 3
|
||||
led_blink_count.custom_minimum_size = Vector2(60, 0)
|
||||
_set_spinbox_enabled(led_blink_count, available)
|
||||
led_blink_row.add_child(led_blink_count)
|
||||
|
||||
led_mode.item_selected.connect(func(_idx: int) -> void: _update_led_fields(cid))
|
||||
|
||||
var led_presets := HBoxContainer.new()
|
||||
led_presets.add_theme_constant_override("separation", 6)
|
||||
vbox.add_child(led_presets)
|
||||
|
||||
_add_led_preset_btn(led_presets, "Aus", cid, available, func() -> void: _led_apply_preset(cid, "clear"))
|
||||
_add_led_preset_btn(led_presets, "Rot", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(1, 0.2, 0.2), 128))
|
||||
_add_led_preset_btn(led_presets, "Grün", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(0.2, 1, 0.3), 128))
|
||||
_add_led_preset_btn(led_presets, "Blau", cid, available, func() -> void: _led_apply_preset(cid, "color", Color(0.2, 0.4, 1), 128))
|
||||
_add_led_preset_btn(led_presets, "Find", cid, available, func() -> void: _led_apply_preset(cid, "find-me"))
|
||||
|
||||
var values_label := Label.new()
|
||||
values_label.text = "—"
|
||||
values_label.add_theme_font_size_override("font_size", 12)
|
||||
@ -229,9 +348,22 @@ func _populate_clients(clients: Array) -> void:
|
||||
"tap_single": tap_single,
|
||||
"tap_double": tap_double,
|
||||
"tap_triple": tap_triple,
|
||||
"led_color": led_color,
|
||||
"led_mode": led_mode,
|
||||
"led_intensity": led_intensity,
|
||||
"led_progress": led_progress,
|
||||
"led_digit": led_digit,
|
||||
"led_blink_ms": led_blink_ms,
|
||||
"led_blink_count": led_blink_count,
|
||||
"led_color_row": led_color_row,
|
||||
"led_intensity_row": led_intensity_row,
|
||||
"led_progress_row": led_progress_row,
|
||||
"led_digit_row": led_digit_row,
|
||||
"led_blink_row": led_blink_row,
|
||||
"values_label": values_label,
|
||||
"last_tap": "",
|
||||
}
|
||||
_update_led_fields(cid)
|
||||
|
||||
empty_hint.visible = _client_ui.is_empty()
|
||||
start_stream_btn.disabled = _client_ui.is_empty() or not has_available or _streaming
|
||||
@ -278,6 +410,8 @@ func _handle_message(text: String) -> void:
|
||||
_on_accel(data)
|
||||
"tap":
|
||||
_on_tap(data)
|
||||
"led_ring_status":
|
||||
_log("← LED #%s: %s" % [data.get("client_id", "?"), "OK" if data.get("success", false) else data.get("error", "?")])
|
||||
_:
|
||||
if not _streaming or not msg_type.ends_with("_status"):
|
||||
_log("← %s" % _format_response(data))
|
||||
@ -361,6 +495,111 @@ func _on_tap(data: Dictionary) -> void:
|
||||
stream_output.text = "tap\n" + "\n".join(summary)
|
||||
|
||||
|
||||
func _set_spinbox_enabled(spin: SpinBox, enabled: bool) -> void:
|
||||
spin.editable = enabled
|
||||
spin.get_line_edit().editable = enabled
|
||||
|
||||
|
||||
func _add_led_preset_btn(
|
||||
row: HBoxContainer,
|
||||
text: String,
|
||||
cid: int,
|
||||
available: bool,
|
||||
on_pressed: Callable
|
||||
) -> void:
|
||||
var btn := Button.new()
|
||||
btn.text = text
|
||||
btn.disabled = not available
|
||||
btn.pressed.connect(on_pressed)
|
||||
row.add_child(btn)
|
||||
|
||||
|
||||
func _update_led_fields(cid: int) -> void:
|
||||
if not _client_ui.has(cid):
|
||||
return
|
||||
var ui: Dictionary = _client_ui[cid]
|
||||
var mode: String = ui["led_mode"].get_item_text(ui["led_mode"].selected)
|
||||
|
||||
ui["led_color_row"].visible = mode in LED_MODES_COLOR_INTENSITY
|
||||
ui["led_intensity_row"].visible = mode in LED_MODES_COLOR_INTENSITY
|
||||
ui["led_progress_row"].visible = mode == "progress"
|
||||
ui["led_digit_row"].visible = mode == "digit"
|
||||
ui["led_blink_row"].visible = mode == "blink"
|
||||
|
||||
|
||||
func _select_led_mode(ui: Dictionary, mode: String) -> void:
|
||||
for i in ui["led_mode"].item_count:
|
||||
if ui["led_mode"].get_item_text(i) == mode:
|
||||
ui["led_mode"].select(i)
|
||||
return
|
||||
|
||||
|
||||
func _led_apply_preset(
|
||||
cid: int,
|
||||
mode: String,
|
||||
color: Color = Color.WHITE,
|
||||
intensity: int = 128,
|
||||
param: int = 50
|
||||
) -> void:
|
||||
if not _client_ui.has(cid):
|
||||
return
|
||||
var ui: Dictionary = _client_ui[cid]
|
||||
_select_led_mode(ui, mode)
|
||||
if mode in LED_MODES_COLOR_INTENSITY:
|
||||
ui["led_color"].color = color
|
||||
ui["led_intensity"].value = intensity
|
||||
if mode == "progress":
|
||||
ui["led_progress"].value = param
|
||||
if mode == "digit":
|
||||
ui["led_digit"].value = param
|
||||
if mode == "blink":
|
||||
ui["led_blink_ms"].value = param
|
||||
ui["led_blink_count"].value = 3
|
||||
_update_led_fields(cid)
|
||||
_apply_led_for_client(cid)
|
||||
|
||||
|
||||
func _apply_led_for_client(cid: int) -> void:
|
||||
if not _client_ui.has(cid):
|
||||
return
|
||||
_send(_build_led_payload(cid))
|
||||
|
||||
|
||||
func _build_led_payload(cid: int) -> Dictionary:
|
||||
var ui: Dictionary = _client_ui[cid]
|
||||
var mode: String = ui["led_mode"].get_item_text(ui["led_mode"].selected)
|
||||
var payload := {"type": "set_led_ring", "client_id": cid, "mode": mode}
|
||||
|
||||
match mode:
|
||||
"clear", "find-me":
|
||||
pass
|
||||
"color":
|
||||
_payload_add_color_intensity(payload, ui)
|
||||
"progress":
|
||||
payload["progress"] = int(ui["led_progress"].value)
|
||||
_payload_add_color_intensity(payload, ui)
|
||||
"digit":
|
||||
payload["digit"] = int(ui["led_digit"].value)
|
||||
_payload_add_color_intensity(payload, ui)
|
||||
"blink":
|
||||
payload["blink_ms"] = int(ui["led_blink_ms"].value)
|
||||
payload["blink_count"] = int(ui["led_blink_count"].value)
|
||||
_payload_add_color_intensity(payload, ui)
|
||||
|
||||
return payload
|
||||
|
||||
|
||||
func _payload_add_color_intensity(payload: Dictionary, ui: Dictionary) -> void:
|
||||
_payload_add_rgb(payload, ui["led_color"].color)
|
||||
payload["intensity"] = int(ui["led_intensity"].value)
|
||||
|
||||
|
||||
func _payload_add_rgb(payload: Dictionary, color: Color) -> void:
|
||||
payload["r"] = int(color.r * 255)
|
||||
payload["g"] = int(color.g * 255)
|
||||
payload["b"] = int(color.b * 255)
|
||||
|
||||
|
||||
func _make_panel_style(bg: Color, border: Color, border_width: int) -> StyleBoxFlat:
|
||||
var style := StyleBoxFlat.new()
|
||||
style.bg_color = bg
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user