Compare commits
13 Commits
v1.0.1
...
96c27bb949
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96c27bb949 | ||
|
|
cc617dd5be | ||
|
|
653a90de62 | ||
|
|
4b16a2a2c4 | ||
|
|
c51d3dafff | ||
|
|
68c2a59d09 | ||
|
|
45a2ccd9f3 | ||
|
|
0ca1caf148 | ||
|
|
7fb1f1de9d | ||
|
|
386cc857b0 | ||
|
|
3e6136983a | ||
|
|
2c1862aed3 | ||
|
|
4b815fc9d1 |
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
cmake_minimum_required(VERSION 3.20)
|
cmake_minimum_required(VERSION 3.20)
|
||||||
project(ObsidianDragon
|
project(ObsidianDragon
|
||||||
VERSION 1.0.1
|
VERSION 1.0.0
|
||||||
LANGUAGES C CXX
|
LANGUAGES C CXX
|
||||||
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
DESCRIPTION "DragonX Cryptocurrency Wallet"
|
||||||
)
|
)
|
||||||
@@ -373,14 +373,12 @@ endif()
|
|||||||
# Windows application icon + VERSIONINFO (.rc -> .res -> linked into .exe)
|
# Windows application icon + VERSIONINFO (.rc -> .res -> linked into .exe)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set(OBSIDIAN_ICO_PATH "${CMAKE_SOURCE_DIR}/res/img/ObsidianDragon.ico")
|
set(OBSIDIAN_ICO_PATH "${CMAKE_SOURCE_DIR}/res/img/ObsidianDragon.ico")
|
||||||
# Generate manifest with version from project()
|
|
||||||
configure_file(
|
|
||||||
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest.in
|
|
||||||
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest
|
|
||||||
@ONLY
|
|
||||||
)
|
|
||||||
set(OBSIDIAN_MANIFEST_PATH "${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest")
|
set(OBSIDIAN_MANIFEST_PATH "${CMAKE_SOURCE_DIR}/res/ObsidianDragon.manifest")
|
||||||
# Generate .rc with version from project()
|
# Version numbers for the VERSIONINFO resource block
|
||||||
|
set(DRAGONX_VER_MAJOR 1)
|
||||||
|
set(DRAGONX_VER_MINOR 0)
|
||||||
|
set(DRAGONX_VER_PATCH 0)
|
||||||
|
set(DRAGONX_VERSION "1.0.0")
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.rc
|
${CMAKE_SOURCE_DIR}/res/ObsidianDragon.rc
|
||||||
${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc
|
${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc
|
||||||
@@ -389,13 +387,6 @@ if(WIN32)
|
|||||||
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
set(WIN_RC_FILE ${CMAKE_BINARY_DIR}/generated/ObsidianDragon.rc)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Generate version.h from the single project(VERSION ...) declaration
|
|
||||||
configure_file(
|
|
||||||
${CMAKE_SOURCE_DIR}/src/config/version.h.in
|
|
||||||
${CMAKE_SOURCE_DIR}/src/config/version.h
|
|
||||||
@ONLY
|
|
||||||
)
|
|
||||||
|
|
||||||
# Generate INCBIN font embedding source with absolute paths to .ttf files
|
# Generate INCBIN font embedding source with absolute paths to .ttf files
|
||||||
configure_file(
|
configure_file(
|
||||||
${CMAKE_SOURCE_DIR}/src/embedded/embedded_fonts.cpp.in
|
${CMAKE_SOURCE_DIR}/src/embedded/embedded_fonts.cpp.in
|
||||||
|
|||||||
113
build.sh
113
build.sh
@@ -5,7 +5,7 @@
|
|||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# ./build.sh # Dev build (Linux, debug-friendly)
|
# ./build.sh # Dev build (Linux, debug-friendly)
|
||||||
# ./build.sh --linux-release # Linux release (zip + AppImage)
|
# ./build.sh --linux-release # Linux release + AppImage
|
||||||
# ./build.sh --win-release # Windows cross-compile (mingw-w64)
|
# ./build.sh --win-release # Windows cross-compile (mingw-w64)
|
||||||
# ./build.sh --mac-release # macOS .app bundle + DMG
|
# ./build.sh --mac-release # macOS .app bundle + DMG
|
||||||
# ./build.sh --linux-release --win-release # Multiple targets
|
# ./build.sh --linux-release --win-release # Multiple targets
|
||||||
@@ -51,7 +51,7 @@ DragonX Wallet — Unified Build Script
|
|||||||
Usage: $0 [options]
|
Usage: $0 [options]
|
||||||
|
|
||||||
Targets (at least one required, or none for dev build):
|
Targets (at least one required, or none for dev build):
|
||||||
--linux-release Linux release (zip + AppImage) -> release/linux/
|
--linux-release Linux release build + AppImage -> release/linux/
|
||||||
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
--win-release Windows cross-compile (mingw-w64) -> release/windows/
|
||||||
--mac-release macOS .app bundle + DMG -> release/mac/
|
--mac-release macOS .app bundle + DMG -> release/mac/
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ Cross-compiling from Linux:
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
$0 # Quick dev build (Linux)
|
$0 # Quick dev build (Linux)
|
||||||
$0 --linux-release # Linux release (zip + AppImage)
|
$0 --linux-release # Linux release + AppImage
|
||||||
$0 --win-release # Windows cross-compile
|
$0 --win-release # Windows cross-compile
|
||||||
$0 --mac-release # macOS bundle + DMG (native or osxcross)
|
$0 --mac-release # macOS bundle + DMG (native or osxcross)
|
||||||
$0 --clean --linux-release --win-release # Clean + both
|
$0 --clean --linux-release --win-release # Clean + both
|
||||||
@@ -218,7 +218,7 @@ build_dev() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
# RELEASE: LINUX — build + strip + bundle daemon + zip + AppImage
|
# RELEASE: LINUX — build + strip + bundle daemon + AppImage
|
||||||
# ═══════════════════════════════════════════════════════════════════════════════
|
# ═══════════════════════════════════════════════════════════════════════════════
|
||||||
build_release_linux() {
|
build_release_linux() {
|
||||||
header "Release: Linux x86_64"
|
header "Release: Linux x86_64"
|
||||||
@@ -249,41 +249,17 @@ build_release_linux() {
|
|||||||
# ── Bundle daemon ────────────────────────────────────────────────────────
|
# ── Bundle daemon ────────────────────────────────────────────────────────
|
||||||
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
bundle_linux_daemon "bin" || warn "Daemon not bundled — wallet-only build"
|
||||||
|
|
||||||
# ── Bundle Sapling params ────────────────────────────────────────────────
|
|
||||||
SAPLING_SPEND="" SAPLING_OUTPUT=""
|
|
||||||
find_sapling_params && {
|
|
||||||
cp -f "$SAPLING_SPEND" "bin/sapling-spend.params"
|
|
||||||
cp -f "$SAPLING_OUTPUT" "bin/sapling-output.params"
|
|
||||||
info "Bundled Sapling params"
|
|
||||||
} || warn "Sapling params not found — not bundled"
|
|
||||||
|
|
||||||
# ── Package: release/linux/ ──────────────────────────────────────────────
|
# ── Package: release/linux/ ──────────────────────────────────────────────
|
||||||
rm -rf "$out"
|
rm -rf "$out"
|
||||||
mkdir -p "$out"
|
mkdir -p "$out"
|
||||||
|
|
||||||
local DIST="ObsidianDragon-Linux-x64"
|
cp bin/ObsidianDragon "$out/"
|
||||||
local dist_dir="$out/$DIST"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$out/"
|
||||||
mkdir -p "$dist_dir"
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$out/"
|
||||||
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$out/"
|
||||||
|
cp -r bin/res "$out/" 2>/dev/null || true
|
||||||
|
|
||||||
cp bin/ObsidianDragon "$dist_dir/"
|
# ── AppImage ─────────────────────────────────────────────────────────────
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$dist_dir/"
|
|
||||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$dist_dir/"
|
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$dist_dir/"
|
|
||||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$dist_dir/"
|
|
||||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$dist_dir/"
|
|
||||||
# Bundle xmrig for mining support
|
|
||||||
local XMRIG_LINUX="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
|
||||||
[[ -f "$XMRIG_LINUX" ]] && { cp "$XMRIG_LINUX" "$dist_dir/"; chmod +x "$dist_dir/xmrig"; info "Bundled xmrig"; } || warn "xmrig not found — mining unavailable in zip"
|
|
||||||
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
|
||||||
|
|
||||||
# ── Zip ──────────────────────────────────────────────────────────────────
|
|
||||||
if command -v zip &>/dev/null; then
|
|
||||||
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
|
||||||
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
|
||||||
fi
|
|
||||||
rm -rf "$dist_dir"
|
|
||||||
|
|
||||||
# ── AppImage (single-file) ───────────────────────────────────────────────
|
|
||||||
info "Creating AppImage ..."
|
info "Creating AppImage ..."
|
||||||
local APPDIR="$bd/AppDir"
|
local APPDIR="$bd/AppDir"
|
||||||
rm -rf "$APPDIR"
|
rm -rf "$APPDIR"
|
||||||
@@ -295,16 +271,10 @@ build_release_linux() {
|
|||||||
cp bin/ObsidianDragon "$APPDIR/usr/bin/"
|
cp bin/ObsidianDragon "$APPDIR/usr/bin/"
|
||||||
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
cp -r bin/res/* "$APPDIR/usr/share/ObsidianDragon/res/" 2>/dev/null || true
|
||||||
|
|
||||||
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
# Daemon inside AppImage
|
||||||
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
[[ -f bin/dragonxd ]] && cp bin/dragonxd "$APPDIR/usr/bin/"
|
||||||
# Daemon data files must be alongside the daemon binary (usr/bin/)
|
[[ -f bin/dragonx-cli ]] && cp bin/dragonx-cli "$APPDIR/usr/bin/"
|
||||||
# because dragonxd searches relative to its own directory.
|
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/share/ObsidianDragon/"
|
||||||
[[ -f bin/asmap.dat ]] && cp bin/asmap.dat "$APPDIR/usr/bin/"
|
|
||||||
[[ -f bin/sapling-spend.params ]] && cp bin/sapling-spend.params "$APPDIR/usr/bin/"
|
|
||||||
[[ -f bin/sapling-output.params ]] && cp bin/sapling-output.params "$APPDIR/usr/bin/"
|
|
||||||
# Bundle xmrig for mining support
|
|
||||||
local XMRIG_LINUX_AI="$SCRIPT_DIR/prebuilt-binaries/xmrig-hac/xmrig"
|
|
||||||
[[ -f "$XMRIG_LINUX_AI" ]] && { cp "$XMRIG_LINUX_AI" "$APPDIR/usr/bin/"; chmod +x "$APPDIR/usr/bin/xmrig"; }
|
|
||||||
|
|
||||||
# Desktop entry
|
# Desktop entry
|
||||||
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK'
|
cat > "$APPDIR/usr/share/applications/ObsidianDragon.desktop" <<'DESK'
|
||||||
@@ -378,11 +348,32 @@ APPRUN
|
|||||||
|
|
||||||
local ARCH
|
local ARCH
|
||||||
ARCH=$(uname -m)
|
ARCH=$(uname -m)
|
||||||
|
local IMG_NAME="ObsidianDragon-${ARCH}.AppImage"
|
||||||
cd "$bd"
|
cd "$bd"
|
||||||
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "ObsidianDragon-${ARCH}.AppImage" 2>/dev/null && {
|
ARCH="$ARCH" "$APPIMAGETOOL" "$APPDIR" "$IMG_NAME" 2>/dev/null && {
|
||||||
cp "ObsidianDragon-${ARCH}.AppImage" "$out/ObsidianDragon.AppImage"
|
cp "$IMG_NAME" "$out/"
|
||||||
|
# Rename to match Windows convention: ObsidianDragon.AppImage
|
||||||
|
mv "$out/$IMG_NAME" "$out/ObsidianDragon.AppImage"
|
||||||
info "AppImage: $out/ObsidianDragon.AppImage ($(du -h "$out/ObsidianDragon.AppImage" | cut -f1))"
|
info "AppImage: $out/ObsidianDragon.AppImage ($(du -h "$out/ObsidianDragon.AppImage" | cut -f1))"
|
||||||
} || warn "AppImage creation failed — binaries zip still in release/linux/"
|
} || warn "AppImage creation failed (appimagetool issue) — raw binary still in release/linux/"
|
||||||
|
|
||||||
|
# Clean up: keep only AppImage + zip in release/linux/
|
||||||
|
if [[ -f "$out/ObsidianDragon.AppImage" ]]; then
|
||||||
|
# AppImage succeeded — remove everything except AppImage
|
||||||
|
find "$out" -maxdepth 1 -type f ! -name 'ObsidianDragon.AppImage' -delete
|
||||||
|
rm -rf "$out/res" 2>/dev/null
|
||||||
|
|
||||||
|
# Create zip matching Windows naming convention
|
||||||
|
local DIST="ObsidianDragon-Linux-x64"
|
||||||
|
local dist_dir="$out/$DIST"
|
||||||
|
mkdir -p "$dist_dir"
|
||||||
|
cp "$out/ObsidianDragon.AppImage" "$dist_dir/"
|
||||||
|
if command -v zip &>/dev/null; then
|
||||||
|
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
||||||
|
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
||||||
|
fi
|
||||||
|
rm -rf "$dist_dir"
|
||||||
|
fi
|
||||||
|
|
||||||
info "Linux release artifacts: $out/"
|
info "Linux release artifacts: $out/"
|
||||||
ls -lh "$out/"
|
ls -lh "$out/"
|
||||||
@@ -610,25 +601,31 @@ HDR
|
|||||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
||||||
done
|
done
|
||||||
|
|
||||||
# Bundle Sapling params + asmap for the zip distribution
|
cat > "$dist_dir/README.txt" <<'README'
|
||||||
# (The single-file exe has these embedded via INCBIN, but the zip
|
DragonX Wallet - Windows Edition
|
||||||
# needs them on disk so the daemon can find them in its work dir.)
|
================================
|
||||||
for f in sapling-spend.params sapling-output.params asmap.dat; do
|
|
||||||
[[ -f "$DD/$f" ]] && cp "$DD/$f" "$dist_dir/"
|
|
||||||
done
|
|
||||||
|
|
||||||
cp -r bin/res "$dist_dir/" 2>/dev/null || true
|
SINGLE-FILE DISTRIBUTION
|
||||||
|
========================
|
||||||
|
This wallet is a true single-file executable with all resources embedded.
|
||||||
|
Just run ObsidianDragon.exe — no additional files needed!
|
||||||
|
|
||||||
# ── Single-file exe (all resources embedded) ────────────────────────────
|
On first run, the wallet will automatically extract:
|
||||||
|
- Sapling parameters to %APPDATA%\ZcashParams\
|
||||||
|
- asmap.dat to %APPDATA%\Hush\DRAGONX\
|
||||||
|
|
||||||
|
For support: https://git.dragonx.is/dragonx/ObsidianDragon
|
||||||
|
README
|
||||||
|
|
||||||
|
# Copy single-file exe to release dir
|
||||||
cp bin/ObsidianDragon.exe "$out/"
|
cp bin/ObsidianDragon.exe "$out/"
|
||||||
info "Single-file exe: $out/ObsidianDragon.exe ($(du -h "$out/ObsidianDragon.exe" | cut -f1))"
|
|
||||||
|
|
||||||
# ── Zip ──────────────────────────────────────────────────────────────────
|
|
||||||
if command -v zip &>/dev/null; then
|
if command -v zip &>/dev/null; then
|
||||||
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
(cd "$out" && zip -r "$DIST.zip" "$DIST")
|
||||||
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
info "Zip: $out/$DIST.zip ($(du -h "$out/$DIST.zip" | cut -f1))"
|
||||||
|
# Clean up: keep .zip + single-file exe, remove loose directory
|
||||||
|
rm -rf "$dist_dir"
|
||||||
fi
|
fi
|
||||||
rm -rf "$dist_dir"
|
|
||||||
|
|
||||||
info "Windows release artifacts: $out/"
|
info "Windows release artifacts: $out/"
|
||||||
ls -lh "$out/"
|
ls -lh "$out/"
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<assemblyIdentity
|
<assemblyIdentity
|
||||||
type="win32"
|
type="win32"
|
||||||
name="DragonX.ObsidianDragon.Wallet"
|
name="DragonX.ObsidianDragon.Wallet"
|
||||||
version="1.0.1.0"
|
version="1.0.0.0"
|
||||||
processorArchitecture="amd64"
|
processorArchitecture="amd64"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
|
|
||||||
|
|
||||||
<!-- Application identity —————————————————————————————— -->
|
|
||||||
<assemblyIdentity
|
|
||||||
type="win32"
|
|
||||||
name="DragonX.ObsidianDragon.Wallet"
|
|
||||||
version="@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.0"
|
|
||||||
processorArchitecture="amd64"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<description>ObsidianDragon Wallet</description>
|
|
||||||
|
|
||||||
<!-- Common Controls v6 (themed buttons, etc.) ————————— -->
|
|
||||||
<dependency>
|
|
||||||
<dependentAssembly>
|
|
||||||
<assemblyIdentity
|
|
||||||
type="win32"
|
|
||||||
name="Microsoft.Windows.Common-Controls"
|
|
||||||
version="6.0.0.0"
|
|
||||||
processorArchitecture="*"
|
|
||||||
publicKeyToken="6595b64144ccf1df"
|
|
||||||
language="*"
|
|
||||||
/>
|
|
||||||
</dependentAssembly>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- DPI awareness (Per-Monitor V2) ————————————————————— -->
|
|
||||||
<application xmlns="urn:schemas-microsoft-com:asm.v3">
|
|
||||||
<windowsSettings>
|
|
||||||
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware>
|
|
||||||
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2,PerMonitor</dpiAwareness>
|
|
||||||
<activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
|
|
||||||
</windowsSettings>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
<!-- Supported OS declarations (Windows 7 → 11) ———————— -->
|
|
||||||
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
|
|
||||||
<application>
|
|
||||||
<!-- Windows 7 -->
|
|
||||||
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
|
|
||||||
<!-- Windows 8 -->
|
|
||||||
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
|
|
||||||
<!-- Windows 8.1 -->
|
|
||||||
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
|
|
||||||
<!-- Windows 10 / 11 -->
|
|
||||||
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
|
|
||||||
</application>
|
|
||||||
</compatibility>
|
|
||||||
|
|
||||||
</assembly>
|
|
||||||
@@ -19,8 +19,8 @@
|
|||||||
#include <winver.h>
|
#include <winver.h>
|
||||||
|
|
||||||
VS_VERSION_INFO VERSIONINFO
|
VS_VERSION_INFO VERSIONINFO
|
||||||
FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
|
FILEVERSION @DRAGONX_VER_MAJOR@,@DRAGONX_VER_MINOR@,@DRAGONX_VER_PATCH@,0
|
||||||
PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0
|
PRODUCTVERSION @DRAGONX_VER_MAJOR@,@DRAGONX_VER_MINOR@,@DRAGONX_VER_PATCH@,0
|
||||||
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
|
||||||
FILEFLAGS 0x0L
|
FILEFLAGS 0x0L
|
||||||
FILEOS VOS_NT_WINDOWS32
|
FILEOS VOS_NT_WINDOWS32
|
||||||
@@ -33,12 +33,12 @@ BEGIN
|
|||||||
BEGIN
|
BEGIN
|
||||||
VALUE "CompanyName", "DragonX Developers\0"
|
VALUE "CompanyName", "DragonX Developers\0"
|
||||||
VALUE "FileDescription", "ObsidianDragon Wallet\0"
|
VALUE "FileDescription", "ObsidianDragon Wallet\0"
|
||||||
VALUE "FileVersion", "@PROJECT_VERSION@\0"
|
VALUE "FileVersion", "@DRAGONX_VERSION@\0"
|
||||||
VALUE "InternalName", "ObsidianDragon\0"
|
VALUE "InternalName", "ObsidianDragon\0"
|
||||||
VALUE "LegalCopyright", "Copyright 2024-2026 DragonX Developers. GPLv3.\0"
|
VALUE "LegalCopyright", "Copyright 2024-2026 DragonX Developers. GPLv3.\0"
|
||||||
VALUE "OriginalFilename", "ObsidianDragon.exe\0"
|
VALUE "OriginalFilename", "ObsidianDragon.exe\0"
|
||||||
VALUE "ProductName", "ObsidianDragon\0"
|
VALUE "ProductName", "ObsidianDragon\0"
|
||||||
VALUE "ProductVersion", "@PROJECT_VERSION@\0"
|
VALUE "ProductVersion", "@DRAGONX_VERSION@\0"
|
||||||
END
|
END
|
||||||
END
|
END
|
||||||
BLOCK "VarFileInfo"
|
BLOCK "VarFileInfo"
|
||||||
|
|||||||
7
setup.sh
7
setup.sh
@@ -627,13 +627,8 @@ XMRIG_SRC="$PROJECT_DIR/external/xmrig-hac"
|
|||||||
XMRIG_PREBUILT="$PROJECT_DIR/prebuilt-binaries/xmrig-hac"
|
XMRIG_PREBUILT="$PROJECT_DIR/prebuilt-binaries/xmrig-hac"
|
||||||
|
|
||||||
# Clean previous prebuilt xmrig binaries so we always rebuild
|
# Clean previous prebuilt xmrig binaries so we always rebuild
|
||||||
# Only clean the binary for the platform(s) we are actually building,
|
|
||||||
# otherwise a plain ./setup.sh deletes xmrig.exe without rebuilding it.
|
|
||||||
if ! $CHECK_ONLY; then
|
if ! $CHECK_ONLY; then
|
||||||
rm -f "$XMRIG_PREBUILT/xmrig" 2>/dev/null || true
|
rm -f "$XMRIG_PREBUILT/xmrig" "$XMRIG_PREBUILT/xmrig.exe" 2>/dev/null || true
|
||||||
if $SETUP_WIN; then
|
|
||||||
rm -f "$XMRIG_PREBUILT/xmrig.exe" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Helper: clone xmrig-hac if not present
|
# Helper: clone xmrig-hac if not present
|
||||||
|
|||||||
32
src/app.cpp
32
src/app.cpp
@@ -1855,34 +1855,10 @@ bool App::startEmbeddedDaemon()
|
|||||||
}
|
}
|
||||||
DEBUG_LOGF("Sapling params extracted successfully\n");
|
DEBUG_LOGF("Sapling params extracted successfully\n");
|
||||||
} else {
|
} else {
|
||||||
// Fallback: check for params bundled alongside the executable
|
daemon_status_ = "Sapling parameters not found. They should be in: " + rpc::Connection::getSaplingParamsDir();
|
||||||
// (zip distributions bundle sapling-*.params next to the binary)
|
DEBUG_LOGF("Sapling params not found and no embedded resources available!\n");
|
||||||
namespace fs = std::filesystem;
|
DEBUG_LOGF("Expected location: %s\n", rpc::Connection::getSaplingParamsDir().c_str());
|
||||||
std::string exe_dir = util::Platform::getExecutableDirectory();
|
return false;
|
||||||
std::string daemon_dir = resources::getDaemonDirectory();
|
|
||||||
const char* paramFiles[] = { "sapling-spend.params", "sapling-output.params", "asmap.dat" };
|
|
||||||
bool copied = false;
|
|
||||||
if (!exe_dir.empty()) {
|
|
||||||
std::error_code ec;
|
|
||||||
fs::create_directories(daemon_dir, ec);
|
|
||||||
for (const char* name : paramFiles) {
|
|
||||||
fs::path src = fs::path(exe_dir) / name;
|
|
||||||
fs::path dst = fs::path(daemon_dir) / name;
|
|
||||||
if (fs::exists(src) && !fs::exists(dst)) {
|
|
||||||
DEBUG_LOGF("Copying bundled %s from exe dir to %s\n", name, daemon_dir.c_str());
|
|
||||||
fs::copy_file(src, dst, ec);
|
|
||||||
if (!ec) copied = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (copied && rpc::Connection::verifySaplingParams()) {
|
|
||||||
DEBUG_LOGF("Sapling params copied from exe directory successfully\n");
|
|
||||||
} else {
|
|
||||||
daemon_status_ = "Sapling parameters not found. They should be in: " + rpc::Connection::getSaplingParamsDir();
|
|
||||||
DEBUG_LOGF("Sapling params not found and no embedded resources available!\n");
|
|
||||||
DEBUG_LOGF("Expected location: %s\n", rpc::Connection::getSaplingParamsDir().c_str());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
|
#define DRAGONX_VERSION "1.0.0"
|
||||||
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
|
|
||||||
|
|
||||||
#define DRAGONX_VERSION "1.0.1"
|
|
||||||
#define DRAGONX_VERSION_MAJOR 1
|
#define DRAGONX_VERSION_MAJOR 1
|
||||||
#define DRAGONX_VERSION_MINOR 0
|
#define DRAGONX_VERSION_MINOR 0
|
||||||
#define DRAGONX_VERSION_PATCH 1
|
#define DRAGONX_VERSION_PATCH 0
|
||||||
|
|
||||||
#define DRAGONX_APP_NAME "ObsidianDragon"
|
#define DRAGONX_APP_NAME "ObsidianDragon"
|
||||||
#define DRAGONX_ORG_NAME "Hush"
|
#define DRAGONX_ORG_NAME "Hush"
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
// DragonX Wallet - ImGui Edition
|
|
||||||
// Copyright 2024-2026 The Hush Developers
|
|
||||||
// Released under the GPLv3
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
// !! DO NOT EDIT version.h — it is generated from version.h.in by CMake.
|
|
||||||
// !! Change the version in CMakeLists.txt: project(... VERSION x.y.z ...)
|
|
||||||
|
|
||||||
#define DRAGONX_VERSION "@PROJECT_VERSION@"
|
|
||||||
#define DRAGONX_VERSION_MAJOR @PROJECT_VERSION_MAJOR@
|
|
||||||
#define DRAGONX_VERSION_MINOR @PROJECT_VERSION_MINOR@
|
|
||||||
#define DRAGONX_VERSION_PATCH @PROJECT_VERSION_PATCH@
|
|
||||||
|
|
||||||
#define DRAGONX_APP_NAME "ObsidianDragon"
|
|
||||||
#define DRAGONX_ORG_NAME "Hush"
|
|
||||||
|
|
||||||
// Default RPC settings
|
|
||||||
#define DRAGONX_DEFAULT_RPC_HOST "127.0.0.1"
|
|
||||||
#define DRAGONX_DEFAULT_RPC_PORT "21769"
|
|
||||||
|
|
||||||
// Coin parameters
|
|
||||||
#define DRAGONX_TICKER "DRGX"
|
|
||||||
#define DRAGONX_COIN_NAME "DragonX"
|
|
||||||
#define DRAGONX_URI_SCHEME "drgx"
|
|
||||||
#define DRAGONX_ZATOSHI_PER_COIN 100000000
|
|
||||||
#define DRAGONX_DEFAULT_FEE 0.0001
|
|
||||||
|
|
||||||
// Config file names
|
|
||||||
#define DRAGONX_CONF_FILENAME "DRAGONX.conf"
|
|
||||||
#define DRAGONX_WALLET_FILENAME "wallet.dat"
|
|
||||||
@@ -466,18 +466,6 @@ bool XmrigManager::startProcess(const std::string& xmrigPath, const std::string&
|
|||||||
dup2(pipefd[1], STDERR_FILENO);
|
dup2(pipefd[1], STDERR_FILENO);
|
||||||
close(pipefd[1]);
|
close(pipefd[1]);
|
||||||
|
|
||||||
// Detach from controlling terminal's stdin to prevent SIGTTIN/SIGTTOU
|
|
||||||
// when running in a new process group (setpgid below).
|
|
||||||
int devnull = open("/dev/null", O_RDONLY);
|
|
||||||
if (devnull >= 0) {
|
|
||||||
dup2(devnull, STDIN_FILENO);
|
|
||||||
close(devnull);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ignore job-control signals that a background process group may receive
|
|
||||||
signal(SIGTTIN, SIG_IGN);
|
|
||||||
signal(SIGTTOU, SIG_IGN);
|
|
||||||
|
|
||||||
// New process group so we can kill the whole group
|
// New process group so we can kill the whole group
|
||||||
setpgid(0, 0);
|
setpgid(0, 0);
|
||||||
|
|
||||||
|
|||||||
20
src/main.cpp
20
src/main.cpp
@@ -419,20 +419,12 @@ int main(int argc, char* argv[])
|
|||||||
if (!g_single_instance.tryLock()) {
|
if (!g_single_instance.tryLock()) {
|
||||||
fprintf(stderr, "Another instance of ObsidianDragon is already running.\n");
|
fprintf(stderr, "Another instance of ObsidianDragon is already running.\n");
|
||||||
DEBUG_LOGF("Please close the existing instance first.\n");
|
DEBUG_LOGF("Please close the existing instance first.\n");
|
||||||
#ifdef _WIN32
|
|
||||||
MessageBoxW(nullptr, L"Another instance of ObsidianDragon is already running.\nPlease close it first.",
|
|
||||||
L"ObsidianDragon", MB_OK | MB_ICONINFORMATION);
|
|
||||||
#endif
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize SDL
|
// Initialize SDL
|
||||||
if (!InitSDL()) {
|
if (!InitSDL()) {
|
||||||
fprintf(stderr, "Failed to initialize SDL!\n");
|
fprintf(stderr, "Failed to initialize SDL!\n");
|
||||||
#ifdef _WIN32
|
|
||||||
MessageBoxW(nullptr, L"Failed to initialize SDL. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
|
|
||||||
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
|
|
||||||
#endif
|
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,8 +495,6 @@ int main(int argc, char* argv[])
|
|||||||
nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
|
nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||||
if (!nativeHwnd) {
|
if (!nativeHwnd) {
|
||||||
fprintf(stderr, "Failed to create native Win32 window (error %lu)\n", GetLastError());
|
fprintf(stderr, "Failed to create native Win32 window (error %lu)\n", GetLastError());
|
||||||
MessageBoxW(nullptr, L"Failed to create window. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
|
|
||||||
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
|
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -544,8 +534,6 @@ int main(int argc, char* argv[])
|
|||||||
SDL_DestroyProperties(createProps);
|
SDL_DestroyProperties(createProps);
|
||||||
if (window == nullptr) {
|
if (window == nullptr) {
|
||||||
fprintf(stderr, "Error: SDL_CreateWindowWithProperties(): %s\n", SDL_GetError());
|
fprintf(stderr, "Error: SDL_CreateWindowWithProperties(): %s\n", SDL_GetError());
|
||||||
MessageBoxW(nullptr, L"Failed to create SDL window. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
|
|
||||||
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
|
|
||||||
DestroyWindow(nativeHwnd);
|
DestroyWindow(nativeHwnd);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -572,8 +560,6 @@ int main(int argc, char* argv[])
|
|||||||
dragonx::platform::DX11Context dx;
|
dragonx::platform::DX11Context dx;
|
||||||
if (!dx.init(window)) {
|
if (!dx.init(window)) {
|
||||||
fprintf(stderr, "Error: Failed to initialize DirectX 11 context\n");
|
fprintf(stderr, "Error: Failed to initialize DirectX 11 context\n");
|
||||||
MessageBoxW(nullptr, L"Failed to initialize DirectX 11.\nPlease ensure your graphics drivers are up to date.\n\nCheck the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
|
|
||||||
L"ObsidianDragon - Graphics Error", MB_OK | MB_ICONERROR);
|
|
||||||
SDL_DestroyWindow(window);
|
SDL_DestroyWindow(window);
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
return 1;
|
return 1;
|
||||||
@@ -649,8 +635,6 @@ int main(int argc, char* argv[])
|
|||||||
// Initialize ImGui with DX11 backend
|
// Initialize ImGui with DX11 backend
|
||||||
if (!InitImGui(window, dx)) {
|
if (!InitImGui(window, dx)) {
|
||||||
fprintf(stderr, "Failed to initialize ImGui!\n");
|
fprintf(stderr, "Failed to initialize ImGui!\n");
|
||||||
MessageBoxW(nullptr, L"Failed to initialize ImGui. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
|
|
||||||
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
|
|
||||||
Shutdown(window, dx);
|
Shutdown(window, dx);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
@@ -824,10 +808,6 @@ int main(int argc, char* argv[])
|
|||||||
dragonx::App app;
|
dragonx::App app;
|
||||||
if (!app.init()) {
|
if (!app.init()) {
|
||||||
fprintf(stderr, "Failed to initialize application!\n");
|
fprintf(stderr, "Failed to initialize application!\n");
|
||||||
#ifdef _WIN32
|
|
||||||
MessageBoxW(nullptr, L"Failed to initialize application. Please check the debug log at\n%APPDATA%\\ObsidianDragon\\dragonx-debug.log",
|
|
||||||
L"ObsidianDragon - Startup Error", MB_OK | MB_ICONERROR);
|
|
||||||
#endif
|
|
||||||
#ifdef DRAGONX_USE_DX11
|
#ifdef DRAGONX_USE_DX11
|
||||||
Shutdown(window, dx);
|
Shutdown(window, dx);
|
||||||
#else
|
#else
|
||||||
|
|||||||
@@ -61,8 +61,13 @@ bool DX11Context::init(SDL_Window* window)
|
|||||||
D3D_FEATURE_LEVEL_10_0,
|
D3D_FEATURE_LEVEL_10_0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
UINT createDeviceFlags = 0;
|
||||||
|
#ifdef DRAGONX_DEBUG
|
||||||
|
createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||||
|
#endif
|
||||||
|
|
||||||
// Need BGRA support for DirectComposition
|
// Need BGRA support for DirectComposition
|
||||||
UINT createDeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
createDeviceFlags |= D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||||
|
|
||||||
HRESULT hr = D3D11CreateDevice(
|
HRESULT hr = D3D11CreateDevice(
|
||||||
nullptr, // Default adapter
|
nullptr, // Default adapter
|
||||||
|
|||||||
@@ -509,18 +509,6 @@ std::string getXmrigPath()
|
|||||||
return xmrigName;
|
return xmrigName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check alongside the wallet executable (zip / AppImage distributions)
|
|
||||||
{
|
|
||||||
std::string exeDir = util::Platform::getExecutableDirectory();
|
|
||||||
if (!exeDir.empty()) {
|
|
||||||
std::string exeDirPath = exeDir + "/" + xmrigName;
|
|
||||||
if (std::filesystem::exists(exeDirPath)) {
|
|
||||||
DEBUG_LOGF("[INFO] getXmrigPath: found alongside wallet at %s\n", exeDirPath.c_str());
|
|
||||||
return exeDirPath;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check development paths
|
// Check development paths
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
// Not applicable for cross-compiled Windows builds
|
// Not applicable for cross-compiled Windows builds
|
||||||
|
|||||||
@@ -757,26 +757,15 @@ inline void ApplySmoothScroll(float speed = 12.0f)
|
|||||||
s.current = actualY;
|
s.current = actualY;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Capture mouse wheel when the cursor is inside this child window.
|
// Capture mouse wheel when hovered, but not when a popup (combo dropdown
|
||||||
// Use a direct position check instead of IsWindowHovered() which can
|
// etc.) is open — let the popup handle its own scrolling exclusively.
|
||||||
// return false when an ActiveId exists (focused input, held button) or
|
bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
|
||||||
// when tooltip/popup windows from row hovers interfere with hover
|
if (!popupOpen && ImGui::IsWindowHovered(ImGuiHoveredFlags_ChildWindows)) {
|
||||||
// routing. Still skip when a real popup (combo dropdown, context menu)
|
float wheel = ImGui::GetIO().MouseWheel;
|
||||||
// is open so the popup handles its own scrolling.
|
if (wheel != 0.0f) {
|
||||||
{
|
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
|
||||||
bool popupOpen = ImGui::IsPopupOpen("", ImGuiPopupFlags_AnyPopupId | ImGuiPopupFlags_AnyPopupLevel);
|
s.target -= wheel * step;
|
||||||
ImVec2 mpos = ImGui::GetIO().MousePos;
|
s.target = ImClamp(s.target, 0.0f, scrollMaxY);
|
||||||
ImVec2 wmin = win->Pos;
|
|
||||||
ImVec2 wmax = ImVec2(wmin.x + win->Size.x, wmin.y + win->Size.y);
|
|
||||||
bool inside = (mpos.x >= wmin.x && mpos.x < wmax.x &&
|
|
||||||
mpos.y >= wmin.y && mpos.y < wmax.y);
|
|
||||||
if (!popupOpen && inside) {
|
|
||||||
float wheel = ImGui::GetIO().MouseWheel;
|
|
||||||
if (wheel != 0.0f) {
|
|
||||||
float step = ImGui::GetTextLineHeightWithSpacing() * 3.0f;
|
|
||||||
s.target -= wheel * step;
|
|
||||||
s.target = ImClamp(s.target, 0.0f, scrollMaxY);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,8 +122,6 @@ static bool sp_verbose_logging = false;
|
|||||||
static std::set<std::string> sp_debug_categories;
|
static std::set<std::string> sp_debug_categories;
|
||||||
static bool sp_debug_cats_dirty = false; // true when changed but daemon not yet restarted
|
static bool sp_debug_cats_dirty = false; // true when changed but daemon not yet restarted
|
||||||
static bool sp_debug_expanded = false; // collapsible card state
|
static bool sp_debug_expanded = false; // collapsible card state
|
||||||
static bool sp_effects_expanded = false; // "Advanced Effects..." toggle
|
|
||||||
static bool sp_tools_expanded = false; // "Tools & Actions..." toggle
|
|
||||||
static bool sp_confirm_clear_ztx = false; // confirmation dialog for clearing z-tx history
|
static bool sp_confirm_clear_ztx = false; // confirmation dialog for clearing z-tx history
|
||||||
|
|
||||||
// (APPEARANCE card now uses ChannelsSplit like all other cards)
|
// (APPEARANCE card now uses ChannelsSplit like all other cards)
|
||||||
@@ -379,11 +377,11 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImFont* sub1 = Type().subtitle1();
|
ImFont* sub1 = Type().subtitle1();
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// THEME & LANGUAGE — card (draw-first approach; avoids ChannelsSplit
|
// APPEARANCE — card (draw-first approach; avoids ChannelsSplit which
|
||||||
// which breaks BeginCombo popup rendering in some ImGui versions)
|
// breaks BeginCombo popup rendering in some ImGui versions)
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
{
|
{
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("theme_language"));
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("appearance"));
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
||||||
|
|
||||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
||||||
@@ -563,59 +561,9 @@ void RenderSettingsPage(App* app) {
|
|||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
|
||||||
// --- Font Scale slider (always visible) ---
|
// --- Visual Effects (checkboxes on one row, Quality+Blur paired) ---
|
||||||
{
|
{
|
||||||
ImGui::PushFont(body2);
|
ImGui::PushFont(body2);
|
||||||
ImGui::TextUnformatted(TR("font_scale"));
|
|
||||||
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size, contentW);
|
|
||||||
ImGui::SetNextItemWidth(fontSliderW);
|
|
||||||
sp_font_scale = Layout::userFontScale();
|
|
||||||
float prev_font_scale = sp_font_scale;
|
|
||||||
{
|
|
||||||
char fs_fmt[16];
|
|
||||||
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
|
|
||||||
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
|
|
||||||
ImGuiSliderFlags_AlwaysClamp);
|
|
||||||
}
|
|
||||||
sp_font_scale = std::max(1.0f, std::min(1.5f,
|
|
||||||
std::round(sp_font_scale * 20.0f) / 20.0f));
|
|
||||||
if (sp_font_scale != prev_font_scale)
|
|
||||||
Layout::setUserFontScaleVisual(sp_font_scale);
|
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
||||||
Layout::setUserFontScale(sp_font_scale);
|
|
||||||
saveSettingsPageState(app->settings());
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
|
|
||||||
ImGui::PopFont();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
|
||||||
|
|
||||||
// --- Collapsible: Advanced Effects... ---
|
|
||||||
{
|
|
||||||
const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
|
||||||
ImGui::PushFont(body2);
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
|
||||||
{
|
|
||||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
|
||||||
if (ImGui::Button("##EffectsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
|
|
||||||
sp_effects_expanded = !sp_effects_expanded;
|
|
||||||
}
|
|
||||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
|
|
||||||
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
|
|
||||||
ImFont* iconFont = Type().iconSmall();
|
|
||||||
if (!iconFont) iconFont = body2;
|
|
||||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
|
||||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
|
|
||||||
}
|
|
||||||
ImGui::PopStyleColor(3);
|
|
||||||
ImGui::PopFont();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sp_effects_expanded) {
|
|
||||||
ImGui::PushFont(body2);
|
|
||||||
|
|
||||||
// Checkbox row: Low-spec | Console scanline | Theme effects | Gradient background
|
// Checkbox row: Low-spec | Console scanline | Theme effects | Gradient background
|
||||||
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
|
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
|
||||||
@@ -655,6 +603,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
|
||||||
|
|
||||||
|
// Simple background is lightweight — always interactive, even in low-spec mode
|
||||||
ImGui::SameLine(0, Layout::spacingLg());
|
ImGui::SameLine(0, Layout::spacingLg());
|
||||||
if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &sp_gradient_background)) {
|
if (ImGui::Checkbox(TrId("simple_background", "simple_bg").c_str(), &sp_gradient_background)) {
|
||||||
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
|
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
|
||||||
@@ -686,10 +635,12 @@ void RenderSettingsPage(App* app) {
|
|||||||
float baseX = ImGui::GetCursorScreenPos().x;
|
float baseX = ImGui::GetCursorScreenPos().x;
|
||||||
float rightX = baseX + ctrlW + Layout::spacingLg();
|
float rightX = baseX + ctrlW + Layout::spacingLg();
|
||||||
|
|
||||||
|
// Acrylic label + slider (left column)
|
||||||
ImGui::TextUnformatted(TR("acrylic"));
|
ImGui::TextUnformatted(TR("acrylic"));
|
||||||
float row1Y = ImGui::GetCursorScreenPos().y;
|
float row1Y = ImGui::GetCursorScreenPos().y;
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
{
|
{
|
||||||
|
// Build display format: "Off" at zero, percentage otherwise
|
||||||
char blur_fmt[16];
|
char blur_fmt[16];
|
||||||
if (sp_blur_amount < 0.01f)
|
if (sp_blur_amount < 0.01f)
|
||||||
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
|
snprintf(blur_fmt, sizeof(blur_fmt), "Off");
|
||||||
@@ -697,6 +648,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
|
snprintf(blur_fmt, sizeof(blur_fmt), "%.0f%%%%", sp_blur_amount * 25.0f);
|
||||||
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
|
if (ImGui::SliderFloat("##AcrylicBlur", &sp_blur_amount, 0.0f, 4.0f, blur_fmt,
|
||||||
ImGuiSliderFlags_AlwaysClamp)) {
|
ImGuiSliderFlags_AlwaysClamp)) {
|
||||||
|
// Snap to off when dragged near 0%
|
||||||
if (sp_blur_amount > 0.0f && sp_blur_amount < 0.15f) sp_blur_amount = 0.0f;
|
if (sp_blur_amount > 0.0f && sp_blur_amount < 0.15f) sp_blur_amount = 0.0f;
|
||||||
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
|
sp_acrylic_enabled = (sp_blur_amount > 0.001f);
|
||||||
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
|
effects::ImGuiAcrylic::ApplyBlurAmount(sp_blur_amount);
|
||||||
@@ -706,6 +658,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
|
||||||
float afterRow1Y = ImGui::GetCursorScreenPos().y;
|
float afterRow1Y = ImGui::GetCursorScreenPos().y;
|
||||||
|
|
||||||
|
// Noise label + slider (right column, same row)
|
||||||
float lblH = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y;
|
float lblH = ImGui::GetTextLineHeight() + ImGui::GetStyle().ItemSpacing.y;
|
||||||
ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y - lblH));
|
ImGui::SetCursorScreenPos(ImVec2(rightX, row1Y - lblH));
|
||||||
ImGui::TextUnformatted(TR("noise"));
|
ImGui::TextUnformatted(TR("noise"));
|
||||||
@@ -725,8 +678,10 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
|
||||||
|
|
||||||
|
// Reset cursor to left column, past row 1
|
||||||
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow1Y));
|
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow1Y));
|
||||||
|
|
||||||
|
// Row 2: UI Opacity + Window Opacity (labels above)
|
||||||
ImGui::TextUnformatted(TR("ui_opacity"));
|
ImGui::TextUnformatted(TR("ui_opacity"));
|
||||||
float row2Y = ImGui::GetCursorScreenPos().y;
|
float row2Y = ImGui::GetCursorScreenPos().y;
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
@@ -742,6 +697,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
|
||||||
float afterRow2Y = ImGui::GetCursorScreenPos().y;
|
float afterRow2Y = ImGui::GetCursorScreenPos().y;
|
||||||
|
|
||||||
|
// Window label + slider (right column, same row)
|
||||||
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y - lblH));
|
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y - lblH));
|
||||||
ImGui::TextUnformatted(TR("window_opacity"));
|
ImGui::TextUnformatted(TR("window_opacity"));
|
||||||
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y));
|
ImGui::SetCursorScreenPos(ImVec2(rightX, row2Y));
|
||||||
@@ -756,11 +712,12 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_window_opacity"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_window_opacity"));
|
||||||
|
|
||||||
|
// Reset cursor to left column, past row 2
|
||||||
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow2Y));
|
ImGui::SetCursorScreenPos(ImVec2(baseX, afterRow2Y));
|
||||||
|
|
||||||
ImGui::EndDisabled(); // low-spec
|
ImGui::EndDisabled(); // low-spec
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
} // sp_effects_expanded
|
}
|
||||||
} else {
|
} else {
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Narrow: stacked combos + 2-column effects (original layout)
|
// Narrow: stacked combos + 2-column effects (original layout)
|
||||||
@@ -853,62 +810,11 @@ void RenderSettingsPage(App* app) {
|
|||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
|
||||||
// --- Font Scale slider (always visible) ---
|
// --- Visual Effects (checkboxes + controls) ---
|
||||||
{
|
{
|
||||||
ImGui::PushFont(body2);
|
ImGui::PushFont(body2);
|
||||||
ImGui::TextUnformatted(TR("font_scale"));
|
|
||||||
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
|
|
||||||
availWidth - pad * 2);
|
|
||||||
ImGui::SetNextItemWidth(fontSliderW);
|
|
||||||
sp_font_scale = Layout::userFontScale();
|
|
||||||
float prev_font_scale = sp_font_scale;
|
|
||||||
{
|
|
||||||
char fs_fmt[16];
|
|
||||||
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
|
|
||||||
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
|
|
||||||
ImGuiSliderFlags_AlwaysClamp);
|
|
||||||
}
|
|
||||||
sp_font_scale = std::max(1.0f, std::min(1.5f,
|
|
||||||
std::round(sp_font_scale * 20.0f) / 20.0f));
|
|
||||||
if (sp_font_scale != prev_font_scale)
|
|
||||||
Layout::setUserFontScaleVisual(sp_font_scale);
|
|
||||||
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
|
||||||
Layout::setUserFontScale(sp_font_scale);
|
|
||||||
saveSettingsPageState(app->settings());
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
|
|
||||||
ImGui::PopFont();
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
|
||||||
|
|
||||||
// --- Collapsible: Advanced Effects... ---
|
|
||||||
{
|
|
||||||
const char* arrow = sp_effects_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
|
||||||
ImGui::PushFont(body2);
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
|
||||||
{
|
|
||||||
float narrowContentW = availWidth - pad * 2;
|
|
||||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
|
||||||
if (ImGui::Button("##EffectsToggleN", ImVec2(narrowContentW, ImGui::GetFrameHeight()))) {
|
|
||||||
sp_effects_expanded = !sp_effects_expanded;
|
|
||||||
}
|
|
||||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
|
|
||||||
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("advanced_effects"));
|
|
||||||
ImFont* iconFont = Type().iconSmall();
|
|
||||||
if (!iconFont) iconFont = body2;
|
|
||||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
|
||||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + narrowContentW - arrowW, textY), OnSurfaceMedium(), arrow);
|
|
||||||
}
|
|
||||||
ImGui::PopStyleColor(3);
|
|
||||||
ImGui::PopFont();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sp_effects_expanded) {
|
|
||||||
ImGui::PushFont(body2);
|
|
||||||
|
|
||||||
|
// Checkbox row 1: Low-spec
|
||||||
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
|
if (ImGui::Checkbox(TrId("low_spec_mode", "low_spec").c_str(), &sp_low_spec_mode)) {
|
||||||
effects::setLowSpecMode(sp_low_spec_mode);
|
effects::setLowSpecMode(sp_low_spec_mode);
|
||||||
if (sp_low_spec_mode) {
|
if (sp_low_spec_mode) {
|
||||||
@@ -946,6 +852,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_low_spec"));
|
||||||
|
|
||||||
|
// Simple background is lightweight — always interactive, even in low-spec mode
|
||||||
if (ImGui::Checkbox(TrId("settings_gradient_bg", "gradient_bg").c_str(), &sp_gradient_background)) {
|
if (ImGui::Checkbox(TrId("settings_gradient_bg", "gradient_bg").c_str(), &sp_gradient_background)) {
|
||||||
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
|
schema::SkinManager::instance().setGradientMode(sp_gradient_background);
|
||||||
saveSettingsPageState(app->settings());
|
saveSettingsPageState(app->settings());
|
||||||
@@ -954,6 +861,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
|
|
||||||
ImGui::BeginDisabled(sp_low_spec_mode);
|
ImGui::BeginDisabled(sp_low_spec_mode);
|
||||||
|
|
||||||
|
// Checkbox row 2: Console scanline | Theme effects
|
||||||
if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) {
|
if (ImGui::Checkbox(TrId("console_scanline", "scanline").c_str(), &sp_scanline_enabled)) {
|
||||||
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
|
ConsoleTab::s_scanline_enabled = sp_scanline_enabled;
|
||||||
app->settings()->setScanlineEnabled(sp_scanline_enabled);
|
app->settings()->setScanlineEnabled(sp_scanline_enabled);
|
||||||
@@ -968,8 +876,9 @@ void RenderSettingsPage(App* app) {
|
|||||||
}
|
}
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_theme_effects"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_theme_effects"));
|
||||||
|
|
||||||
|
// Acrylic blur slider (label above)
|
||||||
float ctrlW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
|
float ctrlW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
|
||||||
availWidth - pad * 2.0f);
|
contentW);
|
||||||
ImGui::TextUnformatted(TR("acrylic"));
|
ImGui::TextUnformatted(TR("acrylic"));
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
{
|
{
|
||||||
@@ -988,6 +897,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
|
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_blur"));
|
||||||
|
|
||||||
|
// Noise opacity slider (label above)
|
||||||
ImGui::TextUnformatted(TR("noise"));
|
ImGui::TextUnformatted(TR("noise"));
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
{
|
{
|
||||||
@@ -1004,6 +914,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
|
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_noise"));
|
||||||
|
|
||||||
|
// UI Opacity slider (label above)
|
||||||
ImGui::TextUnformatted(TR("ui_opacity"));
|
ImGui::TextUnformatted(TR("ui_opacity"));
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
{
|
{
|
||||||
@@ -1017,6 +928,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
|
if (ImGui::IsItemDeactivatedAfterEdit()) saveSettingsPageState(app->settings());
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_ui_opacity"));
|
||||||
|
|
||||||
|
// Window Opacity slider (label above)
|
||||||
ImGui::TextUnformatted(TR("window_opacity"));
|
ImGui::TextUnformatted(TR("window_opacity"));
|
||||||
ImGui::SetNextItemWidth(ctrlW);
|
ImGui::SetNextItemWidth(ctrlW);
|
||||||
{
|
{
|
||||||
@@ -1031,7 +943,44 @@ void RenderSettingsPage(App* app) {
|
|||||||
|
|
||||||
ImGui::EndDisabled(); // low-spec
|
ImGui::EndDisabled(); // low-spec
|
||||||
ImGui::PopFont();
|
ImGui::PopFont();
|
||||||
} // sp_effects_expanded
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================
|
||||||
|
// Font Scale slider (always enabled, not affected by low-spec)
|
||||||
|
// ============================================================
|
||||||
|
{
|
||||||
|
ImGui::PushFont(body2);
|
||||||
|
ImGui::Spacing();
|
||||||
|
ImGui::TextUnformatted(TR("font_scale"));
|
||||||
|
float fontSliderW = std::max(S.drawElement("components.settings-page", "effects-input-min-width").size,
|
||||||
|
availWidth - pad * 2);
|
||||||
|
ImGui::SetNextItemWidth(fontSliderW);
|
||||||
|
// Sync from Layout so Alt+scroll hotkey changes are reflected
|
||||||
|
sp_font_scale = Layout::userFontScale();
|
||||||
|
float prev_font_scale = sp_font_scale;
|
||||||
|
{
|
||||||
|
char fs_fmt[16];
|
||||||
|
snprintf(fs_fmt, sizeof(fs_fmt), "%.2fx", sp_font_scale);
|
||||||
|
ImGui::SliderFloat("##FontScale", &sp_font_scale, 1.0f, 1.5f, fs_fmt,
|
||||||
|
ImGuiSliderFlags_AlwaysClamp);
|
||||||
|
}
|
||||||
|
// Snap to 0.05 increments for consistent stepping.
|
||||||
|
// Visual scaling uses FontScaleMain (no atlas rebuild),
|
||||||
|
// atlas rebuild is deferred to slider release for crisp text.
|
||||||
|
sp_font_scale = std::max(1.0f, std::min(1.5f,
|
||||||
|
std::round(sp_font_scale * 20.0f) / 20.0f));
|
||||||
|
if (sp_font_scale != prev_font_scale) {
|
||||||
|
// While dragging: update layout scale without atlas rebuild
|
||||||
|
Layout::setUserFontScaleVisual(sp_font_scale);
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemDeactivatedAfterEdit()) {
|
||||||
|
// On release: rebuild font atlas at final size
|
||||||
|
Layout::setUserFontScale(sp_font_scale);
|
||||||
|
saveSettingsPageState(app->settings());
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_font_scale"));
|
||||||
|
ImGui::PopFont();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bottom padding
|
// Bottom padding
|
||||||
@@ -1051,7 +1000,7 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImGui::Dummy(ImVec2(0, gap));
|
ImGui::Dummy(ImVec2(0, gap));
|
||||||
|
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
// WALLET — card (privacy/daemon toggles + collapsible tools)
|
// WALLET — card (Keys, Backup, Tools, Maintenance, Node/RPC)
|
||||||
// ====================================================================
|
// ====================================================================
|
||||||
{
|
{
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
|
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("wallet"));
|
||||||
@@ -1064,6 +1013,56 @@ void RenderSettingsPage(App* app) {
|
|||||||
ImGui::Indent(pad);
|
ImGui::Indent(pad);
|
||||||
|
|
||||||
float contentW = availWidth - pad * 2;
|
float contentW = availWidth - pad * 2;
|
||||||
|
float btnSpacing = Layout::spacingMd();
|
||||||
|
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
|
||||||
|
|
||||||
|
// Calculate button width that fits available space
|
||||||
|
// 6 buttons total: on wide screens 3+3, on narrow screens 2+2+2
|
||||||
|
int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
|
||||||
|
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
|
||||||
|
// Clamp to reasonable size
|
||||||
|
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
|
||||||
|
bw = std::max(minBtnW, bw);
|
||||||
|
|
||||||
|
// Row 1 — Tools & Actions
|
||||||
|
{
|
||||||
|
if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
|
||||||
|
AddressBookDialog::show();
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book"));
|
||||||
|
ImGui::SameLine(0, btnSpacing);
|
||||||
|
if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button")))
|
||||||
|
ValidateAddressDialog::show();
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate"));
|
||||||
|
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
|
||||||
|
if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button")))
|
||||||
|
RequestPaymentDialog::show();
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment"));
|
||||||
|
if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
|
||||||
|
if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button")))
|
||||||
|
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining"));
|
||||||
|
ImGui::SameLine(0, btnSpacing);
|
||||||
|
if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button")))
|
||||||
|
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge"));
|
||||||
|
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
|
||||||
|
if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) {
|
||||||
|
sp_confirm_clear_ztx = true;
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Thin divider
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
{
|
||||||
|
float divAlpha = S.drawElement("components.settings-page", "section-divider-alpha").opacity;
|
||||||
|
if (divAlpha <= 0.0f) divAlpha = 0.08f;
|
||||||
|
ImU32 baseDivCol = S.resolveColor("var(--status-divider)", IM_COL32(255, 255, 255, 20));
|
||||||
|
ImU32 divCol = material::ScaleAlpha(baseDivCol, divAlpha / 0.08f);
|
||||||
|
ImVec2 p = ImGui::GetCursorScreenPos();
|
||||||
|
dl->AddLine(ImVec2(p.x, p.y), ImVec2(p.x + contentW, p.y), divCol);
|
||||||
|
}
|
||||||
|
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
||||||
|
|
||||||
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
|
// Privacy, Network & Daemon checkboxes — all on one line, shrink text to fit
|
||||||
{
|
{
|
||||||
@@ -1112,94 +1111,47 @@ void RenderSettingsPage(App* app) {
|
|||||||
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
|
if (scale < 1.0f) ImGui::SetWindowFontScale(1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingSm()));
|
// Mine when idle — checkbox + delay combo
|
||||||
|
|
||||||
// --- Collapsible: Tools & Actions... ---
|
|
||||||
{
|
{
|
||||||
const char* arrow = sp_tools_expanded ? ICON_MD_EXPAND_LESS : ICON_MD_EXPAND_MORE;
|
if (ImGui::Checkbox(TrId("mine_when_idle", "mine_idle").c_str(), &sp_mine_when_idle)) {
|
||||||
ImGui::PushFont(body2);
|
saveSettingsPageState(app->settings());
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0,0,0,0));
|
}
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1,1,1,0.05f));
|
if (ImGui::IsItemHovered())
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1,1,1,0.08f));
|
ImGui::SetTooltip("%s", TR("tt_mine_idle"));
|
||||||
{
|
|
||||||
ImVec2 hdrPos = ImGui::GetCursorScreenPos();
|
if (sp_mine_when_idle) {
|
||||||
if (ImGui::Button("##ToolsToggle", ImVec2(contentW, ImGui::GetFrameHeight()))) {
|
ImGui::SameLine(0, Layout::spacingMd());
|
||||||
sp_tools_expanded = !sp_tools_expanded;
|
ImGui::AlignTextToFramePadding();
|
||||||
|
ImGui::TextColored(ImVec4(1, 1, 1, 0.5f), "%s", TR("settings_idle_after"));
|
||||||
|
ImGui::SameLine(0, Layout::spacingSm());
|
||||||
|
|
||||||
|
struct DelayOption { int seconds; const char* label; };
|
||||||
|
static const DelayOption delays[] = {
|
||||||
|
{30, "30s"}, {60, "1m"}, {120, "2m"}, {300, "5m"}, {600, "10m"}
|
||||||
|
};
|
||||||
|
const char* previewLabel = "2m";
|
||||||
|
for (const auto& d : delays) {
|
||||||
|
if (d.seconds == sp_mine_idle_delay) { previewLabel = d.label; break; }
|
||||||
}
|
}
|
||||||
float textY = hdrPos.y + (ImGui::GetFrameHeight() - body2->LegacySize) * 0.5f;
|
|
||||||
dl->AddText(body2, body2->LegacySize, ImVec2(hdrPos.x, textY), OnSurfaceMedium(), TR("tools_actions"));
|
ImGui::SetNextItemWidth(schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f));
|
||||||
ImFont* iconFont = Type().iconSmall();
|
if (ImGui::BeginCombo("##IdleDelay", previewLabel, ImGuiComboFlags_NoArrowButton)) {
|
||||||
if (!iconFont) iconFont = body2;
|
for (const auto& d : delays) {
|
||||||
float arrowW = iconFont->CalcTextSizeA(iconFont->LegacySize, FLT_MAX, 0, arrow).x;
|
bool selected = (d.seconds == sp_mine_idle_delay);
|
||||||
dl->AddText(iconFont, iconFont->LegacySize, ImVec2(hdrPos.x + contentW - arrowW, textY), OnSurfaceMedium(), arrow);
|
if (ImGui::Selectable(d.label, selected)) {
|
||||||
|
sp_mine_idle_delay = d.seconds;
|
||||||
|
saveSettingsPageState(app->settings());
|
||||||
|
}
|
||||||
|
if (selected) ImGui::SetItemDefaultFocus();
|
||||||
|
}
|
||||||
|
ImGui::EndCombo();
|
||||||
|
}
|
||||||
|
if (ImGui::IsItemHovered())
|
||||||
|
ImGui::SetTooltip("%s", TR("tt_idle_delay"));
|
||||||
}
|
}
|
||||||
ImGui::PopStyleColor(3);
|
|
||||||
ImGui::PopFont();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sp_tools_expanded) {
|
// Bottom row — Keys & Data left-aligned, Setup Wizard right-aligned
|
||||||
float btnSpacing = Layout::spacingMd();
|
|
||||||
int btnsPerRow = (contentW >= 600.0f) ? 3 : 2;
|
|
||||||
float bw = (contentW - btnSpacing * (btnsPerRow - 1)) / btnsPerRow;
|
|
||||||
float minBtnW = S.drawElement("components.settings-page", "wallet-btn-min-width").sizeOr(100.0f);
|
|
||||||
bw = std::max(minBtnW, bw);
|
|
||||||
|
|
||||||
if (TactileButton(TR("settings_address_book"), ImVec2(bw, 0), S.resolveFont("button")))
|
|
||||||
AddressBookDialog::show();
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_address_book"));
|
|
||||||
ImGui::SameLine(0, btnSpacing);
|
|
||||||
if (TactileButton(TR("settings_validate_address"), ImVec2(bw, 0), S.resolveFont("button")))
|
|
||||||
ValidateAddressDialog::show();
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_validate"));
|
|
||||||
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
|
|
||||||
if (TactileButton(TR("settings_request_payment"), ImVec2(bw, 0), S.resolveFont("button")))
|
|
||||||
RequestPaymentDialog::show();
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_request_payment"));
|
|
||||||
if (btnsPerRow >= 3) { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); } else { ImGui::SameLine(0, btnSpacing); }
|
|
||||||
if (TactileButton(TR("settings_shield_mining"), ImVec2(bw, 0), S.resolveFont("button")))
|
|
||||||
ShieldDialog::show(ShieldDialog::Mode::ShieldCoinbase);
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_shield_mining"));
|
|
||||||
ImGui::SameLine(0, btnSpacing);
|
|
||||||
if (TactileButton(TR("settings_merge_to_address"), ImVec2(bw, 0), S.resolveFont("button")))
|
|
||||||
ShieldDialog::show(ShieldDialog::Mode::MergeToAddress);
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_merge"));
|
|
||||||
if (btnsPerRow >= 3) { ImGui::SameLine(0, btnSpacing); } else { ImGui::Dummy(ImVec2(0, Layout::spacingXs())); }
|
|
||||||
if (TactileButton(TR("settings_clear_ztx"), ImVec2(bw, 0), S.resolveFont("button"))) {
|
|
||||||
sp_confirm_clear_ztx = true;
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered()) ImGui::SetTooltip("%s", TR("tt_clear_ztx"));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, bottomPad));
|
|
||||||
ImGui::Unindent(pad);
|
|
||||||
|
|
||||||
ImVec2 cardMax(cardMin.x + availWidth, ImGui::GetCursorScreenPos().y);
|
|
||||||
dl->ChannelsSetCurrent(0);
|
|
||||||
DrawGlassPanel(dl, cardMin, cardMax, glassSpec);
|
|
||||||
dl->ChannelsMerge();
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMax.y));
|
|
||||||
ImGui::Dummy(ImVec2(availWidth, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::Dummy(ImVec2(0, gap));
|
|
||||||
|
|
||||||
// ====================================================================
|
|
||||||
// BACKUP & DATA — card
|
|
||||||
// ====================================================================
|
|
||||||
{
|
|
||||||
Type().textColored(TypeStyle::Overline, OnSurfaceMedium(), TR("backup_data"));
|
|
||||||
ImGui::Dummy(ImVec2(0, Layout::spacingXs()));
|
|
||||||
|
|
||||||
ImVec2 cardMin = ImGui::GetCursorScreenPos();
|
|
||||||
dl->ChannelsSplit(2);
|
|
||||||
dl->ChannelsSetCurrent(1);
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(cardMin.x, cardMin.y + pad));
|
|
||||||
ImGui::Indent(pad);
|
|
||||||
|
|
||||||
float contentW = availWidth - pad * 2;
|
|
||||||
float btnPad = S.drawElement("components.settings-page", "wallet-btn-padding").sizeOr(24.0f);
|
|
||||||
|
|
||||||
{
|
{
|
||||||
const char* r1[] = {TR("settings_import_key"), TR("settings_export_key"), TR("settings_export_all"), TR("settings_backup"), TR("settings_export_csv")};
|
const char* r1[] = {TR("settings_import_key"), TR("settings_export_key"), TR("settings_export_all"), TR("settings_backup"), TR("settings_export_csv")};
|
||||||
const char* t1[] = {
|
const char* t1[] = {
|
||||||
@@ -1213,12 +1165,13 @@ void RenderSettingsPage(App* app) {
|
|||||||
float sp = Layout::spacingSm();
|
float sp = Layout::spacingSm();
|
||||||
ImFont* btnFont = S.resolveFont("button");
|
ImFont* btnFont = S.resolveFont("button");
|
||||||
|
|
||||||
|
// Measure natural widths
|
||||||
float btnPadX = btnPad * 2;
|
float btnPadX = btnPad * 2;
|
||||||
float naturalW = 0;
|
float naturalW = 0;
|
||||||
for (int i = 0; i < 5; i++)
|
for (int i = 0; i < 5; i++)
|
||||||
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
|
naturalW += ImGui::CalcTextSize(r1[i]).x + btnPadX;
|
||||||
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
|
float wizW = ImGui::CalcTextSize(wizLabel).x + btnPadX;
|
||||||
float totalW = naturalW + wizW + sp * 6;
|
float totalW = naturalW + wizW + sp * 6; // 5 gaps between data btns + 1 gap before wizard
|
||||||
|
|
||||||
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
float scale = (totalW > contentW) ? contentW / totalW : 1.0f;
|
||||||
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
if (scale < 1.0f) ImGui::SetWindowFontScale(scale);
|
||||||
|
|||||||
@@ -528,8 +528,7 @@ inline void SettingsScreen::renderAboutSection() {
|
|||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - nameSize.x) * 0.5f);
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - nameSize.x) * 0.5f);
|
||||||
Typography::instance().text(TypeStyle::H6, appName);
|
Typography::instance().text(TypeStyle::H6, appName);
|
||||||
|
|
||||||
char version[64];
|
const char* version = "Version 1.0.0-imgui";
|
||||||
snprintf(version, sizeof(version), "Version %s-imgui", DRAGONX_VERSION);
|
|
||||||
ImVec2 versionSize = ImGui::CalcTextSize(version);
|
ImVec2 versionSize = ImGui::CalcTextSize(version);
|
||||||
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - versionSize.x) * 0.5f);
|
ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (availWidth - versionSize.x) * 0.5f);
|
||||||
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), version);
|
Typography::instance().textColored(TypeStyle::Caption, OnSurfaceMedium(), version);
|
||||||
|
|||||||
@@ -153,15 +153,10 @@ void RenderMiningTab(App* app)
|
|||||||
s_pool_mode = app->settings()->getPoolMode();
|
s_pool_mode = app->settings()->getPoolMode();
|
||||||
strncpy(s_pool_url, app->settings()->getPoolUrl().c_str(), sizeof(s_pool_url) - 1);
|
strncpy(s_pool_url, app->settings()->getPoolUrl().c_str(), sizeof(s_pool_url) - 1);
|
||||||
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
|
strncpy(s_pool_worker, app->settings()->getPoolWorker().c_str(), sizeof(s_pool_worker) - 1);
|
||||||
s_pool_state_loaded = true;
|
|
||||||
}
|
// If pool worker is empty or placeholder, default to user's first address
|
||||||
|
|
||||||
// Default pool worker to user's first shielded address once addresses are available
|
|
||||||
{
|
|
||||||
static bool s_pool_worker_defaulted = false;
|
|
||||||
std::string workerStr(s_pool_worker);
|
std::string workerStr(s_pool_worker);
|
||||||
if (!s_pool_worker_defaulted && !state.addresses.empty() &&
|
if (workerStr.empty() || workerStr == "x") {
|
||||||
(workerStr.empty() || workerStr == "x")) {
|
|
||||||
std::string defaultAddr;
|
std::string defaultAddr;
|
||||||
for (const auto& addr : state.addresses) {
|
for (const auto& addr : state.addresses) {
|
||||||
if (addr.type == "shielded" && !addr.address.empty()) {
|
if (addr.type == "shielded" && !addr.address.empty()) {
|
||||||
@@ -180,10 +175,9 @@ void RenderMiningTab(App* app)
|
|||||||
if (!defaultAddr.empty()) {
|
if (!defaultAddr.empty()) {
|
||||||
strncpy(s_pool_worker, defaultAddr.c_str(), sizeof(s_pool_worker) - 1);
|
strncpy(s_pool_worker, defaultAddr.c_str(), sizeof(s_pool_worker) - 1);
|
||||||
s_pool_worker[sizeof(s_pool_worker) - 1] = '\0';
|
s_pool_worker[sizeof(s_pool_worker) - 1] = '\0';
|
||||||
s_pool_settings_dirty = true;
|
|
||||||
}
|
}
|
||||||
s_pool_worker_defaulted = true;
|
|
||||||
}
|
}
|
||||||
|
s_pool_state_loaded = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Persist pool settings when dirty and no field is active
|
// Persist pool settings when dirty and no field is active
|
||||||
@@ -872,42 +866,9 @@ void RenderMiningTab(App* app)
|
|||||||
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
|
||||||
ImGui::SetTooltip("%s", idleOn ? TR("mining_idle_on_tooltip") : TR("mining_idle_off_tooltip"));
|
ImGui::SetTooltip("%s", idleOn ? TR("mining_idle_on_tooltip") : TR("mining_idle_off_tooltip"));
|
||||||
}
|
}
|
||||||
|
ImGui::SetCursorScreenPos(savedCur);
|
||||||
|
|
||||||
idleRightEdge = btnX - 4.0f * dp;
|
idleRightEdge = btnX - 4.0f * dp;
|
||||||
|
|
||||||
// Idle delay combo (to the left of the icon when enabled)
|
|
||||||
if (idleOn) {
|
|
||||||
struct DelayOption { int seconds; const char* label; };
|
|
||||||
static const DelayOption delays[] = {
|
|
||||||
{30, "30s"}, {60, "1m"}, {120, "2m"}, {300, "5m"}, {600, "10m"}
|
|
||||||
};
|
|
||||||
int curDelay = app->settings()->getMineIdleDelay();
|
|
||||||
const char* previewLabel = "2m";
|
|
||||||
for (const auto& d : delays) {
|
|
||||||
if (d.seconds == curDelay) { previewLabel = d.label; break; }
|
|
||||||
}
|
|
||||||
float comboW = schema::UI().drawElement("components.settings-page", "idle-combo-width").sizeOr(64.0f);
|
|
||||||
float comboX = idleRightEdge - comboW;
|
|
||||||
float comboY = curY + (headerH - ImGui::GetFrameHeight()) * 0.5f;
|
|
||||||
ImGui::SetCursorScreenPos(ImVec2(comboX, comboY));
|
|
||||||
ImGui::SetNextItemWidth(comboW);
|
|
||||||
if (ImGui::BeginCombo("##IdleDelay", previewLabel, ImGuiComboFlags_NoArrowButton)) {
|
|
||||||
for (const auto& d : delays) {
|
|
||||||
bool selected = (d.seconds == curDelay);
|
|
||||||
if (ImGui::Selectable(d.label, selected)) {
|
|
||||||
app->settings()->setMineIdleDelay(d.seconds);
|
|
||||||
app->settings()->save();
|
|
||||||
}
|
|
||||||
if (selected) ImGui::SetItemDefaultFocus();
|
|
||||||
}
|
|
||||||
ImGui::EndCombo();
|
|
||||||
}
|
|
||||||
if (ImGui::IsItemHovered())
|
|
||||||
ImGui::SetTooltip("%s", TR("tt_idle_delay"));
|
|
||||||
idleRightEdge = comboX - 4.0f * dp;
|
|
||||||
}
|
|
||||||
|
|
||||||
ImGui::SetCursorScreenPos(savedCur);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Active mining indicator (left of idle toggle)
|
// Active mining indicator (left of idle toggle)
|
||||||
|
|||||||
@@ -174,9 +174,6 @@ void I18n::loadBuiltinEnglish()
|
|||||||
|
|
||||||
// Settings sections
|
// Settings sections
|
||||||
strings_["appearance"] = "APPEARANCE";
|
strings_["appearance"] = "APPEARANCE";
|
||||||
strings_["theme_language"] = "THEME & LANGUAGE";
|
|
||||||
strings_["advanced_effects"] = "Advanced Effects...";
|
|
||||||
strings_["tools_actions"] = "Tools & Actions...";
|
|
||||||
strings_["wallet"] = "WALLET";
|
strings_["wallet"] = "WALLET";
|
||||||
strings_["node_security"] = "NODE & SECURITY";
|
strings_["node_security"] = "NODE & SECURITY";
|
||||||
strings_["node"] = "NODE";
|
strings_["node"] = "NODE";
|
||||||
|
|||||||
Reference in New Issue
Block a user