Compare commits
No commits in common. "main" and "windows-continuous" have entirely different histories.
main
...
windows-co
24 changed files with 238 additions and 862 deletions
|
|
@ -1,8 +1,7 @@
|
||||||
name: Windows Release
|
name: Windows Release
|
||||||
|
|
||||||
# Forgejo-orchestrated Windows portable zip.
|
# Continuous Windows portable build on every push to main.
|
||||||
# Tower `desktop-release` triggers the private GitHub builder (MSVC), then
|
# Needs self-hosted runner label `windows-release` (MSVC 2022).
|
||||||
# verifies the public Forgejo release tag `windows-continuous`.
|
|
||||||
# See docs/windows-runner.md
|
# See docs/windows-runner.md
|
||||||
|
|
||||||
on:
|
on:
|
||||||
|
|
@ -19,18 +18,30 @@ concurrency:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
windows-x86_64:
|
windows-x86_64:
|
||||||
runs-on: desktop-release
|
runs-on: windows-release
|
||||||
timeout-minutes: 360
|
timeout-minutes: 240
|
||||||
steps:
|
steps:
|
||||||
- name: Resolve expected asset
|
- name: Checkout
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
rm -rf .git
|
||||||
|
git init .
|
||||||
|
origin_url="${GITHUB_SERVER_URL#https://}"
|
||||||
|
origin_url="${origin_url#http://}"
|
||||||
|
git remote add origin "https://oauth2:${FORGEJO_TOKEN}@${origin_url}/${GITHUB_REPOSITORY}.git"
|
||||||
|
git -c http.sslVerify=false fetch --depth=1 origin "${GITHUB_SHA}"
|
||||||
|
git checkout --force FETCH_HEAD
|
||||||
|
git submodule update --init --recursive
|
||||||
|
|
||||||
|
- name: Resolve version
|
||||||
id: ver
|
id: ver
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
# Shallow metadata from the triggering commit (no full Windows checkout).
|
ver="$(grep -E 'CURRENT_VERSION_STRING' src/VersionConstants.hpp | head -1 | sed -E 's/.*"([^"]+)".*/\1/')"
|
||||||
ver="$(curl -fsSL -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
|
||||||
"${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/contents/src/VersionConstants.hpp?ref=${GITHUB_SHA}" \
|
|
||||||
| python3 -c "import sys,json,base64,re; d=json.load(sys.stdin); t=base64.b64decode(d['content']).decode(); print(re.search(r'CURRENT_VERSION_STRING\s*=\s*\"([^\"]+)\"', t).group(1))")"
|
|
||||||
short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
short_sha="$(echo "${GITHUB_SHA}" | cut -c1-7)"
|
||||||
asset_ver="${ver}+${short_sha}"
|
asset_ver="${ver}+${short_sha}"
|
||||||
zip_name="infinipaint-${asset_ver}-win-x86_64-portable.zip"
|
zip_name="infinipaint-${asset_ver}-win-x86_64-portable.zip"
|
||||||
|
|
@ -39,87 +50,129 @@ jobs:
|
||||||
echo "tag=windows-continuous"
|
echo "tag=windows-continuous"
|
||||||
echo "asset_ver=${asset_ver}"
|
echo "asset_ver=${asset_ver}"
|
||||||
echo "zip_name=${zip_name}"
|
echo "zip_name=${zip_name}"
|
||||||
echo "short_sha=${short_sha}"
|
|
||||||
} >> "$GITHUB_OUTPUT"
|
} >> "$GITHUB_OUTPUT"
|
||||||
echo "Expect Forgejo asset: ${zip_name}"
|
|
||||||
|
|
||||||
- name: Dispatch or reuse GitHub Windows build
|
- name: Ensure Conan
|
||||||
id: gh
|
shell: pwsh
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
GH_BUILDER_TOKEN: ${{ secrets.GH_BUILDER_TOKEN }}
|
|
||||||
WANT_SHA: ${{ github.sha }}
|
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
$ErrorActionPreference = "Stop"
|
||||||
if [ -z "${GH_BUILDER_TOKEN:-}" ]; then
|
if (-not (Get-Command conan -ErrorAction SilentlyContinue)) {
|
||||||
echo "GH_BUILDER_TOKEN secret missing on Forgejo repo" >&2
|
python -m pip install --user "conan>=2,<3"
|
||||||
exit 1
|
Get-ChildItem "$env:USERPROFILE\AppData\Roaming\Python" -Recurse -Filter conan.exe -ErrorAction SilentlyContinue |
|
||||||
fi
|
ForEach-Object { $env:Path = "$($_.DirectoryName);$env:Path" }
|
||||||
api="https://api.github.com/repos/littlecodedragon/infinipaint"
|
}
|
||||||
auth=(-H "Authorization: Bearer ${GH_BUILDER_TOKEN}" -H "Accept: application/vnd.github+json")
|
conan --version
|
||||||
gh_sha=$(curl -fsSL "${auth[@]}" "${api}/commits/main" | python3 -c "import sys,json; print(json.load(sys.stdin)['sha'])")
|
|
||||||
if [ "$gh_sha" != "$WANT_SHA" ]; then
|
|
||||||
echo "WARN: GitHub main=${gh_sha:0:7} != Forgejo ${WANT_SHA:0:7} — keep remotes synced (push both)." >&2
|
|
||||||
fi
|
|
||||||
runs=$(curl -fsSL "${auth[@]}" "${api}/actions/workflows/windows_release.yml/runs?branch=main&per_page=15")
|
|
||||||
run_id=$(echo "$runs" | python3 -c "
|
|
||||||
import sys, json, os
|
|
||||||
want = os.environ['WANT_SHA']
|
|
||||||
runs = json.load(sys.stdin).get('workflow_runs', [])
|
|
||||||
# Prefer live or successful run for this SHA.
|
|
||||||
for r in runs:
|
|
||||||
if r.get('head_sha') != want:
|
|
||||||
continue
|
|
||||||
st, conc = r.get('status'), r.get('conclusion')
|
|
||||||
if st in ('queued', 'in_progress') or (st == 'completed' and conc == 'success'):
|
|
||||||
print(r['id'])
|
|
||||||
break
|
|
||||||
")
|
|
||||||
if [ -z "${run_id}" ]; then
|
|
||||||
echo "No usable GitHub run for ${WANT_SHA:0:7} — dispatching workflow_dispatch"
|
|
||||||
code=$(curl -sS -o /tmp/gh-dispatch.json -w '%{http_code}' -X POST "${auth[@]}" \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
"${api}/actions/workflows/windows_release.yml/dispatches" \
|
|
||||||
-d '{"ref":"main"}')
|
|
||||||
[ "$code" = "204" ] || { cat /tmp/gh-dispatch.json; exit 1; }
|
|
||||||
sleep 8
|
|
||||||
runs=$(curl -fsSL "${auth[@]}" "${api}/actions/workflows/windows_release.yml/runs?branch=main&per_page=5")
|
|
||||||
run_id=$(echo "$runs" | python3 -c "import sys,json; print(json.load(sys.stdin)['workflow_runs'][0]['id'])")
|
|
||||||
fi
|
|
||||||
echo "run_id=${run_id}" >> "$GITHUB_OUTPUT"
|
|
||||||
echo "Tracking GitHub run ${run_id}"
|
|
||||||
|
|
||||||
- name: Wait for GitHub build + Forgejo public release
|
- name: Build Windows x86_64
|
||||||
shell: bash
|
shell: cmd
|
||||||
|
run: |
|
||||||
|
call windowsinstall\conan_init_x86_64.bat
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
call windowsinstall\build_x86_64.bat
|
||||||
|
if errorlevel 1 exit /b 1
|
||||||
|
|
||||||
|
- name: Package portable zip
|
||||||
|
id: pkg
|
||||||
|
shell: pwsh
|
||||||
env:
|
env:
|
||||||
GH_BUILDER_TOKEN: ${{ secrets.GH_BUILDER_TOKEN }}
|
|
||||||
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
RUN_ID: ${{ steps.gh.outputs.run_id }}
|
|
||||||
ZIP_NAME: ${{ steps.ver.outputs.zip_name }}
|
ZIP_NAME: ${{ steps.ver.outputs.zip_name }}
|
||||||
|
run: |
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
$exe = Get-ChildItem -Path . -Recurse -Filter infinipaint.exe |
|
||||||
|
Where-Object { $_.FullName -match 'Release' } |
|
||||||
|
Select-Object -First 1
|
||||||
|
if (-not $exe) { throw "infinipaint.exe not found under Release" }
|
||||||
|
$outDir = Join-Path (Get-Location) "dist\portable"
|
||||||
|
Remove-Item -Recurse -Force $outDir -ErrorAction SilentlyContinue
|
||||||
|
New-Item -ItemType Directory -Force -Path $outDir | Out-Null
|
||||||
|
Copy-Item $exe.FullName (Join-Path $outDir "infinipaint.exe")
|
||||||
|
$dllDir = Join-Path (Get-Location) "windowsinstall\dlls-x86_64"
|
||||||
|
if (Test-Path $dllDir) {
|
||||||
|
Copy-Item (Join-Path $dllDir "*.dll") $outDir -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
$dataSrc = Join-Path (Get-Location) "assets\data"
|
||||||
|
if (-not (Test-Path $dataSrc)) { $dataSrc = Join-Path (Get-Location) "data" }
|
||||||
|
Copy-Item -Recurse $dataSrc (Join-Path $outDir "data")
|
||||||
|
New-Item -ItemType Directory -Force -Path "dist" | Out-Null
|
||||||
|
$zipPath = Join-Path (Get-Location) "dist\$env:ZIP_NAME"
|
||||||
|
if (Test-Path $zipPath) { Remove-Item $zipPath }
|
||||||
|
Compress-Archive -Path (Join-Path $outDir "*") -DestinationPath $zipPath
|
||||||
|
"zip_path=$zipPath" | Out-File -FilePath $env:GITHUB_OUTPUT -Append -Encoding utf8
|
||||||
|
Get-Item $zipPath | Format-List FullName, Length
|
||||||
|
|
||||||
|
- name: Publish Forgejo release
|
||||||
|
shell: bash
|
||||||
|
env:
|
||||||
|
FORGEJO_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
VERSION_TAG: ${{ steps.ver.outputs.tag }}
|
||||||
ASSET_VER: ${{ steps.ver.outputs.asset_ver }}
|
ASSET_VER: ${{ steps.ver.outputs.asset_ver }}
|
||||||
SHORT_SHA: ${{ steps.ver.outputs.short_sha }}
|
VERSION: ${{ steps.ver.outputs.version }}
|
||||||
|
ZIP_NAME: ${{ steps.ver.outputs.zip_name }}
|
||||||
|
FORGEJO_CURL_INSECURE: "true"
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
api="https://api.github.com/repos/littlecodedragon/infinipaint"
|
zip_path="dist/${ZIP_NAME}"
|
||||||
auth=(-H "Authorization: Bearer ${GH_BUILDER_TOKEN}" -H "Accept: application/vnd.github+json")
|
test -f "$zip_path"
|
||||||
fj="https://forgejo.fsociety00.cc/api/v1/repos/copilot/infinipaint/releases/tags/windows-continuous"
|
command -v jq >/dev/null
|
||||||
for i in $(seq 1 240); do
|
command -v curl >/dev/null
|
||||||
g=$(curl -fsSL "${auth[@]}" "${api}/actions/runs/${RUN_ID}")
|
|
||||||
status=$(echo "$g" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d.get('status'), d.get('conclusion') or '-')")
|
curl_tls=()
|
||||||
rel=$(curl -fsSL -H "Authorization: token ${FORGEJO_TOKEN}" "$fj" || echo '{}')
|
if [ "${FORGEJO_CURL_INSECURE}" = "true" ]; then
|
||||||
rname=$(echo "$rel" | python3 -c "import sys,json;print(json.load(sys.stdin).get('name',''))" 2>/dev/null || true)
|
curl_tls+=(--insecure)
|
||||||
assets=$(echo "$rel" | python3 -c "import sys,json;print([a.get('name') for a in json.load(sys.stdin).get('assets',[])])" 2>/dev/null || true)
|
|
||||||
echo "$(date -u +%H:%M:%S) github=${status} forgejo=${rname} assets=${assets}"
|
|
||||||
if echo "$assets" | grep -Fq "${ZIP_NAME}" || echo "$rname" | grep -Fq "${SHORT_SHA}"; then
|
|
||||||
echo "Public Forgejo release is up to date."
|
|
||||||
exit 0
|
|
||||||
fi
|
fi
|
||||||
if echo "$status" | grep -q '^completed failure\|^completed cancelled\|^completed timed_out'; then
|
|
||||||
echo "GitHub Windows build failed: ${status}" >&2
|
api_base="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/releases"
|
||||||
echo "$g" | python3 -c "import sys,json;print(json.load(sys.stdin).get('html_url',''))"
|
body=$(printf '%s\n' \
|
||||||
exit 1
|
"Continuous Windows build from \`main\`." \
|
||||||
fi
|
"" \
|
||||||
sleep 60
|
"- Version: **${VERSION}**" \
|
||||||
|
"- Commit: \`${GITHUB_SHA}\`" \
|
||||||
|
"- Asset: \`${ZIP_NAME}\`" \
|
||||||
|
"" \
|
||||||
|
"Updated on every push to \`main\`. Requires runner label \`windows-release\` (see docs/windows-runner.md).")
|
||||||
|
|
||||||
|
payload=$(jq -n \
|
||||||
|
--arg tag "$VERSION_TAG" \
|
||||||
|
--arg name "Windows continuous (${ASSET_VER})" \
|
||||||
|
--arg body "$body" \
|
||||||
|
'{tag_name:$tag, name:$name, body:$body, draft:false, prerelease:true}')
|
||||||
|
|
||||||
|
tag_enc=$(jq -nr --arg t "$VERSION_TAG" '$t|@uri')
|
||||||
|
resp_file=$(mktemp)
|
||||||
|
status=$(curl "${curl_tls[@]}" -sS -o "$resp_file" -w '%{http_code}' \
|
||||||
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
|
"${api_base}/tags/${tag_enc}")
|
||||||
|
|
||||||
|
release_id=""
|
||||||
|
if [ "$status" = "200" ]; then
|
||||||
|
release_id=$(jq -r '.id' < "$resp_file")
|
||||||
|
curl "${curl_tls[@]}" -sS -X PATCH \
|
||||||
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" \
|
||||||
|
"${api_base}/${release_id}" >/dev/null
|
||||||
|
jq -r --arg n "$ZIP_NAME" '.assets[]? | select(.name==$n) | .id' < "$resp_file" | while read -r aid; do
|
||||||
|
[ -n "$aid" ] || continue
|
||||||
|
curl "${curl_tls[@]}" -sS -X DELETE \
|
||||||
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
|
"${api_base}/${release_id}/assets/${aid}" >/dev/null || true
|
||||||
done
|
done
|
||||||
echo "Timed out waiting for Forgejo asset ${ZIP_NAME}" >&2
|
elif [ "$status" = "404" ]; then
|
||||||
exit 1
|
status=$(curl "${curl_tls[@]}" -sS -o "$resp_file" -w '%{http_code}' -X POST \
|
||||||
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "$payload" \
|
||||||
|
"$api_base")
|
||||||
|
[ "$status" = "200" ] || [ "$status" = "201" ] || { cat "$resp_file"; exit 1; }
|
||||||
|
release_id=$(jq -r '.id' < "$resp_file")
|
||||||
|
else
|
||||||
|
cat "$resp_file"; exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
name_enc=$(jq -nr --arg n "$ZIP_NAME" '$n|@uri')
|
||||||
|
curl "${curl_tls[@]}" -sS -X POST \
|
||||||
|
-H "Authorization: token ${FORGEJO_TOKEN}" \
|
||||||
|
-H "Content-Type: application/octet-stream" \
|
||||||
|
--data-binary @"${zip_path}" \
|
||||||
|
"${api_base}/${release_id}/assets?name=${name_enc}" \
|
||||||
|
| jq -r '.browser_download_url // .name // .'
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ set(sources "src/main.cpp"
|
||||||
"src/DrawData.cpp"
|
"src/DrawData.cpp"
|
||||||
"src/FontData.cpp"
|
"src/FontData.cpp"
|
||||||
"src/ClientData.cpp"
|
"src/ClientData.cpp"
|
||||||
"src/CollabPermissions.cpp"
|
|
||||||
"src/GridManager.cpp"
|
"src/GridManager.cpp"
|
||||||
"src/WorldGrid.cpp"
|
"src/WorldGrid.cpp"
|
||||||
"src/TimePoint.cpp"
|
"src/TimePoint.cpp"
|
||||||
|
|
|
||||||
22
FORK.md
22
FORK.md
|
|
@ -4,14 +4,14 @@ Upstream: [ErrorAtLine0/infinipaint](https://github.com/ErrorAtLine0/infinipaint
|
||||||
**Public forge:** https://forgejo.fsociety00.cc/copilot/infinipaint
|
**Public forge:** https://forgejo.fsociety00.cc/copilot/infinipaint
|
||||||
GitHub (`littlecodedragon/infinipaint`) is **private builder only** — not a public fork mirror. Source of truth stays on Forgejo.
|
GitHub (`littlecodedragon/infinipaint`) is **private builder only** — not a public fork mirror. Source of truth stays on Forgejo.
|
||||||
|
|
||||||
Save format: `INFPNT000009` / version `0.9.0-fork` (not network-compatible with stock 0.6.x peers).
|
Save format: `INFPNT000008` / version `0.8.0-fork` (not network-compatible with stock 0.6.x peers).
|
||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
| Workflow | Trigger | Runner | Output |
|
| Workflow | Trigger | Runner | Output |
|
||||||
|----------|---------|--------|--------|
|
|----------|---------|--------|--------|
|
||||||
| `.forgejo/workflows/windows_release.yml` | push `main` on Forgejo | Tower **`desktop-release`** | dispatches/waits private GitHub MSVC build, verifies Forgejo tag `windows-continuous` |
|
| `.github/workflows/windows_release.yml` | push `main` to private GitHub mirror | GitHub-hosted **`windows-latest`** | uploads public Forgejo tag `windows-continuous` |
|
||||||
| `.github/workflows/windows_release.yml` | private GitHub mirror | GitHub-hosted **`windows-latest`** | compiles zip + uploads public Forgejo `windows-continuous` |
|
| `.forgejo/workflows/windows_release.yml` | every push to `main` | Forgejo self-hosted `windows-release` (optional) | same tag on Forgejo |
|
||||||
|
|
||||||
**Friends (Windows):** https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous — zip entpacken, `infinipaint.exe` starten.
|
**Friends (Windows):** https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuous — zip entpacken, `infinipaint.exe` starten.
|
||||||
Details: [docs/windows-runner.md](docs/windows-runner.md).
|
Details: [docs/windows-runner.md](docs/windows-runner.md).
|
||||||
|
|
@ -33,18 +33,4 @@ Not automatic on import. Choose **Paste Image + Freistellen** from the canvas ri
|
||||||
|
|
||||||
## Fog of War / layer lock (host)
|
## Fog of War / layer lock (host)
|
||||||
|
|
||||||
In the layer panel (host / local file only): mark a leaf layer as **Fog of War layer** (must sit **above** the content it covers), then paint fog with brush/shapes on that layer. Players receive a **stripped** world — only objects **fully** covered by opaque fog (not merely overlapping) are omitted from join sync and from their save. Partially covered images stay. Moving or reshaping fog (or content under it) live-updates clients: newly covered objects are unloaded, revealed ones are sent again with their resources. The host file stays complete. Fog draws at the layer’s real alpha (1.0 = fully opaque for host and players). **Lock for players** is a quick toggle that sets layer-default Draw/Move/Delete to Deny; fog layers are always locked for players (move/edit/erase rejected on the host). Players also **cannot hide or fade** a fog layer (eye / alpha); only the host can toggle fog visibility, and the host rejects client DisplayData that would strip FoW. FoW visibility is still global (not per-person).
|
In the layer panel (host / local file only): mark a leaf layer as **Fog of War layer** (must sit **above** the content it covers), then paint fog with brush/shapes on that layer. Players receive a **stripped** world — only objects **fully** covered by opaque fog (not merely overlapping) are omitted from join sync and from their save. Partially covered images stay. Moving or reshaping fog (or content under it) live-updates clients: newly covered objects are unloaded, revealed ones are sent again with their resources. The host file stays complete. Fog draws at the layer’s real alpha (1.0 = fully opaque for host and players). **Lock for players** blocks client edits on a normal layer; fog layers are always locked for players (move/edit/erase rejected on the host).
|
||||||
|
|
||||||
## Collab permissions (host)
|
|
||||||
|
|
||||||
Rights: **Draw**, **Move**, **Delete**, **Hide**, **Layers** (create/reorder/delete layers). Each right is Allow / Deny / Inherit.
|
|
||||||
|
|
||||||
Resolution (first concrete wins): Person×Layer override → Layer defaults → Person defaults → World default (Inherit at world = Allow).
|
|
||||||
|
|
||||||
Host UI:
|
|
||||||
|
|
||||||
- Player list → **Session Defaults…** (world / new-player / new-layer templates)
|
|
||||||
- Player list → **Rights** per connected player (person defaults; copied from new-player template on join)
|
|
||||||
- Layer panel (host): layer defaults + per-player overrides for the selected layer
|
|
||||||
|
|
||||||
Clients cannot escalate their own ACL; host DisplayData/ClientData updates are sanitized.
|
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,10 @@
|
||||||
# Windows continuous release (Forgejo → private GitHub builder)
|
# Windows continuous release (GitHub Actions as private builder)
|
||||||
|
|
||||||
Native InfiniPaint Windows builds need **MSVC** (see `docs/BUILDING.md` +
|
Native InfiniPaint Windows builds need **MSVC** (see `docs/BUILDING.md` +
|
||||||
`windowsinstall/`). There is no Linux cross-build path.
|
`windowsinstall/`). There is no Linux cross-build path.
|
||||||
|
|
||||||
Policy:
|
Policy: **Forgejo is the public fork**; GitHub is only a **private** mirror used
|
||||||
|
to run `windows-latest` CI. Friends download the zip from Forgejo — not GitHub.
|
||||||
- **Forgejo** (`copilot/infinipaint`) is the **public** fork and download site.
|
|
||||||
- **GitHub** (`littlecodedragon/infinipaint`) is a **private** builder mirror only
|
|
||||||
(`windows-latest` MSVC). Must stay private.
|
|
||||||
- Tower **`desktop-release`** runs the Forgejo workflow: it dispatches/waits on
|
|
||||||
the private GitHub build, then checks the public Forgejo release.
|
|
||||||
|
|
||||||
## Public download (friends)
|
## Public download (friends)
|
||||||
|
|
||||||
|
|
@ -17,31 +12,36 @@ https://forgejo.fsociety00.cc/copilot/infinipaint/releases/tag/windows-continuou
|
||||||
|
|
||||||
Portable zip → extract → run `infinipaint.exe`.
|
Portable zip → extract → run `infinipaint.exe`.
|
||||||
|
|
||||||
## Workflows
|
## Workflow
|
||||||
|
|
||||||
| File | Runner | Role |
|
File: `.github/workflows/windows_release.yml`
|
||||||
|------|--------|------|
|
|
||||||
| `.forgejo/workflows/windows_release.yml` | `desktop-release` (Tower) | Orchestrate + verify Forgejo release |
|
|
||||||
| `.github/workflows/windows_release.yml` | `windows-latest` (private) | Compile + upload Forgejo asset |
|
|
||||||
|
|
||||||
Forgejo repo secret: **`GH_BUILDER_TOKEN`** (GitHub PAT with `actions:write` /
|
| | |
|
||||||
`contents:read` on the private mirror).
|
|---|---|
|
||||||
|
| Trigger | every push to `main` on the private GitHub mirror, plus `workflow_dispatch` |
|
||||||
|
| Runner | `windows-latest` (GitHub-hosted, MSVC ~195 / VS 18) |
|
||||||
|
| Build | Conan profile `win-ci` + **Ninja** (avoids CMake<4 vs `Visual Studio 18 2026`) |
|
||||||
|
| Output | public Forgejo prerelease **`windows-continuous`** (+ private GitHub copy) |
|
||||||
|
|
||||||
GitHub Actions secret: **`FORGEJO_TOKEN`** (Forgejo PAT with release write on
|
Required GitHub Actions secret: `FORGEJO_TOKEN` (Forgejo PAT with release write on
|
||||||
`copilot/infinipaint`).
|
`copilot/infinipaint`).
|
||||||
|
|
||||||
## Tray
|
Local Tower Windows can keep using `windowsinstall/conan_init_x86_64.bat`
|
||||||
|
(`conan/profiles/win-x86_64`, MSVC 194). CI generates a `win-ci` profile for
|
||||||
|
whatever MSVC `windows-latest` ships.
|
||||||
|
|
||||||
Tower CI job tray (`ci-runner-job-tray.py`) tracks:
|
## GitHub remote (private builder)
|
||||||
|
|
||||||
- `Forgejo desktop-release` — host unit (orchestrator + APK jobs)
|
|
||||||
- `Forgejo InfiniPaint Windows` — Forgejo Actions run for this workflow
|
|
||||||
|
|
||||||
## Sync remotes
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git push origin main # Forgejo (public)
|
git remote add github https://github.com/littlecodedragon/infinipaint.git
|
||||||
git push github main # private builder
|
git push -u github main
|
||||||
```
|
```
|
||||||
|
|
||||||
Keep GitHub **private**.
|
Keep the repo **private** (Settings → Change visibility, or
|
||||||
|
`gh repo edit littlecodedragon/infinipaint --visibility private`).
|
||||||
|
|
||||||
|
## Forgejo note
|
||||||
|
|
||||||
|
`.forgejo/workflows/windows_release.yml` still targets a self-hosted
|
||||||
|
`windows-release` runner (optional). Primary public zips come from the GitHub
|
||||||
|
builder → Forgejo release upload above.
|
||||||
|
|
|
||||||
0
linuxinstall/install-local.sh
Executable file → Normal file
0
linuxinstall/install-local.sh
Executable file → Normal file
|
|
@ -45,10 +45,10 @@ void CanvasComponentAllocator::register_class(World& world) {
|
||||||
.writeUpdate = [](const CanvasComponentAllocator& o, cereal::PortableBinaryOutputArchive& a) {
|
.writeUpdate = [](const CanvasComponentAllocator& o, cereal::PortableBinaryOutputArchive& a) {
|
||||||
a(*o.comp);
|
a(*o.comp);
|
||||||
},
|
},
|
||||||
.readUpdate = [&world](CanvasComponentAllocator& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& c) {
|
.readUpdate = [](CanvasComponentAllocator& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& c) {
|
||||||
// Host rejects client edits without Draw on that layer (still consume the payload).
|
// Host rejects client edits of fog/locked-layer components (still consume the payload).
|
||||||
if(c && o.comp && o.comp->compContainer
|
if(c && o.comp && o.comp->compContainer && o.comp->compContainer->parentLayer
|
||||||
&& !world.drawProg.layerMan.client_may_modify_component(o.comp->compContainer, CollabRight::Draw)) {
|
&& o.comp->compContainer->parentLayer->is_player_edit_blocked()) {
|
||||||
auto junk = std::unique_ptr<CanvasComponent>(CanvasComponent::allocate_comp(o.comp->get_type()));
|
auto junk = std::unique_ptr<CanvasComponent>(CanvasComponent::allocate_comp(o.comp->get_type()));
|
||||||
a(*junk);
|
a(*junk);
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,7 @@ enum class ClientDataCommand : uint8_t {
|
||||||
SET_WINDOW_SIZE,
|
SET_WINDOW_SIZE,
|
||||||
SET_CAM_COORDS,
|
SET_CAM_COORDS,
|
||||||
SEND_CHAT_MESSAGE,
|
SEND_CHAT_MESSAGE,
|
||||||
SET_GRID_SIZE,
|
SET_GRID_SIZE
|
||||||
SET_PERSON_DEFAULTS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientData::ClientData() {
|
ClientData::ClientData() {
|
||||||
|
|
@ -55,7 +54,6 @@ void ClientData::set_from_init_struct(const InitStruct& initStruct) {
|
||||||
cursorColor = initStruct.cursorColor;
|
cursorColor = initStruct.cursorColor;
|
||||||
displayName = initStruct.displayName;
|
displayName = initStruct.displayName;
|
||||||
gridSize = initStruct.gridSize;
|
gridSize = initStruct.gridSize;
|
||||||
personDefaults = initStruct.personDefaults;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientData::register_class(World& world) {
|
void ClientData::register_class(World& world) {
|
||||||
|
|
@ -94,11 +92,6 @@ void ClientData::register_class(World& world) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ClientDataCommand::SET_PERSON_DEFAULTS:
|
|
||||||
a(o->personDefaults);
|
|
||||||
world.drawProg.layerMan.ensure_editing_layer_is_editable();
|
|
||||||
world.set_to_layout_gui_if_focus();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.writeConstructorFuncServer = default_serialize_write_func<ClientData>,
|
.writeConstructorFuncServer = default_serialize_write_func<ClientData>,
|
||||||
|
|
@ -148,20 +141,6 @@ void ClientData::register_class(World& world) {
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ClientDataCommand::SET_PERSON_DEFAULTS: {
|
|
||||||
// Only the host may change ACLs — junk-consume client attempts.
|
|
||||||
CollabPermissionSet incoming;
|
|
||||||
a(incoming);
|
|
||||||
if(c)
|
|
||||||
break;
|
|
||||||
o->personDefaults = incoming;
|
|
||||||
o.send_server_update_to_all_clients(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
||||||
a(ClientDataCommand::SET_PERSON_DEFAULTS, o->personDefaults);
|
|
||||||
});
|
|
||||||
world.drawProg.layerMan.ensure_editing_layer_is_editable();
|
|
||||||
world.set_to_layout_gui_if_focus();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
@ -208,15 +187,6 @@ void ClientData::scale_up_step(const NetworkingObjects::NetObjTemporaryPtr<Clien
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void ClientData::set_person_defaults(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, const CollabPermissionSet& defaults) {
|
|
||||||
if(o->personDefaults == defaults)
|
|
||||||
return;
|
|
||||||
o->personDefaults = defaults;
|
|
||||||
o.send_update_to_all(RELIABLE_COMMAND_CHANNEL, [](const NetObjTemporaryPtr<ClientData>& o, cereal::PortableBinaryOutputArchive & a) {
|
|
||||||
a(ClientDataCommand::SET_PERSON_DEFAULTS, o->personDefaults);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientData::set_display_name(const std::string& newDisplayName) {
|
void ClientData::set_display_name(const std::string& newDisplayName) {
|
||||||
displayName = newDisplayName;
|
displayName = newDisplayName;
|
||||||
}
|
}
|
||||||
|
|
@ -249,14 +219,6 @@ const Vector3f& ClientData::get_cursor_color() const {
|
||||||
return cursorColor;
|
return cursorColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollabPermissionSet& ClientData::get_person_defaults() const {
|
|
||||||
return personDefaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
CollabPermissionSet& ClientData::person_defaults_mut() {
|
|
||||||
return personDefaults;
|
|
||||||
}
|
|
||||||
|
|
||||||
void ClientData::draw_cursor(SkCanvas* canvas, const DrawData& drawData) const {
|
void ClientData::draw_cursor(SkCanvas* canvas, const DrawData& drawData) const {
|
||||||
if((drawData.cam.c.inverseScale << 10) > camCoords.inverseScale && drawData.clampDrawMinimum < camCoords.inverseScale) {
|
if((drawData.cam.c.inverseScale << 10) > camCoords.inverseScale && drawData.clampDrawMinimum < camCoords.inverseScale) {
|
||||||
canvas->save();
|
canvas->save();
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include <Helpers/Serializers.hpp>
|
#include <Helpers/Serializers.hpp>
|
||||||
#include "CoordSpaceHelper.hpp"
|
#include "CoordSpaceHelper.hpp"
|
||||||
#include "CollabPermissions.hpp"
|
|
||||||
#include <Helpers/NetworkingObjects/NetObjTemporaryPtr.hpp>
|
#include <Helpers/NetworkingObjects/NetObjTemporaryPtr.hpp>
|
||||||
#include <Helpers/NetworkingObjects/NetObjManager.hpp>
|
#include <Helpers/NetworkingObjects/NetObjManager.hpp>
|
||||||
|
|
||||||
|
|
@ -33,7 +32,6 @@ class ClientData {
|
||||||
Vector3f cursorColor = {1.0f, 1.0f, 1.0f};
|
Vector3f cursorColor = {1.0f, 1.0f, 1.0f};
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
uint32_t gridSize = 0;
|
uint32_t gridSize = 0;
|
||||||
CollabPermissionSet personDefaults = CollabPermissionSet::all_inherit();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
ClientData();
|
ClientData();
|
||||||
|
|
@ -44,8 +42,6 @@ class ClientData {
|
||||||
static void set_camera_coords(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, const CoordSpaceHelper& newCoords);
|
static void set_camera_coords(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, const CoordSpaceHelper& newCoords);
|
||||||
static void send_chat_message(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, World& world, const std::string& chatMessage);
|
static void send_chat_message(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, World& world, const std::string& chatMessage);
|
||||||
static void scale_up_step(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, World& world);
|
static void scale_up_step(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, World& world);
|
||||||
/** Host-only: push person-default ACL to all peers. */
|
|
||||||
static void set_person_defaults(const NetworkingObjects::NetObjTemporaryPtr<ClientData>& o, const CollabPermissionSet& defaults);
|
|
||||||
void set_display_name(const std::string& newDisplayName);
|
void set_display_name(const std::string& newDisplayName);
|
||||||
void set_cursor_color(const Vector3f& newCursorColor);
|
void set_cursor_color(const Vector3f& newCursorColor);
|
||||||
|
|
||||||
|
|
@ -55,13 +51,11 @@ class ClientData {
|
||||||
uint32_t get_grid_size() const;
|
uint32_t get_grid_size() const;
|
||||||
const std::string& get_display_name() const;
|
const std::string& get_display_name() const;
|
||||||
const Vector3f& get_cursor_color() const;
|
const Vector3f& get_cursor_color() const;
|
||||||
const CollabPermissionSet& get_person_defaults() const;
|
|
||||||
CollabPermissionSet& person_defaults_mut();
|
|
||||||
|
|
||||||
void draw_cursor(SkCanvas* canvas, const DrawData& drawData) const;
|
void draw_cursor(SkCanvas* canvas, const DrawData& drawData) const;
|
||||||
|
|
||||||
template <typename Archive> void serialize(Archive& a) {
|
template <typename Archive> void serialize(Archive& a) {
|
||||||
a(camCoords, windowSize, cursorPos, cursorColor, displayName, gridSize, personDefaults);
|
a(camCoords, windowSize, cursorPos, cursorColor, displayName, gridSize);
|
||||||
}
|
}
|
||||||
private:
|
private:
|
||||||
void set_from_init_struct(const InitStruct& initStruct);
|
void set_from_init_struct(const InitStruct& initStruct);
|
||||||
|
|
@ -72,6 +66,5 @@ class ClientData {
|
||||||
Vector3f cursorColor;
|
Vector3f cursorColor;
|
||||||
std::string displayName;
|
std::string displayName;
|
||||||
uint32_t gridSize;
|
uint32_t gridSize;
|
||||||
CollabPermissionSet personDefaults;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,114 +0,0 @@
|
||||||
#include "CollabPermissions.hpp"
|
|
||||||
|
|
||||||
CollabTriState CollabPermissionSet::get(CollabRight right) const {
|
|
||||||
switch(right) {
|
|
||||||
case CollabRight::Draw: return draw;
|
|
||||||
case CollabRight::Move: return move;
|
|
||||||
case CollabRight::Delete: return erase;
|
|
||||||
case CollabRight::Hide: return hide;
|
|
||||||
case CollabRight::ManageLayers: return manageLayers;
|
|
||||||
case CollabRight::COUNT: break;
|
|
||||||
}
|
|
||||||
return CollabTriState::Inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CollabPermissionSet::set(CollabRight right, CollabTriState state) {
|
|
||||||
switch(right) {
|
|
||||||
case CollabRight::Draw: draw = state; break;
|
|
||||||
case CollabRight::Move: move = state; break;
|
|
||||||
case CollabRight::Delete: erase = state; break;
|
|
||||||
case CollabRight::Hide: hide = state; break;
|
|
||||||
case CollabRight::ManageLayers: manageLayers = state; break;
|
|
||||||
case CollabRight::COUNT: break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CollabPermissionSet::any_concrete() const {
|
|
||||||
return draw != CollabTriState::Inherit
|
|
||||||
|| move != CollabTriState::Inherit
|
|
||||||
|| erase != CollabTriState::Inherit
|
|
||||||
|| hide != CollabTriState::Inherit
|
|
||||||
|| manageLayers != CollabTriState::Inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
CollabPermissionSet CollabPermissionSet::all_allow() {
|
|
||||||
CollabPermissionSet s;
|
|
||||||
s.draw = CollabTriState::Allow;
|
|
||||||
s.move = CollabTriState::Allow;
|
|
||||||
s.erase = CollabTriState::Allow;
|
|
||||||
s.hide = CollabTriState::Allow;
|
|
||||||
s.manageLayers = CollabTriState::Allow;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
CollabPermissionSet CollabPermissionSet::all_inherit() {
|
|
||||||
return CollabPermissionSet{};
|
|
||||||
}
|
|
||||||
|
|
||||||
CollabPermissionSet CollabPermissionSet::content_deny() {
|
|
||||||
CollabPermissionSet s;
|
|
||||||
s.draw = CollabTriState::Deny;
|
|
||||||
s.move = CollabTriState::Deny;
|
|
||||||
s.erase = CollabTriState::Deny;
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool collab_resolve_allow(
|
|
||||||
CollabRight right,
|
|
||||||
const CollabPermissionSet* personLayerOverride,
|
|
||||||
const CollabPermissionSet& layerDefaults,
|
|
||||||
const CollabPermissionSet& personDefaults,
|
|
||||||
const CollabPermissionSet& worldDefault)
|
|
||||||
{
|
|
||||||
auto pick = [&](const CollabPermissionSet& set) -> CollabTriState {
|
|
||||||
return set.get(right);
|
|
||||||
};
|
|
||||||
if(personLayerOverride) {
|
|
||||||
CollabTriState s = pick(*personLayerOverride);
|
|
||||||
if(s != CollabTriState::Inherit)
|
|
||||||
return s == CollabTriState::Allow;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
CollabTriState s = pick(layerDefaults);
|
|
||||||
if(s != CollabTriState::Inherit)
|
|
||||||
return s == CollabTriState::Allow;
|
|
||||||
}
|
|
||||||
{
|
|
||||||
CollabTriState s = pick(personDefaults);
|
|
||||||
if(s != CollabTriState::Inherit)
|
|
||||||
return s == CollabTriState::Allow;
|
|
||||||
}
|
|
||||||
CollabTriState s = pick(worldDefault);
|
|
||||||
// World Inherit → Allow (open by default).
|
|
||||||
return s != CollabTriState::Deny;
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* collab_right_label(CollabRight right) {
|
|
||||||
switch(right) {
|
|
||||||
case CollabRight::Draw: return "Draw";
|
|
||||||
case CollabRight::Move: return "Move";
|
|
||||||
case CollabRight::Delete: return "Delete";
|
|
||||||
case CollabRight::Hide: return "Hide";
|
|
||||||
case CollabRight::ManageLayers: return "Layers";
|
|
||||||
case CollabRight::COUNT: break;
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
const char* collab_tristate_label(CollabTriState state) {
|
|
||||||
switch(state) {
|
|
||||||
case CollabTriState::Inherit: return "Inherit";
|
|
||||||
case CollabTriState::Allow: return "Allow";
|
|
||||||
case CollabTriState::Deny: return "Deny";
|
|
||||||
}
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
|
|
||||||
CollabTriState collab_cycle_tristate(CollabTriState state) {
|
|
||||||
switch(state) {
|
|
||||||
case CollabTriState::Inherit: return CollabTriState::Allow;
|
|
||||||
case CollabTriState::Allow: return CollabTriState::Deny;
|
|
||||||
case CollabTriState::Deny: return CollabTriState::Inherit;
|
|
||||||
}
|
|
||||||
return CollabTriState::Inherit;
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
/*
|
|
||||||
* InfiniPaint (fork)
|
|
||||||
* Collab ACL: World → Person → Layer → Person×Layer (Allow/Deny/Inherit)
|
|
||||||
*/
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <cstdint>
|
|
||||||
#include <map>
|
|
||||||
#include <string>
|
|
||||||
#include <cereal/types/map.hpp>
|
|
||||||
#include <cereal/types/string.hpp>
|
|
||||||
|
|
||||||
enum class CollabRight : uint8_t {
|
|
||||||
Draw = 0,
|
|
||||||
Move,
|
|
||||||
Delete,
|
|
||||||
Hide,
|
|
||||||
ManageLayers,
|
|
||||||
COUNT
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class CollabTriState : uint8_t {
|
|
||||||
Inherit = 0,
|
|
||||||
Allow = 1,
|
|
||||||
Deny = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CollabPermissionSet {
|
|
||||||
CollabTriState draw = CollabTriState::Inherit;
|
|
||||||
CollabTriState move = CollabTriState::Inherit;
|
|
||||||
CollabTriState erase = CollabTriState::Inherit;
|
|
||||||
CollabTriState hide = CollabTriState::Inherit;
|
|
||||||
CollabTriState manageLayers = CollabTriState::Inherit;
|
|
||||||
|
|
||||||
CollabTriState get(CollabRight right) const;
|
|
||||||
void set(CollabRight right, CollabTriState state);
|
|
||||||
bool any_concrete() const;
|
|
||||||
|
|
||||||
static CollabPermissionSet all_allow();
|
|
||||||
static CollabPermissionSet all_inherit();
|
|
||||||
static CollabPermissionSet content_deny(); // Draw/Move/Delete Deny, rest Inherit
|
|
||||||
|
|
||||||
template <typename Archive>
|
|
||||||
void serialize(Archive& a) {
|
|
||||||
a(draw, move, erase, hide, manageLayers);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const CollabPermissionSet&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CollabSessionDefaults {
|
|
||||||
// Concrete fallback when everything inherits (Inherit treated as Allow).
|
|
||||||
CollabPermissionSet worldDefault = CollabPermissionSet::all_allow();
|
|
||||||
// Copied onto ClientData when a player joins.
|
|
||||||
CollabPermissionSet newPlayerDefaults = CollabPermissionSet::all_inherit();
|
|
||||||
// Copied onto new layers' DisplayData.layerDefaults.
|
|
||||||
CollabPermissionSet newLayerDefaults = CollabPermissionSet::all_inherit();
|
|
||||||
|
|
||||||
template <typename Archive>
|
|
||||||
void serialize(Archive& a) {
|
|
||||||
a(worldDefault, newPlayerDefaults, newLayerDefaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool operator==(const CollabSessionDefaults&) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Resolve Allow/Deny for peer on a layer. Host callers should short-circuit before this. */
|
|
||||||
bool collab_resolve_allow(
|
|
||||||
CollabRight right,
|
|
||||||
const CollabPermissionSet* personLayerOverride, // nullable
|
|
||||||
const CollabPermissionSet& layerDefaults,
|
|
||||||
const CollabPermissionSet& personDefaults,
|
|
||||||
const CollabPermissionSet& worldDefault);
|
|
||||||
|
|
||||||
const char* collab_right_label(CollabRight right);
|
|
||||||
const char* collab_tristate_label(CollabTriState state);
|
|
||||||
CollabTriState collab_cycle_tristate(CollabTriState state);
|
|
||||||
|
|
@ -48,8 +48,6 @@
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include "../CanvasComponents/ImageCanvasComponent.hpp"
|
#include "../CanvasComponents/ImageCanvasComponent.hpp"
|
||||||
#include "Layers/DrawingProgramLayer.hpp"
|
#include "Layers/DrawingProgramLayer.hpp"
|
||||||
#include "../CollabPermissions.hpp"
|
|
||||||
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
|
|
||||||
#include "../ImagePreprocess.hpp"
|
#include "../ImagePreprocess.hpp"
|
||||||
#include <Helpers/Random.hpp>
|
#include <Helpers/Random.hpp>
|
||||||
#include <cereal/types/utility.hpp>
|
#include <cereal/types/utility.hpp>
|
||||||
|
|
@ -310,44 +308,7 @@ std::optional<InputManager::TextBoxStartInfo> DrawingProgram::get_text_box_start
|
||||||
return drawTool->get_text_box_start_info();
|
return drawTool->get_text_box_start_info();
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgram::register_collab_defaults_class(World& world) {
|
|
||||||
world.delayedUpdateObjectManager.register_class<CollabSessionDefaults>(world.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors<CollabSessionDefaults>{
|
|
||||||
.readUpdate = [](CollabSessionDefaults& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& c) {
|
|
||||||
CollabSessionDefaults incoming;
|
|
||||||
a(incoming);
|
|
||||||
// Clients cannot escalate session defaults.
|
|
||||||
if(c)
|
|
||||||
return;
|
|
||||||
o = incoming;
|
|
||||||
},
|
|
||||||
.postUpdateFunc = [&world](CollabSessionDefaults&) {
|
|
||||||
world.drawProg.layerMan.ensure_editing_layer_is_editable();
|
|
||||||
world.set_to_layout_gui_if_focus();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgram::ensure_collab_defaults() {
|
|
||||||
if(!collabDefaults)
|
|
||||||
collabDefaults = world.netObjMan.make_obj<CollabSessionDefaults>();
|
|
||||||
}
|
|
||||||
|
|
||||||
const CollabSessionDefaults& DrawingProgram::collab_defaults() const {
|
|
||||||
static const CollabSessionDefaults kFallback{};
|
|
||||||
return collabDefaults ? *collabDefaults : kFallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgram::set_collab_defaults(const CollabSessionDefaults& defaults) {
|
|
||||||
ensure_collab_defaults();
|
|
||||||
if(*collabDefaults == defaults)
|
|
||||||
return;
|
|
||||||
*collabDefaults = defaults;
|
|
||||||
world.delayedUpdateObjectManager.send_update_to_all<CollabSessionDefaults>(collabDefaults, false);
|
|
||||||
layerMan.ensure_editing_layer_is_editable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgram::server_init_no_file() {
|
void DrawingProgram::server_init_no_file() {
|
||||||
ensure_collab_defaults();
|
|
||||||
layerMan.server_init_no_file();
|
layerMan.server_init_no_file();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -361,13 +322,10 @@ void DrawingProgram::scale_up(const WorldScalar& scaleUpAmount) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgram::write_components_server(cereal::PortableBinaryOutputArchive& a) {
|
void DrawingProgram::write_components_server(cereal::PortableBinaryOutputArchive& a) {
|
||||||
ensure_collab_defaults();
|
|
||||||
collabDefaults.write_create_message(a);
|
|
||||||
layerMan.write_components_server(a);
|
layerMan.write_components_server(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgram::read_components_client(cereal::PortableBinaryInputArchive& a) {
|
void DrawingProgram::read_components_client(cereal::PortableBinaryInputArchive& a) {
|
||||||
collabDefaults = world.netObjMan.read_create_message<CollabSessionDefaults>(a, nullptr);
|
|
||||||
layerMan.read_components_client(a);
|
layerMan.read_components_client(a);
|
||||||
drawCache.build({});
|
drawCache.build({});
|
||||||
}
|
}
|
||||||
|
|
@ -377,7 +335,6 @@ void DrawingProgram::init_server_callbacks() {
|
||||||
std::vector<std::pair<NetworkingObjects::NetObjID, CoordSpaceHelper>> transforms;
|
std::vector<std::pair<NetworkingObjects::NetObjID, CoordSpaceHelper>> transforms;
|
||||||
message(transforms);
|
message(transforms);
|
||||||
|
|
||||||
world.currentNetUpdateClient = client;
|
|
||||||
auto clientData = world.netObjMan.get_obj_temporary_ref_from_id<ClientData>(NetworkingObjects::NetObjID(client->customID));
|
auto clientData = world.netObjMan.get_obj_temporary_ref_from_id<ClientData>(NetworkingObjects::NetObjID(client->customID));
|
||||||
if(clientData->get_grid_size() < world.ownClientData->get_grid_size()) {
|
if(clientData->get_grid_size() < world.ownClientData->get_grid_size()) {
|
||||||
WorldScalar scaleUpAmount = get_canvas_scale_up_amount(world.ownClientData->get_grid_size(), clientData->get_grid_size());
|
WorldScalar scaleUpAmount = get_canvas_scale_up_amount(world.ownClientData->get_grid_size(), clientData->get_grid_size());
|
||||||
|
|
@ -392,13 +349,12 @@ void DrawingProgram::init_server_callbacks() {
|
||||||
auto objPtr = world.netObjMan.get_obj_temporary_ref_from_id<CanvasComponentContainer>(netID);
|
auto objPtr = world.netObjMan.get_obj_temporary_ref_from_id<CanvasComponentContainer>(netID);
|
||||||
if(!objPtr)
|
if(!objPtr)
|
||||||
continue;
|
continue;
|
||||||
if(!layerMan.client_may_modify_component(objPtr.get(), CollabRight::Move)) {
|
if(!layerMan.client_may_modify_component(objPtr.get())) {
|
||||||
rejectedRestore.emplace_back(netID, objPtr->coords);
|
rejectedRestore.emplace_back(netID, objPtr->coords);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
allowed.emplace_back(netID, coord);
|
allowed.emplace_back(netID, coord);
|
||||||
}
|
}
|
||||||
world.currentNetUpdateClient = nullptr;
|
|
||||||
if(!allowed.empty()) {
|
if(!allowed.empty()) {
|
||||||
process_transform_message(allowed);
|
process_transform_message(allowed);
|
||||||
world.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_MANY_COMPONENTS, allowed);
|
world.netServer->send_items_to_all_clients(RELIABLE_COMMAND_CHANNEL, CLIENT_TRANSFORM_MANY_COMPONENTS, allowed);
|
||||||
|
|
@ -421,16 +377,15 @@ void DrawingProgram::init_server_callbacks() {
|
||||||
world.netServer->add_recv_callback(SERVER_SET_COMPONENT_GROUP_IDS, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
|
world.netServer->add_recv_callback(SERVER_SET_COMPONENT_GROUP_IDS, [&](std::shared_ptr<NetServer::ClientData> client, cereal::PortableBinaryInputArchive& message) {
|
||||||
std::vector<std::pair<NetworkingObjects::NetObjID, uint64_t>> groupIds;
|
std::vector<std::pair<NetworkingObjects::NetObjID, uint64_t>> groupIds;
|
||||||
message(groupIds);
|
message(groupIds);
|
||||||
world.currentNetUpdateClient = client;
|
(void)client;
|
||||||
std::vector<std::pair<NetworkingObjects::NetObjID, uint64_t>> allowed;
|
std::vector<std::pair<NetworkingObjects::NetObjID, uint64_t>> allowed;
|
||||||
allowed.reserve(groupIds.size());
|
allowed.reserve(groupIds.size());
|
||||||
for(auto& [netID, groupId] : groupIds) {
|
for(auto& [netID, groupId] : groupIds) {
|
||||||
auto objPtr = world.netObjMan.get_obj_temporary_ref_from_id<CanvasComponentContainer>(netID);
|
auto objPtr = world.netObjMan.get_obj_temporary_ref_from_id<CanvasComponentContainer>(netID);
|
||||||
if(!objPtr || !layerMan.client_may_modify_component(objPtr.get(), CollabRight::Move))
|
if(!objPtr || !layerMan.client_may_modify_component(objPtr.get()))
|
||||||
continue;
|
continue;
|
||||||
allowed.emplace_back(netID, groupId);
|
allowed.emplace_back(netID, groupId);
|
||||||
}
|
}
|
||||||
world.currentNetUpdateClient = nullptr;
|
|
||||||
if(allowed.empty())
|
if(allowed.empty())
|
||||||
return;
|
return;
|
||||||
process_group_id_message(allowed);
|
process_group_id_message(allowed);
|
||||||
|
|
@ -511,7 +466,7 @@ void DrawingProgram::send_transforms_for(const std::vector<CanvasComponentContai
|
||||||
for(auto& obj : objsToSendTransformsFor) {
|
for(auto& obj : objsToSendTransformsFor) {
|
||||||
if(!obj || !obj->obj)
|
if(!obj || !obj->obj)
|
||||||
continue;
|
continue;
|
||||||
if(!world.netObjMan.is_server() && !layerMan.own_peer_may(CollabRight::Move, obj->obj->parentLayer))
|
if(!world.netObjMan.is_server() && !layerMan.client_may_modify_component(obj->obj.get()))
|
||||||
continue;
|
continue;
|
||||||
transforms.emplace_back(obj->obj.get_net_id(), obj->obj->coords);
|
transforms.emplace_back(obj->obj.get_net_id(), obj->obj->coords);
|
||||||
}
|
}
|
||||||
|
|
@ -1211,16 +1166,11 @@ void DrawingProgram::draw_drag_circle(SkCanvas* canvas, const Vector2f& sPos, co
|
||||||
void DrawingProgram::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
void DrawingProgram::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version) {
|
||||||
if(version >= VersionNumber(0, 6, 0))
|
if(version >= VersionNumber(0, 6, 0))
|
||||||
a(controls.lockedCameraScale);
|
a(controls.lockedCameraScale);
|
||||||
ensure_collab_defaults();
|
|
||||||
if(version >= VersionNumber(0, 9, 0))
|
|
||||||
a(*collabDefaults);
|
|
||||||
layerMan.load_file(a, version);
|
layerMan.load_file(a, version);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgram::save_file(cereal::PortableBinaryOutputArchive& a) const {
|
void DrawingProgram::save_file(cereal::PortableBinaryOutputArchive& a) const {
|
||||||
a(controls.lockedCameraScale);
|
a(controls.lockedCameraScale);
|
||||||
const_cast<DrawingProgram*>(this)->ensure_collab_defaults();
|
|
||||||
a(*collabDefaults);
|
|
||||||
layerMan.save_file(a);
|
layerMan.save_file(a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,6 @@
|
||||||
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
|
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
|
||||||
#include "Layers/DrawingProgramLayerManager.hpp"
|
#include "Layers/DrawingProgramLayerManager.hpp"
|
||||||
#include "DrawingProgramSelection.hpp"
|
#include "DrawingProgramSelection.hpp"
|
||||||
#include "../CollabPermissions.hpp"
|
|
||||||
|
|
||||||
class World;
|
class World;
|
||||||
class PhoneDrawingProgramScreen;
|
class PhoneDrawingProgramScreen;
|
||||||
|
|
@ -72,12 +71,6 @@ class DrawingProgram {
|
||||||
|
|
||||||
DrawingProgramCache drawCache;
|
DrawingProgramCache drawCache;
|
||||||
DrawingProgramLayerManager layerMan;
|
DrawingProgramLayerManager layerMan;
|
||||||
/** Host-owned session ACL defaults (world / new player / new layer). */
|
|
||||||
NetworkingObjects::NetObjOwnerPtr<CollabSessionDefaults> collabDefaults;
|
|
||||||
void ensure_collab_defaults();
|
|
||||||
const CollabSessionDefaults& collab_defaults() const;
|
|
||||||
void set_collab_defaults(const CollabSessionDefaults& defaults);
|
|
||||||
static void register_collab_defaults_class(World& world);
|
|
||||||
|
|
||||||
Vector4f* get_foreground_color_ptr();
|
Vector4f* get_foreground_color_ptr();
|
||||||
void switch_to_tool(DrawingProgramToolType newToolType, bool force = false);
|
void switch_to_tool(DrawingProgramToolType newToolType, bool force = false);
|
||||||
|
|
|
||||||
|
|
@ -364,10 +364,6 @@ void DrawingProgramSelection::expand_selection_with_groups() {
|
||||||
for(auto* c : drawP.layerMan.get_flattened_component_list()) {
|
for(auto* c : drawP.layerMan.get_flattened_component_list()) {
|
||||||
if(c->obj->groupId != 0 && groupIds.contains(c->obj->groupId) && !already.contains(c)
|
if(c->obj->groupId != 0 && groupIds.contains(c->obj->groupId) && !already.contains(c)
|
||||||
&& drawP.layerMan.can_current_peer_edit_layer(c->obj->parentLayer)) {
|
&& drawP.layerMan.can_current_peer_edit_layer(c->obj->parentLayer)) {
|
||||||
// Same as click/lasso select: must leave the draw cache, otherwise
|
|
||||||
// transforms move the selection draw while the BVH surface stays put
|
|
||||||
// (looks like size-dependent drift between group members).
|
|
||||||
drawP.drawCache.erase_component(c);
|
|
||||||
selectedSet.push_back(c);
|
selectedSet.push_back(c);
|
||||||
already.insert(c);
|
already.insert(c);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,10 +40,9 @@ void DrawingProgramLayer::set_component_list_callbacks(DrawingProgramLayerListIt
|
||||||
if(c->obj->get_comp().get_type() == CanvasComponentType::IMAGE)
|
if(c->obj->get_comp().get_type() == CanvasComponentType::IMAGE)
|
||||||
layerMan.drawP.updateableComponents.emplace(&(*c));
|
layerMan.drawP.updateableComponents.emplace(&(*c));
|
||||||
|
|
||||||
// Live ACL / fog enforcement on host
|
// Live fog / lock enforcement on host
|
||||||
if(layerMan.addToCacheOnComponentInsert && layerMan.drawP.world.netServer) {
|
if(layerMan.addToCacheOnComponentInsert && layerMan.drawP.world.netServer) {
|
||||||
if(layerMan.drawP.world.currentNetUpdateClient
|
if(layerMan.drawP.world.currentNetUpdateClient && layerListItem.is_player_edit_blocked()) {
|
||||||
&& !layerMan.current_net_client_may(CollabRight::Draw, &layerListItem)) {
|
|
||||||
layerMan.defer_reject_component(&layerListItem, c->obj.get_net_id());
|
layerMan.defer_reject_component(&layerListItem, c->obj.get_net_id());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,13 +19,10 @@
|
||||||
#include "DrawingProgramLayerListItem.hpp"
|
#include "DrawingProgramLayerListItem.hpp"
|
||||||
#include "DrawingProgramLayerFolder.hpp"
|
#include "DrawingProgramLayerFolder.hpp"
|
||||||
#include <cereal/types/string.hpp>
|
#include <cereal/types/string.hpp>
|
||||||
#include <cereal/types/map.hpp>
|
|
||||||
#include "DrawingProgramLayer.hpp"
|
#include "DrawingProgramLayer.hpp"
|
||||||
#include "FogStripper.hpp"
|
|
||||||
#include "../../World.hpp"
|
#include "../../World.hpp"
|
||||||
#include "../../MainProgram.hpp"
|
#include "../../MainProgram.hpp"
|
||||||
#include "SerializedBlendMode.hpp"
|
#include "SerializedBlendMode.hpp"
|
||||||
#include "../../DrawingProgram/DrawingProgram.hpp"
|
|
||||||
|
|
||||||
using namespace NetworkingObjects;
|
using namespace NetworkingObjects;
|
||||||
|
|
||||||
|
|
@ -181,39 +178,17 @@ DrawingProgramLayer& DrawingProgramLayerListItem::get_layer() const {
|
||||||
return *layerData;
|
return *layerData;
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::DisplayData::migrate_player_locked_into_defaults() {
|
|
||||||
if(playerLocked && !fogLayer)
|
|
||||||
layerDefaults = CollabPermissionSet::content_deny();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::DisplayData::sync_player_locked_from_defaults() {
|
|
||||||
if(fogLayer) {
|
|
||||||
playerLocked = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
playerLocked = layerDefaults.draw == CollabTriState::Deny
|
|
||||||
&& layerDefaults.move == CollabTriState::Deny
|
|
||||||
&& layerDefaults.erase == CollabTriState::Deny;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version, DrawingProgramLayerManager& layerMan) {
|
void DrawingProgramLayerListItem::load_file(cereal::PortableBinaryInputArchive& a, VersionNumber version, DrawingProgramLayerManager& layerMan) {
|
||||||
nameData = layerMan.drawP.world.netObjMan.make_obj<NameData>();
|
nameData = layerMan.drawP.world.netObjMan.make_obj<NameData>();
|
||||||
a(*nameData);
|
a(*nameData);
|
||||||
|
|
||||||
displayData = layerMan.drawP.world.netObjMan.make_obj<DisplayData>();
|
displayData = layerMan.drawP.world.netObjMan.make_obj<DisplayData>();
|
||||||
if(version >= VersionNumber(0, 9, 0)) {
|
if(version >= VersionNumber(0, 8, 0)) {
|
||||||
a(*displayData);
|
a(*displayData);
|
||||||
|
// Normalize cereal bools (non-0/1 bytes must not stick as "true" forever).
|
||||||
displayData->visible = displayData->visible ? true : false;
|
displayData->visible = displayData->visible ? true : false;
|
||||||
displayData->playerLocked = displayData->playerLocked ? true : false;
|
displayData->playerLocked = displayData->playerLocked ? true : false;
|
||||||
displayData->fogLayer = displayData->fogLayer ? true : false;
|
displayData->fogLayer = displayData->fogLayer ? true : false;
|
||||||
displayData->sync_player_locked_from_defaults();
|
|
||||||
}
|
|
||||||
else if(version >= VersionNumber(0, 8, 0)) {
|
|
||||||
a(displayData->alpha, displayData->visible, displayData->blendMode, displayData->playerLocked, displayData->fogLayer);
|
|
||||||
displayData->visible = displayData->visible ? true : false;
|
|
||||||
displayData->playerLocked = displayData->playerLocked ? true : false;
|
|
||||||
displayData->fogLayer = displayData->fogLayer ? true : false;
|
|
||||||
displayData->migrate_player_locked_into_defaults();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
a(displayData->alpha, displayData->visible, displayData->blendMode);
|
a(displayData->alpha, displayData->visible, displayData->blendMode);
|
||||||
|
|
@ -283,11 +258,6 @@ const std::string& DrawingProgramLayerListItem::get_name() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::set_alpha(DrawingProgramLayerManager& layerMan, float newAlpha) const {
|
void DrawingProgramLayerListItem::set_alpha(DrawingProgramLayerManager& layerMan, float newAlpha) const {
|
||||||
// Players must not fade fog out — same authority as visibility.
|
|
||||||
if(is_fog_layer() && !FogStripper::world_is_host(layerMan.drawP.world))
|
|
||||||
return;
|
|
||||||
if(!FogStripper::world_is_host(layerMan.drawP.world) && !layerMan.own_peer_may(CollabRight::Hide, this))
|
|
||||||
return;
|
|
||||||
if(displayData && displayData->alpha != newAlpha) {
|
if(displayData && displayData->alpha != newAlpha) {
|
||||||
displayData->alpha = newAlpha;
|
displayData->alpha = newAlpha;
|
||||||
layerMan.drawP.drawCache.clear_own_cached_surfaces();
|
layerMan.drawP.drawCache.clear_own_cached_surfaces();
|
||||||
|
|
@ -300,11 +270,6 @@ float DrawingProgramLayerListItem::get_alpha() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::set_visible(DrawingProgramLayerManager& layerMan, bool newVisible) const {
|
void DrawingProgramLayerListItem::set_visible(DrawingProgramLayerManager& layerMan, bool newVisible) const {
|
||||||
// Fog of War is host-controlled; clients hiding it would sync and strip FoW for everyone.
|
|
||||||
if(is_fog_layer() && !FogStripper::world_is_host(layerMan.drawP.world))
|
|
||||||
return;
|
|
||||||
if(!FogStripper::world_is_host(layerMan.drawP.world) && !layerMan.own_peer_may(CollabRight::Hide, this))
|
|
||||||
return;
|
|
||||||
if(displayData && displayData->visible != newVisible) {
|
if(displayData && displayData->visible != newVisible) {
|
||||||
displayData->visible = newVisible;
|
displayData->visible = newVisible;
|
||||||
layerMan.drawP.drawCache.clear_own_cached_surfaces();
|
layerMan.drawP.drawCache.clear_own_cached_surfaces();
|
||||||
|
|
@ -332,23 +297,12 @@ void DrawingProgramLayerListItem::set_player_locked(DrawingProgramLayerManager&
|
||||||
if(!displayData)
|
if(!displayData)
|
||||||
return;
|
return;
|
||||||
bool newLocked = locked || displayData->fogLayer;
|
bool newLocked = locked || displayData->fogLayer;
|
||||||
CollabPermissionSet nextDefaults = displayData->layerDefaults;
|
if(displayData->playerLocked != newLocked) {
|
||||||
if(newLocked)
|
|
||||||
nextDefaults = CollabPermissionSet::content_deny();
|
|
||||||
else if(displayData->playerLocked) {
|
|
||||||
// Unlock: clear content Deny back to Inherit for Draw/Move/Delete.
|
|
||||||
nextDefaults.draw = CollabTriState::Inherit;
|
|
||||||
nextDefaults.move = CollabTriState::Inherit;
|
|
||||||
nextDefaults.erase = CollabTriState::Inherit;
|
|
||||||
}
|
|
||||||
if(displayData->playerLocked == newLocked && displayData->layerDefaults == nextDefaults)
|
|
||||||
return;
|
|
||||||
displayData->playerLocked = newLocked;
|
displayData->playerLocked = newLocked;
|
||||||
displayData->layerDefaults = nextDefaults;
|
|
||||||
layerMan.drawP.drawCache.clear_own_cached_surfaces();
|
layerMan.drawP.drawCache.clear_own_cached_surfaces();
|
||||||
layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all<DisplayData>(displayData, false);
|
layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all<DisplayData>(displayData, false);
|
||||||
layerMan.refresh_fog_client_projection();
|
layerMan.refresh_fog_client_projection();
|
||||||
layerMan.ensure_editing_layer_is_editable();
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrawingProgramLayerListItem::get_player_locked() const {
|
bool DrawingProgramLayerListItem::get_player_locked() const {
|
||||||
|
|
@ -374,55 +328,6 @@ bool DrawingProgramLayerListItem::is_player_edit_blocked() const {
|
||||||
return is_fog_layer() || get_player_locked();
|
return is_fog_layer() || get_player_locked();
|
||||||
}
|
}
|
||||||
|
|
||||||
const CollabPermissionSet& DrawingProgramLayerListItem::get_layer_defaults() const {
|
|
||||||
static const CollabPermissionSet kInherit = CollabPermissionSet::all_inherit();
|
|
||||||
return displayData ? displayData->layerDefaults : kInherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::set_layer_defaults(DrawingProgramLayerManager& layerMan, const CollabPermissionSet& defaults) const {
|
|
||||||
if(!displayData || displayData->layerDefaults == defaults)
|
|
||||||
return;
|
|
||||||
displayData->layerDefaults = defaults;
|
|
||||||
displayData->sync_player_locked_from_defaults();
|
|
||||||
layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all<DisplayData>(displayData, false);
|
|
||||||
layerMan.ensure_editing_layer_is_editable();
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::map<std::string, CollabPermissionSet>& DrawingProgramLayerListItem::get_person_layer_overrides() const {
|
|
||||||
static const std::map<std::string, CollabPermissionSet> kEmpty;
|
|
||||||
return displayData ? displayData->personOverrides : kEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::set_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName, const CollabPermissionSet& overrides) const {
|
|
||||||
if(!displayData || displayName.empty())
|
|
||||||
return;
|
|
||||||
if(!overrides.any_concrete()) {
|
|
||||||
clear_person_layer_override(layerMan, displayName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto it = displayData->personOverrides.find(displayName);
|
|
||||||
if(it != displayData->personOverrides.end() && it->second == overrides)
|
|
||||||
return;
|
|
||||||
displayData->personOverrides[displayName] = overrides;
|
|
||||||
layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all<DisplayData>(displayData, false);
|
|
||||||
layerMan.ensure_editing_layer_is_editable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::clear_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName) const {
|
|
||||||
if(!displayData)
|
|
||||||
return;
|
|
||||||
if(displayData->personOverrides.erase(displayName) == 0)
|
|
||||||
return;
|
|
||||||
layerMan.drawP.world.delayedUpdateObjectManager.send_update_to_all<DisplayData>(displayData, false);
|
|
||||||
layerMan.ensure_editing_layer_is_editable();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::apply_new_layer_permission_defaults(DrawingProgramLayerManager& layerMan) const {
|
|
||||||
if(!displayData || is_folder())
|
|
||||||
return;
|
|
||||||
set_layer_defaults(layerMan, layerMan.drawP.collab_defaults().newLayerDefaults);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DrawingProgramLayerListItem::set_metainfo(DrawingProgramLayerManager& layerMan, const DrawingProgramLayerListItemMetaInfo& metaInfo) {
|
void DrawingProgramLayerListItem::set_metainfo(DrawingProgramLayerManager& layerMan, const DrawingProgramLayerListItemMetaInfo& metaInfo) {
|
||||||
set_blend_mode(layerMan, metaInfo.blendMode);
|
set_blend_mode(layerMan, metaInfo.blendMode);
|
||||||
set_alpha(layerMan, metaInfo.alpha);
|
set_alpha(layerMan, metaInfo.alpha);
|
||||||
|
|
@ -488,27 +393,6 @@ void DrawingProgramLayerListItem::register_class(World& w) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
w.delayedUpdateObjectManager.register_class<DisplayData>(w.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors<DisplayData>{
|
w.delayedUpdateObjectManager.register_class<DisplayData>(w.netObjMan, NetworkingObjects::DelayUpdateSerializedClassManager::CustomConstructors<DisplayData>{
|
||||||
.readUpdate = [](DisplayData& o, cereal::PortableBinaryInputArchive& a, const std::shared_ptr<NetServer::ClientData>& c) {
|
|
||||||
DisplayData incoming;
|
|
||||||
a(incoming);
|
|
||||||
// Client→host: never allow stripping fog, unlocking, hiding, fading fog, or escalating ACL.
|
|
||||||
if(c) {
|
|
||||||
if(o.fogLayer || incoming.fogLayer) {
|
|
||||||
incoming.fogLayer = true;
|
|
||||||
incoming.playerLocked = true;
|
|
||||||
incoming.visible = true;
|
|
||||||
incoming.alpha = o.alpha;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
incoming.fogLayer = false;
|
|
||||||
incoming.playerLocked = o.playerLocked;
|
|
||||||
}
|
|
||||||
incoming.layerDefaults = o.layerDefaults;
|
|
||||||
incoming.personOverrides = o.personOverrides;
|
|
||||||
}
|
|
||||||
o = incoming;
|
|
||||||
o.sync_player_locked_from_defaults();
|
|
||||||
},
|
|
||||||
.postUpdateFunc = [&](DisplayData& o) {
|
.postUpdateFunc = [&](DisplayData& o) {
|
||||||
(void)o;
|
(void)o;
|
||||||
w.drawProg.drawCache.clear_own_cached_surfaces();
|
w.drawProg.drawCache.clear_own_cached_surfaces();
|
||||||
|
|
|
||||||
|
|
@ -20,12 +20,10 @@
|
||||||
#include <Helpers/NetworkingObjects/NetObjManager.hpp>
|
#include <Helpers/NetworkingObjects/NetObjManager.hpp>
|
||||||
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
|
#include <Helpers/NetworkingObjects/NetObjOrderedList.hpp>
|
||||||
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
|
#include <Helpers/NetworkingObjects/DelayUpdateSerializedClassManager.hpp>
|
||||||
#include <map>
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include "DrawingProgramLayerFolder.hpp"
|
#include "DrawingProgramLayerFolder.hpp"
|
||||||
#include "DrawingProgramLayer.hpp"
|
#include "DrawingProgramLayer.hpp"
|
||||||
#include "SerializedBlendMode.hpp"
|
#include "SerializedBlendMode.hpp"
|
||||||
#include "../../CollabPermissions.hpp"
|
|
||||||
#include "../../CanvasComponents/CanvasComponentContainer.hpp"
|
#include "../../CanvasComponents/CanvasComponentContainer.hpp"
|
||||||
|
|
||||||
class DrawingProgramLayerFolder;
|
class DrawingProgramLayerFolder;
|
||||||
|
|
@ -91,14 +89,6 @@ class DrawingProgramLayerListItem {
|
||||||
// Fog layers are always treated as player-locked for edit checks.
|
// Fog layers are always treated as player-locked for edit checks.
|
||||||
bool is_player_edit_blocked() const;
|
bool is_player_edit_blocked() const;
|
||||||
|
|
||||||
const CollabPermissionSet& get_layer_defaults() const;
|
|
||||||
void set_layer_defaults(DrawingProgramLayerManager& layerMan, const CollabPermissionSet& defaults) const;
|
|
||||||
const std::map<std::string, CollabPermissionSet>& get_person_layer_overrides() const;
|
|
||||||
void set_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName, const CollabPermissionSet& overrides) const;
|
|
||||||
void clear_person_layer_override(DrawingProgramLayerManager& layerMan, const std::string& displayName) const;
|
|
||||||
/** Apply session new-layer defaults (call after create). */
|
|
||||||
void apply_new_layer_permission_defaults(DrawingProgramLayerManager& layerMan) const;
|
|
||||||
|
|
||||||
void set_metainfo(DrawingProgramLayerManager& layerMan, const DrawingProgramLayerListItemMetaInfo& metaInfo);
|
void set_metainfo(DrawingProgramLayerManager& layerMan, const DrawingProgramLayerListItemMetaInfo& metaInfo);
|
||||||
DrawingProgramLayerListItemMetaInfo get_metainfo() const;
|
DrawingProgramLayerListItemMetaInfo get_metainfo() const;
|
||||||
|
|
||||||
|
|
@ -125,13 +115,9 @@ class DrawingProgramLayerListItem {
|
||||||
SerializedBlendMode blendMode = SerializedBlendMode::BLEND_SRC_OVER;
|
SerializedBlendMode blendMode = SerializedBlendMode::BLEND_SRC_OVER;
|
||||||
bool playerLocked = false;
|
bool playerLocked = false;
|
||||||
bool fogLayer = false;
|
bool fogLayer = false;
|
||||||
CollabPermissionSet layerDefaults = CollabPermissionSet::all_inherit();
|
|
||||||
std::map<std::string, CollabPermissionSet> personOverrides;
|
|
||||||
template <typename Archive> void serialize(Archive& a) {
|
template <typename Archive> void serialize(Archive& a) {
|
||||||
a(alpha, visible, blendMode, playerLocked, fogLayer, layerDefaults, personOverrides);
|
a(alpha, visible, blendMode, playerLocked, fogLayer);
|
||||||
}
|
}
|
||||||
void migrate_player_locked_into_defaults();
|
|
||||||
void sync_player_locked_from_defaults();
|
|
||||||
};
|
};
|
||||||
NetworkingObjects::NetObjOwnerPtr<NameData> nameData;
|
NetworkingObjects::NetObjOwnerPtr<NameData> nameData;
|
||||||
NetworkingObjects::NetObjOwnerPtr<DisplayData> displayData;
|
NetworkingObjects::NetObjOwnerPtr<DisplayData> displayData;
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@
|
||||||
#include "DrawingProgramLayerManager.hpp"
|
#include "DrawingProgramLayerManager.hpp"
|
||||||
#include "FogStripper.hpp"
|
#include "FogStripper.hpp"
|
||||||
#include "../../World.hpp"
|
#include "../../World.hpp"
|
||||||
#include "../../ClientData.hpp"
|
|
||||||
#include "Helpers/NetworkingObjects/NetObjID.hpp"
|
#include "Helpers/NetworkingObjects/NetObjID.hpp"
|
||||||
#include "Helpers/NetworkingObjects/NetObjOrderedList.hpp"
|
#include "Helpers/NetworkingObjects/NetObjOrderedList.hpp"
|
||||||
#include "Helpers/NetworkingObjects/NetObjOwnerPtr.decl.hpp"
|
#include "Helpers/NetworkingObjects/NetObjOwnerPtr.decl.hpp"
|
||||||
|
|
@ -292,88 +291,20 @@ void DrawingProgramLayerManager::disable_add_to_cache_and_commit_update_block(co
|
||||||
addToCacheOnComponentInsert = true;
|
addToCacheOnComponentInsert = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::peer_may(CollabRight right, const DrawingProgramLayerListItem* layer, const ClientData* peer) const {
|
bool DrawingProgramLayerManager::can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const {
|
||||||
if(!layer)
|
if(!layer)
|
||||||
return false;
|
return false;
|
||||||
if(FogStripper::world_is_host(drawP.world) && (!peer || peer == drawP.world.ownClientData.get()))
|
|
||||||
return true;
|
|
||||||
// Content rights on fog layers are always denied for non-host peers.
|
|
||||||
if(layer->is_fog_layer()
|
|
||||||
&& (right == CollabRight::Draw || right == CollabRight::Move || right == CollabRight::Delete))
|
|
||||||
return false;
|
|
||||||
if(right == CollabRight::Hide && layer->is_fog_layer())
|
|
||||||
return false;
|
|
||||||
|
|
||||||
CollabPermissionSet personDefaults = CollabPermissionSet::all_inherit();
|
|
||||||
const CollabPermissionSet* personLayer = nullptr;
|
|
||||||
std::string peerName;
|
|
||||||
if(peer) {
|
|
||||||
personDefaults = peer->get_person_defaults();
|
|
||||||
peerName = peer->get_display_name();
|
|
||||||
auto& overrides = layer->get_person_layer_overrides();
|
|
||||||
auto it = overrides.find(peerName);
|
|
||||||
if(it != overrides.end())
|
|
||||||
personLayer = &it->second;
|
|
||||||
}
|
|
||||||
return collab_resolve_allow(
|
|
||||||
right,
|
|
||||||
personLayer,
|
|
||||||
layer->get_layer_defaults(),
|
|
||||||
personDefaults,
|
|
||||||
drawP.collab_defaults().worldDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::own_peer_may(CollabRight right, const DrawingProgramLayerListItem* layer) const {
|
|
||||||
if(FogStripper::world_is_host(drawP.world))
|
if(FogStripper::world_is_host(drawP.world))
|
||||||
return true;
|
return true;
|
||||||
return peer_may(right, layer, drawP.world.ownClientData.get());
|
return !layer->is_player_edit_blocked();
|
||||||
}
|
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::peer_may_manage_layers(const ClientData* peer) const {
|
|
||||||
if(FogStripper::world_is_host(drawP.world) && (!peer || peer == drawP.world.ownClientData.get()))
|
|
||||||
return true;
|
|
||||||
// ManageLayers is not layer-scoped — resolve with empty layer defaults / no person×layer.
|
|
||||||
CollabPermissionSet personDefaults = peer ? peer->get_person_defaults() : CollabPermissionSet::all_inherit();
|
|
||||||
return collab_resolve_allow(
|
|
||||||
CollabRight::ManageLayers,
|
|
||||||
nullptr,
|
|
||||||
CollabPermissionSet::all_inherit(),
|
|
||||||
personDefaults,
|
|
||||||
drawP.collab_defaults().worldDefault);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::current_net_client_may(CollabRight right, const DrawingProgramLayerListItem* layer) const {
|
|
||||||
if(!drawP.world.currentNetUpdateClient)
|
|
||||||
return true; // local/host-originated
|
|
||||||
auto peer = drawP.world.netObjMan.get_obj_temporary_ref_from_id<ClientData>(
|
|
||||||
NetworkingObjects::NetObjID(drawP.world.currentNetUpdateClient->customID));
|
|
||||||
if(!peer)
|
|
||||||
return false;
|
|
||||||
if(right == CollabRight::ManageLayers)
|
|
||||||
return peer_may_manage_layers(peer.get());
|
|
||||||
return peer_may(right, layer, peer.get());
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const {
|
|
||||||
return own_peer_may(CollabRight::Draw, layer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::client_may_modify_component(const CanvasComponentContainer* comp) const {
|
bool DrawingProgramLayerManager::client_may_modify_component(const CanvasComponentContainer* comp) const {
|
||||||
return client_may_modify_component(comp, CollabRight::Draw);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DrawingProgramLayerManager::client_may_modify_component(const CanvasComponentContainer* comp, CollabRight right) const {
|
|
||||||
// Host-side authority check for CLIENT messages — must NOT use can_current_peer_edit_layer
|
// Host-side authority check for CLIENT messages — must NOT use can_current_peer_edit_layer
|
||||||
// (that always returns true on the host).
|
// (that always returns true on the host).
|
||||||
if(!comp || !comp->parentLayer)
|
if(!comp || !comp->parentLayer)
|
||||||
return false;
|
return false;
|
||||||
if(!drawP.world.currentNetUpdateClient) {
|
return !comp->parentLayer->is_player_edit_blocked();
|
||||||
// Transform callbacks set currentNetUpdateClient; fallback: legacy lock/fog.
|
|
||||||
if(comp->parentLayer->is_player_edit_blocked())
|
|
||||||
return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return current_net_client_may(right, comp->parentLayer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawingProgramLayerManager::ensure_editing_layer_is_editable() {
|
void DrawingProgramLayerManager::ensure_editing_layer_is_editable() {
|
||||||
|
|
|
||||||
|
|
@ -19,12 +19,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "DrawingProgramLayerListItem.hpp"
|
#include "DrawingProgramLayerListItem.hpp"
|
||||||
#include "DrawingProgramLayerManagerGUI.hpp"
|
#include "DrawingProgramLayerManagerGUI.hpp"
|
||||||
#include "../../CollabPermissions.hpp"
|
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
class ClientData;
|
|
||||||
|
|
||||||
class DrawingProgramLayerManager {
|
class DrawingProgramLayerManager {
|
||||||
private:
|
private:
|
||||||
DrawingProgram& drawP;
|
DrawingProgram& drawP;
|
||||||
|
|
@ -92,11 +89,6 @@ class DrawingProgramLayerManager {
|
||||||
|
|
||||||
bool can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const;
|
bool can_current_peer_edit_layer(const DrawingProgramLayerListItem* layer) const;
|
||||||
bool client_may_modify_component(const CanvasComponentContainer* comp) const;
|
bool client_may_modify_component(const CanvasComponentContainer* comp) const;
|
||||||
bool client_may_modify_component(const CanvasComponentContainer* comp, CollabRight right) const;
|
|
||||||
bool own_peer_may(CollabRight right, const DrawingProgramLayerListItem* layer) const;
|
|
||||||
bool peer_may(CollabRight right, const DrawingProgramLayerListItem* layer, const ClientData* peer) const;
|
|
||||||
bool peer_may_manage_layers(const ClientData* peer) const;
|
|
||||||
bool current_net_client_may(CollabRight right, const DrawingProgramLayerListItem* layer) const;
|
|
||||||
void ensure_editing_layer_is_editable();
|
void ensure_editing_layer_is_editable();
|
||||||
/** Host: recompute fog coverage and erase/reveal (+ resources) on clients. */
|
/** Host: recompute fog coverage and erase/reveal (+ resources) on clients. */
|
||||||
void refresh_fog_client_projection();
|
void refresh_fog_client_projection();
|
||||||
|
|
|
||||||
|
|
@ -26,8 +26,6 @@
|
||||||
#include "Helpers/ConvertVec.hpp"
|
#include "Helpers/ConvertVec.hpp"
|
||||||
#include "Helpers/NetworkingObjects/NetObjOrderedList.hpp"
|
#include "Helpers/NetworkingObjects/NetObjOrderedList.hpp"
|
||||||
#include "SerializedBlendMode.hpp"
|
#include "SerializedBlendMode.hpp"
|
||||||
#include "../../CollabPermissions.hpp"
|
|
||||||
#include "../../ClientData.hpp"
|
|
||||||
|
|
||||||
#include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
|
#include "../../GUIStuff/ElementHelpers/TextLabelHelpers.hpp"
|
||||||
#include "../../GUIStuff/ElementHelpers/ButtonHelpers.hpp"
|
#include "../../GUIStuff/ElementHelpers/ButtonHelpers.hpp"
|
||||||
|
|
@ -146,18 +144,14 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] {
|
gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] {
|
||||||
// Fog stays visible for players — eye is informational only on clients.
|
|
||||||
const bool fogVisibilityLocked = layer->is_fog_layer() && !FogStripper::world_is_host(world);
|
|
||||||
svg_icon_button(gui, "visible button", layer->get_visible() ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", {
|
svg_icon_button(gui, "visible button", layer->get_visible() ? "data/icons/eyeopen.svg" : "data/icons/eyeclose.svg", {
|
||||||
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
||||||
.size = TreeListing::ENTRY_HEIGHT,
|
.size = TreeListing::ENTRY_HEIGHT,
|
||||||
.onClick = [&, layer, fogVisibilityLocked] {
|
.onClick = [&, layer] {
|
||||||
if(!fogVisibilityLocked && layerMan.own_peer_may(CollabRight::Hide, layer.get()))
|
|
||||||
layer->set_visible(layerMan, !layer->get_visible());
|
layer->set_visible(layerMan, !layer->get_visible());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
if(layerMan.peer_may_manage_layers(world.ownClientData.get())) {
|
|
||||||
gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] {
|
gui.set_z_index_keep_clipping_region(gui.get_z_index() + 1, [&] {
|
||||||
svg_icon_button(gui, "delete button", "data/icons/trash.svg", {
|
svg_icon_button(gui, "delete button", "data/icons/trash.svg", {
|
||||||
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
.drawType = SelectableButton::DrawType::TRANSPARENT_ALL,
|
||||||
|
|
@ -167,7 +161,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
},
|
},
|
||||||
.onDoubleClick = [&](const TreeListingObjIndexList& objIndex) {
|
.onDoubleClick = [&](const TreeListingObjIndexList& objIndex) {
|
||||||
auto layer = get_layer_from_obj_index(objIndex);
|
auto layer = get_layer_from_obj_index(objIndex);
|
||||||
|
|
@ -200,8 +193,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
.moveObj = [&](const std::vector<TreeListingObjIndexList>& objectIndicesTreeListing, const TreeListingObjIndexList& newObjIndexTreeListing) {
|
.moveObj = [&](const std::vector<TreeListingObjIndexList>& objectIndicesTreeListing, const TreeListingObjIndexList& newObjIndexTreeListing) {
|
||||||
if(!layerMan.peer_may_manage_layers(world.ownClientData.get()))
|
|
||||||
return;
|
|
||||||
NetObjID listObj;
|
NetObjID listObj;
|
||||||
size_t index;
|
size_t index;
|
||||||
struct ParentObjectIDPair {
|
struct ParentObjectIDPair {
|
||||||
|
|
@ -402,7 +393,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if(layerMan.peer_may_manage_layers(world.ownClientData.get())) {
|
|
||||||
left_to_right_line_layout(gui, [&]() {
|
left_to_right_line_layout(gui, [&]() {
|
||||||
input_text(gui, "input new name", &nameForNew, {
|
input_text(gui, "input new name", &nameForNew, {
|
||||||
.onEnter = [&] {
|
.onEnter = [&] {
|
||||||
|
|
@ -429,7 +419,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
auto editingLayerLock = editingLayer.lock();
|
auto editingLayerLock = editingLayer.lock();
|
||||||
if(editingLayerLock) {
|
if(editingLayerLock) {
|
||||||
|
|
@ -487,67 +476,6 @@ void DrawingProgramLayerManagerGUI::setup_list_gui() {
|
||||||
if(layer)
|
if(layer)
|
||||||
layer->set_player_locked(layerMan, !layer->get_player_locked());
|
layer->set_player_locked(layerMan, !layer->get_player_locked());
|
||||||
});
|
});
|
||||||
text_label(gui, "Layer defaults (Inherit → Person → World)");
|
|
||||||
{
|
|
||||||
const CollabPermissionSet& layerSet = editingLayerLock->get_layer_defaults();
|
|
||||||
left_to_right_line_layout(gui, [&]() {
|
|
||||||
for(uint8_t i = 0; i < static_cast<uint8_t>(CollabRight::COUNT); i++) {
|
|
||||||
CollabRight right = static_cast<CollabRight>(i);
|
|
||||||
gui.new_id(200 + i, [&] {
|
|
||||||
std::string label = std::string(collab_right_label(right)) + ":"
|
|
||||||
+ collab_tristate_label(layerSet.get(right));
|
|
||||||
text_button(gui, "layer perm", label.c_str(), {
|
|
||||||
.onClick = [&, right] {
|
|
||||||
auto layer = editingLayer.lock();
|
|
||||||
if(!layer)
|
|
||||||
return;
|
|
||||||
CollabPermissionSet next = layer->get_layer_defaults();
|
|
||||||
next.set(right, collab_cycle_tristate(next.get(right)));
|
|
||||||
layer->set_layer_defaults(layerMan, next);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if(world.clients) {
|
|
||||||
text_label(gui, "Per-player overrides on this layer");
|
|
||||||
size_t pnum = 0;
|
|
||||||
for(auto& client : world.clients->get_data()) {
|
|
||||||
if(client == world.ownClientData)
|
|
||||||
continue;
|
|
||||||
gui.new_id(300 + pnum++, [&] {
|
|
||||||
const std::string pname = client->get_display_name();
|
|
||||||
text_label(gui, pname);
|
|
||||||
CollabPermissionSet overrideSet = CollabPermissionSet::all_inherit();
|
|
||||||
auto it = editingLayerLock->get_person_layer_overrides().find(pname);
|
|
||||||
if(it != editingLayerLock->get_person_layer_overrides().end())
|
|
||||||
overrideSet = it->second;
|
|
||||||
left_to_right_line_layout(gui, [&]() {
|
|
||||||
for(uint8_t i = 0; i < static_cast<uint8_t>(CollabRight::COUNT); i++) {
|
|
||||||
CollabRight right = static_cast<CollabRight>(i);
|
|
||||||
gui.new_id(i, [&] {
|
|
||||||
std::string label = std::string(collab_right_label(right)) + ":"
|
|
||||||
+ collab_tristate_label(overrideSet.get(right));
|
|
||||||
text_button(gui, "player layer perm", label.c_str(), {
|
|
||||||
.onClick = [&, right, pname] {
|
|
||||||
auto layer = editingLayer.lock();
|
|
||||||
if(!layer)
|
|
||||||
return;
|
|
||||||
CollabPermissionSet next = CollabPermissionSet::all_inherit();
|
|
||||||
auto found = layer->get_person_layer_overrides().find(pname);
|
|
||||||
if(found != layer->get_person_layer_overrides().end())
|
|
||||||
next = found->second;
|
|
||||||
next.set(right, collab_cycle_tristate(next.get(right)));
|
|
||||||
layer->set_person_layer_override(layerMan, pname, next);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
text_label(gui, "Fog layers are always locked for players.");
|
text_label(gui, "Fog layers are always locked for players.");
|
||||||
|
|
@ -632,7 +560,6 @@ NetworkingObjects::NetObjOrderedListIterator<DrawingProgramLayerListItem> Drawin
|
||||||
|
|
||||||
auto insertedLayerPair = create_in_proper_position(newItem);
|
auto insertedLayerPair = create_in_proper_position(newItem);
|
||||||
auto& it = insertedLayerPair.second;
|
auto& it = insertedLayerPair.second;
|
||||||
it->obj->apply_new_layer_permission_defaults(layerMan);
|
|
||||||
world.undo.push(std::make_unique<AddLayerWorldUndoAction>(std::make_unique<DrawingProgramLayerListItemUndoData>(it->obj->get_undo_data(world.undo)), it->pos, world.undo.get_undoid_from_netid(insertedLayerPair.first), world.undo.get_undoid_from_netid(it->obj.get_net_id())));
|
world.undo.push(std::make_unique<AddLayerWorldUndoAction>(std::make_unique<DrawingProgramLayerListItemUndoData>(it->obj->get_undo_data(world.undo)), it->pos, world.undo.get_undoid_from_netid(insertedLayerPair.first), world.undo.get_undoid_from_netid(it->obj.get_net_id())));
|
||||||
world.main.g.gui.set_to_layout();
|
world.main.g.gui.set_to_layout();
|
||||||
refresh_gui_data();
|
refresh_gui_data();
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,6 @@
|
||||||
#include "RichText/TextStyleModifier.hpp"
|
#include "RichText/TextStyleModifier.hpp"
|
||||||
#include "VersionConstants.hpp"
|
#include "VersionConstants.hpp"
|
||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
#include "ClientData.hpp"
|
|
||||||
#include "CollabPermissions.hpp"
|
|
||||||
#include <SDL3/SDL_dialog.h>
|
#include <SDL3/SDL_dialog.h>
|
||||||
#include <SDL3/SDL_filesystem.h>
|
#include <SDL3/SDL_filesystem.h>
|
||||||
#include <SDL3/SDL_render.h>
|
#include <SDL3/SDL_render.h>
|
||||||
|
|
@ -1207,84 +1205,33 @@ void Toolbar::color_palette(const char* id, Vector4f* color, const std::function
|
||||||
}
|
}
|
||||||
|
|
||||||
void Toolbar::player_list() {
|
void Toolbar::player_list() {
|
||||||
using namespace GUIStuff;
|
|
||||||
using namespace ElementHelpers;
|
|
||||||
auto& gui = main.g.gui;
|
auto& gui = main.g.gui;
|
||||||
auto& world = *main.world;
|
|
||||||
auto& layerMan = world.drawProg.layerMan;
|
|
||||||
|
|
||||||
auto draw_perm_buttons = [&](const char* rowId, const CollabPermissionSet& shown,
|
center_obstructing_window_gui("player client list", CLAY_SIZING_FIT(500), CLAY_SIZING_FIT(0), [&] {
|
||||||
const std::function<void(CollabRight)>& cycleRight) {
|
|
||||||
gui.new_id(rowId, [&] {
|
|
||||||
left_to_right_line_layout(gui, [&]() {
|
|
||||||
for(uint8_t i = 0; i < static_cast<uint8_t>(CollabRight::COUNT); i++) {
|
|
||||||
CollabRight right = static_cast<CollabRight>(i);
|
|
||||||
gui.new_id(i, [&] {
|
|
||||||
std::string label = std::string(collab_right_label(right)) + ":"
|
|
||||||
+ collab_tristate_label(shown.get(right));
|
|
||||||
text_button(gui, "perm", label.c_str(), {
|
|
||||||
.onClick = [cycleRight, right] {
|
|
||||||
cycleRight(right);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
center_obstructing_window_gui("player client list", CLAY_SIZING_FIT(640), CLAY_SIZING_FIT(0), [&] {
|
|
||||||
gui.new_id("client list", [&] {
|
gui.new_id("client list", [&] {
|
||||||
text_label_centered(gui, "Player List");
|
text_label_centered(gui, "Player List");
|
||||||
if(!world.clientStillConnecting) {
|
if(!main.world->clientStillConnecting) {
|
||||||
if(world.netServer) {
|
if(main.world->netServer) {
|
||||||
text_button(gui, "force all jump to host", "Bring Everyone Here", {
|
text_button(gui, "force all jump to host", "Bring Everyone Here", {
|
||||||
.wide = true,
|
.wide = true,
|
||||||
.onClick = [&] {
|
.onClick = [&] {
|
||||||
world.drawProg.force_clients_to_own_view();
|
main.world->drawProg.force_clients_to_own_view();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
text_button(gui, "toggle session defaults", collabSessionDefaultsOpen ? "Hide Session Defaults" : "Session Defaults…", {
|
|
||||||
.wide = true,
|
|
||||||
.onClick = [&] {
|
|
||||||
collabSessionDefaultsOpen = !collabSessionDefaultsOpen;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if(collabSessionDefaultsOpen) {
|
|
||||||
const CollabSessionDefaults& cur = world.drawProg.collab_defaults();
|
|
||||||
text_label(gui, "World default (fallback when everything inherits)");
|
|
||||||
draw_perm_buttons("world default row", cur.worldDefault, [&](CollabRight right) {
|
|
||||||
auto d = world.drawProg.collab_defaults();
|
|
||||||
d.worldDefault.set(right, collab_cycle_tristate(d.worldDefault.get(right)));
|
|
||||||
world.drawProg.set_collab_defaults(d);
|
|
||||||
});
|
|
||||||
text_label(gui, "New player defaults (copied on join)");
|
|
||||||
draw_perm_buttons("new player default row", cur.newPlayerDefaults, [&](CollabRight right) {
|
|
||||||
auto d = world.drawProg.collab_defaults();
|
|
||||||
d.newPlayerDefaults.set(right, collab_cycle_tristate(d.newPlayerDefaults.get(right)));
|
|
||||||
world.drawProg.set_collab_defaults(d);
|
|
||||||
});
|
|
||||||
text_label(gui, "New layer defaults (copied on create)");
|
|
||||||
draw_perm_buttons("new layer default row", cur.newLayerDefaults, [&](CollabRight right) {
|
|
||||||
auto d = world.drawProg.collab_defaults();
|
|
||||||
d.newLayerDefaults.set(right, collab_cycle_tristate(d.newLayerDefaults.get(right)));
|
|
||||||
world.drawProg.set_collab_defaults(d);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
left_to_right_line_layout(gui, [&]() {
|
left_to_right_line_layout(gui, [&]() {
|
||||||
CLAY_AUTO_ID({
|
CLAY_AUTO_ID({
|
||||||
.layout = {
|
.layout = {
|
||||||
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
.sizing = {.width = CLAY_SIZING_FIXED(20), .height = CLAY_SIZING_FIXED(20)}
|
||||||
},
|
},
|
||||||
.backgroundColor = convert_vec4<Clay_Color>(SkColor4f{world.ownClientData->get_cursor_color().x(), world.ownClientData->get_cursor_color().y(), world.ownClientData->get_cursor_color().z(), 1.0f}),
|
.backgroundColor = convert_vec4<Clay_Color>(SkColor4f{main.world->ownClientData->get_cursor_color().x(), main.world->ownClientData->get_cursor_color().y(), main.world->ownClientData->get_cursor_color().z(), 1.0f}),
|
||||||
.cornerRadius = CLAY_CORNER_RADIUS(3)
|
.cornerRadius = CLAY_CORNER_RADIUS(3)
|
||||||
}) {}
|
}) {}
|
||||||
text_label(gui, world.ownClientData->get_display_name() + " (you / host)");
|
text_label(gui, main.world->ownClientData->get_display_name());
|
||||||
});
|
});
|
||||||
size_t num = 0;
|
size_t num = 0;
|
||||||
for(auto& client : world.clients->get_data()) {
|
for(auto& client : main.world->clients->get_data()) {
|
||||||
if(client != world.ownClientData) {
|
if(client != main.world->ownClientData) {
|
||||||
gui.new_id(num++, [&] {
|
gui.new_id(num++, [&] {
|
||||||
left_to_right_line_layout(gui, [&]() {
|
left_to_right_line_layout(gui, [&]() {
|
||||||
CLAY_AUTO_ID({
|
CLAY_AUTO_ID({
|
||||||
|
|
@ -1297,40 +1244,15 @@ void Toolbar::player_list() {
|
||||||
text_label(gui, client->get_display_name());
|
text_label(gui, client->get_display_name());
|
||||||
CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
|
CLAY_AUTO_ID({.layout = {.sizing = {.width = CLAY_SIZING_GROW(0), .height = CLAY_SIZING_GROW(0)}}}) {}
|
||||||
text_button(gui, "teleport button", "Jump To", { .onClick = [&] {
|
text_button(gui, "teleport button", "Jump To", { .onClick = [&] {
|
||||||
world.drawData.cam.smooth_move_to(world, client->get_cam_coords(), client->get_window_size());
|
main.world->drawData.cam.smooth_move_to(*main.world, client->get_cam_coords(), client->get_window_size());
|
||||||
}});
|
}});
|
||||||
if(world.netServer) {
|
|
||||||
text_button(gui, "perms button", "Rights", {
|
|
||||||
.onClick = [&, name = client->get_display_name()] {
|
|
||||||
if(collabPermsExpandedPlayer == name)
|
|
||||||
collabPermsExpandedPlayer.clear();
|
|
||||||
else
|
|
||||||
collabPermsExpandedPlayer = name;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
if(world.netServer && collabPermsExpandedPlayer == client->get_display_name()) {
|
|
||||||
text_label(gui, "Person defaults (Inherit → World)");
|
|
||||||
NetworkingObjects::NetObjID clientId = client.get_net_id();
|
|
||||||
draw_perm_buttons("person defaults", client->get_person_defaults(), [&, clientId](CollabRight right) {
|
|
||||||
auto peer = world.netObjMan.get_obj_temporary_ref_from_id<ClientData>(clientId);
|
|
||||||
if(!peer)
|
|
||||||
return;
|
|
||||||
CollabPermissionSet next = peer->get_person_defaults();
|
|
||||||
next.set(right, collab_cycle_tristate(next.get(right)));
|
|
||||||
ClientData::set_person_defaults(peer, next);
|
|
||||||
layerMan.ensure_editing_layer_is_editable();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text_button(gui, "close list", "Done", { .wide = true, .onClick = [&] {
|
text_button(gui, "close list", "Done", { .wide = true, .onClick = [&] {
|
||||||
playerMenuOpen = false;
|
playerMenuOpen = false;
|
||||||
collabSessionDefaultsOpen = false;
|
|
||||||
collabPermsExpandedPlayer.clear();
|
|
||||||
}});
|
}});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -153,8 +153,6 @@ class Toolbar {
|
||||||
bool menuPopUpOpen = false;
|
bool menuPopUpOpen = false;
|
||||||
bool optionsMenuOpen = false;
|
bool optionsMenuOpen = false;
|
||||||
bool playerMenuOpen = false;
|
bool playerMenuOpen = false;
|
||||||
bool collabSessionDefaultsOpen = false;
|
|
||||||
std::string collabPermsExpandedPlayer;
|
|
||||||
|
|
||||||
float finalCalculatedGuiScale = 1.0f;
|
float finalCalculatedGuiScale = 1.0f;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,6 @@ VersionNumber header_to_version_number(const std::string& header) {
|
||||||
m["INFPNT000006"] = VersionNumber(0, 6, 0);
|
m["INFPNT000006"] = VersionNumber(0, 6, 0);
|
||||||
m["INFPNT000007"] = VersionNumber(0, 7, 0);
|
m["INFPNT000007"] = VersionNumber(0, 7, 0);
|
||||||
m["INFPNT000008"] = VersionNumber(0, 8, 0);
|
m["INFPNT000008"] = VersionNumber(0, 8, 0);
|
||||||
m["INFPNT000009"] = VersionNumber(0, 9, 0);
|
|
||||||
}
|
}
|
||||||
auto it = m.find(header);
|
auto it = m.find(header);
|
||||||
if(it == m.end())
|
if(it == m.end())
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ namespace VersionConstants {
|
||||||
VersionNumber header_to_version_number(const std::string& header);
|
VersionNumber header_to_version_number(const std::string& header);
|
||||||
|
|
||||||
constexpr int SAVEFILE_HEADER_LEN = 12; // DO NOT CHANGE THIS HEADER LENGTH
|
constexpr int SAVEFILE_HEADER_LEN = 12; // DO NOT CHANGE THIS HEADER LENGTH
|
||||||
const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000009"; // Change whenever the save file is incompatible with the previous version
|
const std::string CURRENT_SAVEFILE_HEADER = "INFPNT000008"; // Change whenever the save file is incompatible with the previous version
|
||||||
const std::string CURRENT_VERSION_STRING = "0.9.0-fork";
|
const std::string CURRENT_VERSION_STRING = "0.8.0-fork";
|
||||||
constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 9, 0);
|
constexpr VersionNumber CURRENT_VERSION_NUMBER(0, 8, 0);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,6 @@ World::World(MainProgram& initMain, const CustomEvents::OpenInfiniPaintFileEvent
|
||||||
saveThumbnail = worldInfo.saveThumbnail;
|
saveThumbnail = worldInfo.saveThumbnail;
|
||||||
|
|
||||||
init_net_obj_type_list();
|
init_net_obj_type_list();
|
||||||
drawProg.ensure_collab_defaults();
|
|
||||||
netObjMan.set_netobj_destroy_callback([&undo = undo](const NetworkingObjects::NetObjID& idToDestroy) {
|
netObjMan.set_netobj_destroy_callback([&undo = undo](const NetworkingObjects::NetObjID& idToDestroy) {
|
||||||
undo.remove_by_netid(idToDestroy);
|
undo.remove_by_netid(idToDestroy);
|
||||||
});
|
});
|
||||||
|
|
@ -136,7 +135,6 @@ void World::init_net_obj_type_list() {
|
||||||
NetworkingObjects::register_ordered_list_class<CanvasComponentContainer>(netObjMan);
|
NetworkingObjects::register_ordered_list_class<CanvasComponentContainer>(netObjMan);
|
||||||
DrawingProgramLayerListItem::register_class(*this);
|
DrawingProgramLayerListItem::register_class(*this);
|
||||||
NetworkingObjects::register_ordered_list_class<DrawingProgramLayerListItem>(netObjMan);
|
NetworkingObjects::register_ordered_list_class<DrawingProgramLayerListItem>(netObjMan);
|
||||||
DrawingProgram::register_collab_defaults_class(*this);
|
|
||||||
canvasTheme.register_class();
|
canvasTheme.register_class();
|
||||||
ClientData::register_class(*this);
|
ClientData::register_class(*this);
|
||||||
NetworkingObjects::register_unordered_set_class<ClientData>(netObjMan);
|
NetworkingObjects::register_unordered_set_class<ClientData>(netObjMan);
|
||||||
|
|
@ -435,7 +433,6 @@ void World::start_hosting(const std::string& initNetSource, const std::string& s
|
||||||
newClientData.camCoords = ownClientData->get_cam_coords();
|
newClientData.camCoords = ownClientData->get_cam_coords();
|
||||||
newClientData.windowSize = ownClientData->get_window_size();
|
newClientData.windowSize = ownClientData->get_window_size();
|
||||||
newClientData.gridSize = ownClientData->get_grid_size();
|
newClientData.gridSize = ownClientData->get_grid_size();
|
||||||
newClientData.personDefaults = drawProg.collab_defaults().newPlayerDefaults;
|
|
||||||
|
|
||||||
NetworkingObjects::NetObjTemporaryPtr<ClientData> clientDataObjPtr = clients->emplace_direct(clients, newClientData);
|
NetworkingObjects::NetObjTemporaryPtr<ClientData> clientDataObjPtr = clients->emplace_direct(clients, newClientData);
|
||||||
client->customID = clientDataObjPtr.get_net_id().data;
|
client->customID = clientDataObjPtr.get_net_id().data;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue