Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 87ec8c2ce9 | |||
| b4a2eb8bc2 | |||
| ae178d4a18 | |||
| e5ad71bc59 | |||
| b0ac94509a | |||
| 2e98622454 | |||
| e48bb6fc03 |
@@ -8,3 +8,5 @@ build/
|
||||
Wow*.exe
|
||||
*.backup.exe
|
||||
Logs/
|
||||
local-assets/
|
||||
!Wow_Original.exe
|
||||
|
||||
+17
-13
@@ -1,20 +1,24 @@
|
||||
[submodule "vendor/warcraftxl"]
|
||||
path = vendor/warcraftxl
|
||||
url = https://github.com/WarcraftXL/wxl-core.git
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-modern-assets"]
|
||||
path = vendor/modules/wxl-modern-assets
|
||||
url = https://github.com/WarcraftXL/wxl-modern-assets.git
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-modern-render"]
|
||||
path = vendor/modules/wxl-modern-render
|
||||
url = https://github.com/WarcraftXL/wxl-modern-render.git
|
||||
branch = main
|
||||
[submodule "vendor/modules/wxl-unit-outline"]
|
||||
path = vendor/modules/wxl-unit-outline
|
||||
url = https://github.com/WarcraftXL/wxl-unit-outline.git
|
||||
branch = main
|
||||
branch = v1.1
|
||||
[submodule "vendor/modules/wxl-modern-adt"]
|
||||
path = vendor/modules/wxl-modern-adt
|
||||
url = https://github.com/WarcraftXL/wxl-modern-adt.git
|
||||
branch = main
|
||||
[submodule "vendor/warcraftxl"]
|
||||
path = vendor/warcraftxl
|
||||
url = https://github.com/WarcraftXL/wxl-core.git
|
||||
branch = v1.1
|
||||
[submodule "vendor/modules/wxl-db2"]
|
||||
path = vendor/modules/wxl-db2
|
||||
url = https://github.com/WarcraftXL/wxl-db2.git
|
||||
[submodule "vendor/modules/wxl-modern-m2"]
|
||||
path = vendor/modules/wxl-modern-m2
|
||||
url = https://github.com/WarcraftXL/wxl-modern-m2.git
|
||||
[submodule "vendor/modules/wxl-modern-wmo"]
|
||||
path = vendor/modules/wxl-modern-wmo
|
||||
url = https://github.com/WarcraftXL/wxl-modern-wmo.git
|
||||
[submodule "vendor/modules/wxl-grasswind"]
|
||||
path = vendor/modules/wxl-grasswind
|
||||
url = https://github.com/WarcraftXL/wxl-grasswind.git
|
||||
|
||||
+65
-53
@@ -1,67 +1,79 @@
|
||||
cmake_minimum_required(VERSION 3.20)
|
||||
project(MoonWellWarcraftXL LANGUAGES CXX)
|
||||
|
||||
# WarcraftXL remains an upstream submodule. MoonWell modules are attached to
|
||||
# its aggregate DLL target here, so updating the framework does not mix our
|
||||
# client-specific offsets and policy into the upstream checkout.
|
||||
# WarcraftXL is an untouched upstream v1.1 submodule. Every project-specific
|
||||
# feature below is built as a separately loaded Extensions/<name>/<name>.dll.
|
||||
add_subdirectory(vendor/warcraftxl)
|
||||
|
||||
set(WXL_CORE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/warcraftxl")
|
||||
set(WXL_EXTERNAL_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/vendor/modules")
|
||||
set(WXL_LOCAL_MODULES_DIR "${CMAKE_CURRENT_SOURCE_DIR}/modules")
|
||||
|
||||
function(wxl_collect_sources output)
|
||||
file(GLOB_RECURSE collected CONFIGURE_DEPENDS ${ARGN})
|
||||
set(${output} "${collected}" PARENT_SCOPE)
|
||||
function(wxl_add_external_extension extension_name extension_dir)
|
||||
if(NOT CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
return()
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE extension_sources CONFIGURE_DEPENDS "${extension_dir}/src/*.cpp")
|
||||
if(NOT extension_sources)
|
||||
message(FATAL_ERROR "WarcraftXL extension '${extension_name}' has no sources in ${extension_dir}/src")
|
||||
endif()
|
||||
|
||||
set(extension_shared_sources "")
|
||||
if(extension_name STREQUAL "wxl-modern-m2")
|
||||
file(GLOB_RECURSE extension_shared_sources CONFIGURE_DEPENDS
|
||||
"${WXL_CORE_DIR}/src/engine/assets/shared/models/m2/*.cpp")
|
||||
elseif(extension_name STREQUAL "wxl-modern-blp")
|
||||
file(GLOB_RECURSE extension_shared_sources CONFIGURE_DEPENDS
|
||||
"${WXL_CORE_DIR}/src/engine/assets/shared/textures/blp/*.cpp")
|
||||
endif()
|
||||
|
||||
file(GLOB wxl_sdk_sources CONFIGURE_DEPENDS "${WXL_CORE_DIR}/src/game/*.cpp")
|
||||
add_library(${extension_name} SHARED
|
||||
${extension_sources}
|
||||
${extension_shared_sources}
|
||||
${wxl_sdk_sources})
|
||||
set_target_properties(${extension_name} PROPERTIES
|
||||
OUTPUT_NAME "${extension_name}"
|
||||
PREFIX "")
|
||||
target_include_directories(${extension_name} PRIVATE
|
||||
"${extension_dir}/src"
|
||||
"${WXL_CORE_DIR}/include"
|
||||
"${WXL_CORE_DIR}/src")
|
||||
target_compile_features(${extension_name} PRIVATE cxx_std_20)
|
||||
target_compile_definitions(${extension_name} PRIVATE
|
||||
WIN32_LEAN_AND_MEAN NOMINMAX _CRT_SECURE_NO_WARNINGS WXL_EXTENSION)
|
||||
|
||||
if(CLIENT_PATH)
|
||||
add_custom_command(TARGET ${extension_name} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory
|
||||
"${CLIENT_PATH}/Extensions/${extension_name}"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different "$<TARGET_FILE:${extension_name}>"
|
||||
"${CLIENT_PATH}/Extensions/${extension_name}/${extension_name}.dll"
|
||||
COMMENT "Deploy ${extension_name}.dll -> Extensions/${extension_name}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
if(TARGET WarcraftXL)
|
||||
wxl_collect_sources(WXL_MODERN_ASSETS_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/src/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/shared/*.cpp")
|
||||
if(WXL_MODERN_ASSETS_EXCLUDE_REGEX)
|
||||
list(FILTER WXL_MODERN_ASSETS_RUNTIME EXCLUDE REGEX
|
||||
"${WXL_MODERN_ASSETS_EXCLUDE_REGEX}")
|
||||
endif()
|
||||
wxl_collect_sources(WXL_MODERN_RENDER_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-render/src/*.cpp")
|
||||
wxl_collect_sources(WXL_UNIT_OUTLINE_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-unit-outline/src/*.cpp")
|
||||
wxl_collect_sources(WXL_MODERN_ADT_RUNTIME
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/src/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/shared/*.cpp")
|
||||
# Official WarcraftXL 1.1 extensions.
|
||||
wxl_add_external_extension(wxl-db2 "${WXL_EXTERNAL_MODULES_DIR}/wxl-db2")
|
||||
wxl_add_external_extension(wxl-modern-adt "${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt")
|
||||
wxl_add_external_extension(wxl-modern-blp "${WXL_LOCAL_MODULES_DIR}/wxl-modern-blp")
|
||||
wxl_add_external_extension(wxl-modern-m2 "${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-m2")
|
||||
wxl_add_external_extension(wxl-modern-wmo "${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-wmo")
|
||||
wxl_add_external_extension(wxl-grasswind "${WXL_EXTERNAL_MODULES_DIR}/wxl-grasswind")
|
||||
wxl_add_external_extension(wxl-unit-outline "${WXL_EXTERNAL_MODULES_DIR}/wxl-unit-outline")
|
||||
|
||||
target_sources(WarcraftXL PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/moonwell/src/MoonWell.cpp"
|
||||
${WXL_MODERN_ASSETS_RUNTIME}
|
||||
${WXL_MODERN_RENDER_RUNTIME}
|
||||
${WXL_UNIT_OUTLINE_RUNTIME}
|
||||
${WXL_MODERN_ADT_RUNTIME})
|
||||
# MoonWell-owned extensions. wxl-fdid-moonwell sorts after wxl-db2 and before
|
||||
# the modern asset extensions, so it can layer custom CSV mappings over wxl.fdid.
|
||||
wxl_add_external_extension(MoonWell "${WXL_LOCAL_MODULES_DIR}/moonwell")
|
||||
wxl_add_external_extension(MoonwellCustomization
|
||||
"${WXL_LOCAL_MODULES_DIR}/moonwell-customization")
|
||||
wxl_add_external_extension(wxl-fdid-moonwell "${WXL_LOCAL_MODULES_DIR}/moonwell-fdid")
|
||||
wxl_add_external_extension(wxl-moonwell-storage-fallback
|
||||
"${WXL_LOCAL_MODULES_DIR}/moonwell-storage-fallback")
|
||||
|
||||
# wxl-modern-render needs its module-root includes and D3D12 import library.
|
||||
include("${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-render/module.cmake")
|
||||
endif()
|
||||
|
||||
if(TARGET WarcraftXLHost)
|
||||
wxl_collect_sources(WXL_MOONWELL_HOST
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/modules/moonwell/host/*.cpp")
|
||||
wxl_collect_sources(WXL_MODERN_ASSETS_HOST
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/host/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-assets/shared/*.cpp")
|
||||
if(WXL_MODERN_ASSETS_EXCLUDE_REGEX)
|
||||
list(FILTER WXL_MODERN_ASSETS_HOST EXCLUDE REGEX
|
||||
"${WXL_MODERN_ASSETS_EXCLUDE_REGEX}")
|
||||
endif()
|
||||
wxl_collect_sources(WXL_MODERN_ADT_HOST
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/host/*.cpp"
|
||||
"${WXL_EXTERNAL_MODULES_DIR}/wxl-modern-adt/shared/*.cpp")
|
||||
|
||||
target_sources(WarcraftXLHost PRIVATE
|
||||
${WXL_MOONWELL_HOST}
|
||||
${WXL_MODERN_ASSETS_HOST}
|
||||
${WXL_MODERN_ADT_HOST})
|
||||
endif()
|
||||
|
||||
# Minimal native-D3D9 proxy retained as a recovery renderer. The regular
|
||||
# package uses WarcraftXL's D3D9On12 proxy required by wxl-modern-render.
|
||||
# Recovery loader retained for existing -NativeRenderer workflows. WarcraftXL
|
||||
# 1.1 already ships a native D3D9 forwarding proxy as its standard renderer.
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
|
||||
add_library(MoonWellLoader SHARED
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/loader/d3d9.cpp"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
Исходники клиентских изменений MoonWell для World of Warcraft 3.3.5a (build 12340).
|
||||
|
||||
Клиент переведён на [WarcraftXL](https://github.com/WarcraftXL): `Wow.exe` больше не содержит
|
||||
Клиент переведён на [WarcraftXL](https://github.com/WarcraftXL/wxl-core) **1.1.220**: `Wow.exe` больше не содержит
|
||||
MoonWell-патчей. Неподписанный оригинальный executable загружается как обычно, локальный
|
||||
`d3d9.dll` proxy подхватывает `WarcraftXL.dll`, а модуль MoonWell применяет проверенные изменения
|
||||
только к памяти запущенного процесса.
|
||||
@@ -14,25 +14,25 @@ MoonWell-патчей. Неподписанный оригинальный execu
|
||||
- одиннадцатое значение `GetCharacterInfo` — `charFlags`, включая флаг предателя `0x40000000`;
|
||||
- загрузка WarcraftXL без import-table patch и без отдельного injector.
|
||||
|
||||
Large Address Aware намеренно не включён: Windows читает этот PE-флаг до загрузки DLL, поэтому
|
||||
сохранить его и одновременно оставить файл байт-в-байт оригинальным невозможно. Это означает
|
||||
стандартный 2-ГБ лимит адресного пространства для 32-битного клиента.
|
||||
При установке сборщик включает Large Address Aware в копии `Wow.exe`; исходный
|
||||
`Wow_Original.exe` остаётся неизменным и используется как проверенный шаблон.
|
||||
|
||||
## Структура
|
||||
|
||||
```text
|
||||
modules/moonwell/ MoonWell runtime-модуль WarcraftXL
|
||||
modules/ отдельные MoonWell extensions для WarcraftXL 1.1
|
||||
vendor/warcraftxl/ закреплённый upstream git submodule
|
||||
vendor/modules/ закреплённые модули WarcraftXL
|
||||
src/Data/ исходники MPQ-патчей
|
||||
tool/ сборщик MPQ
|
||||
build-warcraftxl.ps1 сборка Win32 runtime/proxy и x64 asset host
|
||||
build-warcraftxl.ps1 сборка Win32 runtime, proxy и extensions
|
||||
run.ps1 полная сборка, установка и запуск клиента
|
||||
Wow_Original.exe локальный оригинал build 12340 (не хранится в Git)
|
||||
```
|
||||
|
||||
Подключены модули `wxl-modern-assets`, `wxl-modern-render`, `wxl-unit-outline` и
|
||||
`wxl-modern-adt`. Их точные ревизии вместе с ревизией ядра закреплены git submodule.
|
||||
Подключены совместимые с 1.1 extensions: `wxl-db2`, `wxl-modern-adt`, `wxl-modern-m2`,
|
||||
`wxl-modern-wmo`, `wxl-grasswind` и `wxl-unit-outline`. BLP-адаптер и MoonWell-изменения
|
||||
собираются как отдельные DLL; исходники upstream core не изменяются.
|
||||
Для обновления всех зависимостей WarcraftXL:
|
||||
|
||||
```powershell
|
||||
@@ -44,7 +44,7 @@ git submodule update --remote vendor/warcraftxl vendor/modules/*
|
||||
|
||||
## Требования
|
||||
|
||||
- Visual Studio 2022 с C++ toolchain для Win32 и x64;
|
||||
- Visual Studio 2022 с C++ toolchain для Win32;
|
||||
- CMake 3.20+;
|
||||
- Rust/Cargo для существующего MPQ-сборщика;
|
||||
- локальный оригинальный `Wow_Original.exe` с SHA-256
|
||||
@@ -64,7 +64,7 @@ git submodule update --init --recursive
|
||||
.\deploy.ps1
|
||||
```
|
||||
|
||||
Скрипт инициализирует зависимости, собирает MPQ-патчи, Win32 runtime, D3D9 proxy и x64 Host,
|
||||
Скрипт инициализирует зависимости, собирает MPQ-патчи, Win32 runtime, native D3D9 proxy и extensions,
|
||||
обновляет `dist`, устанавливает комплект в клиент и формирует `manifest.json`. Путь берётся из
|
||||
`WOW_HOME`/`.env`, а при их отсутствии используется `C:\Program Files (x86)\World of Warcraft`.
|
||||
|
||||
@@ -77,8 +77,6 @@ git submodule update --init --recursive
|
||||
# Быстро обновить только WarcraftXL без пересборки MPQ
|
||||
.\deploy.ps1 -SkipDataBuild
|
||||
|
||||
# Аварийный native D3D9 вместо D3D9On12
|
||||
.\deploy.ps1 -NativeRenderer
|
||||
```
|
||||
|
||||
Без установки в клиент:
|
||||
@@ -99,18 +97,8 @@ git submodule update --init --recursive
|
||||
1. проверяет хеш `Wow_Original.exe`;
|
||||
2. сохраняет прежний модифицированный клиент как `Wow.moonwell-patched.backup.exe`;
|
||||
3. восстанавливает оригинальный `Wow.exe`;
|
||||
4. устанавливает `WarcraftXL.dll`, D3D9On12 proxy и `Utils\WarcraftXLHost.exe`;
|
||||
5. монтирует шейдеры modern ADT как loose patch `Data\Patch-WXL.MPQ`;
|
||||
6. при `-PackagePath` кладёт полный runtime-комплект в `dist` для launcher/S3.
|
||||
|
||||
Основной proxy использует D3D9On12, необходимый `wxl-modern-render`. Эффекты постобработки в самом
|
||||
модуле по умолчанию выключены. Для диагностики или несовместимой видеосистемы можно собрать и
|
||||
установить native-переходник без modern-render:
|
||||
|
||||
```powershell
|
||||
.\build-warcraftxl.ps1 -Configuration Release -NativeRenderer `
|
||||
-ClientPath 'C:\Program Files (x86)\World of Warcraft' -Deploy
|
||||
```
|
||||
4. устанавливает `WarcraftXL.dll`, native D3D9 proxy и DLL в `Extensions\`;
|
||||
5. при `-PackagePath` кладёт полный runtime-комплект в `dist` для launcher/S3.
|
||||
|
||||
Резервная DLL всегда также сохраняется как `Utils\d3d9-native.dll`.
|
||||
|
||||
@@ -121,8 +109,7 @@ $env:WOW_HOME = 'C:\Program Files (x86)\World of Warcraft'
|
||||
.\run.ps1
|
||||
```
|
||||
|
||||
Диагностика запуска находится в `Logs\wxl-core.log`, `Logs\d3d9proxy.log` и
|
||||
`Utils\WarcraftXLHost.log` внутри клиента.
|
||||
Диагностика запуска находится в `Logs\wxl-core.log` и `Logs\d3d9proxy.log` внутри клиента.
|
||||
|
||||
## MPQ-пакеты
|
||||
|
||||
@@ -138,6 +125,6 @@ WarcraftXL не требуется и в объектное хранилище
|
||||
|
||||
## Лицензирование
|
||||
|
||||
WarcraftXL распространяется по GPL-3.0 и подключён как отдельный upstream submodule. MoonWell-модуль,
|
||||
скомпонованный в `WarcraftXL.dll`, должен распространяться с соблюдением GPL-3.0 и доступным
|
||||
соответствующим исходным кодом. Репозиторий не должен публиковать Blizzard assets или `Wow.exe`.
|
||||
WarcraftXL распространяется по GPL-3.0 и подключён как отдельный неизменённый upstream submodule.
|
||||
MoonWell extensions распространяются с доступным соответствующим исходным кодом. Репозиторий не
|
||||
должен публиковать Blizzard assets или `Wow.exe`.
|
||||
|
||||
Binary file not shown.
+36
-29
@@ -88,7 +88,6 @@ if (-not (Test-Path -LiteralPath $cmake)) {
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Failed to initialize WarcraftXL submodules.' }
|
||||
|
||||
$win32BuildDir = Join-Path $repoRoot 'build\warcraftxl-win32'
|
||||
$hostBuildDir = Join-Path $repoRoot 'build\warcraftxl-host-x64'
|
||||
$stockExe = Join-Path $repoRoot 'Wow_Original.exe'
|
||||
|
||||
if ($Deploy -or -not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
@@ -104,22 +103,30 @@ if ($Deploy -or -not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
|
||||
& $cmake -S $repoRoot -B $win32BuildDir -A Win32 '-DCLIENT_PATH='
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Win32 configure failed.' }
|
||||
& $cmake --build $win32BuildDir --config $Configuration --target WarcraftXL d3d9 MoonWellLoader
|
||||
& $cmake --build $win32BuildDir --config $Configuration --parallel 4
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Win32 build failed.' }
|
||||
|
||||
& $cmake -S $repoRoot -B $hostBuildDir -A x64 '-DWXL_BUILD_HOST=ON' '-DCLIENT_PATH='
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Host configure failed.' }
|
||||
& $cmake --build $hostBuildDir --config $Configuration --target WarcraftXLHost
|
||||
if ($LASTEXITCODE -ne 0) { throw 'WarcraftXL Host build failed.' }
|
||||
|
||||
$win32ArtifactDir = Join-Path $win32BuildDir "vendor\warcraftxl\$Configuration"
|
||||
$hostArtifactDir = Join-Path $hostBuildDir "vendor\warcraftxl\$Configuration"
|
||||
$warcraftXL = Join-Path $win32ArtifactDir 'WarcraftXL.dll'
|
||||
$modernProxy = Join-Path $win32ArtifactDir 'd3d9.dll'
|
||||
$nativeProxy = Join-Path $win32BuildDir "artifacts\$Configuration\d3d9-native.dll"
|
||||
$hostExe = Join-Path $hostArtifactDir 'WarcraftXLHost.exe'
|
||||
$selectedProxy = if ($NativeRenderer) { $nativeProxy } else { $modernProxy }
|
||||
$adtResources = Join-Path $repoRoot 'vendor\modules\wxl-modern-adt\_resources'
|
||||
$selectedProxy = Join-Path $win32ArtifactDir 'd3d9.dll'
|
||||
$recoveryProxy = Join-Path $win32BuildDir "artifacts\$Configuration\d3d9-native.dll"
|
||||
$extensionNames = @(
|
||||
'MoonWell',
|
||||
'MoonwellCustomization',
|
||||
'wxl-db2',
|
||||
'wxl-fdid-moonwell',
|
||||
'wxl-grasswind',
|
||||
'wxl-modern-adt',
|
||||
'wxl-modern-blp',
|
||||
'wxl-modern-m2',
|
||||
'wxl-modern-wmo',
|
||||
'wxl-moonwell-storage-fallback',
|
||||
'wxl-unit-outline'
|
||||
)
|
||||
|
||||
if ($NativeRenderer) {
|
||||
Write-Warning '-NativeRenderer is no longer needed: WarcraftXL 1.1 uses native D3D9 by default.'
|
||||
}
|
||||
|
||||
function Install-WarcraftXLArtifacts {
|
||||
param(
|
||||
@@ -128,14 +135,18 @@ function Install-WarcraftXLArtifacts {
|
||||
)
|
||||
|
||||
$utils = Join-Path $Destination 'Utils'
|
||||
$loosePatch = Join-Path $Destination 'Data\Patch-WXL.MPQ'
|
||||
|
||||
New-Item `
|
||||
-ItemType Directory `
|
||||
-Path $Destination, $utils, $loosePatch `
|
||||
-Path $Destination, $utils `
|
||||
-Force |
|
||||
Out-Null
|
||||
|
||||
$legacyHost = Join-Path $utils 'WarcraftXLHost.exe'
|
||||
if (Test-Path -LiteralPath $legacyHost -PathType Leaf) {
|
||||
Remove-Item -LiteralPath $legacyHost -Force
|
||||
Write-Host "Removed obsolete WarcraftXL 1.0 host: $legacyHost"
|
||||
}
|
||||
|
||||
$destinationExe = Join-Path $Destination 'Wow.exe'
|
||||
|
||||
Copy-Item `
|
||||
@@ -156,20 +167,17 @@ function Install-WarcraftXLArtifacts {
|
||||
-Force
|
||||
|
||||
Copy-Item `
|
||||
-LiteralPath $nativeProxy `
|
||||
-LiteralPath $recoveryProxy `
|
||||
-Destination (Join-Path $utils 'd3d9-native.dll') `
|
||||
-Force
|
||||
|
||||
Copy-Item `
|
||||
-LiteralPath $hostExe `
|
||||
-Destination (Join-Path $utils 'WarcraftXLHost.exe') `
|
||||
-Force
|
||||
|
||||
Get-ChildItem -LiteralPath $adtResources | ForEach-Object {
|
||||
foreach ($extensionName in $extensionNames) {
|
||||
$extensionSource = Join-Path $win32BuildDir "$Configuration\$extensionName.dll"
|
||||
$extensionDestination = Join-Path $Destination "Extensions\$extensionName"
|
||||
New-Item -ItemType Directory -Path $extensionDestination -Force | Out-Null
|
||||
Copy-Item `
|
||||
-LiteralPath $_.FullName `
|
||||
-Destination $loosePatch `
|
||||
-Recurse `
|
||||
-LiteralPath $extensionSource `
|
||||
-Destination (Join-Path $extensionDestination "$extensionName.dll") `
|
||||
-Force
|
||||
}
|
||||
}
|
||||
@@ -196,7 +204,7 @@ if ($Deploy) {
|
||||
}
|
||||
|
||||
Install-WarcraftXLArtifacts -Destination $ClientPath
|
||||
Write-Host "Installed WarcraftXL runtime, host and modules in $ClientPath"
|
||||
Write-Host "Installed WarcraftXL 1.1 runtime and extensions in $ClientPath"
|
||||
}
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
@@ -205,5 +213,4 @@ if (-not [string]::IsNullOrWhiteSpace($PackagePath)) {
|
||||
Write-Host "Packaged stock Wow.exe and WarcraftXL artifacts in $PackagePath"
|
||||
}
|
||||
|
||||
$renderer = if ($NativeRenderer) { 'native D3D9 recovery proxy' } else { 'WarcraftXL D3D9On12 proxy' }
|
||||
Write-Host "WarcraftXL build complete ($renderer): $win32ArtifactDir"
|
||||
Write-Host "WarcraftXL 1.1 build complete (native D3D9 proxy): $win32ArtifactDir"
|
||||
|
||||
@@ -10,12 +10,5 @@
|
||||
"pet_template_display_id": 5448
|
||||
},
|
||||
"mounts": [],
|
||||
"pets": [
|
||||
{
|
||||
"folder": "src/Data/patch-Z/Creature/Catslime",
|
||||
"name": "Cat Slime",
|
||||
"name_ru": "Котослизень",
|
||||
"display_scale": 1.0
|
||||
}
|
||||
]
|
||||
"pets": []
|
||||
}
|
||||
|
||||
+2
-5
@@ -58,9 +58,6 @@ if ($runningClients.Count) {
|
||||
Write-Host 'Stopping the running WoW client...'
|
||||
$runningClients | Stop-Process -Force
|
||||
Start-Sleep -Seconds 2
|
||||
Get-Process WarcraftXLHost -ErrorAction SilentlyContinue | Where-Object {
|
||||
$_.Path -and $_.Path.StartsWith($ClientPath, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
} | Stop-Process -Force
|
||||
}
|
||||
|
||||
$env:WOW_HOME = $ClientPath
|
||||
@@ -120,12 +117,12 @@ if (-not $SkipManifest) {
|
||||
}
|
||||
|
||||
$wowHash = (Get-FileHash -LiteralPath (Join-Path $ClientPath 'Wow.exe') -Algorithm SHA256).Hash
|
||||
$renderer = if ($NativeRenderer) { 'native D3D9 recovery proxy' } else { 'WarcraftXL D3D9On12' }
|
||||
$renderer = 'WarcraftXL 1.1 native D3D9 proxy'
|
||||
Write-Host ''
|
||||
Write-Host 'Deployment complete.'
|
||||
Write-Host " Renderer: $renderer"
|
||||
Write-Host " Wow.exe SHA-256: $wowHash"
|
||||
Write-Host " Host: $(Join-Path $ClientPath 'Utils\WarcraftXLHost.exe')"
|
||||
Write-Host " Extensions: $(Join-Path $ClientPath 'Extensions')"
|
||||
|
||||
if ($Launch) {
|
||||
Write-Host 'Launching Wow.exe...'
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
# Asset manifest
|
||||
|
||||
## Blood Elf Female
|
||||
|
||||
- Retail build: `WOW-68887patch12.0.7_Retail`
|
||||
- Race ID: `10`
|
||||
- ChrModelID: `20`
|
||||
- Character model FileDataID: `1100258`
|
||||
- Option: Demon Hunter Horns
|
||||
- Retail OptionID: `126`
|
||||
- Runtime element type: Geoset in an attached collection M2
|
||||
- Collection model FileDataID: `7760202`
|
||||
- Collection compatibility: `RETAIL_WITH_WXL`
|
||||
- Runtime virtual path: `MoonWell\Customization\BloodElfFemale\7760202_be_f.m2`
|
||||
- Prototype attachment point: `11` (stock head point). Retail collection point `19` is absent from
|
||||
the 3.3.5 Blood Elf model and cannot safely be force-attached.
|
||||
|
||||
| Prototype choice | Retail ChoiceID | Retail ElementID | GeosetID | Compatibility |
|
||||
|---|---:|---:|---:|---|
|
||||
| None | 1850 | — | — | RETAIL_WITH_WXL |
|
||||
| Betrayer | 1851 | 2051 | 2401 | RETAIL_WITH_WXL |
|
||||
| Beast | 1852 | 2052 | 2402 | RETAIL_WITH_WXL |
|
||||
| Dreadlord | 1853 | 2053 | 2403 | RETAIL_WITH_WXL |
|
||||
|
||||
The source catalog reports two collection textures:
|
||||
|
||||
| FileDataID | Internal path | Type | Purpose |
|
||||
|---:|---|---|---|
|
||||
| 1277002 | `item\objectcomponents\collections\bloodelf_female_dh_belt.blp` | BLP | Collection material dependency |
|
||||
| 2764488 | `item\objectcomponents\collections\bloodelffemale_dh_horns.blp` | BLP | Collection material dependency |
|
||||
|
||||
Skin files exported with the model are FileDataID `7766030` (base), `7766031`, `7766032`, and
|
||||
`7766033` (LODs). The MVP requests the base sibling `7760202_be_f00.skin`.
|
||||
|
||||
No fallback asset is committed or silently substituted. Classification is `RETAIL_WITH_WXL`, not
|
||||
`RETAIL_NATIVE`: stock 3.3.5 cannot parse this modern collection without the WarcraftXL modern-M2
|
||||
extension. If it fails in a real client session, the documented next fallback is a small WotLK M2
|
||||
mounted at the same virtual path; such a placeholder would be classified `WOTLK_FALLBACK` and must be
|
||||
recorded here before use.
|
||||
@@ -0,0 +1,12 @@
|
||||
# Glue integration follow-up
|
||||
|
||||
The runtime prototype intentionally does not alter character creation. WarcraftXL v1.1 exposes typed
|
||||
Glue/Lua bindings in `game/Glue.hpp` and `game/Script.hpp`, while the existing MoonWell extension shows
|
||||
the validated `Lua.RegisterFunction` / `Lua.ValidateFunctionPointer` bridge used by build 12340.
|
||||
|
||||
A safe follow-up is to register
|
||||
`ModelFrame:SetMoonwellCustomization(optionID, choiceID)` through that existing bridge, resolve the
|
||||
Glue model's `CharModelObject`, and pass it to the same `CustomizationState` and renderer used here.
|
||||
Cleanup must follow Glue model destruction rather than `OnWorldLeave`. This should be implemented only
|
||||
after the runtime collection attachment has passed interactive visual/lifetime testing; no extra client
|
||||
offset or duplicate renderer is justified for the current vertical slice.
|
||||
@@ -0,0 +1,92 @@
|
||||
# MoonwellCustomization vertical slice
|
||||
|
||||
## Scope
|
||||
|
||||
`MoonwellCustomization.dll` is a runtime-only WarcraftXL 1.1 extension for WoW 3.3.5a build
|
||||
12340. It changes only an active Blood Elf Female player. State is kept in memory and is deliberately
|
||||
separate from rendering; there are no server packets, database changes, AzerothCore changes, or new
|
||||
Wow.exe offsets.
|
||||
|
||||
The data flow is:
|
||||
|
||||
`CustomizationOption (126) -> CustomizationChoice -> CustomizationElement (Geoset) -> collection M2`
|
||||
|
||||
The explicit prototype state is None plus three retail choices. The renderer owns one independent
|
||||
collection render context at a time. Reapplying the same choice is a no-op; replacing a choice releases
|
||||
the old context first. The overlay copies the active character's placement and bone palette without
|
||||
joining the stock attachment readiness chain. `OnWorldLeave` always releases it and drops all borrowed
|
||||
player pointers.
|
||||
|
||||
## Verified WarcraftXL API
|
||||
|
||||
The repository pins `vendor/warcraftxl` to `v1.1.220` (`4895cef`) on the `v1.1` branch. Source code,
|
||||
not README assumptions, establishes the following behavior:
|
||||
|
||||
- `WXL_Query`/`WXL_Load` are the extension entry points; the runtime loads DLLs found below
|
||||
`Extensions/<name>/`.
|
||||
- `WXL_Api::UiAddPanel` registers an immediate-mode overlay panel. WarcraftXL v1.1 hard-codes `F9`
|
||||
as its overlay toggle; press it and select **MoonWell Customization**.
|
||||
- `world::ActivePlayerGuid` and `world::ResolveObject(..., kTypeMaskPlayer)` find the active player;
|
||||
`unit::Model` returns its body-model/scene-node binding.
|
||||
- The upstream typed `SceneNode` and `CharModelObject` views provide owner, race, gender, and root
|
||||
scene node. The extension adds no offsets.
|
||||
- `m2::GetRenderCtx` always creates a new scene model; `AttachToScene(..., slot 19, true)` attaches a
|
||||
collection M2. Core explicitly documents 19 as the collection-M2 attachment point.
|
||||
- `OnM2SkinFinalize` is the safe pre-finalize window to filter collection geosets. Runtime changes
|
||||
call `FinalizeSkin` once; a recursion guard prevents its event from re-entering the filter.
|
||||
- `OnBuildBonePalette` is post-engine and therefore the safe point to copy the parent character bone
|
||||
palette into the collection model immediately before upload.
|
||||
- `OnItemSlotChange` and `OnItemSlotClear` schedule a replacement on the next logic tick, after native
|
||||
equipment work. Native equipment is never overridden.
|
||||
- `OnWorldLeave` is the cleanup event. Events and UI run on WarcraftXL's main/render paths; no worker
|
||||
thread calls client model bindings.
|
||||
|
||||
## Build
|
||||
|
||||
From a Visual Studio 2022 developer environment or PowerShell:
|
||||
|
||||
```powershell
|
||||
.\build-warcraftxl.ps1 -Configuration Release
|
||||
```
|
||||
|
||||
The required artifact is:
|
||||
|
||||
`build\warcraftxl-win32\Release\MoonwellCustomization.dll`
|
||||
|
||||
The target is Win32. The top-level CMake build compiles the extension together with the existing
|
||||
WarcraftXL SDK sources; there is no second CMake project.
|
||||
|
||||
## Install and run
|
||||
|
||||
1. Install the asset files described in `docs/wow-export-assets.md` into a client MPQ.
|
||||
2. Copy the DLL to
|
||||
`Extensions\MoonwellCustomization\MoonwellCustomization.dll` under the client directory. The build
|
||||
script does this automatically when invoked with `-Deploy -ClientPath <client>`.
|
||||
3. Start the stock build-12340 client through the installed WarcraftXL `d3d9.dll` proxy.
|
||||
4. Press `F9` to open the WarcraftXL overlay and select **MoonWell Customization**.
|
||||
5. On Blood Elf Female, press None / Betrayer / Beast / Dreadlord. None must remove the collection;
|
||||
repeated selections must not stack it. Equip and remove a helmet, then log out and back in.
|
||||
6. On every other race or gender, the panel reports that the prototype is unsupported and exposes no
|
||||
choice controls.
|
||||
|
||||
Expected log lines are tagged `[MoonwellCustomization]`: `loaded`, `player detected`, race/gender,
|
||||
apply, model load, attach, detach, unsupported player, and actionable failure text.
|
||||
|
||||
## Known limits
|
||||
|
||||
- The repository intentionally excludes Blizzard assets. Without the collection M2 and sibling skin,
|
||||
the client loader can fall back to its placeholder model; that is an asset-install failure, not a
|
||||
successful visual test.
|
||||
- Retail collection model 7760202 is a modern MD21 asset. It requires the existing `wxl-modern-m2`,
|
||||
`wxl-modern-blp`, `wxl-db2`, and FDID extensions in this repository.
|
||||
- Bone copying assumes the retail Blood Elf Female collection uses the same ordered skeleton as its
|
||||
parent model. The code clamps to the smaller palette, but a retail build changing bone order would
|
||||
need an explicit bone map.
|
||||
- `FinalizeSkin` in WXL 1.1 has small internal re-finalize leaks. The implementation therefore performs
|
||||
it only on an actual choice change/equipment refresh, never per frame.
|
||||
- This source-level build proves compilation and packaging. A successful visual result and crash-free
|
||||
logout still require an interactive client session with the external assets installed.
|
||||
|
||||
The next minimal step toward a broader retail-like system is to move the static option table to an
|
||||
external, versioned catalog and add renderers for Texture and independent M2Attachment elements while
|
||||
keeping the same `optionID -> choiceID` state boundary.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Exporting the Blood Elf Female prototype assets
|
||||
|
||||
The local wow.export checkout has no documented headless CLI, so this document does not invent one.
|
||||
Its MoonWell exporter generated the inspected catalog at:
|
||||
|
||||
`moonwell-customization/wotlk-races/catalog.json`
|
||||
|
||||
The exact source is retail `WOW-68887patch12.0.7_Retail`. Use wow.export's UI to select that product,
|
||||
then export the WotLK races customization catalog/asset set. From the generated output, take:
|
||||
|
||||
| FileDataID | Exported file / internal path | Type | Why |
|
||||
|---:|---|---|---|
|
||||
| 7760202 | `assets/collections/7760202/7760202_be_f.m2` | M2 | Blood Elf Female collection containing Demon Hunter horn geosets |
|
||||
| 7766030 | `assets/collections/7760202/7760202_be_f00.skin` | SKIN | Base geometry/index profile required by the M2 |
|
||||
| 1277002 | `item/objectcomponents/collections/bloodelf_female_dh_belt.blp` | BLP | Declared collection texture dependency |
|
||||
| 2764488 | `item/objectcomponents/collections/bloodelffemale_dh_horns.blp` | BLP | Declared collection texture dependency |
|
||||
|
||||
Do not add these files to git. Stage them under the ignored `local-assets/` directory, then put them in
|
||||
a local MPQ with these virtual names:
|
||||
|
||||
```text
|
||||
MoonWell\Customization\BloodElfFemale\7760202_be_f.m2
|
||||
MoonWell\Customization\BloodElfFemale\7760202_be_f00.skin
|
||||
item\objectcomponents\collections\bloodelf_female_dh_belt.blp
|
||||
item\objectcomponents\collections\bloodelffemale_dh_horns.blp
|
||||
```
|
||||
|
||||
Install that MPQ as a high-priority `Data\patch-*.MPQ` beside the existing MoonWell patches. Keep the
|
||||
three LOD skins (`7766031`–`7766033`) locally for later testing, but the current MVP does not request
|
||||
them explicitly.
|
||||
|
||||
To reproduce the DB result without changing wow.export, run the repository's read-only catalog helper:
|
||||
|
||||
```powershell
|
||||
node tools/find_customization_assets.js `
|
||||
C:\Users\sindo\wow.export\moonwell-customization\wotlk-races\catalog.json `
|
||||
"Blood Elf" female Horns
|
||||
```
|
||||
|
||||
It must report OptionID `126`, then ChoiceIDs `1851`, `1852`, `1853`. Their skinned-model elements
|
||||
map geoset type `24` and IDs `1`, `2`, `3` to skin sections `2401`, `2402`, `2403`. The helper reads
|
||||
already-exported JSON only; it does not access CASC and is not a runtime dependency.
|
||||
+2
-3
@@ -50,9 +50,8 @@ extern "C" HRESULT WINAPI Direct3DCreate9Ex(UINT sdkVersion, IDirect3D9Ex** outp
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
|
||||
// Compatibility exports referenced by the current WarcraftXL core. MoonWell
|
||||
// does not enable its D3D9On12 backend, so these intentionally report no D3D12
|
||||
// device and a neutral supersampling factor.
|
||||
// Legacy compatibility exports retained for the recovery proxy. WarcraftXL 1.1
|
||||
// uses native D3D9 and ignores the former D3D9On12 bridge.
|
||||
extern "C" void* WxlD3D12Device() { return nullptr; }
|
||||
extern "C" void* WxlD3D12Queue() { return nullptr; }
|
||||
extern "C" void WxlD3D12DrainDebug() {}
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
namespace moonwell::customization
|
||||
{
|
||||
enum class ElementType : uint8_t
|
||||
{
|
||||
None,
|
||||
M2Attachment,
|
||||
Geoset,
|
||||
Texture,
|
||||
};
|
||||
|
||||
struct CustomizationElement
|
||||
{
|
||||
ElementType type;
|
||||
uint32_t geosetId;
|
||||
const char* modelPath;
|
||||
};
|
||||
|
||||
struct CustomizationChoice
|
||||
{
|
||||
uint32_t id;
|
||||
const char* name;
|
||||
std::array<CustomizationElement, 1> elements;
|
||||
};
|
||||
|
||||
struct CustomizationOption
|
||||
{
|
||||
uint32_t id;
|
||||
const char* name;
|
||||
std::array<CustomizationChoice, 4> choices;
|
||||
};
|
||||
|
||||
inline constexpr uint32_t kBloodElfRace = 10;
|
||||
inline constexpr uint32_t kFemaleGender = 1;
|
||||
inline constexpr uint32_t kCollectionFileDataId = 7760202;
|
||||
// Stock 3.3.5 Blood Elf models have no retail collection point 19.
|
||||
// Use the native head attachment point; forcing absent point 19 makes
|
||||
// the parent's drawable gate fail and hides the whole character.
|
||||
inline constexpr uint32_t kCollectionAttachmentSlot = 11;
|
||||
inline constexpr char kCollectionPath[] =
|
||||
"MoonWell\\Customization\\BloodElfFemale\\7760202_be_f.m2";
|
||||
|
||||
// WOTLK_FALLBACK: native 3.3.5 head components for Blood Elf Female.
|
||||
// The retail collection needs a real bone map to the differently ordered
|
||||
// 3.3.5 skeleton, so the working vertical slice uses native components.
|
||||
inline constexpr CustomizationOption kHornsOption = {
|
||||
126,
|
||||
"Head Decorations (WotLK fallback)",
|
||||
{{
|
||||
{1850, "None", {{{ElementType::None, 0, nullptr}}}},
|
||||
{1851, "Blindfold", {{{ElementType::M2Attachment, 0,
|
||||
"ITEM\\OBJECTCOMPONENTS\\HEAD\\Helm_Blindfold_A_01_BeF.m2"}}}},
|
||||
{1852, "Circlet I", {{{ElementType::M2Attachment, 0,
|
||||
"ITEM\\OBJECTCOMPONENTS\\HEAD\\Helm_Circlet_A_01_BeF.m2"}}}},
|
||||
{1853, "Circlet II", {{{ElementType::M2Attachment, 0,
|
||||
"ITEM\\OBJECTCOMPONENTS\\HEAD\\Helm_Circlet_B_01_BeF.m2"}}}},
|
||||
}},
|
||||
};
|
||||
|
||||
inline const CustomizationChoice* FindChoice(uint32_t id)
|
||||
{
|
||||
for (const auto& choice : kHornsOption.choices)
|
||||
if (choice.id == id) return &choice;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,436 @@
|
||||
#include "CustomizationRenderer.hpp"
|
||||
|
||||
#include <wxl/PluginApi.h>
|
||||
|
||||
#include "engine/events/Event.hpp"
|
||||
#include "game/M2.hpp"
|
||||
#include "game/World.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
#include <windows.h>
|
||||
|
||||
namespace moonwell::customization
|
||||
{
|
||||
namespace m2 = wxl::game::m2;
|
||||
namespace world = wxl::game::world;
|
||||
namespace sm2 = wxl::structure::m2;
|
||||
|
||||
namespace
|
||||
{
|
||||
bool IsReadableRange(const void* address, size_t size)
|
||||
{
|
||||
if (!address || !size) return false;
|
||||
|
||||
const uintptr_t begin = reinterpret_cast<uintptr_t>(address);
|
||||
if (size > std::numeric_limits<uintptr_t>::max() - begin) return false;
|
||||
const uintptr_t end = begin + size;
|
||||
uintptr_t cursor = begin;
|
||||
|
||||
while (cursor < end)
|
||||
{
|
||||
MEMORY_BASIC_INFORMATION memory{};
|
||||
if (!VirtualQuery(reinterpret_cast<const void*>(cursor), &memory, sizeof memory))
|
||||
return false;
|
||||
if (memory.State != MEM_COMMIT ||
|
||||
(memory.Protect & (PAGE_NOACCESS | PAGE_GUARD)) != 0)
|
||||
return false;
|
||||
|
||||
const uintptr_t regionEnd = reinterpret_cast<uintptr_t>(memory.BaseAddress) +
|
||||
memory.RegionSize;
|
||||
if (regionEnd <= cursor) return false;
|
||||
cursor = std::min(regionEnd, end);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomizationRenderer::SetError(const char* message)
|
||||
{
|
||||
lastError_ = message;
|
||||
api_->Log(WXL_LOG_ERROR, "MoonwellCustomization", "failed: %s", message);
|
||||
}
|
||||
|
||||
bool CustomizationRenderer::DetectPlayer()
|
||||
{
|
||||
playerDetected_ = false;
|
||||
supported_ = false;
|
||||
charModelObject_ = nullptr;
|
||||
parentRenderContext_ = nullptr;
|
||||
race_ = gender_ = 0;
|
||||
|
||||
const auto guid = world::ActivePlayerGuid();
|
||||
void* player = guid ? world::ResolveObject(guid, world::kTypeMaskPlayer) : nullptr;
|
||||
if (!player)
|
||||
{
|
||||
lastError_ = "Waiting for active player object";
|
||||
return false;
|
||||
}
|
||||
|
||||
// The native equipment events are the public WXL seam that supplies
|
||||
// the actual CharModelObject. Unit::Model is not useful for identity:
|
||||
// it exposes a different model layer. Match the root instance's world
|
||||
// transform to the active player's authoritative world position so an
|
||||
// event for another visible character cannot be adopted accidentally.
|
||||
auto* cmo = static_cast<m2::off::CharModelObject*>(observedCharModelObject_);
|
||||
if (!cmo || !cmo->sceneNode)
|
||||
{
|
||||
lastError_ = "Waiting for active player CharModelObject event";
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* root = static_cast<m2::off::M2Instance*>(cmo->sceneNode);
|
||||
float playerPosition[3]{};
|
||||
world::UnitPosition(player, playerPosition);
|
||||
const float dxRow = root->placement[12] - playerPosition[0];
|
||||
const float dyRow = root->placement[13] - playerPosition[1];
|
||||
const float dzRow = root->placement[14] - playerPosition[2];
|
||||
const float rowDistanceSq = dxRow * dxRow + dyRow * dyRow + dzRow * dzRow;
|
||||
const float dxColumn = root->placement[3] - playerPosition[0];
|
||||
const float dyColumn = root->placement[7] - playerPosition[1];
|
||||
const float dzColumn = root->placement[11] - playerPosition[2];
|
||||
const float columnDistanceSq = dxColumn * dxColumn + dyColumn * dyColumn + dzColumn * dzColumn;
|
||||
if (std::min(rowDistanceSq, columnDistanceSq) > 9.0f)
|
||||
{
|
||||
lastError_ = "Waiting for matching active player CharModelObject";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (cmo->raceId == 0 ||
|
||||
cmo->raceId > 11 || cmo->genderId > 1)
|
||||
{
|
||||
lastError_ = "Active player CharModelObject is not ready";
|
||||
return false;
|
||||
}
|
||||
|
||||
charModelObject_ = cmo;
|
||||
parentRenderContext_ = cmo->sceneNode;
|
||||
race_ = cmo->raceId;
|
||||
gender_ = cmo->genderId;
|
||||
playerDetected_ = true;
|
||||
supported_ = race_ == kBloodElfRace && gender_ == kFemaleGender;
|
||||
lastError_ = supported_ ? "Ready" : "Prototype supports Blood Elf Female only.";
|
||||
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization", "player detected");
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization", "race=%u gender=%u", race_, gender_);
|
||||
if (!supported_)
|
||||
api_->Log(WXL_LOG_WARN, "MoonwellCustomization", "unsupported player race");
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomizationRenderer::OnWorldEnter()
|
||||
{
|
||||
inWorld_ = true;
|
||||
detectDelay_ = 0.0f;
|
||||
DetectPlayer();
|
||||
}
|
||||
|
||||
void CustomizationRenderer::OnWorldLeave()
|
||||
{
|
||||
inWorld_ = false;
|
||||
Detach();
|
||||
observedCharModelObject_ = nullptr;
|
||||
charModelObject_ = nullptr;
|
||||
parentRenderContext_ = nullptr;
|
||||
playerDetected_ = supported_ = false;
|
||||
race_ = gender_ = 0;
|
||||
detectDelay_ = 0.0f;
|
||||
refreshRequested_ = false;
|
||||
geosetApplied_ = false;
|
||||
attached_ = false;
|
||||
finalizeInProgress_ = false;
|
||||
lastError_ = "Outside world";
|
||||
}
|
||||
|
||||
void CustomizationRenderer::OnUpdate(float dt)
|
||||
{
|
||||
// OnWorldEnter is an edge notification and can be missed when the
|
||||
// extension is loaded after a world transition has already begun.
|
||||
// Reconcile against the authoritative active-player GUID so the
|
||||
// renderer also recovers after late loads and unusual glue flows.
|
||||
const auto activePlayerGuid = world::ActivePlayerGuid();
|
||||
if (!activePlayerGuid)
|
||||
{
|
||||
if (inWorld_) OnWorldLeave();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!inWorld_)
|
||||
{
|
||||
inWorld_ = true;
|
||||
detectDelay_ = 0.0f;
|
||||
lastError_ = "Waiting for active player model";
|
||||
}
|
||||
|
||||
if (!playerDetected_)
|
||||
{
|
||||
detectDelay_ -= dt;
|
||||
if (detectDelay_ <= 0.0f)
|
||||
{
|
||||
detectDelay_ = 0.25f;
|
||||
DetectPlayer();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshRequested_)
|
||||
{
|
||||
refreshRequested_ = false;
|
||||
const uint32_t choiceId = CurrentChoice();
|
||||
if (choiceId != 0 && choiceId != kHornsOption.choices[0].id)
|
||||
{
|
||||
Detach();
|
||||
if (const auto* choice = FindChoice(choiceId)) Attach(*choice);
|
||||
}
|
||||
}
|
||||
|
||||
if (attachmentRenderContext_ && attachmentModel_ && !geosetApplied_)
|
||||
{
|
||||
const auto* choice = FindChoice(CurrentChoice());
|
||||
if (choice && choice->elements[0].type == ElementType::Geoset)
|
||||
FilterGeoset(attachmentModel_, choice->elements[0].geosetId, true);
|
||||
}
|
||||
|
||||
// CreateSceneModel loads asynchronously. Attaching it before the stock
|
||||
// readiness gate succeeds makes the parent character non-drawable too.
|
||||
// Keep it unattached while loading, then join the stock head point only after the
|
||||
// child itself reports drawable.
|
||||
if (attachmentRenderContext_ && parentRenderContext_)
|
||||
{
|
||||
auto* child = static_cast<m2::off::M2Instance*>(attachmentRenderContext_);
|
||||
auto* parent = static_cast<m2::off::M2Instance*>(parentRenderContext_);
|
||||
const auto* choice = FindChoice(CurrentChoice());
|
||||
const bool collectionGeoset = choice &&
|
||||
choice->elements[0].type == ElementType::Geoset;
|
||||
if (collectionGeoset && !attached_)
|
||||
{
|
||||
std::memcpy(child->placement, parent->placement, sizeof child->placement);
|
||||
child->viewDistSq = parent->viewDistSq;
|
||||
child->alphaBase = parent->alphaBase;
|
||||
}
|
||||
|
||||
if (geosetApplied_ && !attached_)
|
||||
{
|
||||
const int drawable = wxl::game::Native<m2::off::M2_IsDrawableFn>(
|
||||
m2::off::kIsDrawable)(child, nullptr, 0, 0);
|
||||
if (drawable)
|
||||
{
|
||||
m2::AttachToScene(parentRenderContext_, child,
|
||||
kCollectionAttachmentSlot, false);
|
||||
if (reinterpret_cast<void*>(static_cast<uintptr_t>(child->parent)) ==
|
||||
parentRenderContext_)
|
||||
{
|
||||
attached_ = true;
|
||||
lastError_ = "Ready";
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization",
|
||||
"attached ready overlay slot=%u", kCollectionAttachmentSlot);
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError_ = "Native head attachment was rejected";
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
lastError_ = "Collection loaded; waiting for drawable readiness";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CustomizationRenderer::OnEquipmentChanged(void* charModelObject)
|
||||
{
|
||||
if (!playerDetected_ && charModelObject)
|
||||
{
|
||||
observedCharModelObject_ = charModelObject;
|
||||
DetectPlayer();
|
||||
}
|
||||
|
||||
// Item events are emitted around native equipment maintenance. Rebuild on
|
||||
// the next logic tick so native slot work is complete before slot 19 is touched.
|
||||
if (charModelObject == charModelObject_)
|
||||
{
|
||||
const auto* choice = FindChoice(CurrentChoice());
|
||||
if (choice && choice->elements[0].type != ElementType::None)
|
||||
refreshRequested_ = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CustomizationRenderer::OnSkinFinalize(void* model)
|
||||
{
|
||||
if (!model || model != attachmentModel_ || finalizeInProgress_) return;
|
||||
const auto* choice = FindChoice(CurrentChoice());
|
||||
if (choice && choice->elements[0].type == ElementType::Geoset)
|
||||
FilterGeoset(model, choice->elements[0].geosetId, false);
|
||||
}
|
||||
|
||||
void CustomizationRenderer::OnBuildBonePalette(void* renderContext)
|
||||
{
|
||||
if (!renderContext || renderContext != attachmentRenderContext_) return;
|
||||
|
||||
const auto* choice = FindChoice(CurrentChoice());
|
||||
if (!choice || choice->elements[0].type != ElementType::Geoset) return;
|
||||
|
||||
auto* child = static_cast<m2::off::M2Instance*>(renderContext);
|
||||
auto* parent = static_cast<m2::off::M2Instance*>(parentRenderContext_);
|
||||
if (!parent || !child->bonePalettePtr || !parent->bonePalettePtr)
|
||||
return;
|
||||
|
||||
m2::M2Model childModel(reinterpret_cast<void*>(child->model));
|
||||
m2::M2Model parentModel(reinterpret_cast<void*>(parent->model));
|
||||
if (!childModel || !parentModel || !childModel.GetHeader() || !parentModel.GetHeader()) return;
|
||||
|
||||
const uint32_t count = std::min(childModel.GetHeader()->bones.count,
|
||||
parentModel.GetHeader()->bones.count);
|
||||
if (count)
|
||||
std::memcpy(reinterpret_cast<void*>(child->bonePalettePtr),
|
||||
reinterpret_cast<const void*>(parent->bonePalettePtr),
|
||||
static_cast<size_t>(count) * m2::off::kBonePaletteStride);
|
||||
}
|
||||
|
||||
bool CustomizationRenderer::FilterGeoset(void* rawModel, uint32_t geosetId, bool finalize)
|
||||
{
|
||||
m2::M2Model model(rawModel);
|
||||
auto* skin = model ? model.GetSkin() : nullptr;
|
||||
if (!IsReadableRange(skin, sizeof *skin) || !skin->indices ||
|
||||
!skin->submeshes || !skin->indexCount || !skin->submeshCount)
|
||||
return false;
|
||||
|
||||
// A newly created async scene model can expose the skin object before
|
||||
// its offset-valued arrays have been relocated to pointers. Do not
|
||||
// touch those arrays until both complete ranges are readable.
|
||||
constexpr uint32_t kMaxIndexCount = 10u * 1024u * 1024u;
|
||||
constexpr uint32_t kMaxSubmeshCount = 100u * 1024u;
|
||||
if (skin->indexCount > kMaxIndexCount || skin->submeshCount > kMaxSubmeshCount ||
|
||||
!IsReadableRange(skin->indices,
|
||||
static_cast<size_t>(skin->indexCount) * sizeof(uint16_t)) ||
|
||||
!IsReadableRange(skin->submeshes,
|
||||
static_cast<size_t>(skin->submeshCount) * sizeof(sm2::M2SkinSection)))
|
||||
return false;
|
||||
|
||||
bool found = false;
|
||||
for (uint32_t i = 0; i < skin->submeshCount; ++i)
|
||||
{
|
||||
const sm2::M2SkinSection& section = skin->submeshes[i];
|
||||
const uint32_t start = (uint32_t(section.level) << 16) | section.indexStart;
|
||||
const uint32_t count = section.indexCount;
|
||||
if (start > skin->indexCount || count > skin->indexCount - start) continue;
|
||||
if (section.skinSectionId == geosetId)
|
||||
{
|
||||
found = true;
|
||||
continue;
|
||||
}
|
||||
std::fill_n(skin->indices + start, count, uint16_t(0));
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
SetError("requested customization geoset is absent from collection skin");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (finalize)
|
||||
{
|
||||
finalizeInProgress_ = true;
|
||||
model.FinalizeSkin();
|
||||
finalizeInProgress_ = false;
|
||||
}
|
||||
attachmentModel_ = rawModel;
|
||||
geosetApplied_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CustomizationRenderer::Attach(const CustomizationChoice& choice)
|
||||
{
|
||||
if (!supported_ || !charModelObject_ || !parentRenderContext_)
|
||||
{
|
||||
SetError("supported player model is unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto& element = choice.elements[0];
|
||||
if (!element.modelPath)
|
||||
{
|
||||
SetError("customization model path is absent");
|
||||
return false;
|
||||
}
|
||||
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization", "loading model %s", element.modelPath);
|
||||
auto* parent = static_cast<m2::off::M2Instance*>(parentRenderContext_);
|
||||
void* scene = reinterpret_cast<void*>(static_cast<uintptr_t>(parent->scene));
|
||||
if (!scene)
|
||||
{
|
||||
SetError("active player scene is unavailable");
|
||||
return false;
|
||||
}
|
||||
|
||||
// CreateSceneModel's native receiver is the owning scene, not the
|
||||
// CharModelObject. Passing the CMO makes the loader interpret cmo+4
|
||||
// as the scene's model table and faults inside 0x0081C4F0.
|
||||
void* child = m2::GetRenderCtx(scene, const_cast<char*>(element.modelPath));
|
||||
if (!child)
|
||||
{
|
||||
SetError("CreateSceneModel returned null");
|
||||
return false;
|
||||
}
|
||||
|
||||
attachmentRenderContext_ = child;
|
||||
auto* instance = static_cast<m2::off::M2Instance*>(child);
|
||||
attachmentModel_ = reinterpret_cast<void*>(instance->model);
|
||||
geosetApplied_ = element.type != ElementType::Geoset;
|
||||
attached_ = false;
|
||||
|
||||
// Native WotLK head components must retain their own local transform;
|
||||
// AttachToScene composes it with the parent's head point. Collection
|
||||
// geosets use a full-character coordinate system and are seeded below.
|
||||
if (element.type == ElementType::Geoset)
|
||||
{
|
||||
auto* parentInstance = static_cast<m2::off::M2Instance*>(parentRenderContext_);
|
||||
std::memcpy(instance->placement, parentInstance->placement, sizeof instance->placement);
|
||||
instance->viewDistSq = parentInstance->viewDistSq;
|
||||
instance->alphaBase = parentInstance->alphaBase;
|
||||
}
|
||||
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization", "created overlay geoset=%u",
|
||||
choice.elements[0].geosetId);
|
||||
lastError_ = "Loading collection model";
|
||||
return true;
|
||||
}
|
||||
|
||||
void CustomizationRenderer::Detach()
|
||||
{
|
||||
if (!attachmentRenderContext_) return;
|
||||
if (attached_ && parentRenderContext_)
|
||||
m2::DetachSlot(parentRenderContext_, kCollectionAttachmentSlot);
|
||||
else
|
||||
m2::ReleaseRenderCtx(attachmentRenderContext_);
|
||||
attachmentRenderContext_ = nullptr;
|
||||
attachmentModel_ = nullptr;
|
||||
geosetApplied_ = false;
|
||||
attached_ = false;
|
||||
finalizeInProgress_ = false;
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization", "detached");
|
||||
}
|
||||
|
||||
bool CustomizationRenderer::Apply(uint32_t optionId, uint32_t choiceId)
|
||||
{
|
||||
const auto* choice = FindChoice(choiceId);
|
||||
if (!choice || optionId != kHornsOption.id) return false;
|
||||
if (!supported_)
|
||||
{
|
||||
lastError_ = "Prototype supports Blood Elf Female only.";
|
||||
return false;
|
||||
}
|
||||
const bool isNone = choice->elements[0].type == ElementType::None;
|
||||
if (CurrentChoice() == choiceId && (isNone || attachmentRenderContext_)) return true;
|
||||
|
||||
api_->Log(WXL_LOG_INFO, "MoonwellCustomization", "apply option=%u choice=%u",
|
||||
optionId, choiceId);
|
||||
Detach();
|
||||
state_.SetChoice(optionId, choiceId);
|
||||
if (isNone) return true;
|
||||
return Attach(*choice);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
|
||||
#include "CustomizationState.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
struct WXL_Api;
|
||||
|
||||
namespace moonwell::customization
|
||||
{
|
||||
class CustomizationRenderer
|
||||
{
|
||||
public:
|
||||
explicit CustomizationRenderer(const WXL_Api* api) : api_(api) {}
|
||||
|
||||
void OnWorldEnter();
|
||||
void OnWorldLeave();
|
||||
void OnUpdate(float dt);
|
||||
void OnEquipmentChanged(void* charModelObject);
|
||||
void OnSkinFinalize(void* model);
|
||||
void OnBuildBonePalette(void* renderContext);
|
||||
|
||||
bool Apply(uint32_t optionId, uint32_t choiceId);
|
||||
bool PlayerDetected() const { return playerDetected_; }
|
||||
bool Supported() const { return supported_; }
|
||||
uint32_t Race() const { return race_; }
|
||||
uint32_t Gender() const { return gender_; }
|
||||
uint32_t CurrentChoice() const { return state_.Choice(kHornsOption.id); }
|
||||
const char* LastError() const { return lastError_; }
|
||||
|
||||
private:
|
||||
bool DetectPlayer();
|
||||
bool Attach(const CustomizationChoice& choice);
|
||||
void Detach();
|
||||
bool FilterGeoset(void* model, uint32_t geosetId, bool finalize);
|
||||
void SetError(const char* message);
|
||||
|
||||
const WXL_Api* api_ = nullptr;
|
||||
CustomizationState state_;
|
||||
void* observedCharModelObject_ = nullptr;
|
||||
void* charModelObject_ = nullptr;
|
||||
void* parentRenderContext_ = nullptr;
|
||||
void* attachmentRenderContext_ = nullptr;
|
||||
void* attachmentModel_ = nullptr;
|
||||
uint32_t race_ = 0;
|
||||
uint32_t gender_ = 0;
|
||||
float detectDelay_ = 0.0f;
|
||||
bool inWorld_ = false;
|
||||
bool playerDetected_ = false;
|
||||
bool supported_ = false;
|
||||
bool refreshRequested_ = false;
|
||||
bool geosetApplied_ = false;
|
||||
bool attached_ = false;
|
||||
bool finalizeInProgress_ = false;
|
||||
const char* lastError_ = "Waiting for player";
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
#include "CustomizationData.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace moonwell::customization
|
||||
{
|
||||
class CustomizationState
|
||||
{
|
||||
public:
|
||||
uint32_t Choice(uint32_t optionId) const
|
||||
{
|
||||
return optionId == kHornsOption.id ? hornsChoice_ : 0;
|
||||
}
|
||||
|
||||
bool SetChoice(uint32_t optionId, uint32_t choiceId)
|
||||
{
|
||||
if (optionId != kHornsOption.id || !FindChoice(choiceId)) return false;
|
||||
hornsChoice_ = choiceId;
|
||||
return true;
|
||||
}
|
||||
|
||||
void Clear() { hornsChoice_ = 0; }
|
||||
|
||||
private:
|
||||
uint32_t hornsChoice_ = 0;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
#include "CustomizationRenderer.hpp"
|
||||
|
||||
#include <wxl/PluginApi.h>
|
||||
|
||||
#include "engine/events/Event.hpp"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
|
||||
namespace moonwell::customization
|
||||
{
|
||||
namespace ev = wxl::events;
|
||||
|
||||
const WXL_Api* g_api = nullptr;
|
||||
std::unique_ptr<CustomizationRenderer> g_renderer;
|
||||
|
||||
void __cdecl OnWorldEnter(void*, const void*) { g_renderer->OnWorldEnter(); }
|
||||
void __cdecl OnWorldLeave(void*, const void*) { g_renderer->OnWorldLeave(); }
|
||||
|
||||
void __cdecl OnUpdate(void*, const void* raw)
|
||||
{
|
||||
const auto* args = static_cast<const ev::UpdateArgs*>(raw);
|
||||
g_renderer->OnUpdate(args ? args->dt : 0.0f);
|
||||
}
|
||||
|
||||
void __cdecl OnItemSlotChange(void*, const void* raw)
|
||||
{
|
||||
const auto* args = static_cast<const ev::ItemSlotChangeArgs*>(raw);
|
||||
if (args) g_renderer->OnEquipmentChanged(args->charModelObj);
|
||||
}
|
||||
|
||||
void __cdecl OnItemSlotClear(void*, const void* raw)
|
||||
{
|
||||
const auto* args = static_cast<const ev::ItemSlotClearArgs*>(raw);
|
||||
if (args) g_renderer->OnEquipmentChanged(args->charModelObj);
|
||||
}
|
||||
|
||||
void __cdecl OnSkinFinalize(void*, const void* raw)
|
||||
{
|
||||
const auto* args = static_cast<const ev::M2SkinFinalizeArgs*>(raw);
|
||||
if (args) g_renderer->OnSkinFinalize(args->model);
|
||||
}
|
||||
|
||||
void __cdecl OnBuildBonePalette(void*, const void* raw)
|
||||
{
|
||||
const auto* args = static_cast<const ev::BuildBonePaletteArgs*>(raw);
|
||||
if (args) g_renderer->OnBuildBonePalette(args->renderCtx);
|
||||
}
|
||||
|
||||
void DrawPanel(void*)
|
||||
{
|
||||
char line[128]{};
|
||||
if (!g_renderer->PlayerDetected())
|
||||
{
|
||||
g_api->UiText("Player: detecting...");
|
||||
g_api->UiText(g_renderer->LastError());
|
||||
return;
|
||||
}
|
||||
|
||||
std::snprintf(line, sizeof(line), "Race ID: %u%s", g_renderer->Race(),
|
||||
g_renderer->Race() == kBloodElfRace ? " (Blood Elf)" : "");
|
||||
g_api->UiText(line);
|
||||
std::snprintf(line, sizeof(line), "Gender ID: %u%s", g_renderer->Gender(),
|
||||
g_renderer->Gender() == kFemaleGender ? " (Female)" : "");
|
||||
g_api->UiText(line);
|
||||
g_api->UiSeparator();
|
||||
|
||||
if (!g_renderer->Supported())
|
||||
{
|
||||
g_api->UiText("Prototype supports Blood Elf Female only.");
|
||||
return;
|
||||
}
|
||||
|
||||
g_api->UiText("Option: Head Decorations (WotLK fallback)");
|
||||
for (const auto& choice : kHornsOption.choices)
|
||||
{
|
||||
std::snprintf(line, sizeof(line), "%s%s", choice.name,
|
||||
g_renderer->CurrentChoice() == choice.id ? " [selected]" : "");
|
||||
if (g_api->UiButton(line))
|
||||
g_renderer->Apply(kHornsOption.id, choice.id);
|
||||
}
|
||||
g_api->UiSeparator();
|
||||
g_api->UiText(g_renderer->LastError());
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonwellCustomization", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
using namespace moonwell::customization;
|
||||
if (!api || api->apiVersion != WXL_API_VERSION ||
|
||||
api->structSize < sizeof(WXL_Api) || !api->Subscribe || !api->UiAddPanel)
|
||||
return 0;
|
||||
|
||||
g_api = api;
|
||||
g_renderer = std::make_unique<CustomizationRenderer>(api);
|
||||
api->Subscribe(uint32_t(ev::Event::OnWorldEnter), &OnWorldEnter, nullptr);
|
||||
api->Subscribe(uint32_t(ev::Event::OnWorldLeave), &OnWorldLeave, nullptr);
|
||||
api->Subscribe(uint32_t(ev::Event::OnUpdate), &OnUpdate, nullptr);
|
||||
api->Subscribe(uint32_t(ev::Event::OnItemSlotChange), &OnItemSlotChange, nullptr);
|
||||
api->Subscribe(uint32_t(ev::Event::OnItemSlotClear), &OnItemSlotClear, nullptr);
|
||||
api->Subscribe(uint32_t(ev::Event::OnM2SkinFinalize), &OnSkinFinalize, nullptr);
|
||||
api->Subscribe(uint32_t(ev::Event::OnBuildBonePalette), &OnBuildBonePalette, nullptr);
|
||||
api->UiAddPanel("MoonWell Customization", &DrawPanel, nullptr);
|
||||
api->Log(WXL_LOG_INFO, "MoonwellCustomization", "loaded");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// FileDataID overlay for selected retail assets shipped in MoonWell patches.
|
||||
|
||||
#include "game/Io.hpp"
|
||||
#include "wxl/FdidApi.h"
|
||||
#include "wxl/PluginApi.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace moonwell::fdid
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view kFileDataMap = "WXLFileData.csv";
|
||||
|
||||
std::once_flag g_loadOnce;
|
||||
std::unordered_map<uint32_t, std::string> g_paths;
|
||||
const WXL_Api* g_api = nullptr;
|
||||
const char*(__cdecl* g_resolveTexture)(uint32_t) = nullptr;
|
||||
const char*(__cdecl* g_resolveModel)(uint32_t) = nullptr;
|
||||
|
||||
bool ReadAll(const char* path, std::vector<uint8_t>& out)
|
||||
{
|
||||
void* handle = nullptr;
|
||||
if (!wxl::game::io::FileOpen(path, wxl::game::io::kOpenWholeFile, &handle) || !handle)
|
||||
return false;
|
||||
uint32_t high = 0;
|
||||
const uint32_t size = wxl::game::io::FileSize(handle, &high);
|
||||
if (high != 0)
|
||||
{
|
||||
wxl::game::io::FileClose(handle);
|
||||
return false;
|
||||
}
|
||||
out.resize(size);
|
||||
uint32_t read = 0;
|
||||
const bool ok = (size == 0 || wxl::game::io::FileRead(handle, out.data(), size, &read))
|
||||
&& read == size;
|
||||
wxl::game::io::FileClose(handle);
|
||||
if (!ok) out.clear();
|
||||
return ok;
|
||||
}
|
||||
|
||||
std::string_view Trim(std::string_view value)
|
||||
{
|
||||
while (!value.empty() && (value.front() == ' ' || value.front() == '\t' ||
|
||||
value.front() == '\r' || value.front() == '\n'))
|
||||
value.remove_prefix(1);
|
||||
while (!value.empty() && (value.back() == ' ' || value.back() == '\t' ||
|
||||
value.back() == '\r' || value.back() == '\n'))
|
||||
value.remove_suffix(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
std::vector<uint8_t> bytes;
|
||||
if (!ReadAll(kFileDataMap.data(), bytes))
|
||||
{
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_WARN, "MoonWellFdid", "FileDataID map '%s' was not found",
|
||||
kFileDataMap.data());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view text(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
size_t lineStart = 0;
|
||||
while (lineStart < text.size())
|
||||
{
|
||||
size_t lineEnd = text.find('\n', lineStart);
|
||||
if (lineEnd == std::string_view::npos) lineEnd = text.size();
|
||||
std::string_view line = Trim(text.substr(lineStart, lineEnd - lineStart));
|
||||
lineStart = lineEnd + 1;
|
||||
|
||||
if (line.empty() || line.front() == '#') continue;
|
||||
const size_t comma = line.find(',');
|
||||
if (comma == std::string_view::npos) continue;
|
||||
|
||||
const std::string_view idText = Trim(line.substr(0, comma));
|
||||
std::string_view pathText = Trim(line.substr(comma + 1));
|
||||
uint32_t fileDataId = 0;
|
||||
const auto parsed = std::from_chars(idText.data(), idText.data() + idText.size(), fileDataId);
|
||||
if (parsed.ec != std::errc{} || parsed.ptr != idText.data() + idText.size() ||
|
||||
fileDataId == 0 || pathText.empty())
|
||||
continue;
|
||||
|
||||
std::string path(pathText);
|
||||
for (char& c : path) if (c == '/') c = '\\';
|
||||
g_paths[fileDataId] = std::move(path);
|
||||
}
|
||||
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_INFO, "MoonWellFdid", "loaded %zu custom FileDataID path(s)",
|
||||
g_paths.size());
|
||||
}
|
||||
|
||||
const char* ResolveCustom(uint32_t fileDataId)
|
||||
{
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
const auto found = g_paths.find(fileDataId);
|
||||
return found == g_paths.end() ? nullptr : found->second.c_str();
|
||||
}
|
||||
|
||||
const char* __cdecl ResolveTexture(uint32_t fileDataId)
|
||||
{
|
||||
if (const char* custom = ResolveCustom(fileDataId)) return custom;
|
||||
return g_resolveTexture ? g_resolveTexture(fileDataId) : nullptr;
|
||||
}
|
||||
|
||||
const char* __cdecl ResolveModel(uint32_t fileDataId)
|
||||
{
|
||||
if (const char* custom = ResolveCustom(fileDataId)) return custom;
|
||||
return g_resolveModel ? g_resolveModel(fileDataId) : nullptr;
|
||||
}
|
||||
|
||||
bool PatchResolver(WXL_FdidApi* fdid)
|
||||
{
|
||||
if (!fdid || fdid->apiVersion != WXL_FDID_API_VERSION) return false;
|
||||
g_resolveTexture = fdid->ResolveTexture;
|
||||
g_resolveModel = fdid->ResolveModel;
|
||||
|
||||
DWORD oldProtection = 0;
|
||||
if (!VirtualProtect(fdid, sizeof(*fdid), PAGE_READWRITE, &oldProtection)) return false;
|
||||
fdid->ResolveTexture = &ResolveTexture;
|
||||
fdid->ResolveModel = &ResolveModel;
|
||||
DWORD ignored = 0;
|
||||
VirtualProtect(fdid, sizeof(*fdid), oldProtection, &ignored);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonWellFdid", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
moonwell::fdid::g_api = api;
|
||||
auto* fdid = static_cast<WXL_FdidApi*>(api->GetInterface("wxl.fdid", WXL_FDID_API_VERSION));
|
||||
if (!moonwell::fdid::PatchResolver(fdid))
|
||||
{
|
||||
api->Log(WXL_LOG_ERROR, "MoonWellFdid", "wxl.fdid v1 is unavailable");
|
||||
return 0;
|
||||
}
|
||||
api->Log(WXL_LOG_INFO, "MoonWellFdid", "custom FileDataID overlay installed");
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// MoonWell's specific-archive fallback, kept outside the WarcraftXL core.
|
||||
|
||||
#include "wxl/PluginApi.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
|
||||
namespace
|
||||
{
|
||||
const WXL_Api* g_api = nullptr;
|
||||
using FileOpenFn = int(__stdcall*)(void* archive, const char* name, uint32_t flags, void** out);
|
||||
FileOpenFn g_nextOpen = nullptr;
|
||||
std::atomic<uint32_t> g_logCount{0};
|
||||
|
||||
int __stdcall FileOpenHook(void* archive, const char* name, uint32_t flags, void** out)
|
||||
{
|
||||
if (!g_nextOpen) return 0;
|
||||
|
||||
const int nativeResult = g_nextOpen(archive, name, flags, out);
|
||||
if (nativeResult || archive == nullptr || !name || !*name) return nativeResult;
|
||||
|
||||
// A dependency requested through one concrete MPQ may live in a higher-priority loose
|
||||
// Patch-*.MPQ directory. Retry only after that exact archive misses, using the client's
|
||||
// normal global search path. This is the MoonWell-only behavior that used to patch core.
|
||||
if (out) *out = nullptr;
|
||||
const int fallbackResult = g_nextOpen(nullptr, name, flags, out);
|
||||
if (fallbackResult && g_api && g_api->Log)
|
||||
{
|
||||
const uint32_t index = g_logCount.fetch_add(1, std::memory_order_relaxed);
|
||||
if (index < 32)
|
||||
g_api->Log(WXL_LOG_INFO, "MoonWellStorageFallback",
|
||||
"specific archive miss resolved globally: '%s'", name);
|
||||
}
|
||||
return fallbackResult;
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonWellStorageFallback", 1,
|
||||
WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION || !api->HookAttachByName) return 0;
|
||||
g_api = api;
|
||||
const int installed = api->HookAttachByName(
|
||||
"Io.FileOpen", reinterpret_cast<void*>(&FileOpenHook),
|
||||
reinterpret_cast<void**>(&g_nextOpen), WXL_HOOK_DEFAULT_PRIORITY);
|
||||
api->Log(installed ? WXL_LOG_INFO : WXL_LOG_ERROR, "MoonWellStorageFallback", "%s",
|
||||
installed ? "specific-archive fallback installed" : "Io.FileOpen hook failed");
|
||||
return installed;
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// FileDataID resolver for selected retail assets shipped in MoonWell patches.
|
||||
|
||||
#include "Host.hpp"
|
||||
#include "core/Logger.hpp"
|
||||
#include "mpq/MpqStore.hpp"
|
||||
|
||||
#include <charconv>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace moonwell::host
|
||||
{
|
||||
namespace
|
||||
{
|
||||
constexpr std::string_view kFileDataMap = "WXLFileData.csv";
|
||||
|
||||
std::once_flag g_loadOnce;
|
||||
std::unordered_map<uint32_t, std::string> g_paths;
|
||||
|
||||
std::string_view Trim(std::string_view value)
|
||||
{
|
||||
while (!value.empty() && (value.front() == ' ' || value.front() == '\t' ||
|
||||
value.front() == '\r' || value.front() == '\n'))
|
||||
value.remove_prefix(1);
|
||||
while (!value.empty() && (value.back() == ' ' || value.back() == '\t' ||
|
||||
value.back() == '\r' || value.back() == '\n'))
|
||||
value.remove_suffix(1);
|
||||
return value;
|
||||
}
|
||||
|
||||
void Load()
|
||||
{
|
||||
const std::string root = wxl::host::ClientRoot();
|
||||
wxl::host::mpq::MpqStore store;
|
||||
std::vector<uint8_t> bytes;
|
||||
if (root.empty() || !store.Mount(root) || !store.ReadAll(kFileDataMap, bytes))
|
||||
{
|
||||
WLOG_WARN("moonwell: FileDataID map '%.*s' was not found",
|
||||
int(kFileDataMap.size()), kFileDataMap.data());
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view text(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
||||
size_t lineStart = 0;
|
||||
while (lineStart < text.size())
|
||||
{
|
||||
size_t lineEnd = text.find('\n', lineStart);
|
||||
if (lineEnd == std::string_view::npos) lineEnd = text.size();
|
||||
std::string_view line = Trim(text.substr(lineStart, lineEnd - lineStart));
|
||||
lineStart = lineEnd + 1;
|
||||
|
||||
if (line.empty() || line.front() == '#') continue;
|
||||
const size_t comma = line.find(',');
|
||||
if (comma == std::string_view::npos) continue;
|
||||
|
||||
const std::string_view idText = Trim(line.substr(0, comma));
|
||||
std::string_view pathText = Trim(line.substr(comma + 1));
|
||||
uint32_t fileDataId = 0;
|
||||
const auto parsed = std::from_chars(idText.data(), idText.data() + idText.size(), fileDataId);
|
||||
if (parsed.ec != std::errc{} || parsed.ptr != idText.data() + idText.size() ||
|
||||
fileDataId == 0 || pathText.empty())
|
||||
continue;
|
||||
|
||||
std::string path(pathText);
|
||||
for (char& c : path) if (c == '/') c = '\\';
|
||||
g_paths[fileDataId] = std::move(path);
|
||||
}
|
||||
|
||||
WLOG_INFO("moonwell: loaded %zu FileDataID path(s) from %.*s",
|
||||
g_paths.size(), int(kFileDataMap.size()), kFileDataMap.data());
|
||||
}
|
||||
|
||||
bool Resolve(uint32_t fileDataId, std::string& outPath)
|
||||
{
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
const auto found = g_paths.find(fileDataId);
|
||||
if (found == g_paths.end()) return false;
|
||||
outPath = found->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
struct Registrar
|
||||
{
|
||||
Registrar() { wxl::host::RegisterResolver("moonwell-filedata", &Resolve); }
|
||||
};
|
||||
|
||||
Registrar g_registrar;
|
||||
}
|
||||
}
|
||||
@@ -5,28 +5,71 @@
|
||||
// changes are verified and applied to the process image during WarcraftXL's
|
||||
// boot phase, before GlueXML is loaded.
|
||||
|
||||
#include "core/Logger.hpp"
|
||||
#include "core/Hook.hpp"
|
||||
#include "core/Mem.hpp"
|
||||
#include "runtime/LuaBindings.hpp"
|
||||
#include "runtime/ModuleInstall.hpp"
|
||||
#include "offsets/engine/Gx.hpp"
|
||||
#include "game/Script.hpp"
|
||||
#include "wxl/PluginApi.h"
|
||||
#include "SpellOverrides.hpp"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <cstdarg>
|
||||
|
||||
namespace moonwell
|
||||
{
|
||||
namespace
|
||||
{
|
||||
const WXL_Api* g_api = nullptr;
|
||||
|
||||
void Log(int level, const char* format, ...)
|
||||
{
|
||||
if (!g_api || !g_api->Log) return;
|
||||
char message[1024]{};
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
vsnprintf_s(message, sizeof(message), _TRUNCATE, format, args);
|
||||
va_end(args);
|
||||
g_api->Log(level, "MoonWell", "%s", message);
|
||||
}
|
||||
|
||||
#define WLOG_INFO(...) Log(WXL_LOG_INFO, __VA_ARGS__)
|
||||
#define WLOG_ERROR(...) Log(WXL_LOG_ERROR, __VA_ARGS__)
|
||||
|
||||
bool PatchMemory(void* destination, const void* source, size_t size)
|
||||
{
|
||||
DWORD oldProtection = 0;
|
||||
if (!VirtualProtect(destination, size, PAGE_EXECUTE_READWRITE, &oldProtection))
|
||||
return false;
|
||||
std::memcpy(destination, source, size);
|
||||
FlushInstructionCache(GetCurrentProcess(), destination, size);
|
||||
DWORD ignored = 0;
|
||||
VirtualProtect(destination, size, oldProtection, &ignored);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class Fn>
|
||||
bool Hook(const char* name, uintptr_t address, Fn* detour, Fn** original)
|
||||
{
|
||||
return g_api && g_api->HookAttach && g_api->HookAttach(
|
||||
name, address, reinterpret_cast<void*>(detour),
|
||||
reinterpret_cast<void**>(original), WXL_HOOK_DEFAULT_PRIORITY) != 0;
|
||||
}
|
||||
|
||||
template <class Fn>
|
||||
bool HookByName(const char* point, Fn* detour, Fn** original)
|
||||
{
|
||||
return g_api && g_api->HookAttachByName && g_api->HookAttachByName(
|
||||
point, reinterpret_cast<void*>(detour), reinterpret_cast<void**>(original),
|
||||
WXL_HOOK_DEFAULT_PRIORITY) != 0;
|
||||
}
|
||||
|
||||
constexpr uint32_t kTraitorFlag = 0x40000000u;
|
||||
bool g_loginCharacterIsTraitor = false;
|
||||
|
||||
using GxSetProjectionFn = wxl::offsets::engine::gx::GxSetProjectionFn;
|
||||
using GxSetProjectionFn = void(__fastcall*)(void* self, void* edx, const void* projection);
|
||||
GxSetProjectionFn g_nextSetProjection = nullptr;
|
||||
|
||||
// FrameXML's stock SetCreature only accepts a creature entry and waits
|
||||
@@ -64,10 +107,10 @@ namespace moonwell
|
||||
int __cdecl SetCreatureDisplayInfoHook(void* state)
|
||||
{
|
||||
const int result = g_nextSetCreature ? g_nextSetCreature(state) : 0;
|
||||
if (!state || !wxl::runtime::lua::IsNumber(state, 3))
|
||||
if (!state || !wxl::game::script::IsNumber(state, 3))
|
||||
return result;
|
||||
|
||||
const double requestedDisplayInfo = wxl::runtime::lua::ToNumber(state, 3);
|
||||
const double requestedDisplayInfo = wxl::game::script::ToNumber(state, 3);
|
||||
if (requestedDisplayInfo <= 0.0 || requestedDisplayInfo > 4294967295.0)
|
||||
return result;
|
||||
|
||||
@@ -102,8 +145,8 @@ namespace moonwell
|
||||
|
||||
void InstallEncounterJournalModelPreview()
|
||||
{
|
||||
if (!wxl::core::hook::Install("MoonWellSetCreatureDisplayInfo", kSetCreature,
|
||||
&SetCreatureDisplayInfoHook, &g_nextSetCreature))
|
||||
if (!Hook("MoonWellSetCreatureDisplayInfo", kSetCreature,
|
||||
&SetCreatureDisplayInfoHook, &g_nextSetCreature))
|
||||
{
|
||||
WLOG_ERROR("moonwell: encounter journal model hook installation failed");
|
||||
return;
|
||||
@@ -151,14 +194,14 @@ namespace moonwell
|
||||
|
||||
int __cdecl SetCharacterCreateCamera(void* state)
|
||||
{
|
||||
const bool enabled = state && wxl::runtime::lua::IsNumber(state, 1)
|
||||
&& wxl::runtime::lua::ToNumber(state, 1) != 0.0;
|
||||
const float faceZoom = state && wxl::runtime::lua::IsNumber(state, 2)
|
||||
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 2)) : 2.0f;
|
||||
const float faceVerticalOffset = state && wxl::runtime::lua::IsNumber(state, 3)
|
||||
? static_cast<float>(wxl::runtime::lua::ToNumber(state, 3)) : -0.65f;
|
||||
double requestedDuration = state && wxl::runtime::lua::IsNumber(state, 4)
|
||||
? wxl::runtime::lua::ToNumber(state, 4) : 500.0;
|
||||
const bool enabled = state && wxl::game::script::IsNumber(state, 1)
|
||||
&& wxl::game::script::ToNumber(state, 1) != 0.0;
|
||||
const float faceZoom = state && wxl::game::script::IsNumber(state, 2)
|
||||
? static_cast<float>(wxl::game::script::ToNumber(state, 2)) : 2.0f;
|
||||
const float faceVerticalOffset = state && wxl::game::script::IsNumber(state, 3)
|
||||
? static_cast<float>(wxl::game::script::ToNumber(state, 3)) : -0.65f;
|
||||
double requestedDuration = state && wxl::game::script::IsNumber(state, 4)
|
||||
? wxl::game::script::ToNumber(state, 4) : 500.0;
|
||||
if (requestedDuration < 0.0) requestedDuration = 0.0;
|
||||
if (requestedDuration > 2000.0) requestedDuration = 2000.0;
|
||||
|
||||
@@ -202,15 +245,16 @@ namespace moonwell
|
||||
|
||||
void InstallCharacterCreateCamera()
|
||||
{
|
||||
namespace gx = wxl::offsets::engine::gx;
|
||||
void** vtable = reinterpret_cast<void**>(gx::kGxDeviceVTable);
|
||||
void** slot = &vtable[gx::kGxSetProjectionSlot];
|
||||
constexpr uintptr_t kGxDeviceVTable = 0x00A2E718;
|
||||
constexpr unsigned kGxSetProjectionSlot = 0xA0 / 4;
|
||||
void** vtable = reinterpret_cast<void**>(kGxDeviceVTable);
|
||||
void** slot = &vtable[kGxSetProjectionSlot];
|
||||
if (*slot == reinterpret_cast<void*>(&CharacterCreateProjectionHook))
|
||||
return;
|
||||
|
||||
g_nextSetProjection = reinterpret_cast<GxSetProjectionFn>(*slot);
|
||||
void* replacement = reinterpret_cast<void*>(&CharacterCreateProjectionHook);
|
||||
if (!g_nextSetProjection || !wxl::core::mem::Patch(slot, &replacement, sizeof(replacement)))
|
||||
if (!g_nextSetProjection || !PatchMemory(slot, &replacement, sizeof(replacement)))
|
||||
{
|
||||
g_nextSetProjection = nullptr;
|
||||
WLOG_ERROR("moonwell: character-create projection hook installation failed");
|
||||
@@ -222,17 +266,17 @@ namespace moonwell
|
||||
int __cdecl SetLoginCharacterFlags(void* state)
|
||||
{
|
||||
uint32_t flags = 0;
|
||||
if (state && wxl::runtime::lua::IsNumber(state, 1))
|
||||
flags = static_cast<uint32_t>(wxl::runtime::lua::ToNumber(state, 1));
|
||||
if (state && wxl::game::script::IsNumber(state, 1))
|
||||
flags = static_cast<uint32_t>(wxl::game::script::ToNumber(state, 1));
|
||||
|
||||
g_loginCharacterIsTraitor = (flags & kTraitorFlag) != 0;
|
||||
wxl::runtime::lua::PushBoolean(state, g_loginCharacterIsTraitor ? 1 : 0);
|
||||
wxl::game::script::PushBoolean(state, g_loginCharacterIsTraitor);
|
||||
return 1;
|
||||
}
|
||||
|
||||
int __cdecl IsTraitor(void* state)
|
||||
{
|
||||
wxl::runtime::lua::PushBoolean(state, g_loginCharacterIsTraitor ? 1 : 0);
|
||||
wxl::game::script::PushBoolean(state, g_loginCharacterIsTraitor);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -259,7 +303,7 @@ namespace moonwell
|
||||
name, reinterpret_cast<void*>(address));
|
||||
return false;
|
||||
}
|
||||
if (!wxl::core::mem::Patch(reinterpret_cast<void*>(address), replacement.data(), N))
|
||||
if (!PatchMemory(reinterpret_cast<void*>(address), replacement.data(), N))
|
||||
{
|
||||
WLOG_ERROR("moonwell: '%s' patch failed at %p", name,
|
||||
reinterpret_cast<void*>(address));
|
||||
@@ -371,7 +415,7 @@ namespace moonwell
|
||||
const auto caveRel = static_cast<int32_t>(reinterpret_cast<uintptr_t>(cave) -
|
||||
(patchAddress + jump.size()));
|
||||
std::memcpy(jump.data() + 1, &caveRel, sizeof(caveRel));
|
||||
if (!wxl::core::mem::Patch(reinterpret_cast<void*>(patchAddress), jump.data(), jump.size()))
|
||||
if (!PatchMemory(reinterpret_cast<void*>(patchAddress), jump.data(), jump.size()))
|
||||
{
|
||||
VirtualFree(cave, 0, MEM_RELEASE);
|
||||
WLOG_ERROR("moonwell: GetCharacterInfo jump patch failed");
|
||||
@@ -390,40 +434,84 @@ namespace moonwell
|
||||
WLOG_ERROR("moonwell: compatibility module incomplete; see mismatches above");
|
||||
else
|
||||
WLOG_INFO("moonwell: compatibility module ready (stock Wow.exe remains untouched)");
|
||||
wxl::core::log::Flush();
|
||||
}
|
||||
|
||||
DWORD WINAPI FlushRuntimeLog(LPVOID)
|
||||
using RegisterFunctionFn = void(__cdecl*)(const char*, wxl::game::script::Function);
|
||||
using ValidateCallbackFn = void(__cdecl*)(uintptr_t);
|
||||
using GetContextFn = void*(__cdecl*)();
|
||||
RegisterFunctionFn g_nextRegisterFunction = nullptr;
|
||||
ValidateCallbackFn g_nextValidateCallback = nullptr;
|
||||
void* g_registeredState = nullptr;
|
||||
bool g_registeringMoonWell = false;
|
||||
|
||||
bool IsMoonWellCallback(uintptr_t callback)
|
||||
{
|
||||
// Let the remainder of RunAll() and the core-ready line reach the
|
||||
// shared buffered logger, then make startup diagnostics durable.
|
||||
Sleep(1000);
|
||||
wxl::core::log::Flush();
|
||||
return 0;
|
||||
return callback == reinterpret_cast<uintptr_t>(&SetLoginCharacterFlags)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&IsTraitor)
|
||||
|| callback == reinterpret_cast<uintptr_t>(&SetCharacterCreateCamera);
|
||||
}
|
||||
|
||||
void InstallRuntimeLogFlush()
|
||||
void __cdecl ValidateCallbackHook(uintptr_t callback)
|
||||
{
|
||||
HANDLE thread = CreateThread(nullptr, 0, &FlushRuntimeLog, nullptr, 0, nullptr);
|
||||
if (thread) CloseHandle(thread);
|
||||
if (!IsMoonWellCallback(callback) && g_nextValidateCallback)
|
||||
g_nextValidateCallback(callback);
|
||||
}
|
||||
|
||||
struct Registration
|
||||
void RegisterLuaFunctionsForCurrentState()
|
||||
{
|
||||
Registration()
|
||||
{
|
||||
wxl::runtime::lua::RegisterFunction(
|
||||
"MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
|
||||
wxl::runtime::lua::RegisterFunction("MoonWellIsTraitor", &IsTraitor);
|
||||
wxl::runtime::lua::RegisterFunction(
|
||||
"MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
|
||||
wxl::runtime::modules::RegisterBoot("moonwell", &InstallBoot);
|
||||
wxl::runtime::modules::Register(
|
||||
"moonwell-character-create-camera", &InstallCharacterCreateCamera);
|
||||
wxl::runtime::modules::Register(
|
||||
"moonwell-encounter-journal-models", &InstallEncounterJournalModelPreview);
|
||||
wxl::runtime::modules::Register("moonwell-log-flush", &InstallRuntimeLogFlush);
|
||||
}
|
||||
} g_registration;
|
||||
if (!g_nextRegisterFunction || g_registeringMoonWell) return;
|
||||
constexpr uintptr_t kGetContext = 0x00817DB0;
|
||||
void* state = reinterpret_cast<GetContextFn>(kGetContext)();
|
||||
if (!state || state == g_registeredState) return;
|
||||
|
||||
g_registeringMoonWell = true;
|
||||
g_nextRegisterFunction("MoonWellSetLoginCharacterFlags", &SetLoginCharacterFlags);
|
||||
g_nextRegisterFunction("MoonWellIsTraitor", &IsTraitor);
|
||||
g_nextRegisterFunction("MoonWellSetCharacterCreateCamera", &SetCharacterCreateCamera);
|
||||
g_registeringMoonWell = false;
|
||||
g_registeredState = state;
|
||||
WLOG_INFO("moonwell: Lua functions registered for state %p", state);
|
||||
}
|
||||
|
||||
void __cdecl RegisterFunctionHook(const char* name, wxl::game::script::Function function)
|
||||
{
|
||||
if (g_nextRegisterFunction) g_nextRegisterFunction(name, function);
|
||||
RegisterLuaFunctionsForCurrentState();
|
||||
}
|
||||
|
||||
bool InstallLuaBridge()
|
||||
{
|
||||
const bool validator = HookByName("Lua.ValidateFunctionPointer", &ValidateCallbackHook,
|
||||
&g_nextValidateCallback);
|
||||
const bool registrar = HookByName("Lua.RegisterFunction", &RegisterFunctionHook,
|
||||
&g_nextRegisterFunction);
|
||||
if (!validator || !registrar)
|
||||
WLOG_ERROR("moonwell: Lua bridge hook installation failed");
|
||||
return validator && registrar;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "MoonWell", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
moonwell::g_api = api;
|
||||
|
||||
moonwell::InstallBoot();
|
||||
const bool lua = moonwell::InstallLuaBridge();
|
||||
moonwell::InstallCharacterCreateCamera();
|
||||
moonwell::InstallEncounterJournalModelPreview();
|
||||
const bool spells = moonwell::spells::Install(api);
|
||||
const bool ready = lua && spells;
|
||||
api->Log(ready ? WXL_LOG_INFO : WXL_LOG_ERROR, "MoonWell", "%s",
|
||||
ready ? "MoonWell extension loaded" : "MoonWell extension loaded with errors");
|
||||
return ready ? 1 : 0;
|
||||
}
|
||||
|
||||
+72
-26
@@ -1,9 +1,9 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Appends compact MoonWell spell overrides to the stock 3.3.5 DBCs at open time.
|
||||
// Appends compact MoonWell spell overrides to stock 3.3.5 DBC bytes.
|
||||
|
||||
#include "Host.hpp"
|
||||
#include "core/Logger.hpp"
|
||||
#include "mpq/MpqStore.hpp"
|
||||
#include "game/Io.hpp"
|
||||
#include "wxl/PluginApi.h"
|
||||
#include "wxl/StorageApi.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <charconv>
|
||||
@@ -15,7 +15,7 @@
|
||||
#include <system_error>
|
||||
#include <vector>
|
||||
|
||||
namespace moonwell::host
|
||||
namespace moonwell::spells
|
||||
{
|
||||
namespace
|
||||
{
|
||||
@@ -38,8 +38,34 @@ namespace moonwell::host
|
||||
|
||||
std::once_flag g_loadOnce;
|
||||
bool g_ready = false;
|
||||
std::vector<uint8_t> g_spellDbc;
|
||||
std::vector<uint8_t> g_skillDbc;
|
||||
const WXL_Api* g_api = nullptr;
|
||||
std::vector<Override> g_overrides;
|
||||
|
||||
void Log(int level, const char* message)
|
||||
{
|
||||
if (g_api && g_api->Log) g_api->Log(level, "MoonWellSpells", "%s", message);
|
||||
}
|
||||
|
||||
bool ReadAll(const char* path, std::vector<uint8_t>& out)
|
||||
{
|
||||
void* handle = nullptr;
|
||||
if (!wxl::game::io::FileOpen(path, wxl::game::io::kOpenWholeFile, &handle) || !handle)
|
||||
return false;
|
||||
uint32_t high = 0;
|
||||
const uint32_t size = wxl::game::io::FileSize(handle, &high);
|
||||
if (high != 0)
|
||||
{
|
||||
wxl::game::io::FileClose(handle);
|
||||
return false;
|
||||
}
|
||||
out.resize(size);
|
||||
uint32_t read = 0;
|
||||
const bool ok = (size == 0 || wxl::game::io::FileRead(handle, out.data(), size, &read))
|
||||
&& read == size;
|
||||
wxl::game::io::FileClose(handle);
|
||||
if (!ok) out.clear();
|
||||
return ok;
|
||||
}
|
||||
|
||||
uint32_t ReadU32(const uint8_t* data)
|
||||
{
|
||||
@@ -226,39 +252,59 @@ namespace moonwell::host
|
||||
|
||||
void Load()
|
||||
{
|
||||
const std::string root = wxl::host::ClientRoot();
|
||||
wxl::host::mpq::MpqStore store;
|
||||
std::vector<uint8_t> overrideBytes, spellBase, skillBase;
|
||||
if (root.empty() || !store.Mount(root) || !store.ReadAll(kOverridesPath, overrideBytes))
|
||||
std::vector<uint8_t> overrideBytes;
|
||||
if (!ReadAll(kOverridesPath.data(), overrideBytes))
|
||||
return;
|
||||
|
||||
std::vector<Override> overrides;
|
||||
if (!ParseOverrides(overrideBytes, overrides) ||
|
||||
!store.ReadAll(kSpellPath, spellBase) || !store.ReadAll(kSkillPath, skillBase) ||
|
||||
!BuildSpellDbc(spellBase, overrides, g_spellDbc) ||
|
||||
!BuildSkillDbc(skillBase, overrides, g_skillDbc))
|
||||
if (!ParseOverrides(overrideBytes, g_overrides))
|
||||
{
|
||||
WLOG_ERROR("moonwell-spells: failed to build custom companion DBCs");
|
||||
Log(WXL_LOG_ERROR, "failed to parse WXLSpellOverrides.tsv");
|
||||
return;
|
||||
}
|
||||
g_ready = true;
|
||||
WLOG_INFO("moonwell-spells: appended %zu companion spell(s)", overrides.size());
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_INFO, "MoonWellSpells", "loaded %zu companion spell override(s)",
|
||||
g_overrides.size());
|
||||
}
|
||||
|
||||
bool Provide(std::string_view name, std::vector<uint8_t>& out)
|
||||
int __cdecl Transform(const char* rawName, const uint8_t* raw, uint32_t rawLen,
|
||||
const WXL_ByteSink* sink)
|
||||
{
|
||||
if (!rawName || !raw || !sink || !sink->Write) return 0;
|
||||
const std::string_view name(rawName);
|
||||
if (!SamePath(name, kSpellPath) && !SamePath(name, kSkillPath)) return false;
|
||||
std::call_once(g_loadOnce, &Load);
|
||||
if (!g_ready) return false;
|
||||
out = SamePath(name, kSpellPath) ? g_spellDbc : g_skillDbc;
|
||||
return true;
|
||||
|
||||
const std::vector<uint8_t> base(raw, raw + rawLen);
|
||||
std::vector<uint8_t> out;
|
||||
const bool built = SamePath(name, kSpellPath)
|
||||
? BuildSpellDbc(base, g_overrides, out)
|
||||
: BuildSkillDbc(base, g_overrides, out);
|
||||
if (!built)
|
||||
{
|
||||
Log(WXL_LOG_ERROR, "failed to build custom companion DBC");
|
||||
return 0;
|
||||
}
|
||||
sink->Write(sink->ctx, out.data(), static_cast<uint32_t>(out.size()));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
struct Registrar
|
||||
bool Install(const WXL_Api* api)
|
||||
{
|
||||
g_api = api;
|
||||
if (!api || !api->GetInterface) return false;
|
||||
auto* storage = static_cast<WXL_StorageApi*>(
|
||||
api->GetInterface("wxl.storage", WXL_STORAGE_API_VERSION));
|
||||
if (!storage || storage->apiVersion != WXL_STORAGE_API_VERSION ||
|
||||
!storage->RegisterClientTransform)
|
||||
{
|
||||
Registrar() { wxl::host::RegisterProvider("moonwell-companion-spells", &Provide); }
|
||||
};
|
||||
|
||||
Registrar g_registrar;
|
||||
Log(WXL_LOG_ERROR, "wxl.storage v1 is unavailable");
|
||||
return false;
|
||||
}
|
||||
storage->RegisterClientTransform(".dbc", &Transform);
|
||||
Log(WXL_LOG_INFO, "DBC override transform registered");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
struct WXL_Api;
|
||||
|
||||
namespace moonwell::spells
|
||||
{
|
||||
bool Install(const WXL_Api* api);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
file(GLOB_RECURSE WXL_EXT_SHARED_SRC CONFIGURE_DEPENDS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src/engine/assets/shared/textures/blp/*.cpp")
|
||||
@@ -0,0 +1,77 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// WarcraftXL 1.1 BLP transform extension.
|
||||
|
||||
#include "engine/assets/shared/textures/blp/BlpTranscode.hpp"
|
||||
#include "wxl/ModernBlpApi.h"
|
||||
#include "wxl/PluginApi.h"
|
||||
#include "wxl/StorageApi.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace
|
||||
{
|
||||
const WXL_Api* g_api = nullptr;
|
||||
constexpr uint32_t kMaxTextureEdge = 1024;
|
||||
|
||||
int __cdecl Transcode(const char* name, const uint8_t* raw, uint32_t rawLen,
|
||||
const WXL_ByteSink* sink)
|
||||
{
|
||||
if (!raw || !sink || !sink->Write) return 0;
|
||||
namespace blp = wxl::modern::assets::textures::blp;
|
||||
|
||||
const std::span<const uint8_t> input(raw, rawLen);
|
||||
std::vector<uint8_t> capped;
|
||||
const bool didCap = blp::CapBlpMips(input, capped, kMaxTextureEdge);
|
||||
const std::span<const uint8_t> source = didCap
|
||||
? std::span<const uint8_t>(capped.data(), capped.size()) : input;
|
||||
|
||||
std::vector<uint8_t> transcoded;
|
||||
if (blp::TranscodeBlp(source, transcoded))
|
||||
{
|
||||
sink->Write(sink->ctx, transcoded.data(), static_cast<uint32_t>(transcoded.size()));
|
||||
if (g_api && g_api->Log)
|
||||
g_api->Log(WXL_LOG_DEBUG, "wxl-modern-blp", "%s%s BGRA->DXT5",
|
||||
name ? name : "?", didCap ? " capped" : "");
|
||||
return 1;
|
||||
}
|
||||
if (!didCap) return 0;
|
||||
|
||||
sink->Write(sink->ctx, capped.data(), static_cast<uint32_t>(capped.size()));
|
||||
return 1;
|
||||
}
|
||||
|
||||
const WXL_ModernBlpApi g_blpApi = {
|
||||
sizeof(WXL_ModernBlpApi), WXL_MODERN_BLP_API_VERSION, &Transcode,
|
||||
};
|
||||
}
|
||||
|
||||
const WXL_PluginInfo* __cdecl WXL_Query(void)
|
||||
{
|
||||
static const WXL_PluginInfo info = {
|
||||
sizeof(WXL_PluginInfo), WXL_API_VERSION, "wxl-modern-blp", 1, WXL_CLIENT_BUILD,
|
||||
};
|
||||
return &info;
|
||||
}
|
||||
|
||||
int __cdecl WXL_Load(const WXL_Api* api)
|
||||
{
|
||||
if (!api || api->apiVersion != WXL_API_VERSION) return 0;
|
||||
g_api = api;
|
||||
auto* storage = static_cast<WXL_StorageApi*>(
|
||||
api->GetInterface("wxl.storage", WXL_STORAGE_API_VERSION));
|
||||
if (!storage || storage->apiVersion != WXL_STORAGE_API_VERSION ||
|
||||
!storage->RegisterClientTransform)
|
||||
{
|
||||
api->Log(WXL_LOG_ERROR, "wxl-modern-blp", "wxl.storage v1 is unavailable");
|
||||
return 0;
|
||||
}
|
||||
|
||||
storage->RegisterClientTransform(".blp", &Transcode);
|
||||
api->PublishInterface("wxl.modern-blp", WXL_MODERN_BLP_API_VERSION,
|
||||
const_cast<WXL_ModernBlpApi*>(&g_blpApi));
|
||||
api->Log(WXL_LOG_INFO, "wxl-modern-blp", "BLP transcode and mip cap active");
|
||||
return 1;
|
||||
}
|
||||
@@ -84,7 +84,7 @@ function Stop-WowRuntime {
|
||||
param([Parameter(Mandatory=$true)][string]$ClientPath)
|
||||
|
||||
$clientRoot = [System.IO.Path]::GetFullPath($ClientPath).TrimEnd('\') + '\'
|
||||
$runtimeProcesses = @(Get-Process -Name "Wow", "WarcraftXLHost" -ErrorAction SilentlyContinue | Where-Object {
|
||||
$runtimeProcesses = @(Get-Process -Name "Wow" -ErrorAction SilentlyContinue | Where-Object {
|
||||
try {
|
||||
$_.Path -and $_.Path.StartsWith($clientRoot, [System.StringComparison]::OrdinalIgnoreCase)
|
||||
} catch {
|
||||
@@ -96,7 +96,7 @@ function Stop-WowRuntime {
|
||||
return
|
||||
}
|
||||
|
||||
Write-Host "Stopping the running WoW client and WarcraftXL host..."
|
||||
Write-Host "Stopping the running WoW client..."
|
||||
$runtimeProcesses | Stop-Process -Force
|
||||
|
||||
try {
|
||||
|
||||
Binary file not shown.
@@ -1,95 +0,0 @@
|
||||
{
|
||||
"fileDataID": 3730958,
|
||||
"textures": [
|
||||
{
|
||||
"fileDataID": 3732460,
|
||||
"file": "catslime_3732460.blp"
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732461,
|
||||
"file": "catslime_3732461.blp"
|
||||
}
|
||||
],
|
||||
"skins": [
|
||||
{
|
||||
"fileDataID": 3732472,
|
||||
"file": "catslime00.skin"
|
||||
}
|
||||
],
|
||||
"lodSkins": [
|
||||
{
|
||||
"fileDataID": 3732473,
|
||||
"file": "catslime_lod01.skin"
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732474,
|
||||
"file": "catslime_lod02.skin"
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732475,
|
||||
"file": "catslime_lod03.skin"
|
||||
}
|
||||
],
|
||||
"anims": [
|
||||
{
|
||||
"fileDataID": 3732462,
|
||||
"file": "catslime0097-00.anim",
|
||||
"animID": 97,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732463,
|
||||
"file": "catslime0096-00.anim",
|
||||
"animID": 96,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732464,
|
||||
"file": "catslime0098-00.anim",
|
||||
"animID": 98,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732465,
|
||||
"file": "catslime0100-00.anim",
|
||||
"animID": 100,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732466,
|
||||
"file": "catslime0099-00.anim",
|
||||
"animID": 99,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732467,
|
||||
"file": "catslime0101-00.anim",
|
||||
"animID": 101,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732468,
|
||||
"file": "catslime0069-00.anim",
|
||||
"animID": 69,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732469,
|
||||
"file": "catslime0123-00.anim",
|
||||
"animID": 123,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732470,
|
||||
"file": "catslime0061-00.anim",
|
||||
"animID": 61,
|
||||
"subAnimID": 0
|
||||
},
|
||||
{
|
||||
"fileDataID": 3732471,
|
||||
"file": "catslime0075-00.anim",
|
||||
"animID": 75,
|
||||
"subAnimID": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -4,6 +4,7 @@
|
||||
## Author: Rochet2 <https://github.com/Rochet2>
|
||||
## SavedVariables: AIO_sv, AIO_sv_Addons
|
||||
## SavedVariablesPerCharacter: AIO_sv_char
|
||||
## X-Hidden: 1
|
||||
|
||||
#dependencies
|
||||
Dep_LibWindow-1.1\LibStub.lua
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
|
||||
-- This file is only there in standalone Ace3 and provides handy dev tool stuff I guess
|
||||
-- for now only /rl to reload your UI :)
|
||||
-- note the complete overkill use of AceAddon and console, ain't it cool?
|
||||
|
||||
-- GLOBALS: next, loadstring, ReloadUI, geterrorhandler
|
||||
-- GLOBALS: BINDING_HEADER_ACE3, BINDING_NAME_RELOADUI, Ace3, LibStub
|
||||
|
||||
-- BINDINGs labels
|
||||
BINDING_HEADER_ACE3 = "Ace3"
|
||||
BINDING_NAME_RELOADUI = "ReloadUI"
|
||||
--
|
||||
|
||||
local gui = LibStub("AceGUI-3.0")
|
||||
local reg = LibStub("AceConfigRegistry-3.0")
|
||||
local dialog = LibStub("AceConfigDialog-3.0")
|
||||
|
||||
Ace3 = LibStub("AceAddon-3.0"):NewAddon("Ace3", "AceConsole-3.0")
|
||||
local Ace3 = Ace3
|
||||
|
||||
local selectedgroup
|
||||
local frame
|
||||
local select
|
||||
local status = {}
|
||||
local configs = {}
|
||||
|
||||
local function frameOnClose()
|
||||
gui:Release(frame)
|
||||
frame = nil
|
||||
end
|
||||
|
||||
local function RefreshConfigs()
|
||||
for name in reg:IterateOptionsTables() do
|
||||
configs[name] = name
|
||||
end
|
||||
end
|
||||
|
||||
local function ConfigSelected(widget, event, value)
|
||||
selectedgroup = value
|
||||
dialog:Open(value, widget)
|
||||
end
|
||||
|
||||
local old_CloseSpecialWindows
|
||||
|
||||
-- GLOBALS: CloseSpecialWindows, next
|
||||
function Ace3:Open()
|
||||
if not old_CloseSpecialWindows then
|
||||
old_CloseSpecialWindows = CloseSpecialWindows
|
||||
CloseSpecialWindows = function()
|
||||
local found = old_CloseSpecialWindows()
|
||||
if frame then
|
||||
frame:Hide()
|
||||
return true
|
||||
end
|
||||
return found
|
||||
end
|
||||
end
|
||||
RefreshConfigs()
|
||||
if next(configs) == nil then
|
||||
self:Print("No Configs are Registered")
|
||||
return
|
||||
end
|
||||
|
||||
if not frame then
|
||||
frame = gui:Create("Frame")
|
||||
frame:ReleaseChildren()
|
||||
frame:SetTitle("Ace3 Options")
|
||||
frame:SetLayout("FILL")
|
||||
frame:SetCallback("OnClose", frameOnClose)
|
||||
|
||||
select = gui:Create("DropdownGroup")
|
||||
select:SetGroupList(configs)
|
||||
select:SetCallback("OnGroupSelected", ConfigSelected)
|
||||
frame:AddChild(select)
|
||||
end
|
||||
if not selectedgroup then
|
||||
selectedgroup = next(configs)
|
||||
end
|
||||
select:SetGroup(selectedgroup)
|
||||
frame:Show()
|
||||
end
|
||||
|
||||
local function RefreshOnUpdate(this)
|
||||
select:SetGroup(selectedgroup)
|
||||
this:SetScript("OnUpdate", nil)
|
||||
end
|
||||
|
||||
function Ace3:ConfigTableChanged(event, appName)
|
||||
if selectedgroup == appName and frame then
|
||||
frame.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
||||
end
|
||||
end
|
||||
|
||||
reg.RegisterCallback(Ace3, "ConfigTableChange", "ConfigTableChanged")
|
||||
|
||||
function Ace3:PrintCmd(input)
|
||||
input = input:trim():match("^(.-);*$")
|
||||
local func, err = loadstring("LibStub(\"AceConsole-3.0\"):Print(" .. input .. ")")
|
||||
if not func then
|
||||
LibStub("AceConsole-3.0"):Print("Error: " .. err)
|
||||
else
|
||||
func()
|
||||
end
|
||||
end
|
||||
|
||||
function Ace3:OnInitialize()
|
||||
self:RegisterChatCommand("ace3", function() self:Open() end)
|
||||
self:RegisterChatCommand("rl", function() ReloadUI() end)
|
||||
self:RegisterChatCommand("print", "PrintCmd")
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
## Interface: 30300
|
||||
## X-Curse-Packaged-Version: r922
|
||||
## X-Curse-Project-Name: Ace3
|
||||
## X-Curse-Project-ID: ace3
|
||||
## X-Curse-Repository-ID: wow/ace3/mainline
|
||||
|
||||
## Title: Lib: Ace3
|
||||
## Notes: AddOn development framework
|
||||
## Author: Ace3 Development Team
|
||||
## Dependencies: LibStub, CallbackHandler-1.0
|
||||
## X-Website: http://www.wowace.com
|
||||
## X-Category: Library
|
||||
## X-License: Limited BSD
|
||||
|
||||
AceAddon-3.0\AceAddon-3.0.xml
|
||||
AceEvent-3.0\AceEvent-3.0.xml
|
||||
AceTimer-3.0\AceTimer-3.0.xml
|
||||
AceBucket-3.0\AceBucket-3.0.xml
|
||||
AceHook-3.0\AceHook-3.0.xml
|
||||
AceDB-3.0\AceDB-3.0.xml
|
||||
AceDBOptions-3.0\AceDBOptions-3.0.xml
|
||||
AceLocale-3.0\AceLocale-3.0.xml
|
||||
AceConsole-3.0\AceConsole-3.0.xml
|
||||
AceGUI-3.0\AceGUI-3.0.xml
|
||||
AceConfig-3.0\AceConfig-3.0.xml
|
||||
AceComm-3.0\AceComm-3.0.xml
|
||||
AceTab-3.0\AceTab-3.0.xml
|
||||
AceSerializer-3.0\AceSerializer-3.0.xml
|
||||
|
||||
Ace3.lua
|
||||
+4
-4
@@ -3,7 +3,7 @@
|
||||
-- as well as associate it with a slash command.
|
||||
-- @class file
|
||||
-- @name AceConfig-3.0
|
||||
-- @release $Id: AceConfig-3.0.lua 969 2010-10-07 02:11:48Z shefki $
|
||||
-- @release $Id: AceConfig-3.0.lua 877 2009-11-02 15:56:50Z nevcairiel $
|
||||
|
||||
--[[
|
||||
AceConfig-3.0
|
||||
@@ -19,8 +19,8 @@ if not AceConfig then return end
|
||||
|
||||
local cfgreg = LibStub("AceConfigRegistry-3.0")
|
||||
local cfgcmd = LibStub("AceConfigCmd-3.0")
|
||||
--TODO: local cfgdlg = LibStub("AceConfigDialog-3.0", true)
|
||||
--TODO: local cfgdrp = LibStub("AceConfigDropdown-3.0", true)
|
||||
local cfgdlg = LibStub("AceConfigDialog-3.0")
|
||||
--TODO: local cfgdrp = LibStub("AceConfigDropdown-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local pcall, error, type, pairs = pcall, error, type, pairs
|
||||
@@ -36,7 +36,7 @@ local pcall, error, type, pairs = pcall, error, type, pairs
|
||||
-- You can supply a slash command (or a table of slash commands) to register with AceConfigCmd directly.
|
||||
-- @paramsig appName, options [, slashcmd]
|
||||
-- @param appName The application name for the config table.
|
||||
-- @param options The option table (or a function to generate one on demand). http://www.wowace.com/addons/ace3/pages/ace-config-3-0-options-tables/
|
||||
-- @param options The option table (or a function to generate one on demand)
|
||||
-- @param slashcmd A slash command to register for the option table, or a table of slash commands.
|
||||
-- @usage
|
||||
-- local AceConfig = LibStub("AceConfig-3.0")
|
||||
+56
-68
@@ -1,10 +1,10 @@
|
||||
--- AceConfigDialog-3.0 generates AceGUI-3.0 based windows based on option tables.
|
||||
-- @class file
|
||||
-- @name AceConfigDialog-3.0
|
||||
-- @release $Id: AceConfigDialog-3.0.lua 967 2010-09-25 08:20:55Z nevcairiel $
|
||||
-- @release $Id: AceConfigDialog-3.0.lua 921 2010-05-09 15:49:14Z nevcairiel $
|
||||
|
||||
local LibStub = LibStub
|
||||
local MAJOR, MINOR = "AceConfigDialog-3.0", 50
|
||||
local MAJOR, MINOR = "AceConfigDialog-3.0", 47
|
||||
local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceConfigDialog then return end
|
||||
@@ -15,7 +15,6 @@ AceConfigDialog.frame = AceConfigDialog.frame or CreateFrame("Frame")
|
||||
|
||||
AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {}
|
||||
AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
|
||||
AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {}
|
||||
|
||||
local gui = LibStub("AceGUI-3.0")
|
||||
local reg = LibStub("AceConfigRegistry-3.0")
|
||||
@@ -24,8 +23,8 @@ local reg = LibStub("AceConfigRegistry-3.0")
|
||||
local tconcat, tinsert, tsort, tremove = table.concat, table.insert, table.sort, table.remove
|
||||
local strmatch, format = string.match, string.format
|
||||
local assert, loadstring, error = assert, loadstring, error
|
||||
local pairs, next, select, type, unpack, wipe = pairs, next, select, type, unpack, wipe
|
||||
local rawset, tostring, tonumber = rawset, tostring, tonumber
|
||||
local pairs, next, select, type, unpack = pairs, next, select, type, unpack
|
||||
local rawset, tostring = rawset, tostring
|
||||
local math_min, math_max, math_floor = math.min, math.max, math.floor
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
@@ -77,7 +76,7 @@ Dispatchers[0] = function(func)
|
||||
end
|
||||
|
||||
local function safecall(func, ...)
|
||||
return Dispatchers[select("#", ...)](func, ...)
|
||||
return Dispatchers[select('#', ...)](func, ...)
|
||||
end
|
||||
|
||||
local width_multiplier = 170
|
||||
@@ -104,7 +103,7 @@ Group Types
|
||||
local new, del, copy
|
||||
--newcount, delcount,createdcount,cached = 0,0,0
|
||||
do
|
||||
local pool = setmetatable({},{__mode="k"})
|
||||
local pool = setmetatable({},{__mode='k'})
|
||||
function new()
|
||||
--newcount = newcount + 1
|
||||
local t = next(pool)
|
||||
@@ -241,7 +240,7 @@ local function GetOptionsMemberValue(membername, option, options, path, appName,
|
||||
info.handler = handler
|
||||
info.option = option
|
||||
info.type = option.type
|
||||
info.uiType = "dialog"
|
||||
info.uiType = 'dialog'
|
||||
info.uiName = MAJOR
|
||||
|
||||
local a, b, c ,d
|
||||
@@ -322,8 +321,8 @@ local function compareOptions(a,b)
|
||||
end
|
||||
local OrderA, OrderB = tempOrders[a] or 100, tempOrders[b] or 100
|
||||
if OrderA == OrderB then
|
||||
local NameA = (type(tempNames[a]) == "string") and tempNames[a] or ""
|
||||
local NameB = (type(tempNames[b]) == "string") and tempNames[b] or ""
|
||||
local NameA = (type(tempNames[a] == "string") and tempNames[a]) or ""
|
||||
local NameB = (type(tempNames[b] == "string") and tempNames[b]) or ""
|
||||
return NameA:upper() < NameB:upper()
|
||||
end
|
||||
if OrderA < 0 then
|
||||
@@ -479,7 +478,7 @@ function AceConfigDialog:SelectGroup(appName, ...)
|
||||
local treevalue
|
||||
local treestatus
|
||||
|
||||
for n = 1, select("#",...) do
|
||||
for n = 1, select('#',...) do
|
||||
local key = select(n, ...)
|
||||
|
||||
if group.childGroups == "tab" or group.childGroups == "select" then
|
||||
@@ -543,7 +542,7 @@ local function OptionOnMouseOver(widget, event)
|
||||
|
||||
GameTooltip:SetText(name, 1, .82, 0, 1)
|
||||
|
||||
if opt.type == "multiselect" then
|
||||
if opt.type == 'multiselect' then
|
||||
GameTooltip:AddLine(user.text,0.5, 0.5, 0.8, 1)
|
||||
end
|
||||
if type(desc) == "string" then
|
||||
@@ -562,10 +561,10 @@ end
|
||||
|
||||
local function GetFuncName(option)
|
||||
local type = option.type
|
||||
if type == "execute" then
|
||||
return "func"
|
||||
if type == 'execute' then
|
||||
return 'func'
|
||||
else
|
||||
return "set"
|
||||
return 'set'
|
||||
end
|
||||
end
|
||||
local function confirmPopup(appName, rootframe, basepath, info, message, func, ...)
|
||||
@@ -595,7 +594,7 @@ local function confirmPopup(appName, rootframe, basepath, info, message, func, .
|
||||
AceConfigDialog:Open(appName, rootframe, unpack(basepath or emptyTbl))
|
||||
del(info)
|
||||
end
|
||||
for i = 1, select("#", ...) do
|
||||
for i = 1, select('#', ...) do
|
||||
t[i] = select(i, ...) or false
|
||||
end
|
||||
t.timeout = 0
|
||||
@@ -654,7 +653,7 @@ local function ActivateControl(widget, event, ...)
|
||||
info.handler = handler
|
||||
info.option = option
|
||||
info.type = option.type
|
||||
info.uiType = "dialog"
|
||||
info.uiType = 'dialog'
|
||||
info.uiName = MAJOR
|
||||
|
||||
local name
|
||||
@@ -765,13 +764,13 @@ local function ActivateControl(widget, event, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local iscustom = user.rootframe:GetUserData("iscustom")
|
||||
local iscustom = user.rootframe:GetUserData('iscustom')
|
||||
local rootframe
|
||||
|
||||
if iscustom then
|
||||
rootframe = user.rootframe
|
||||
end
|
||||
local basepath = user.rootframe:GetUserData("basepath")
|
||||
local basepath = user.rootframe:GetUserData('basepath')
|
||||
if type(func) == "string" then
|
||||
if handler and handler[func] then
|
||||
confirmPopup(user.appName, rootframe, basepath, info, confirmText, handler[func], handler, info, ...)
|
||||
@@ -799,8 +798,8 @@ local function ActivateControl(widget, event, ...)
|
||||
|
||||
|
||||
|
||||
local iscustom = user.rootframe:GetUserData("iscustom")
|
||||
local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
|
||||
local iscustom = user.rootframe:GetUserData('iscustom')
|
||||
local basepath = user.rootframe:GetUserData('basepath') or emptyTbl
|
||||
--full refresh of the frame, some controls dont cause this on all events
|
||||
if option.type == "color" then
|
||||
if event == "OnValueConfirmed" then
|
||||
@@ -835,16 +834,13 @@ local function ActivateControl(widget, event, ...)
|
||||
end
|
||||
|
||||
local function ActivateSlider(widget, event, value)
|
||||
local option = widget:GetUserData("option")
|
||||
local option = widget:GetUserData('option')
|
||||
local min, max, step = option.min or (not option.softMin and 0 or nil), option.max or (not option.softMax and 100 or nil), option.step
|
||||
if min then
|
||||
if min and max then
|
||||
if step then
|
||||
value = math_floor((value - min) / step + 0.5) * step + min
|
||||
end
|
||||
value = math_max(value, min)
|
||||
end
|
||||
if max then
|
||||
value = math_min(value, max)
|
||||
value = math_max(math_min(value,max),min)
|
||||
end
|
||||
ActivateControl(widget,event,value)
|
||||
end
|
||||
@@ -852,10 +848,10 @@ end
|
||||
--called from a checkbox that is part of an internally created multiselect group
|
||||
--this type is safe to refresh on activation of one control
|
||||
local function ActivateMultiControl(widget, event, ...)
|
||||
ActivateControl(widget, event, widget:GetUserData("value"), ...)
|
||||
ActivateControl(widget, event, widget:GetUserData('value'), ...)
|
||||
local user = widget:GetUserDataTable()
|
||||
local iscustom = user.rootframe:GetUserData("iscustom")
|
||||
local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
|
||||
local iscustom = user.rootframe:GetUserData('iscustom')
|
||||
local basepath = user.rootframe:GetUserData('basepath') or emptyTbl
|
||||
if iscustom then
|
||||
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
|
||||
else
|
||||
@@ -866,8 +862,8 @@ end
|
||||
local function MultiControlOnClosed(widget, event, ...)
|
||||
local user = widget:GetUserDataTable()
|
||||
if user.valuechanged then
|
||||
local iscustom = user.rootframe:GetUserData("iscustom")
|
||||
local basepath = user.rootframe:GetUserData("basepath") or emptyTbl
|
||||
local iscustom = user.rootframe:GetUserData('iscustom')
|
||||
local basepath = user.rootframe:GetUserData('basepath') or emptyTbl
|
||||
if iscustom then
|
||||
AceConfigDialog:Open(user.appName, user.rootframe, unpack(basepath))
|
||||
else
|
||||
@@ -877,7 +873,7 @@ local function MultiControlOnClosed(widget, event, ...)
|
||||
end
|
||||
|
||||
local function FrameOnClose(widget, event)
|
||||
local appName = widget:GetUserData("appName")
|
||||
local appName = widget:GetUserData('appName')
|
||||
AceConfigDialog.OpenFrames[appName] = nil
|
||||
gui:Release(widget)
|
||||
end
|
||||
@@ -1088,7 +1084,7 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin
|
||||
local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
|
||||
local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
|
||||
|
||||
if type(image) == "string" then
|
||||
if type(image) == 'string' then
|
||||
control = gui:Create("Icon")
|
||||
if not width then
|
||||
width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
|
||||
@@ -1096,7 +1092,7 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin
|
||||
if not height then
|
||||
height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
|
||||
end
|
||||
if type(imageCoords) == "table" then
|
||||
if type(imageCoords) == 'table' then
|
||||
control:SetImage(image, unpack(imageCoords))
|
||||
else
|
||||
control:SetImage(image)
|
||||
@@ -1150,8 +1146,8 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin
|
||||
local image = GetOptionsMemberValue("image", v, options, path, appName)
|
||||
local imageCoords = GetOptionsMemberValue("imageCoords", v, options, path, appName)
|
||||
|
||||
if type(image) == "string" then
|
||||
if type(imageCoords) == "table" then
|
||||
if type(image) == 'string' then
|
||||
if type(imageCoords) == 'table' then
|
||||
control:SetImage(image, unpack(imageCoords))
|
||||
else
|
||||
control:SetImage(image)
|
||||
@@ -1243,8 +1239,8 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin
|
||||
local text = values[value]
|
||||
local check = gui:Create("CheckBox")
|
||||
check:SetLabel(text)
|
||||
check:SetUserData("value", value)
|
||||
check:SetUserData("text", text)
|
||||
check:SetUserData('value', value)
|
||||
check:SetUserData('text', text)
|
||||
check:SetDisabled(disabled)
|
||||
check:SetTriState(v.tristate)
|
||||
check:SetValue(GetOptionsMemberValue("get",v, options, path, appName, value))
|
||||
@@ -1304,14 +1300,14 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin
|
||||
local imageCoords = GetOptionsMemberValue("imageCoords",v, options, path, appName)
|
||||
local image, width, height = GetOptionsMemberValue("image",v, options, path, appName)
|
||||
|
||||
if type(image) == "string" then
|
||||
if type(image) == 'string' then
|
||||
if not width then
|
||||
width = GetOptionsMemberValue("imageWidth",v, options, path, appName)
|
||||
end
|
||||
if not height then
|
||||
height = GetOptionsMemberValue("imageHeight",v, options, path, appName)
|
||||
end
|
||||
if type(imageCoords) == "table" then
|
||||
if type(imageCoords) == 'table' then
|
||||
control:SetImage(image, unpack(imageCoords))
|
||||
else
|
||||
control:SetImage(image)
|
||||
@@ -1362,7 +1358,7 @@ local function FeedOptions(appName, options,container,rootframe,path,group,inlin
|
||||
end
|
||||
|
||||
local function BuildPath(path, ...)
|
||||
for i = 1, select("#",...) do
|
||||
for i = 1, select('#',...) do
|
||||
tinsert(path, (select(i,...)))
|
||||
end
|
||||
end
|
||||
@@ -1665,12 +1661,9 @@ local function RefreshOnUpdate(this)
|
||||
|
||||
if this.closeAll then
|
||||
for k, v in pairs(AceConfigDialog.OpenFrames) do
|
||||
if not this.closeAllOverride[k] then
|
||||
v:Hide()
|
||||
end
|
||||
v:Hide()
|
||||
end
|
||||
this.closeAll = nil
|
||||
wipe(this.closeAllOverride)
|
||||
end
|
||||
|
||||
for appName in pairs(this.apps) do
|
||||
@@ -1682,7 +1675,7 @@ local function RefreshOnUpdate(this)
|
||||
for key, widget in pairs(AceConfigDialog.BlizOptions[appName]) do
|
||||
local user = widget:GetUserDataTable()
|
||||
if widget:IsVisible() then
|
||||
AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(user.basepath or emptyTbl))
|
||||
AceConfigDialog:Open(widget:GetUserData('appName'), widget, unpack(user.basepath or emptyTbl))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1767,7 +1760,7 @@ function AceConfigDialog:Open(appName, container, ...)
|
||||
tinsert(path, container)
|
||||
container = nil
|
||||
end
|
||||
for n = 1, select("#",...) do
|
||||
for n = 1, select('#',...) do
|
||||
tinsert(path, (select(n, ...)))
|
||||
end
|
||||
|
||||
@@ -1775,10 +1768,10 @@ function AceConfigDialog:Open(appName, container, ...)
|
||||
if container then
|
||||
f = container
|
||||
f:ReleaseChildren()
|
||||
f:SetUserData("appName", appName)
|
||||
f:SetUserData("iscustom", true)
|
||||
f:SetUserData('appName', appName)
|
||||
f:SetUserData('iscustom', true)
|
||||
if #path > 0 then
|
||||
f:SetUserData("basepath", copy(path))
|
||||
f:SetUserData('basepath', copy(path))
|
||||
end
|
||||
local status = AceConfigDialog:GetStatusTable(appName)
|
||||
if not status.width then
|
||||
@@ -1802,9 +1795,9 @@ function AceConfigDialog:Open(appName, container, ...)
|
||||
end
|
||||
f:ReleaseChildren()
|
||||
f:SetCallback("OnClose", FrameOnClose)
|
||||
f:SetUserData("appName", appName)
|
||||
f:SetUserData('appName', appName)
|
||||
if #path > 0 then
|
||||
f:SetUserData("basepath", copy(path))
|
||||
f:SetUserData('basepath', copy(path))
|
||||
end
|
||||
f:SetTitle(name or "")
|
||||
local status = AceConfigDialog:GetStatusTable(appName)
|
||||
@@ -1816,11 +1809,6 @@ function AceConfigDialog:Open(appName, container, ...)
|
||||
f:Show()
|
||||
end
|
||||
del(path)
|
||||
|
||||
if AceConfigDialog.frame.closeAll then
|
||||
-- close all is set, but thats not good, since we're just opening here, so force it
|
||||
AceConfigDialog.frame.closeAllOverride[appName] = true
|
||||
end
|
||||
end
|
||||
|
||||
-- convert pre-39 BlizOptions structure to the new format
|
||||
@@ -1828,7 +1816,7 @@ if oldminor and oldminor < 39 and AceConfigDialog.BlizOptions then
|
||||
local old = AceConfigDialog.BlizOptions
|
||||
local new = {}
|
||||
for key, widget in pairs(old) do
|
||||
local appName = widget:GetUserData("appName")
|
||||
local appName = widget:GetUserData('appName')
|
||||
if not new[appName] then new[appName] = {} end
|
||||
new[appName][key] = widget
|
||||
end
|
||||
@@ -1838,12 +1826,12 @@ else
|
||||
end
|
||||
|
||||
local function FeedToBlizPanel(widget, event)
|
||||
local path = widget:GetUserData("path")
|
||||
AceConfigDialog:Open(widget:GetUserData("appName"), widget, unpack(path or emptyTbl))
|
||||
local path = widget:GetUserData('path')
|
||||
AceConfigDialog:Open(widget:GetUserData('appName'), widget, unpack(path or emptyTbl))
|
||||
end
|
||||
|
||||
local function ClearBlizPanel(widget, event)
|
||||
local appName = widget:GetUserData("appName")
|
||||
local appName = widget:GetUserData('appName')
|
||||
AceConfigDialog.frame.closing[appName] = true
|
||||
AceConfigDialog.frame:SetScript("OnUpdate", RefreshOnUpdate)
|
||||
end
|
||||
@@ -1869,8 +1857,8 @@ function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
|
||||
local BlizOptions = AceConfigDialog.BlizOptions
|
||||
|
||||
local key = appName
|
||||
for n = 1, select("#", ...) do
|
||||
key = key.."\001"..select(n, ...)
|
||||
for n = 1, select('#', ...) do
|
||||
key = key..'\001'..select(n, ...)
|
||||
end
|
||||
|
||||
if not BlizOptions[appName] then
|
||||
@@ -1883,13 +1871,13 @@ function AceConfigDialog:AddToBlizOptions(appName, name, parent, ...)
|
||||
group:SetName(name or appName, parent)
|
||||
|
||||
group:SetTitle(name or appName)
|
||||
group:SetUserData("appName", appName)
|
||||
if select("#", ...) > 0 then
|
||||
group:SetUserData('appName', appName)
|
||||
if select('#', ...) > 0 then
|
||||
local path = {}
|
||||
for n = 1, select("#",...) do
|
||||
for n = 1, select('#',...) do
|
||||
tinsert(path, (select(n, ...)))
|
||||
end
|
||||
group:SetUserData("path", path)
|
||||
group:SetUserData('path', path)
|
||||
end
|
||||
group:SetCallback("OnShow", FeedToBlizPanel)
|
||||
group:SetCallback("OnHide", ClearBlizPanel)
|
||||
+6
-6
@@ -39,8 +39,8 @@
|
||||
-- end
|
||||
-- @class file
|
||||
-- @name AceDB-3.0.lua
|
||||
-- @release $Id: AceDB-3.0.lua 940 2010-06-19 08:01:47Z nevcairiel $
|
||||
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 21
|
||||
-- @release $Id: AceDB-3.0.lua 914 2010-03-08 12:09:22Z nevcairiel $
|
||||
local ACEDB_MAJOR, ACEDB_MINOR = "AceDB-3.0", 20
|
||||
local AceDB, oldminor = LibStub:NewLibrary(ACEDB_MAJOR, ACEDB_MINOR)
|
||||
|
||||
if not AceDB then return end -- No upgrade needed
|
||||
@@ -507,11 +507,11 @@ function DBObjectLib:DeleteProfile(name, silent)
|
||||
error("Cannot delete the active profile in an AceDBObject.", 2)
|
||||
end
|
||||
|
||||
if not rawget(self.profiles, name) and not silent then
|
||||
if not rawget(self.sv.profiles, name) and not silent then
|
||||
error("Cannot delete profile '" .. name .. "'. It does not exist.", 2)
|
||||
end
|
||||
|
||||
self.profiles[name] = nil
|
||||
self.sv.profiles[name] = nil
|
||||
|
||||
-- populate to child namespaces
|
||||
if self.children then
|
||||
@@ -537,7 +537,7 @@ function DBObjectLib:CopyProfile(name, silent)
|
||||
error("Cannot have the same source and destination profiles.", 2)
|
||||
end
|
||||
|
||||
if not rawget(self.profiles, name) and not silent then
|
||||
if not rawget(self.sv.profiles, name) and not silent then
|
||||
error("Cannot copy profile '" .. name .. "'. It does not exist.", 2)
|
||||
end
|
||||
|
||||
@@ -545,7 +545,7 @@ function DBObjectLib:CopyProfile(name, silent)
|
||||
DBObjectLib.ResetProfile(self, nil, true)
|
||||
|
||||
local profile = self.profile
|
||||
local source = self.profiles[name]
|
||||
local source = self.sv.profiles[name]
|
||||
|
||||
copyTable(source, profile)
|
||||
|
||||
+3
-3
@@ -1,8 +1,8 @@
|
||||
--- AceDBOptions-3.0 provides a universal AceConfig options screen for managing AceDB-3.0 profiles.
|
||||
-- @class file
|
||||
-- @name AceDBOptions-3.0
|
||||
-- @release $Id: AceDBOptions-3.0.lua 938 2010-06-13 07:21:38Z nevcairiel $
|
||||
local ACEDBO_MAJOR, ACEDBO_MINOR = "AceDBOptions-3.0", 12
|
||||
-- @release $Id: AceDBOptions-3.0.lua 895 2009-12-06 16:28:55Z nevcairiel $
|
||||
local ACEDBO_MAJOR, ACEDBO_MINOR = "AceDBOptions-3.0", 11
|
||||
local AceDBOptions, oldminor = LibStub:NewLibrary(ACEDBO_MAJOR, ACEDBO_MINOR)
|
||||
|
||||
if not AceDBOptions then return end -- No upgrade needed
|
||||
@@ -30,7 +30,7 @@ local L = {
|
||||
reset_desc = "Reset the current profile back to its default values, in case your configuration is broken, or you simply want to start over.",
|
||||
reset = "Reset Profile",
|
||||
reset_sub = "Reset the current profile to the default",
|
||||
choose_desc = "You can either create a new profile by entering a name in the editbox, or choose one of the already existing profiles.",
|
||||
choose_desc = "You can either create a new profile by entering a name in the editbox, or choose one of the already exisiting profiles.",
|
||||
new = "New",
|
||||
new_sub = "Create a new empty profile.",
|
||||
choose = "Existing Profiles",
|
||||
+141
-70
@@ -5,7 +5,7 @@
|
||||
--
|
||||
-- **Note**: When using AceGUI-3.0 directly, please do not modify the frames of the widgets directly,
|
||||
-- as any "unknown" change to the widgets will cause addons that get your widget out of the widget pool
|
||||
-- to misbehave. If you think some part of a widget should be modifiable, please open a ticket, and we"ll
|
||||
-- to misbehave. If you think some part of a widget should be modifiable, please open a ticket, and we'll
|
||||
-- implement a proper API to modify it.
|
||||
-- @usage
|
||||
-- local AceGUI = LibStub("AceGUI-3.0")
|
||||
@@ -24,8 +24,8 @@
|
||||
-- f:AddChild(btn)
|
||||
-- @class file
|
||||
-- @name AceGUI-3.0
|
||||
-- @release $Id: AceGUI-3.0.lua 924 2010-05-13 15:12:20Z nevcairiel $
|
||||
local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 33
|
||||
-- @release $Id: AceGUI-3.0.lua 919 2010-05-09 11:36:03Z nevcairiel $
|
||||
local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 31
|
||||
local AceGUI, oldminor = LibStub:NewLibrary(ACEGUI_MAJOR, ACEGUI_MINOR)
|
||||
|
||||
if not AceGUI then return end -- No upgrade needed
|
||||
@@ -98,7 +98,7 @@ Dispatchers[0] = function(func)
|
||||
end
|
||||
|
||||
local function safecall(func, ...)
|
||||
return Dispatchers[select("#", ...)](func, ...)
|
||||
return Dispatchers[select('#', ...)](func, ...)
|
||||
end
|
||||
|
||||
-- Recycling functions
|
||||
@@ -173,16 +173,16 @@ function AceGUI:Create(type)
|
||||
if WidgetRegistry[type] then
|
||||
local widget = newWidget(type)
|
||||
|
||||
if rawget(widget, "Acquire") then
|
||||
if rawget(widget,'Acquire') then
|
||||
widget.OnAcquire = widget.Acquire
|
||||
widget.Acquire = nil
|
||||
elseif rawget(widget, "Aquire") then
|
||||
elseif rawget(widget,'Aquire') then
|
||||
widget.OnAcquire = widget.Aquire
|
||||
widget.Aquire = nil
|
||||
end
|
||||
|
||||
if rawget(widget, "Release") then
|
||||
widget.OnRelease = rawget(widget, "Release")
|
||||
if rawget(widget,'Release') then
|
||||
widget.OnRelease = rawget(widget,'Release')
|
||||
widget.Release = nil
|
||||
end
|
||||
|
||||
@@ -191,8 +191,8 @@ function AceGUI:Create(type)
|
||||
else
|
||||
error(("Widget type %s doesn't supply an OnAcquire Function"):format(type))
|
||||
end
|
||||
-- Set the default Layout ("List")
|
||||
safecall(widget.SetLayout, widget, "List")
|
||||
-- Set the default Layout ('List')
|
||||
safecall(widget.SetLayout, widget, 'List')
|
||||
safecall(widget.ResumeLayout, widget)
|
||||
return widget
|
||||
end
|
||||
@@ -204,14 +204,14 @@ end
|
||||
-- If this widget is a Container-Widget, all of its Child-Widgets will be releases as well.
|
||||
-- @param widget The widget to release
|
||||
function AceGUI:Release(widget)
|
||||
safecall(widget.PauseLayout, widget)
|
||||
safecall( widget.PauseLayout, widget )
|
||||
widget:Fire("OnRelease")
|
||||
safecall(widget.ReleaseChildren, widget)
|
||||
safecall( widget.ReleaseChildren, widget )
|
||||
|
||||
if widget.OnRelease then
|
||||
widget:OnRelease()
|
||||
-- else
|
||||
-- error(("Widget type %s doesn't supply an OnRelease Function"):format(widget.type))
|
||||
else
|
||||
error(("Widget type %s doesn't supply an OnRelease Function"):format(widget.type))
|
||||
end
|
||||
for k in pairs(widget.userdata) do
|
||||
widget.userdata[k] = nil
|
||||
@@ -267,6 +267,7 @@ end
|
||||
--[[
|
||||
Widgets must provide the following functions
|
||||
OnAcquire() - Called when the object is acquired, should set everything to a default hidden state
|
||||
OnRelease() - Called when the object is Released, should remove any anchors and hide the Widget
|
||||
|
||||
And the following members
|
||||
frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
||||
@@ -280,7 +281,6 @@ end
|
||||
content - frame or derivitive that children will be anchored to
|
||||
|
||||
The Widget can supply the following Optional Members
|
||||
:OnRelease() - Called when the object is Released, should remove any additional anchors and clear any data
|
||||
:OnWidthSet(width) - Called when the width of the widget is changed
|
||||
:OnHeightSet(height) - Called when the height of the widget is changed
|
||||
Widgets should not use the OnSizeChanged events of thier frame or content members, use these methods instead
|
||||
@@ -294,6 +294,17 @@ end
|
||||
-- Widget Base Template --
|
||||
--------------------------
|
||||
do
|
||||
local function fixlevels(parent,...)
|
||||
local i = 1
|
||||
local child = select(i, ...)
|
||||
while child do
|
||||
child:SetFrameLevel(parent:GetFrameLevel()+1)
|
||||
fixlevels(child, child:GetChildren())
|
||||
i = i + 1
|
||||
child = select(i, ...)
|
||||
end
|
||||
end
|
||||
|
||||
local WidgetBase = AceGUI.WidgetBase
|
||||
|
||||
WidgetBase.SetParent = function(self, parent)
|
||||
@@ -301,6 +312,7 @@ do
|
||||
frame:SetParent(nil)
|
||||
frame:SetParent(parent.content)
|
||||
self.parent = parent
|
||||
--fixlevels(parent.frame,parent.frame:GetChildren())
|
||||
end
|
||||
|
||||
WidgetBase.SetCallback = function(self, name, func)
|
||||
@@ -433,7 +445,7 @@ do
|
||||
if self.LayoutPaused then
|
||||
return
|
||||
end
|
||||
safecall(self.LayoutFunc, self.content, self.children)
|
||||
safecall(self.LayoutFunc,self.content, self.children)
|
||||
end
|
||||
|
||||
--call this function to layout, makes sure layed out objects get a frame to get sizes etc
|
||||
@@ -512,7 +524,7 @@ do
|
||||
end
|
||||
end
|
||||
|
||||
setmetatable(WidgetContainerBase, {__index=WidgetBase})
|
||||
setmetatable(WidgetContainerBase,{__index=WidgetBase})
|
||||
|
||||
--One of these function should be called on each Widget Instance as part of its creation process
|
||||
|
||||
@@ -525,11 +537,10 @@ do
|
||||
widget.base = WidgetContainerBase
|
||||
widget.content.obj = widget
|
||||
widget.frame.obj = widget
|
||||
widget.content:SetScript("OnSizeChanged", ContentResize)
|
||||
widget.frame:SetScript("OnSizeChanged", FrameResize)
|
||||
setmetatable(widget, {__index = WidgetContainerBase})
|
||||
widget.content:SetScript("OnSizeChanged",ContentResize)
|
||||
widget.frame:SetScript("OnSizeChanged",FrameResize)
|
||||
setmetatable(widget,{__index=WidgetContainerBase})
|
||||
widget:SetLayout("List")
|
||||
return widget
|
||||
end
|
||||
|
||||
--- Register a widget-class as a widget.
|
||||
@@ -539,9 +550,8 @@ do
|
||||
widget.events = {}
|
||||
widget.base = WidgetBase
|
||||
widget.frame.obj = widget
|
||||
widget.frame:SetScript("OnSizeChanged", FrameResize)
|
||||
setmetatable(widget, {__index = WidgetBase})
|
||||
return widget
|
||||
widget.frame:SetScript("OnSizeChanged",FrameResize)
|
||||
setmetatable(widget,{__index=WidgetBase})
|
||||
end
|
||||
end
|
||||
|
||||
@@ -614,6 +624,50 @@ function AceGUI:GetWidgetVersion(type)
|
||||
return WidgetVersions[type]
|
||||
end
|
||||
|
||||
--[[ Widget Template
|
||||
|
||||
--------------------------
|
||||
-- Widget Name --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "Type"
|
||||
|
||||
local function OnAcquire(self)
|
||||
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
--Container Support
|
||||
--local content = CreateFrame("Frame",nil,frame)
|
||||
--self.content = content
|
||||
|
||||
--AceGUI:RegisterAsContainer(self)
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor)
|
||||
end
|
||||
|
||||
|
||||
]]
|
||||
|
||||
-------------
|
||||
-- Layouts --
|
||||
-------------
|
||||
@@ -622,13 +676,15 @@ end
|
||||
A Layout is a func that takes 2 parameters
|
||||
content - the frame that widgets will be placed inside
|
||||
children - a table containing the widgets to layout
|
||||
|
||||
]]
|
||||
|
||||
-- Very simple Layout, Children are stacked on top of each other down the left side
|
||||
AceGUI:RegisterLayout("List",
|
||||
function(content, children)
|
||||
local height = 0
|
||||
local width = content.width or content:GetWidth() or 0
|
||||
function(content, children)
|
||||
|
||||
local height = 0
|
||||
local width = content.width or content:GetWidth() or 0
|
||||
for i = 1, #children do
|
||||
local child = children[i]
|
||||
|
||||
@@ -636,21 +692,25 @@ AceGUI:RegisterLayout("List",
|
||||
frame:ClearAllPoints()
|
||||
frame:Show()
|
||||
if i == 1 then
|
||||
frame:SetPoint("TOPLEFT", content)
|
||||
frame:SetPoint("TOPLEFT",content,"TOPLEFT",0,0)
|
||||
else
|
||||
frame:SetPoint("TOPLEFT", children[i-1].frame, "BOTTOMLEFT")
|
||||
frame:SetPoint("TOPLEFT",children[i-1].frame,"BOTTOMLEFT",0,0)
|
||||
end
|
||||
|
||||
if child.width == "fill" then
|
||||
child:SetWidth(width)
|
||||
frame:SetPoint("RIGHT", content)
|
||||
|
||||
frame:SetPoint("RIGHT",content,"RIGHT")
|
||||
if child.OnWidthSet then
|
||||
child:OnWidthSet(content.width or content:GetWidth())
|
||||
end
|
||||
if child.DoLayout then
|
||||
child:DoLayout()
|
||||
end
|
||||
elseif child.width == "relative" then
|
||||
child:SetWidth(width * child.relWidth)
|
||||
|
||||
if child.OnWidthSet then
|
||||
child:OnWidthSet(content.width or content:GetWidth())
|
||||
end
|
||||
if child.DoLayout then
|
||||
child:DoLayout()
|
||||
end
|
||||
@@ -658,43 +718,45 @@ AceGUI:RegisterLayout("List",
|
||||
|
||||
height = height + (frame.height or frame:GetHeight() or 0)
|
||||
end
|
||||
safecall(content.obj.LayoutFinished, content.obj, nil, height)
|
||||
end)
|
||||
|
||||
safecall( content.obj.LayoutFinished, content.obj, nil, height )
|
||||
end
|
||||
)
|
||||
|
||||
-- A single control fills the whole content area
|
||||
AceGUI:RegisterLayout("Fill",
|
||||
function(content, children)
|
||||
function(content, children)
|
||||
if children[1] then
|
||||
children[1]:SetWidth(content:GetWidth() or 0)
|
||||
children[1]:SetHeight(content:GetHeight() or 0)
|
||||
children[1].frame:SetAllPoints(content)
|
||||
children[1].frame:Show()
|
||||
safecall(content.obj.LayoutFinished, content.obj, nil, children[1].frame:GetHeight())
|
||||
safecall( content.obj.LayoutFinished, content.obj, nil, children[1].frame:GetHeight() )
|
||||
end
|
||||
end)
|
||||
|
||||
end
|
||||
)
|
||||
|
||||
AceGUI:RegisterLayout("Flow",
|
||||
function(content, children)
|
||||
--used height so far
|
||||
local height = 0
|
||||
--width used in the current row
|
||||
local usedwidth = 0
|
||||
--height of the current row
|
||||
local rowheight = 0
|
||||
local rowoffset = 0
|
||||
local lastrowoffset
|
||||
|
||||
local width = content.width or content:GetWidth() or 0
|
||||
|
||||
--control at the start of the row
|
||||
local rowstart
|
||||
function(content, children)
|
||||
--used height so far
|
||||
local height = 0
|
||||
--width used in the current row
|
||||
local usedwidth = 0
|
||||
--height of the current row
|
||||
local rowheight = 0
|
||||
local rowoffset = 0
|
||||
local lastrowoffset
|
||||
|
||||
local width = content.width or content:GetWidth() or 0
|
||||
|
||||
--control at the start of the row
|
||||
local rowstart
|
||||
local rowstartoffset
|
||||
local lastrowstart
|
||||
local isfullheight
|
||||
|
||||
local frameoffset
|
||||
local lastframeoffset
|
||||
local oversize
|
||||
local lastrowstart
|
||||
local isfullheight
|
||||
|
||||
local frameoffset
|
||||
local lastframeoffset
|
||||
local oversize
|
||||
for i = 1, #children do
|
||||
local child = children[i]
|
||||
oversize = nil
|
||||
@@ -717,7 +779,7 @@ AceGUI:RegisterLayout("Flow",
|
||||
frame:ClearAllPoints()
|
||||
if i == 1 then
|
||||
-- anchor the first control to the top left
|
||||
frame:SetPoint("TOPLEFT", content)
|
||||
frame:SetPoint("TOPLEFT",content,"TOPLEFT",0,0)
|
||||
rowheight = frameheight
|
||||
rowoffset = frameoffset
|
||||
rowstart = frame
|
||||
@@ -736,7 +798,7 @@ AceGUI:RegisterLayout("Flow",
|
||||
break
|
||||
end
|
||||
--anchor the previous row, we will now know its height and offset
|
||||
rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(height + (rowoffset - rowstartoffset) + 3))
|
||||
rowstart:SetPoint("TOPLEFT",content,"TOPLEFT",0,-(height+(rowoffset-rowstartoffset)+3))
|
||||
height = height + rowheight + 3
|
||||
--save this as the rowstart so we can anchor it after the row is complete and we have the max height and offset of controls in it
|
||||
rowstart = frame
|
||||
@@ -754,21 +816,25 @@ AceGUI:RegisterLayout("Flow",
|
||||
|
||||
--offset is always the larger of the two offsets
|
||||
rowoffset = math_max(rowoffset, frameoffset)
|
||||
rowheight = math_max(rowheight, rowoffset + (frameheight / 2))
|
||||
|
||||
frame:SetPoint("TOPLEFT", children[i-1].frame, "TOPRIGHT", 0, frameoffset - lastframeoffset)
|
||||
rowheight = math_max(rowheight,rowoffset+(frameheight/2))
|
||||
--print("type:", child.type, "offset:",frameoffset-lastframeoffset)
|
||||
frame:SetPoint("TOPLEFT",children[i-1].frame,"TOPRIGHT",0,frameoffset-lastframeoffset)
|
||||
usedwidth = framewidth + usedwidth
|
||||
end
|
||||
end
|
||||
|
||||
if child.width == "fill" then
|
||||
child:SetWidth(width)
|
||||
frame:SetPoint("RIGHT", content)
|
||||
frame:SetPoint("RIGHT",content,"RIGHT",0,0)
|
||||
|
||||
usedwidth = 0
|
||||
rowstart = frame
|
||||
rowstartoffset = frameoffset
|
||||
|
||||
if child.OnWidthSet then
|
||||
child:OnWidthSet(width)
|
||||
end
|
||||
if child.DoLayout then
|
||||
child:DoLayout()
|
||||
end
|
||||
@@ -778,28 +844,33 @@ AceGUI:RegisterLayout("Flow",
|
||||
elseif child.width == "relative" then
|
||||
child:SetWidth(width * child.relWidth)
|
||||
|
||||
if child.OnWidthSet then
|
||||
child:OnWidthSet(width)
|
||||
end
|
||||
|
||||
if child.DoLayout then
|
||||
child:DoLayout()
|
||||
end
|
||||
elseif oversize then
|
||||
if width > 1 then
|
||||
frame:SetPoint("RIGHT", content)
|
||||
frame:SetPoint("RIGHT",content,"RIGHT",0,0)
|
||||
end
|
||||
end
|
||||
|
||||
if child.height == "fill" then
|
||||
frame:SetPoint("BOTTOM", content)
|
||||
frame:SetPoint("BOTTOM",content,"BOTTOM")
|
||||
isfullheight = true
|
||||
end
|
||||
end
|
||||
|
||||
--anchor the last row, if its full height needs a special case since its height has just been changed by the anchor
|
||||
if isfullheight then
|
||||
rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -height)
|
||||
rowstart:SetPoint("TOPLEFT",content,"TOPLEFT",0,-height)
|
||||
elseif rowstart then
|
||||
rowstart:SetPoint("TOPLEFT", content, "TOPLEFT", 0, -(height + (rowoffset - rowstartoffset) + 3))
|
||||
rowstart:SetPoint("TOPLEFT",content,"TOPLEFT",0,-(height+(rowoffset-rowstartoffset)+3))
|
||||
end
|
||||
|
||||
height = height + rowheight + 3
|
||||
safecall(content.obj.LayoutFinished, content.obj, nil, height)
|
||||
end)
|
||||
safecall( content.obj.LayoutFinished, content.obj, nil, height )
|
||||
end
|
||||
)
|
||||
+15
-17
@@ -1,28 +1,26 @@
|
||||
<Ui xmlns="http://www.blizzard.com/wow/ui/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.blizzard.com/wow/ui/
|
||||
..\FrameXML\UI.xsd">
|
||||
<Script file="AceGUI-3.0.lua"/>
|
||||
<!-- Container -->
|
||||
<Script file="widgets\AceGUIContainer-BlizOptionsGroup.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-DropDownGroup.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-Frame.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-InlineGroup.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-ScrollFrame.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-SimpleGroup.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-TabGroup.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-TreeGroup.lua"/>
|
||||
<Script file="widgets\AceGUIContainer-Window.lua"/>
|
||||
<!-- Widgets -->
|
||||
<Script file="widgets\AceGUIWidget-Button.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-CheckBox.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-ColorPicker.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-DropDownGroup.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-DropDown.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-DropDown-Items.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-EditBox.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Heading.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Icon.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-InteractiveLabel.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Keybinding.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Frame.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Window.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Heading.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-InlineGroup.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Keybinding.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-ScrollFrame.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-SimpleGroup.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Slider.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-TabGroup.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-TreeGroup.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Label.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-MultiLineEditBox.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Slider.lua"/>
|
||||
</Ui>
|
||||
<Script file="widgets\AceGUIWidget-BlizOptionsGroup.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-InteractiveLabel.lua"/>
|
||||
<Script file="widgets\AceGUIWidget-Icon.lua"/>
|
||||
</Ui>
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
|
||||
-------------
|
||||
-- Widgets --
|
||||
-------------
|
||||
--[[
|
||||
Widgets must provide the following functions
|
||||
Acquire() - Called when the object is aquired, should set everything to a default hidden state
|
||||
Release() - Called when the object is Released, should remove any anchors and hide the Widget
|
||||
|
||||
And the following members
|
||||
frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
||||
type - the type of the object, same as the name given to :RegisterWidget()
|
||||
|
||||
Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet
|
||||
It will be cleared automatically when a widget is released
|
||||
Placing values directly into a widget object should be avoided
|
||||
|
||||
If the Widget can act as a container for other Widgets the following
|
||||
content - frame or derivitive that children will be anchored to
|
||||
|
||||
The Widget can supply the following Optional Members
|
||||
|
||||
|
||||
]]
|
||||
|
||||
----------------------------------
|
||||
-- Blizzard Options Group --
|
||||
----------------------------------
|
||||
--[[
|
||||
Group Designed to be added to the bliz interface options panel
|
||||
]]
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame = CreateFrame
|
||||
|
||||
do
|
||||
local Type = "BlizOptionsGroup"
|
||||
local Version = 10
|
||||
|
||||
local function OnAcquire(self)
|
||||
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self:SetName()
|
||||
end
|
||||
|
||||
local function okay(this)
|
||||
this.obj:Fire("okay")
|
||||
end
|
||||
|
||||
local function cancel(this)
|
||||
this.obj:Fire("cancel")
|
||||
end
|
||||
|
||||
local function defaults(this)
|
||||
this.obj:Fire("defaults")
|
||||
end
|
||||
|
||||
local function SetName(self, name, parent)
|
||||
self.frame.name = name
|
||||
self.frame.parent = parent
|
||||
end
|
||||
|
||||
local function OnShow(this)
|
||||
this.obj:Fire("OnShow")
|
||||
end
|
||||
|
||||
local function OnHide(this)
|
||||
this.obj:Fire("OnHide")
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
local contentwidth = width - 63
|
||||
if contentwidth < 0 then
|
||||
contentwidth = 0
|
||||
end
|
||||
content:SetWidth(contentwidth)
|
||||
content.width = contentwidth
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
local contentheight = height - 26
|
||||
if contentheight < 0 then
|
||||
contentheight = 0
|
||||
end
|
||||
content:SetHeight(contentheight)
|
||||
content.height = contentheight
|
||||
end
|
||||
|
||||
local function SetTitle(self, title)
|
||||
local content = self.content
|
||||
content:ClearAllPoints()
|
||||
if not title or title == "" then
|
||||
content:SetPoint("TOPLEFT",self.frame,"TOPLEFT",10,-10)
|
||||
self.label:SetText("")
|
||||
else
|
||||
content:SetPoint("TOPLEFT",self.frame,"TOPLEFT",10,-40)
|
||||
self.label:SetText(title)
|
||||
end
|
||||
content:SetPoint("BOTTOMRIGHT",self.frame,"BOTTOMRIGHT",-10,10)
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame")
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.frame = frame
|
||||
self.SetName = SetName
|
||||
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
self.SetTitle = SetTitle
|
||||
|
||||
frame.obj = self
|
||||
frame.okay = okay
|
||||
frame.cancel = cancel
|
||||
frame.defaults = defaults
|
||||
|
||||
frame:Hide()
|
||||
frame:SetScript("OnHide",OnHide)
|
||||
frame:SetScript("OnShow",OnShow)
|
||||
|
||||
local label = frame:CreateFontString(nil,"OVERLAY","GameFontNormalLarge")
|
||||
self.label = label
|
||||
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 10, -15)
|
||||
label:SetPoint("BOTTOMRIGHT", frame, "TOPRIGHT", 10, -45)
|
||||
label:SetJustifyH("LEFT")
|
||||
label:SetJustifyV("TOP")
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,frame)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",frame,"TOPLEFT",15,-10)
|
||||
content:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-10,10)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- WoW APIs
|
||||
local _G = _G
|
||||
local PlaySound = PlaySound
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
--------------------------
|
||||
-- Button --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "Button"
|
||||
local Version = 13
|
||||
|
||||
local function OnAcquire(self)
|
||||
-- restore default values
|
||||
self:SetHeight(24)
|
||||
self:SetWidth(200)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self:SetDisabled(false)
|
||||
end
|
||||
|
||||
local function Button_OnClick(this, ...)
|
||||
PlaySound("igMainMenuOption")
|
||||
this.obj:Fire("OnClick", ...)
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function Button_OnEnter(this)
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function Button_OnLeave(this)
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function SetText(self, text)
|
||||
self.text:SetText(text or "")
|
||||
end
|
||||
|
||||
local function SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.frame:Disable()
|
||||
else
|
||||
self.frame:Enable()
|
||||
end
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local num = AceGUI:GetNextWidgetNum(Type)
|
||||
local name = "AceGUI30Button"..num
|
||||
local frame = CreateFrame("Button",name,UIParent,"UIPanelButtonTemplate2")
|
||||
local self = {}
|
||||
self.num = num
|
||||
self.type = Type
|
||||
self.frame = frame
|
||||
|
||||
local left = _G[name .. "Left"]
|
||||
local right = _G[name .. "Right"]
|
||||
local middle = _G[name .. "Middle"]
|
||||
|
||||
left:SetPoint("TOP", frame, "TOP", 0, 0)
|
||||
left:SetPoint("BOTTOM", frame, "BOTTOM", 0, 0)
|
||||
|
||||
right:SetPoint("TOP", frame, "TOP", 0, 0)
|
||||
right:SetPoint("BOTTOM", frame, "BOTTOM", 0, 0)
|
||||
|
||||
middle:SetPoint("TOP", frame, "TOP", 0, 0)
|
||||
middle:SetPoint("BOTTOM", frame, "BOTTOM", 0, 0)
|
||||
|
||||
local text = frame:GetFontString()
|
||||
self.text = text
|
||||
text:ClearAllPoints()
|
||||
text:SetPoint("TOPLEFT",frame,"TOPLEFT", 15, -1)
|
||||
text:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT", -15, 1)
|
||||
text:SetJustifyV("MIDDLE")
|
||||
|
||||
frame:SetScript("OnClick",Button_OnClick)
|
||||
frame:SetScript("OnEnter",Button_OnEnter)
|
||||
frame:SetScript("OnLeave",Button_OnLeave)
|
||||
|
||||
self.SetText = SetText
|
||||
self.SetDisabled = SetDisabled
|
||||
|
||||
frame:EnableMouse(true)
|
||||
|
||||
frame:SetHeight(24)
|
||||
frame:SetWidth(200)
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+309
@@ -0,0 +1,309 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local select = select
|
||||
|
||||
-- WoW APIs
|
||||
local PlaySound = PlaySound
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: SetDesaturation, GameFontHighlight
|
||||
|
||||
--------------------------
|
||||
-- Check Box --
|
||||
--------------------------
|
||||
--[[
|
||||
Events :
|
||||
OnValueChanged
|
||||
|
||||
]]
|
||||
do
|
||||
local Type = "CheckBox"
|
||||
local Version = 16
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetValue(false)
|
||||
self.tristate = nil
|
||||
self:SetHeight(24)
|
||||
self:SetWidth(200)
|
||||
self:SetImage()
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.check:Hide()
|
||||
self.highlight:Hide()
|
||||
self.down = nil
|
||||
self.checked = nil
|
||||
self:SetType()
|
||||
self:SetDisabled(false)
|
||||
self:SetDescription(nil)
|
||||
end
|
||||
|
||||
local function CheckBox_OnEnter(this)
|
||||
local self = this.obj
|
||||
self.highlight:Show()
|
||||
self:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function CheckBox_OnLeave(this)
|
||||
local self = this.obj
|
||||
self.highlight:Hide()
|
||||
self:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function CheckBox_OnMouseUp(this)
|
||||
local self = this.obj
|
||||
if not self.disabled then
|
||||
self:ToggleChecked()
|
||||
|
||||
if self.checked then
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
else -- for both nil and false (tristate)
|
||||
PlaySound("igMainMenuOptionCheckBoxOff")
|
||||
end
|
||||
|
||||
self:Fire("OnValueChanged",self.checked)
|
||||
self.text:SetPoint("LEFT",self.check,"RIGHT",0,0)
|
||||
end
|
||||
self.down = nil
|
||||
end
|
||||
|
||||
local function CheckBox_OnMouseDown(this)
|
||||
local self = this.obj
|
||||
if not self.disabled then
|
||||
self.text:SetPoint("LEFT",self.check,"RIGHT",1,-1)
|
||||
self.down = true
|
||||
end
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function SetDisabled(self,disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.frame:Disable()
|
||||
self.text:SetTextColor(0.5,0.5,0.5)
|
||||
SetDesaturation(self.check, true)
|
||||
else
|
||||
self.frame:Enable()
|
||||
self.text:SetTextColor(1,1,1)
|
||||
if self.tristate and self.checked == nil then
|
||||
SetDesaturation(self.check, true)
|
||||
else
|
||||
SetDesaturation(self.check, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function SetValue(self,value)
|
||||
local check = self.check
|
||||
self.checked = value
|
||||
if value then
|
||||
SetDesaturation(self.check, false)
|
||||
self.check:Show()
|
||||
else
|
||||
--Nil is the unknown tristate value
|
||||
if self.tristate and value == nil then
|
||||
SetDesaturation(self.check, true)
|
||||
self.check:Show()
|
||||
else
|
||||
SetDesaturation(self.check, false)
|
||||
self.check:Hide()
|
||||
end
|
||||
end
|
||||
SetDisabled(self, self.disabled)
|
||||
end
|
||||
|
||||
local function SetTriState(self, enabled)
|
||||
self.tristate = enabled
|
||||
self:SetValue(self:GetValue())
|
||||
end
|
||||
|
||||
local function GetValue(self)
|
||||
return self.checked
|
||||
end
|
||||
|
||||
local function SetType(self, type)
|
||||
local checkbg = self.checkbg
|
||||
local check = self.check
|
||||
local highlight = self.highlight
|
||||
|
||||
if type == "radio" then
|
||||
checkbg:SetHeight(16)
|
||||
checkbg:SetWidth(16)
|
||||
checkbg:SetTexture("Interface\\Buttons\\UI-RadioButton")
|
||||
checkbg:SetTexCoord(0,0.25,0,1)
|
||||
check:SetHeight(16)
|
||||
check:SetWidth(16)
|
||||
check:SetTexture("Interface\\Buttons\\UI-RadioButton")
|
||||
check:SetTexCoord(0.25,0.5,0,1)
|
||||
check:SetBlendMode("ADD")
|
||||
highlight:SetTexture("Interface\\Buttons\\UI-RadioButton")
|
||||
highlight:SetTexCoord(0.5,0.75,0,1)
|
||||
else
|
||||
checkbg:SetHeight(24)
|
||||
checkbg:SetWidth(24)
|
||||
checkbg:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
|
||||
checkbg:SetTexCoord(0,1,0,1)
|
||||
check:SetHeight(24)
|
||||
check:SetWidth(24)
|
||||
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
|
||||
check:SetTexCoord(0,1,0,1)
|
||||
check:SetBlendMode("BLEND")
|
||||
highlight:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
|
||||
highlight:SetTexCoord(0,1,0,1)
|
||||
end
|
||||
end
|
||||
|
||||
local function ToggleChecked(self)
|
||||
local value = self:GetValue()
|
||||
if self.tristate then
|
||||
--cycle in true, nil, false order
|
||||
if value then
|
||||
self:SetValue(nil)
|
||||
elseif value == nil then
|
||||
self:SetValue(false)
|
||||
else
|
||||
self:SetValue(true)
|
||||
end
|
||||
else
|
||||
self:SetValue(not self:GetValue())
|
||||
end
|
||||
end
|
||||
|
||||
local function SetLabel(self, label)
|
||||
self.text:SetText(label)
|
||||
end
|
||||
|
||||
local function SetDescription(self, desc)
|
||||
if desc then
|
||||
if not self.desc then
|
||||
local desc = self.frame:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||
desc:ClearAllPoints()
|
||||
desc:SetPoint("TOPLEFT", self.check, "TOPRIGHT", 5, -21)
|
||||
desc:SetWidth(self.frame.width - 30)
|
||||
desc:SetJustifyH("LEFT")
|
||||
desc:SetJustifyV("TOP")
|
||||
self.desc = desc
|
||||
end
|
||||
self.desc:Show()
|
||||
--self.text:SetFontObject(GameFontNormal)
|
||||
self.desc:SetText(desc)
|
||||
self:SetHeight(28 + self.desc:GetHeight())
|
||||
else
|
||||
if self.desc then
|
||||
self.desc:SetText("")
|
||||
self.desc:Hide()
|
||||
end
|
||||
self.text:SetFontObject(GameFontHighlight)
|
||||
self:SetHeight(24)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetImage(self, path, ...)
|
||||
local image = self.image
|
||||
image:SetTexture(path)
|
||||
|
||||
if image:GetTexture() then
|
||||
local n = select('#', ...)
|
||||
if n == 4 or n == 8 then
|
||||
image:SetTexCoord(...)
|
||||
else
|
||||
image:SetTexCoord(0, 1, 0, 1)
|
||||
end
|
||||
end
|
||||
self:AlignImage()
|
||||
end
|
||||
|
||||
local function AlignImage(self)
|
||||
local img = self.image:GetTexture()
|
||||
self.text:ClearAllPoints()
|
||||
if not img then
|
||||
self.text:SetPoint("LEFT", self.check, "RIGHT", 0, 0)
|
||||
self.text:SetPoint("RIGHT", self.frame, "RIGHT", 0, 0)
|
||||
else
|
||||
self.text:SetPoint("LEFT", self.image,"RIGHT", 1, 0)
|
||||
self.text:SetPoint("RIGHT", self.frame,"RIGHT", 0, 0)
|
||||
end
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
if self.desc and self.desc:GetText() ~= "" and self.desc:GetText() ~= nil then
|
||||
self.desc:SetWidth(width - 30)
|
||||
self:SetHeight(28 + self.desc:GetHeight())
|
||||
end
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Button",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.SetValue = SetValue
|
||||
self.GetValue = GetValue
|
||||
self.SetDisabled = SetDisabled
|
||||
self.SetType = SetType
|
||||
self.ToggleChecked = ToggleChecked
|
||||
self.SetLabel = SetLabel
|
||||
self.SetTriState = SetTriState
|
||||
self.SetDescription = SetDescription
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.SetImage = SetImage
|
||||
self.AlignImage = AlignImage
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
local text = frame:CreateFontString(nil,"OVERLAY","GameFontHighlight")
|
||||
self.text = text
|
||||
|
||||
frame:SetScript("OnEnter",CheckBox_OnEnter)
|
||||
frame:SetScript("OnLeave",CheckBox_OnLeave)
|
||||
frame:SetScript("OnMouseUp",CheckBox_OnMouseUp)
|
||||
frame:SetScript("OnMouseDown",CheckBox_OnMouseDown)
|
||||
frame:EnableMouse()
|
||||
local checkbg = frame:CreateTexture(nil,"ARTWORK")
|
||||
self.checkbg = checkbg
|
||||
checkbg:SetWidth(24)
|
||||
checkbg:SetHeight(24)
|
||||
checkbg:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
checkbg:SetTexture("Interface\\Buttons\\UI-CheckBox-Up")
|
||||
local check = frame:CreateTexture(nil,"OVERLAY")
|
||||
self.check = check
|
||||
check:SetWidth(24)
|
||||
check:SetHeight(24)
|
||||
check:SetPoint("CENTER",checkbg,"CENTER",0,0)
|
||||
check:SetTexture("Interface\\Buttons\\UI-CheckBox-Check")
|
||||
|
||||
local highlight = frame:CreateTexture(nil, "OVERLAY")
|
||||
self.highlight = highlight
|
||||
highlight:SetTexture("Interface\\Buttons\\UI-CheckBox-Highlight")
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:SetAllPoints(checkbg)
|
||||
highlight:Hide()
|
||||
|
||||
local image = frame:CreateTexture(nil, "OVERLAY")
|
||||
self.image = image
|
||||
image:SetHeight(16)
|
||||
image:SetWidth(16)
|
||||
image:SetPoint("LEFT", check, "RIGHT", 1, 0)
|
||||
|
||||
text:SetJustifyH("LEFT")
|
||||
frame:SetHeight(24)
|
||||
frame:SetWidth(200)
|
||||
text:SetHeight(18)
|
||||
text:SetPoint("LEFT",check,"RIGHT",0,0)
|
||||
text:SetPoint("RIGHT",frame,"RIGHT",0,0)
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+181
@@ -0,0 +1,181 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: ShowUIPanel, HideUIPanel, ColorPickerFrame, OpacitySliderFrame
|
||||
|
||||
--------------------------
|
||||
-- ColorPicker --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "ColorPicker"
|
||||
local Version = 11
|
||||
|
||||
local function OnAcquire(self)
|
||||
self.HasAlpha = false
|
||||
self:SetColor(0,0,0,1)
|
||||
self:SetHeight(24)
|
||||
self:SetWidth(200)
|
||||
end
|
||||
|
||||
local function SetLabel(self, text)
|
||||
self.text:SetText(text)
|
||||
end
|
||||
|
||||
local function SetColor(self,r,g,b,a)
|
||||
self.r = r
|
||||
self.g = g
|
||||
self.b = b
|
||||
self.a = a or 1
|
||||
self.colorSwatch:SetVertexColor(r,g,b,a)
|
||||
end
|
||||
|
||||
local function Control_OnEnter(this)
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function Control_OnLeave(this)
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function SetHasAlpha(self, HasAlpha)
|
||||
self.HasAlpha = HasAlpha
|
||||
end
|
||||
|
||||
local function ColorCallback(self,r,g,b,a,isAlpha)
|
||||
if not self.HasAlpha then
|
||||
a = 1
|
||||
end
|
||||
self:SetColor(r,g,b,a)
|
||||
if ColorPickerFrame:IsVisible() then
|
||||
--colorpicker is still open
|
||||
|
||||
self:Fire("OnValueChanged",r,g,b,a)
|
||||
else
|
||||
--colorpicker is closed, color callback is first, ignore it,
|
||||
--alpha callback is the final call after it closes so confirm now
|
||||
if isAlpha then
|
||||
self:Fire("OnValueConfirmed",r,g,b,a)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ColorSwatch_OnClick(this)
|
||||
HideUIPanel(ColorPickerFrame)
|
||||
local self = this.obj
|
||||
if not self.disabled then
|
||||
ColorPickerFrame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
|
||||
ColorPickerFrame.func = function()
|
||||
local r,g,b = ColorPickerFrame:GetColorRGB()
|
||||
local a = 1 - OpacitySliderFrame:GetValue()
|
||||
ColorCallback(self,r,g,b,a)
|
||||
end
|
||||
|
||||
ColorPickerFrame.hasOpacity = self.HasAlpha
|
||||
ColorPickerFrame.opacityFunc = function()
|
||||
local r,g,b = ColorPickerFrame:GetColorRGB()
|
||||
local a = 1 - OpacitySliderFrame:GetValue()
|
||||
ColorCallback(self,r,g,b,a,true)
|
||||
end
|
||||
local r, g, b, a = self.r, self.g, self.b, self.a
|
||||
if self.HasAlpha then
|
||||
ColorPickerFrame.opacity = 1 - (a or 0)
|
||||
end
|
||||
ColorPickerFrame:SetColorRGB(r, g, b)
|
||||
|
||||
ColorPickerFrame.cancelFunc = function()
|
||||
ColorCallback(self,r,g,b,a,true)
|
||||
end
|
||||
ShowUIPanel(ColorPickerFrame)
|
||||
end
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
local function SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
if self.disabled then
|
||||
self.frame:Disable()
|
||||
self.text:SetTextColor(0.5,0.5,0.5)
|
||||
else
|
||||
self.frame:Enable()
|
||||
self.text:SetTextColor(1,1,1)
|
||||
end
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Button",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.SetLabel = SetLabel
|
||||
self.SetColor = SetColor
|
||||
self.SetDisabled = SetDisabled
|
||||
self.SetHasAlpha = SetHasAlpha
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
local text = frame:CreateFontString(nil,"OVERLAY","GameFontHighlight")
|
||||
self.text = text
|
||||
text:SetJustifyH("LEFT")
|
||||
text:SetTextColor(1,1,1)
|
||||
frame:SetHeight(24)
|
||||
frame:SetWidth(200)
|
||||
text:SetHeight(24)
|
||||
frame:SetScript("OnClick", ColorSwatch_OnClick)
|
||||
frame:SetScript("OnEnter",Control_OnEnter)
|
||||
frame:SetScript("OnLeave",Control_OnLeave)
|
||||
|
||||
local colorSwatch = frame:CreateTexture(nil, "OVERLAY")
|
||||
self.colorSwatch = colorSwatch
|
||||
colorSwatch:SetWidth(19)
|
||||
colorSwatch:SetHeight(19)
|
||||
colorSwatch:SetTexture("Interface\\ChatFrame\\ChatFrameColorSwatch")
|
||||
local texture = frame:CreateTexture(nil, "BACKGROUND")
|
||||
colorSwatch.texture = texture
|
||||
texture:SetWidth(16)
|
||||
texture:SetHeight(16)
|
||||
texture:SetTexture(1,1,1)
|
||||
texture:Show()
|
||||
|
||||
local checkers = frame:CreateTexture(nil, "BACKGROUND")
|
||||
colorSwatch.checkers = checkers
|
||||
checkers:SetTexture("Tileset\\Generic\\Checkers")
|
||||
checkers:SetDesaturated(true)
|
||||
checkers:SetVertexColor(1,1,1,0.75)
|
||||
checkers:SetTexCoord(.25,0,0.5,.25)
|
||||
checkers:SetWidth(14)
|
||||
checkers:SetHeight(14)
|
||||
checkers:Show()
|
||||
|
||||
local highlight = frame:CreateTexture(nil, "BACKGROUND")
|
||||
self.highlight = highlight
|
||||
highlight:SetTexture("Interface\\QuestFrame\\UI-QuestTitleHighlight")
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:SetAllPoints(frame)
|
||||
highlight:Hide()
|
||||
|
||||
texture:SetPoint("CENTER", colorSwatch, "CENTER")
|
||||
checkers:SetPoint("CENTER", colorSwatch, "CENTER")
|
||||
colorSwatch:SetPoint("LEFT", frame, "LEFT", 0, 0)
|
||||
text:SetPoint("LEFT",colorSwatch,"RIGHT",2,0)
|
||||
text:SetPoint("RIGHT",frame,"RIGHT")
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local assert, pairs, type = assert, pairs, type
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame = CreateFrame
|
||||
|
||||
--[[
|
||||
Selection Group controls all have an interface to select a group for thier contents
|
||||
None of them will auto size to thier contents, and should usually be used with a scrollframe
|
||||
unless you know that the controls will fit inside
|
||||
]]
|
||||
|
||||
--------------------------
|
||||
-- Dropdown Group --
|
||||
--------------------------
|
||||
--[[
|
||||
Events :
|
||||
OnGroupSelected
|
||||
|
||||
]]
|
||||
do
|
||||
local Type = "DropdownGroup"
|
||||
local Version = 13
|
||||
|
||||
local function OnAcquire(self)
|
||||
self.dropdown:SetText("")
|
||||
self:SetDropdownWidth(200)
|
||||
self:SetTitle("")
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.dropdown.list = nil
|
||||
self.status = nil
|
||||
for k in pairs(self.localstatus) do
|
||||
self.localstatus[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
local PaneBackdrop = {
|
||||
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 3, right = 3, top = 5, bottom = 3 }
|
||||
}
|
||||
|
||||
local function SetTitle(self,title)
|
||||
self.titletext:SetText(title)
|
||||
self.dropdown.frame:ClearAllPoints()
|
||||
if title and title ~= "" then
|
||||
self.dropdown.frame:SetPoint("TOPRIGHT", self.frame, "TOPRIGHT", -2, 0)
|
||||
else
|
||||
self.dropdown.frame:SetPoint("TOPLEFT", self.frame, "TOPLEFT", -1, 0)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function SelectedGroup(self,event,value)
|
||||
local group = self.parentgroup
|
||||
local status = group.status or group.localstatus
|
||||
status.selected = value
|
||||
self.parentgroup:Fire("OnGroupSelected", value)
|
||||
end
|
||||
|
||||
local function SetGroupList(self,list)
|
||||
self.dropdown:SetList(list)
|
||||
end
|
||||
|
||||
-- called to set an external table to store status in
|
||||
local function SetStatusTable(self, status)
|
||||
assert(type(status) == "table")
|
||||
self.status = status
|
||||
end
|
||||
|
||||
local function SetGroup(self,group)
|
||||
self.dropdown:SetValue(group)
|
||||
local status = self.status or self.localstatus
|
||||
status.selected = group
|
||||
self:Fire("OnGroupSelected", group)
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
local contentwidth = width - 26
|
||||
if contentwidth < 0 then
|
||||
contentwidth = 0
|
||||
end
|
||||
content:SetWidth(contentwidth)
|
||||
content.width = contentwidth
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
local contentheight = height - 63
|
||||
if contentheight < 0 then
|
||||
contentheight = 0
|
||||
end
|
||||
content:SetHeight(contentheight)
|
||||
content.height = contentheight
|
||||
end
|
||||
|
||||
local function LayoutFinished(self, width, height)
|
||||
self:SetHeight((height or 0) + 63)
|
||||
end
|
||||
|
||||
local function SetDropdownWidth(self, width)
|
||||
self.dropdown:SetWidth(width)
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame")
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.SetTitle = SetTitle
|
||||
self.SetGroupList = SetGroupList
|
||||
self.SetGroup = SetGroup
|
||||
self.SetStatusTable = SetStatusTable
|
||||
self.SetDropdownWidth = SetDropdownWidth
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
self.LayoutFinished = LayoutFinished
|
||||
|
||||
self.localstatus = {}
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(100)
|
||||
frame:SetWidth(100)
|
||||
frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
|
||||
local titletext = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||
titletext:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -5)
|
||||
titletext:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -4, -5)
|
||||
titletext:SetJustifyH("LEFT")
|
||||
titletext:SetHeight(18)
|
||||
self.titletext = titletext
|
||||
|
||||
local dropdown = AceGUI:Create("Dropdown")
|
||||
self.dropdown = dropdown
|
||||
dropdown.frame:SetParent(frame)
|
||||
dropdown.frame:SetFrameLevel(dropdown.frame:GetFrameLevel() + 2)
|
||||
dropdown.parentgroup = self
|
||||
dropdown:SetCallback("OnValueChanged",SelectedGroup)
|
||||
dropdown.frame:SetPoint("TOPLEFT",frame,"TOPLEFT", -1, 0)
|
||||
dropdown.frame:Show()
|
||||
dropdown:SetLabel("")
|
||||
|
||||
local border = CreateFrame("Frame",nil,frame)
|
||||
self.border = border
|
||||
border:SetPoint("TOPLEFT",frame,"TOPLEFT",0,-26)
|
||||
border:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,3)
|
||||
|
||||
border:SetBackdrop(PaneBackdrop)
|
||||
border:SetBackdropColor(0.1,0.1,0.1,0.5)
|
||||
border:SetBackdropBorderColor(0.4,0.4,0.4)
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,border)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",border,"TOPLEFT",10,-10)
|
||||
content:SetPoint("BOTTOMRIGHT",border,"BOTTOMRIGHT",-10,10)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+235
@@ -0,0 +1,235 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local tostring = tostring
|
||||
|
||||
-- WoW APIs
|
||||
local PlaySound = PlaySound
|
||||
local GetCursorInfo, ClearCursor, GetSpellName = GetCursorInfo, ClearCursor, GetSpellName
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
local _G = _G
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: AceGUIEditBoxInsertLink, ChatFontNormal, OKAY
|
||||
|
||||
local Type = "EditBox"
|
||||
local Version = 14
|
||||
|
||||
if not AceGUIEditBoxInsertLink then
|
||||
-- upgradeable hook
|
||||
hooksecurefunc("ChatEdit_InsertLink", function(...) return _G.AceGUIEditBoxInsertLink(...) end)
|
||||
end
|
||||
|
||||
function _G.AceGUIEditBoxInsertLink(text)
|
||||
for i = 1, AceGUI:GetNextWidgetNum(Type)-1 do
|
||||
local editbox = _G["AceGUI-3.0EditBox"..i]
|
||||
if editbox and editbox:IsVisible() and editbox:HasFocus() then
|
||||
editbox:Insert(text)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
--------------------------
|
||||
-- Edit box --
|
||||
--------------------------
|
||||
--[[
|
||||
Events :
|
||||
OnTextChanged
|
||||
OnEnterPressed
|
||||
|
||||
]]
|
||||
do
|
||||
local function OnAcquire(self)
|
||||
self:SetHeight(26)
|
||||
self:SetWidth(200)
|
||||
self:SetDisabled(false)
|
||||
self:SetLabel()
|
||||
self.showbutton = true
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self:SetDisabled(false)
|
||||
self:SetText()
|
||||
end
|
||||
|
||||
local function Control_OnEnter(this)
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function Control_OnLeave(this)
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function EditBox_OnEscapePressed(this)
|
||||
this:ClearFocus()
|
||||
end
|
||||
|
||||
local function ShowButton(self)
|
||||
if self.showbutton then
|
||||
self.button:Show()
|
||||
self.editbox:SetTextInsets(0,20,3,3)
|
||||
end
|
||||
end
|
||||
|
||||
local function HideButton(self)
|
||||
self.button:Hide()
|
||||
self.editbox:SetTextInsets(0,0,3,3)
|
||||
end
|
||||
|
||||
local function EditBox_OnEnterPressed(this)
|
||||
local self = this.obj
|
||||
local value = this:GetText()
|
||||
local cancel = self:Fire("OnEnterPressed",value)
|
||||
if not cancel then
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
HideButton(self)
|
||||
end
|
||||
end
|
||||
|
||||
local function Button_OnClick(this)
|
||||
local editbox = this.obj.editbox
|
||||
editbox:ClearFocus()
|
||||
EditBox_OnEnterPressed(editbox)
|
||||
end
|
||||
|
||||
local function EditBox_OnReceiveDrag(this)
|
||||
local self = this.obj
|
||||
local type, id, info = GetCursorInfo()
|
||||
if type == "item" then
|
||||
self:SetText(info)
|
||||
self:Fire("OnEnterPressed",info)
|
||||
ClearCursor()
|
||||
elseif type == "spell" then
|
||||
local name, rank = GetSpellName(id, info)
|
||||
if rank and rank:match("%d") then
|
||||
name = name.."("..rank..")"
|
||||
end
|
||||
self:SetText(name)
|
||||
self:Fire("OnEnterPressed",name)
|
||||
ClearCursor()
|
||||
end
|
||||
HideButton(self)
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function EditBox_OnTextChanged(this)
|
||||
local self = this.obj
|
||||
local value = this:GetText()
|
||||
if tostring(value) ~= tostring(self.lasttext) then
|
||||
self:Fire("OnTextChanged",value)
|
||||
self.lasttext = value
|
||||
ShowButton(self)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.editbox:EnableMouse(false)
|
||||
self.editbox:ClearFocus()
|
||||
self.editbox:SetTextColor(0.5,0.5,0.5)
|
||||
self.label:SetTextColor(0.5,0.5,0.5)
|
||||
else
|
||||
self.editbox:EnableMouse(true)
|
||||
self.editbox:SetTextColor(1,1,1)
|
||||
self.label:SetTextColor(1,.82,0)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetText(self, text)
|
||||
self.lasttext = text or ""
|
||||
self.editbox:SetText(text or "")
|
||||
self.editbox:SetCursorPosition(0)
|
||||
HideButton(self)
|
||||
end
|
||||
|
||||
local function SetLabel(self, text)
|
||||
if text and text ~= "" then
|
||||
self.label:SetText(text)
|
||||
self.label:Show()
|
||||
self.editbox:SetPoint("TOPLEFT",self.frame,"TOPLEFT",7,-18)
|
||||
self:SetHeight(44)
|
||||
self.alignoffset = 30
|
||||
else
|
||||
self.label:SetText("")
|
||||
self.label:Hide()
|
||||
self.editbox:SetPoint("TOPLEFT",self.frame,"TOPLEFT",7,0)
|
||||
self:SetHeight(26)
|
||||
self.alignoffset = 12
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function Constructor()
|
||||
local num = AceGUI:GetNextWidgetNum(Type)
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local editbox = CreateFrame("EditBox","AceGUI-3.0EditBox"..num,frame,"InputBoxTemplate")
|
||||
|
||||
local self = {}
|
||||
self.type = Type
|
||||
self.num = num
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.SetDisabled = SetDisabled
|
||||
self.SetText = SetText
|
||||
self.SetLabel = SetLabel
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
self.editbox = editbox
|
||||
editbox.obj = self
|
||||
|
||||
self.alignoffset = 30
|
||||
|
||||
frame:SetHeight(44)
|
||||
frame:SetWidth(200)
|
||||
|
||||
editbox:SetScript("OnEnter",Control_OnEnter)
|
||||
editbox:SetScript("OnLeave",Control_OnLeave)
|
||||
|
||||
editbox:SetAutoFocus(false)
|
||||
editbox:SetFontObject(ChatFontNormal)
|
||||
editbox:SetScript("OnEscapePressed",EditBox_OnEscapePressed)
|
||||
editbox:SetScript("OnEnterPressed",EditBox_OnEnterPressed)
|
||||
editbox:SetScript("OnTextChanged",EditBox_OnTextChanged)
|
||||
editbox:SetScript("OnReceiveDrag", EditBox_OnReceiveDrag)
|
||||
editbox:SetScript("OnMouseDown", EditBox_OnReceiveDrag)
|
||||
|
||||
editbox:SetTextInsets(0,0,3,3)
|
||||
editbox:SetMaxLetters(256)
|
||||
|
||||
editbox:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",6,0)
|
||||
editbox:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
|
||||
editbox:SetHeight(19)
|
||||
|
||||
local label = frame:CreateFontString(nil,"OVERLAY","GameFontNormalSmall")
|
||||
label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,-2)
|
||||
label:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,-2)
|
||||
label:SetJustifyH("LEFT")
|
||||
label:SetHeight(18)
|
||||
self.label = label
|
||||
|
||||
local button = CreateFrame("Button",nil,editbox,"UIPanelButtonTemplate")
|
||||
button:SetWidth(40)
|
||||
button:SetHeight(20)
|
||||
button:SetPoint("RIGHT",editbox,"RIGHT",-2,0)
|
||||
button:SetText(OKAY)
|
||||
button:SetScript("OnClick", Button_OnClick)
|
||||
button:Hide()
|
||||
|
||||
self.button = button
|
||||
button.obj = self
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+308
@@ -0,0 +1,308 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local pairs, assert, type = pairs, assert, type
|
||||
|
||||
-- WoW APIs
|
||||
local PlaySound = PlaySound
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: CLOSE
|
||||
|
||||
----------------
|
||||
-- Main Frame --
|
||||
----------------
|
||||
--[[
|
||||
Events :
|
||||
OnClose
|
||||
|
||||
]]
|
||||
do
|
||||
local Type = "Frame"
|
||||
local Version = 11
|
||||
|
||||
local FrameBackdrop = {
|
||||
bgFile="Interface\\DialogFrame\\UI-DialogBox-Background",
|
||||
edgeFile="Interface\\DialogFrame\\UI-DialogBox-Border",
|
||||
tile = true, tileSize = 32, edgeSize = 32,
|
||||
insets = { left = 8, right = 8, top = 8, bottom = 8 }
|
||||
}
|
||||
|
||||
local PaneBackdrop = {
|
||||
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 3, right = 3, top = 5, bottom = 3 }
|
||||
}
|
||||
|
||||
local function frameOnClose(this)
|
||||
this.obj:Fire("OnClose")
|
||||
end
|
||||
|
||||
local function closeOnClick(this)
|
||||
PlaySound("gsTitleOptionExit")
|
||||
this.obj:Hide()
|
||||
end
|
||||
|
||||
local function frameOnMouseDown(this)
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function titleOnMouseDown(this)
|
||||
this:GetParent():StartMoving()
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function frameOnMouseUp(this)
|
||||
local frame = this:GetParent()
|
||||
frame:StopMovingOrSizing()
|
||||
local self = frame.obj
|
||||
local status = self.status or self.localstatus
|
||||
status.width = frame:GetWidth()
|
||||
status.height = frame:GetHeight()
|
||||
status.top = frame:GetTop()
|
||||
status.left = frame:GetLeft()
|
||||
end
|
||||
|
||||
local function sizerseOnMouseDown(this)
|
||||
this:GetParent():StartSizing("BOTTOMRIGHT")
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function sizersOnMouseDown(this)
|
||||
this:GetParent():StartSizing("BOTTOM")
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function sizereOnMouseDown(this)
|
||||
this:GetParent():StartSizing("RIGHT")
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function SetTitle(self,title)
|
||||
self.titletext:SetText(title)
|
||||
end
|
||||
|
||||
local function SetStatusText(self,text)
|
||||
self.statustext:SetText(text)
|
||||
end
|
||||
|
||||
local function Hide(self)
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
local function Show(self)
|
||||
self.frame:Show()
|
||||
end
|
||||
|
||||
local function OnAcquire(self)
|
||||
self.frame:SetParent(UIParent)
|
||||
self.frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
self:ApplyStatus()
|
||||
self:Show()
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.status = nil
|
||||
for k in pairs(self.localstatus) do
|
||||
self.localstatus[k] = nil
|
||||
end
|
||||
end
|
||||
|
||||
-- called to set an external table to store status in
|
||||
local function SetStatusTable(self, status)
|
||||
assert(type(status) == "table")
|
||||
self.status = status
|
||||
self:ApplyStatus()
|
||||
end
|
||||
|
||||
local function ApplyStatus(self)
|
||||
local status = self.status or self.localstatus
|
||||
local frame = self.frame
|
||||
self:SetWidth(status.width or 700)
|
||||
self:SetHeight(status.height or 500)
|
||||
if status.top and status.left then
|
||||
frame:SetPoint("TOP",UIParent,"BOTTOM",0,status.top)
|
||||
frame:SetPoint("LEFT",UIParent,"LEFT",status.left,0)
|
||||
else
|
||||
frame:SetPoint("CENTER",UIParent,"CENTER")
|
||||
end
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
local contentwidth = width - 34
|
||||
if contentwidth < 0 then
|
||||
contentwidth = 0
|
||||
end
|
||||
content:SetWidth(contentwidth)
|
||||
content.width = contentwidth
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
local contentheight = height - 57
|
||||
if contentheight < 0 then
|
||||
contentheight = 0
|
||||
end
|
||||
content:SetHeight(contentheight)
|
||||
content.height = contentheight
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = "Frame"
|
||||
|
||||
self.Hide = Hide
|
||||
self.Show = Show
|
||||
self.SetTitle = SetTitle
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetStatusText = SetStatusText
|
||||
self.SetStatusTable = SetStatusTable
|
||||
self.ApplyStatus = ApplyStatus
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
|
||||
self.localstatus = {}
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
frame:SetWidth(700)
|
||||
frame:SetHeight(500)
|
||||
frame:SetPoint("CENTER",UIParent,"CENTER",0,0)
|
||||
frame:EnableMouse()
|
||||
frame:SetMovable(true)
|
||||
frame:SetResizable(true)
|
||||
frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
frame:SetScript("OnMouseDown", frameOnMouseDown)
|
||||
|
||||
frame:SetBackdrop(FrameBackdrop)
|
||||
frame:SetBackdropColor(0,0,0,1)
|
||||
frame:SetScript("OnHide",frameOnClose)
|
||||
frame:SetMinResize(400,200)
|
||||
frame:SetToplevel(true)
|
||||
|
||||
local closebutton = CreateFrame("Button",nil,frame,"UIPanelButtonTemplate")
|
||||
closebutton:SetScript("OnClick", closeOnClick)
|
||||
closebutton:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-27,17)
|
||||
closebutton:SetHeight(20)
|
||||
closebutton:SetWidth(100)
|
||||
closebutton:SetText(CLOSE)
|
||||
|
||||
self.closebutton = closebutton
|
||||
closebutton.obj = self
|
||||
|
||||
local statusbg = CreateFrame("Frame",nil,frame)
|
||||
statusbg:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",15,15)
|
||||
statusbg:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-132,15)
|
||||
statusbg:SetHeight(24)
|
||||
statusbg:SetBackdrop(PaneBackdrop)
|
||||
statusbg:SetBackdropColor(0.1,0.1,0.1)
|
||||
statusbg:SetBackdropBorderColor(0.4,0.4,0.4)
|
||||
self.statusbg = statusbg
|
||||
|
||||
local statustext = statusbg:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
self.statustext = statustext
|
||||
statustext:SetPoint("TOPLEFT",statusbg,"TOPLEFT",7,-2)
|
||||
statustext:SetPoint("BOTTOMRIGHT",statusbg,"BOTTOMRIGHT",-7,2)
|
||||
statustext:SetHeight(20)
|
||||
statustext:SetJustifyH("LEFT")
|
||||
statustext:SetText("")
|
||||
|
||||
local title = CreateFrame("Frame",nil,frame)
|
||||
self.title = title
|
||||
title:EnableMouse()
|
||||
title:SetScript("OnMouseDown",titleOnMouseDown)
|
||||
title:SetScript("OnMouseUp", frameOnMouseUp)
|
||||
|
||||
|
||||
local titlebg = frame:CreateTexture(nil,"OVERLAY")
|
||||
titlebg:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||
titlebg:SetTexCoord(0.31,0.67,0,0.63)
|
||||
titlebg:SetPoint("TOP",frame,"TOP",0,12)
|
||||
titlebg:SetWidth(100)
|
||||
titlebg:SetHeight(40)
|
||||
|
||||
local titlebg_l = frame:CreateTexture(nil,"OVERLAY")
|
||||
titlebg_l:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||
titlebg_l:SetTexCoord(0.21,0.31,0,0.63)
|
||||
titlebg_l:SetPoint("RIGHT",titlebg,"LEFT",0,0)
|
||||
titlebg_l:SetWidth(30)
|
||||
titlebg_l:SetHeight(40)
|
||||
|
||||
local titlebg_right = frame:CreateTexture(nil,"OVERLAY")
|
||||
titlebg_right:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||
titlebg_right:SetTexCoord(0.67,0.77,0,0.63)
|
||||
titlebg_right:SetPoint("LEFT",titlebg,"RIGHT",0,0)
|
||||
titlebg_right:SetWidth(30)
|
||||
titlebg_right:SetHeight(40)
|
||||
|
||||
title:SetAllPoints(titlebg)
|
||||
local titletext = title:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
titletext:SetPoint("TOP",titlebg,"TOP",0,-14)
|
||||
|
||||
self.titletext = titletext
|
||||
|
||||
local sizer_se = CreateFrame("Frame",nil,frame)
|
||||
sizer_se:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
|
||||
sizer_se:SetWidth(25)
|
||||
sizer_se:SetHeight(25)
|
||||
sizer_se:EnableMouse()
|
||||
sizer_se:SetScript("OnMouseDown",sizerseOnMouseDown)
|
||||
sizer_se:SetScript("OnMouseUp", frameOnMouseUp)
|
||||
self.sizer_se = sizer_se
|
||||
|
||||
local line1 = sizer_se:CreateTexture(nil, "BACKGROUND")
|
||||
self.line1 = line1
|
||||
line1:SetWidth(14)
|
||||
line1:SetHeight(14)
|
||||
line1:SetPoint("BOTTOMRIGHT", -8, 8)
|
||||
line1:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
local x = 0.1 * 14/17
|
||||
line1:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
|
||||
|
||||
local line2 = sizer_se:CreateTexture(nil, "BACKGROUND")
|
||||
self.line2 = line2
|
||||
line2:SetWidth(8)
|
||||
line2:SetHeight(8)
|
||||
line2:SetPoint("BOTTOMRIGHT", -8, 8)
|
||||
line2:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
local x = 0.1 * 8/17
|
||||
line2:SetTexCoord(0.05 - x, 0.5, 0.05, 0.5 + x, 0.05, 0.5 - x, 0.5 + x, 0.5)
|
||||
|
||||
local sizer_s = CreateFrame("Frame",nil,frame)
|
||||
sizer_s:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-25,0)
|
||||
sizer_s:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",0,0)
|
||||
sizer_s:SetHeight(25)
|
||||
sizer_s:EnableMouse()
|
||||
sizer_s:SetScript("OnMouseDown",sizersOnMouseDown)
|
||||
sizer_s:SetScript("OnMouseUp", frameOnMouseUp)
|
||||
self.sizer_s = sizer_s
|
||||
|
||||
local sizer_e = CreateFrame("Frame",nil,frame)
|
||||
sizer_e:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,25)
|
||||
sizer_e:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,0)
|
||||
sizer_e:SetWidth(25)
|
||||
sizer_e:EnableMouse()
|
||||
sizer_e:SetScript("OnMouseDown",sizereOnMouseDown)
|
||||
sizer_e:SetScript("OnMouseUp", frameOnMouseUp)
|
||||
self.sizer_e = sizer_e
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,frame)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",frame,"TOPLEFT",17,-27)
|
||||
content:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-17,40)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
--------------------------
|
||||
-- Heading --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "Heading"
|
||||
local Version = 5
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetText("")
|
||||
self:SetFullWidth()
|
||||
self:SetHeight(18)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
local function SetText(self, text)
|
||||
self.label:SetText(text or "")
|
||||
if (text or "") == "" then
|
||||
self.left:SetPoint("RIGHT",self.frame,"RIGHT",-3,0)
|
||||
self.right:Hide()
|
||||
else
|
||||
self.left:SetPoint("RIGHT",self.label,"LEFT",-5,0)
|
||||
self.right:Show()
|
||||
end
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetText = SetText
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(18)
|
||||
|
||||
local label = frame:CreateFontString(nil,"BACKGROUND","GameFontNormal")
|
||||
label:SetPoint("TOP",frame,"TOP",0,0)
|
||||
label:SetPoint("BOTTOM",frame,"BOTTOM",0,0)
|
||||
label:SetJustifyH("CENTER")
|
||||
label:SetHeight(18)
|
||||
self.label = label
|
||||
|
||||
local left = frame:CreateTexture(nil, "BACKGROUND")
|
||||
self.left = left
|
||||
left:SetHeight(8)
|
||||
left:SetPoint("LEFT",frame,"LEFT",3,0)
|
||||
left:SetPoint("RIGHT",label,"LEFT",-5,0)
|
||||
left:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
left:SetTexCoord(0.81, 0.94, 0.5, 1)
|
||||
|
||||
local right = frame:CreateTexture(nil, "BACKGROUND")
|
||||
self.right = right
|
||||
right:SetHeight(8)
|
||||
right:SetPoint("RIGHT",frame,"RIGHT",-3,0)
|
||||
right:SetPoint("LEFT",label,"RIGHT",5,0)
|
||||
right:SetTexture("Interface\\Tooltips\\UI-Tooltip-Border")
|
||||
right:SetTexCoord(0.81, 0.94, 0.5, 1)
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local select = select
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
--------------------------
|
||||
-- Label --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "Icon"
|
||||
local Version = 12
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetHeight(110)
|
||||
self:SetWidth(110)
|
||||
self:SetLabel("")
|
||||
self:SetImage(nil)
|
||||
self:SetImageSize(64, 64)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self:SetDisabled(false)
|
||||
end
|
||||
|
||||
local function SetLabel(self, text)
|
||||
if text and text ~= "" then
|
||||
self.label:Show()
|
||||
self.label:SetText(text)
|
||||
self:SetHeight(self.image:GetHeight() + 25)
|
||||
else
|
||||
self.label:Hide()
|
||||
self:SetHeight(self.image:GetHeight() + 10)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetImage(self, path, ...)
|
||||
local image = self.image
|
||||
image:SetTexture(path)
|
||||
|
||||
if image:GetTexture() then
|
||||
self.imageshown = true
|
||||
local n = select('#', ...)
|
||||
if n == 4 or n == 8 then
|
||||
image:SetTexCoord(...)
|
||||
else
|
||||
image:SetTexCoord(0, 1, 0, 1)
|
||||
end
|
||||
else
|
||||
self.imageshown = nil
|
||||
end
|
||||
end
|
||||
|
||||
local function SetImageSize(self, width, height)
|
||||
self.image:SetWidth(width)
|
||||
self.image:SetHeight(height)
|
||||
--self.frame:SetWidth(width + 30)
|
||||
if self.label:IsShown() then
|
||||
self:SetHeight(height + 25)
|
||||
else
|
||||
self:SetHeight(height + 10)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.frame:Disable()
|
||||
self.label:SetTextColor(0.5,0.5,0.5)
|
||||
self.image:SetVertexColor(0.5, 0.5, 0.5, 0.5)
|
||||
else
|
||||
self.frame:Enable()
|
||||
self.label:SetTextColor(1,1,1)
|
||||
self.image:SetVertexColor(1, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function OnClick(this, button)
|
||||
this.obj:Fire("OnClick", button)
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function OnEnter(this)
|
||||
this.obj.highlight:Show()
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function OnLeave(this)
|
||||
this.obj.highlight:Hide()
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Button",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetLabel = SetLabel
|
||||
self.frame = frame
|
||||
self.SetImage = SetImage
|
||||
self.SetImageSize = SetImageSize
|
||||
|
||||
-- SetText should be deprecated along the way
|
||||
self.SetText = SetLabel
|
||||
self.SetDisabled = SetDisabled
|
||||
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(110)
|
||||
frame:SetWidth(110)
|
||||
frame:EnableMouse(true)
|
||||
frame:SetScript("OnClick", OnClick)
|
||||
frame:SetScript("OnLeave", OnLeave)
|
||||
frame:SetScript("OnEnter", OnEnter)
|
||||
local label = frame:CreateFontString(nil,"BACKGROUND","GameFontHighlight")
|
||||
label:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",0,0)
|
||||
label:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
|
||||
label:SetJustifyH("CENTER")
|
||||
label:SetJustifyV("TOP")
|
||||
label:SetHeight(18)
|
||||
self.label = label
|
||||
|
||||
local image = frame:CreateTexture(nil,"BACKGROUND")
|
||||
self.image = image
|
||||
image:SetWidth(64)
|
||||
image:SetHeight(64)
|
||||
image:SetPoint("TOP",frame,"TOP",0,-5)
|
||||
|
||||
local highlight = frame:CreateTexture(nil,"OVERLAY")
|
||||
self.highlight = highlight
|
||||
highlight:SetAllPoints(image)
|
||||
highlight:SetTexture("Interface\\PaperDollInfoFrame\\UI-Character-Tab-Highlight")
|
||||
highlight:SetTexCoord(0,1,0.23,0.77)
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:Hide()
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-------------
|
||||
-- Widgets --
|
||||
-------------
|
||||
--[[
|
||||
Widgets must provide the following functions
|
||||
Acquire() - Called when the object is aquired, should set everything to a default hidden state
|
||||
Release() - Called when the object is Released, should remove any anchors and hide the Widget
|
||||
|
||||
And the following members
|
||||
frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
||||
type - the type of the object, same as the name given to :RegisterWidget()
|
||||
|
||||
Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet
|
||||
It will be cleared automatically when a widget is released
|
||||
Placing values directly into a widget object should be avoided
|
||||
|
||||
If the Widget can act as a container for other Widgets the following
|
||||
content - frame or derivitive that children will be anchored to
|
||||
|
||||
The Widget can supply the following Optional Members
|
||||
|
||||
|
||||
]]
|
||||
|
||||
--------------------------
|
||||
-- Inline Group --
|
||||
--------------------------
|
||||
--[[
|
||||
This is a simple grouping container, no selection
|
||||
It will resize automatically to the height of the controls added to it
|
||||
]]
|
||||
|
||||
do
|
||||
local Type = "InlineGroup"
|
||||
local Version = 6
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetWidth(300)
|
||||
self:SetHeight(100)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
local PaneBackdrop = {
|
||||
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 3, right = 3, top = 5, bottom = 3 }
|
||||
}
|
||||
|
||||
local function SetTitle(self,title)
|
||||
self.titletext:SetText(title)
|
||||
end
|
||||
|
||||
|
||||
local function LayoutFinished(self, width, height)
|
||||
if self.noAutoHeight then return end
|
||||
self:SetHeight((height or 0) + 40)
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
local contentwidth = width - 20
|
||||
if contentwidth < 0 then
|
||||
contentwidth = 0
|
||||
end
|
||||
content:SetWidth(contentwidth)
|
||||
content.width = contentwidth
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
local contentheight = height - 20
|
||||
if contentheight < 0 then
|
||||
contentheight = 0
|
||||
end
|
||||
content:SetHeight(contentheight)
|
||||
content.height = contentheight
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetTitle = SetTitle
|
||||
self.frame = frame
|
||||
self.LayoutFinished = LayoutFinished
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(100)
|
||||
frame:SetWidth(100)
|
||||
frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
|
||||
local titletext = frame:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
titletext:SetPoint("TOPLEFT",frame,"TOPLEFT",14,0)
|
||||
titletext:SetPoint("TOPRIGHT",frame,"TOPRIGHT",-14,0)
|
||||
titletext:SetJustifyH("LEFT")
|
||||
titletext:SetHeight(18)
|
||||
|
||||
self.titletext = titletext
|
||||
|
||||
local border = CreateFrame("Frame",nil,frame)
|
||||
self.border = border
|
||||
border:SetPoint("TOPLEFT",frame,"TOPLEFT",0,-17)
|
||||
border:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-1,3)
|
||||
|
||||
border:SetBackdrop(PaneBackdrop)
|
||||
border:SetBackdropColor(0.1,0.1,0.1,0.5)
|
||||
border:SetBackdropBorderColor(0.4,0.4,0.4)
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,border)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",border,"TOPLEFT",10,-10)
|
||||
content:SetPoint("BOTTOMRIGHT",border,"BOTTOMRIGHT",-10,10)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local select, max = select, math.max
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: GameFontHighlightSmall
|
||||
|
||||
--------------------------
|
||||
-- Label --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "InteractiveLabel"
|
||||
local Version = 6
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetHeight(18)
|
||||
self:SetWidth(200)
|
||||
self:SetText("")
|
||||
self:SetImage(nil)
|
||||
self:SetColor()
|
||||
self:SetFontObject()
|
||||
self:SetHighlight()
|
||||
self:SetHighlightTexCoord()
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self:SetDisabled(false)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
local function UpdateImageAnchor(self)
|
||||
local width = self.frame.width or self.frame:GetWidth() or 0
|
||||
local image = self.image
|
||||
local label = self.label
|
||||
local frame = self.frame
|
||||
local height
|
||||
|
||||
label:ClearAllPoints()
|
||||
image:ClearAllPoints()
|
||||
|
||||
if self.imageshown then
|
||||
local imagewidth = image:GetWidth()
|
||||
if (width - imagewidth) < 200 or (label:GetText() or "") == "" then
|
||||
--image goes on top centered when less than 200 width for the text, or if there is no text
|
||||
image:SetPoint("TOP",frame,"TOP",0,0)
|
||||
label:SetPoint("TOP",image,"BOTTOM",0,0)
|
||||
label:SetPoint("LEFT",frame,"LEFT",0,0)
|
||||
label:SetWidth(width)
|
||||
height = image:GetHeight() + label:GetHeight()
|
||||
else
|
||||
--image on the left
|
||||
local imageheight = image:GetHeight()
|
||||
local labelheight = label:GetHeight()
|
||||
--center image with label
|
||||
if imageheight > labelheight then
|
||||
image:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
label:SetPoint("LEFT",image,"RIGHT",0,0)
|
||||
else
|
||||
label:SetPoint("TOPLEFT",frame,"TOPLEFT",imagewidth,0)
|
||||
image:SetPoint("RIGHT",label,"LEFT",0,0)
|
||||
end
|
||||
label:SetWidth(width - imagewidth)
|
||||
height = max(imageheight, labelheight)
|
||||
end
|
||||
else
|
||||
--no image shown
|
||||
label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
label:SetWidth(width)
|
||||
height = self.label:GetHeight()
|
||||
end
|
||||
|
||||
self.resizing = true
|
||||
self.frame:SetHeight(height)
|
||||
self.frame.height = height
|
||||
self.resizing = nil
|
||||
end
|
||||
|
||||
local function SetText(self, text)
|
||||
self.label:SetText(text or "")
|
||||
UpdateImageAnchor(self)
|
||||
end
|
||||
|
||||
local function SetColor(self, r, g, b)
|
||||
if not (r and g and b) then
|
||||
r, g, b = 1, 1, 1
|
||||
end
|
||||
self.label:SetVertexColor(r, g, b)
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
if self.resizing then return end
|
||||
UpdateImageAnchor(self)
|
||||
end
|
||||
|
||||
local function SetImage(self, path, ...)
|
||||
local image = self.image
|
||||
image:SetTexture(path)
|
||||
|
||||
if image:GetTexture() then
|
||||
self.imageshown = true
|
||||
local n = select('#', ...)
|
||||
if n == 4 or n == 8 then
|
||||
image:SetTexCoord(...)
|
||||
end
|
||||
else
|
||||
self.imageshown = nil
|
||||
end
|
||||
UpdateImageAnchor(self)
|
||||
end
|
||||
|
||||
local function SetFont(self, font, height, flags)
|
||||
self.label:SetFont(font, height, flags)
|
||||
end
|
||||
|
||||
local function SetFontObject(self, font)
|
||||
self.label:SetFontObject(font or GameFontHighlightSmall)
|
||||
end
|
||||
|
||||
local function SetImageSize(self, width, height)
|
||||
self.image:SetWidth(width)
|
||||
self.image:SetHeight(height)
|
||||
UpdateImageAnchor(self)
|
||||
end
|
||||
|
||||
local function SetHighlight(self, ...)
|
||||
self.highlight:SetTexture(...)
|
||||
end
|
||||
|
||||
local function SetHighlightTexCoord(self, ...)
|
||||
if select('#', ...) >= 1 then
|
||||
self.highlight:SetTexCoord(...)
|
||||
else
|
||||
self.highlight:SetTexCoord(0, 1, 0, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetDisabled(self,disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.frame:EnableMouse(false)
|
||||
self.label:SetTextColor(0.5, 0.5, 0.5)
|
||||
else
|
||||
self.frame:EnableMouse(true)
|
||||
self.label:SetTextColor(1, 1, 1)
|
||||
end
|
||||
end
|
||||
|
||||
local function OnEnter(this)
|
||||
this.obj.highlight:Show()
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function OnLeave(this)
|
||||
this.obj.highlight:Hide()
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function OnClick(this, ...)
|
||||
this.obj:Fire("OnClick", ...)
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
frame:EnableMouse(true)
|
||||
frame:SetScript("OnEnter", OnEnter)
|
||||
frame:SetScript("OnLeave", OnLeave)
|
||||
frame:SetScript("OnMouseDown", OnClick)
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetText = SetText
|
||||
self.SetColor = SetColor
|
||||
self.frame = frame
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.SetImage = SetImage
|
||||
self.SetImageSize = SetImageSize
|
||||
self.SetFont = SetFont
|
||||
self.SetFontObject = SetFontObject
|
||||
self.SetHighlight = SetHighlight
|
||||
self.SetHighlightTexCoord = SetHighlightTexCoord
|
||||
self.SetDisabled = SetDisabled
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(18)
|
||||
frame:SetWidth(200)
|
||||
local label = frame:CreateFontString(nil,"BACKGROUND","GameFontHighlightSmall")
|
||||
label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
label:SetWidth(200)
|
||||
label:SetJustifyH("LEFT")
|
||||
label:SetJustifyV("TOP")
|
||||
self.label = label
|
||||
|
||||
local highlight = frame:CreateTexture(nil, "OVERLAY")
|
||||
highlight:SetTexture(nil)
|
||||
highlight:SetAllPoints()
|
||||
highlight:SetBlendMode("ADD")
|
||||
highlight:Hide()
|
||||
self.highlight = highlight
|
||||
|
||||
local image = frame:CreateTexture(nil,"BACKGROUND")
|
||||
self.image = image
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
|
||||
+230
@@ -0,0 +1,230 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
-- WoW APIs
|
||||
local IsShiftKeyDown, IsControlKeyDown, IsAltKeyDown = IsShiftKeyDown, IsControlKeyDown, IsAltKeyDown
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: NOT_BOUND
|
||||
|
||||
--------------------------
|
||||
-- Keybinding --
|
||||
--------------------------
|
||||
|
||||
do
|
||||
local Type = "Keybinding"
|
||||
local Version = 13
|
||||
|
||||
local ControlBackdrop = {
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 3, right = 3, top = 3, bottom = 3 }
|
||||
}
|
||||
|
||||
local function Control_OnEnter(this)
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function Control_OnLeave(this)
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function keybindingMsgFixWidth(this)
|
||||
this:SetWidth(this.msg:GetWidth()+10)
|
||||
this:SetScript("OnUpdate",nil)
|
||||
end
|
||||
|
||||
local function Keybinding_OnClick(this, button)
|
||||
if button == "LeftButton" or button == "RightButton" then
|
||||
local self = this.obj
|
||||
if self.waitingForKey then
|
||||
this:EnableKeyboard(false)
|
||||
self.msgframe:Hide()
|
||||
this:UnlockHighlight()
|
||||
self.waitingForKey = nil
|
||||
else
|
||||
this:EnableKeyboard(true)
|
||||
self.msgframe:Show()
|
||||
this:LockHighlight()
|
||||
self.waitingForKey = true
|
||||
end
|
||||
end
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local ignoreKeys = nil
|
||||
local function Keybinding_OnKeyDown(this, key)
|
||||
local self = this.obj
|
||||
if self.waitingForKey then
|
||||
local keyPressed = key
|
||||
if keyPressed == "ESCAPE" then
|
||||
keyPressed = ""
|
||||
else
|
||||
if not ignoreKeys then
|
||||
ignoreKeys = {
|
||||
["BUTTON1"] = true, ["BUTTON2"] = true,
|
||||
["UNKNOWN"] = true,
|
||||
["LSHIFT"] = true, ["LCTRL"] = true, ["LALT"] = true,
|
||||
["RSHIFT"] = true, ["RCTRL"] = true, ["RALT"] = true,
|
||||
}
|
||||
end
|
||||
if ignoreKeys[keyPressed] then return end
|
||||
if IsShiftKeyDown() then
|
||||
keyPressed = "SHIFT-"..keyPressed
|
||||
end
|
||||
if IsControlKeyDown() then
|
||||
keyPressed = "CTRL-"..keyPressed
|
||||
end
|
||||
if IsAltKeyDown() then
|
||||
keyPressed = "ALT-"..keyPressed
|
||||
end
|
||||
end
|
||||
|
||||
this:EnableKeyboard(false)
|
||||
self.msgframe:Hide()
|
||||
this:UnlockHighlight()
|
||||
self.waitingForKey = nil
|
||||
|
||||
if not self.disabled then
|
||||
self:SetKey(keyPressed)
|
||||
self:Fire("OnKeyChanged",keyPressed)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function Keybinding_OnMouseDown(this, button)
|
||||
if button == "LeftButton" or button == "RightButton" then
|
||||
return
|
||||
elseif button == "MiddleButton" then
|
||||
button = "BUTTON3"
|
||||
elseif button == "Button4" then
|
||||
button = "BUTTON4"
|
||||
elseif button == "Button5" then
|
||||
button = "BUTTON5"
|
||||
end
|
||||
Keybinding_OnKeyDown(this, button)
|
||||
end
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetWidth(200)
|
||||
self:SetHeight(44)
|
||||
self:SetLabel("")
|
||||
self:SetKey("")
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.waitingForKey = nil
|
||||
self.msgframe:Hide()
|
||||
self:SetDisabled(false)
|
||||
end
|
||||
|
||||
local function SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.button:Disable()
|
||||
self.label:SetTextColor(0.5,0.5,0.5)
|
||||
else
|
||||
self.button:Enable()
|
||||
self.label:SetTextColor(1,1,1)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetKey(self, key)
|
||||
if (key or "") == "" then
|
||||
self.button:SetText(NOT_BOUND)
|
||||
self.button:SetNormalFontObject("GameFontNormal")
|
||||
else
|
||||
self.button:SetText(key)
|
||||
self.button:SetNormalFontObject("GameFontHighlight")
|
||||
end
|
||||
end
|
||||
|
||||
local function SetLabel(self, label)
|
||||
self.label:SetText(label or "")
|
||||
if (label or "") == "" then
|
||||
self.alignoffset = nil
|
||||
self:SetHeight(24)
|
||||
else
|
||||
self.alignoffset = 30
|
||||
self:SetHeight(44)
|
||||
end
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local num = AceGUI:GetNextWidgetNum(Type)
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
|
||||
local button = CreateFrame("Button","AceGUI-3.0 KeybindingButton"..num,frame,"UIPanelButtonTemplate2")
|
||||
|
||||
local self = {}
|
||||
self.type = Type
|
||||
self.num = num
|
||||
|
||||
local text = button:GetFontString()
|
||||
text:SetPoint("LEFT",button,"LEFT",7,0)
|
||||
text:SetPoint("RIGHT",button,"RIGHT",-7,0)
|
||||
|
||||
button:SetScript("OnClick",Keybinding_OnClick)
|
||||
button:SetScript("OnKeyDown",Keybinding_OnKeyDown)
|
||||
button:SetScript("OnEnter",Control_OnEnter)
|
||||
button:SetScript("OnLeave",Control_OnLeave)
|
||||
button:SetScript("OnMouseDown",Keybinding_OnMouseDown)
|
||||
button:RegisterForClicks("AnyDown")
|
||||
button:EnableMouse()
|
||||
|
||||
button:SetHeight(24)
|
||||
button:SetWidth(200)
|
||||
button:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT",0,0)
|
||||
button:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
|
||||
|
||||
frame:SetWidth(200)
|
||||
frame:SetHeight(44)
|
||||
|
||||
self.alignoffset = 30
|
||||
|
||||
self.button = button
|
||||
|
||||
local label = frame:CreateFontString(nil,"OVERLAY","GameFontHighlight")
|
||||
label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
label:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,0)
|
||||
label:SetJustifyH("CENTER")
|
||||
label:SetHeight(18)
|
||||
self.label = label
|
||||
|
||||
local msgframe = CreateFrame("Frame",nil,UIParent)
|
||||
msgframe:SetHeight(30)
|
||||
msgframe:SetBackdrop(ControlBackdrop)
|
||||
msgframe:SetBackdropColor(0,0,0)
|
||||
msgframe:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
msgframe:SetFrameLevel(1000)
|
||||
self.msgframe = msgframe
|
||||
local msg = msgframe:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
msg:SetText("Press a key to bind, ESC to clear the binding or click the button again to cancel")
|
||||
msgframe.msg = msg
|
||||
msg:SetPoint("TOPLEFT",msgframe,"TOPLEFT",5,-5)
|
||||
msgframe:SetScript("OnUpdate", keybindingMsgFixWidth)
|
||||
msgframe:SetPoint("BOTTOM",button,"TOP",0,0)
|
||||
msgframe:Hide()
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetLabel = SetLabel
|
||||
self.SetDisabled = SetDisabled
|
||||
self.SetKey = SetKey
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
button.obj = self
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+32
-36
@@ -1,13 +1,9 @@
|
||||
--[[-----------------------------------------------------------------------------
|
||||
Label Widget
|
||||
Displays text and optionally an icon.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Label", 21
|
||||
local Type, Version = "Label", 20
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
local max, select, pairs = math.max, select, pairs
|
||||
local max, select = math.max, select
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
@@ -17,11 +13,10 @@ local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
-- GLOBALS: GameFontHighlightSmall
|
||||
|
||||
--[[-----------------------------------------------------------------------------
|
||||
Support functions
|
||||
Scripts
|
||||
-------------------------------------------------------------------------------]]
|
||||
|
||||
local function UpdateImageAnchor(self)
|
||||
if self.resizing then return end
|
||||
local frame = self.frame
|
||||
local width = frame.width or frame:GetWidth() or 0
|
||||
local image = self.image
|
||||
@@ -35,22 +30,22 @@ local function UpdateImageAnchor(self)
|
||||
local imagewidth = image:GetWidth()
|
||||
if (width - imagewidth) < 200 or (label:GetText() or "") == "" then
|
||||
-- image goes on top centered when less than 200 width for the text, or if there is no text
|
||||
image:SetPoint("TOP")
|
||||
label:SetPoint("TOP", image, "BOTTOM")
|
||||
label:SetPoint("LEFT")
|
||||
label:SetWidth(width)
|
||||
image:SetPoint("TOP", frame, "TOP", 0, 0)
|
||||
label:SetPoint("TOP", image, "BOTTOM", 0, 0)
|
||||
label:SetPoint("LEFT", frame, "LEFT", 0, 0)
|
||||
label:SetPoint("RIGHT", frame, "RIGHT", 0, 0)
|
||||
height = image:GetHeight() + label:GetHeight()
|
||||
else
|
||||
-- image on the left
|
||||
image:SetPoint("TOPLEFT")
|
||||
image:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
|
||||
label:SetPoint("TOPLEFT", image, "TOPRIGHT", 4, 0)
|
||||
label:SetWidth(width - imagewidth - 4)
|
||||
label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
|
||||
height = max(image:GetHeight(), label:GetHeight())
|
||||
end
|
||||
else
|
||||
-- no image shown
|
||||
label:SetPoint("TOPLEFT")
|
||||
label:SetWidth(width)
|
||||
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
|
||||
label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
|
||||
height = label:GetHeight()
|
||||
end
|
||||
|
||||
@@ -65,30 +60,23 @@ Methods
|
||||
-------------------------------------------------------------------------------]]
|
||||
local methods = {
|
||||
["OnAcquire"] = function(self)
|
||||
-- set the flag to stop constant size updates
|
||||
self.resizing = true
|
||||
-- height is set dynamically by the text and image size
|
||||
self:SetHeight(18)
|
||||
self:SetWidth(200)
|
||||
self:SetText()
|
||||
self:SetText("")
|
||||
self:SetImage(nil)
|
||||
self:SetImageSize(16, 16)
|
||||
self:SetColor()
|
||||
self:SetFontObject()
|
||||
|
||||
-- reset the flag
|
||||
self.resizing = nil
|
||||
-- run the update explicitly
|
||||
UpdateImageAnchor(self)
|
||||
self.label:SetFontObject(nil)
|
||||
self.label:SetFont(GameFontHighlightSmall:GetFont())
|
||||
end,
|
||||
|
||||
-- ["OnRelease"] = nil,
|
||||
|
||||
["OnWidthSet"] = function(self, width)
|
||||
UpdateImageAnchor(self)
|
||||
["OnRelease"] = function(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end,
|
||||
|
||||
["SetText"] = function(self, text)
|
||||
self.label:SetText(text)
|
||||
self.label:SetText(text or "")
|
||||
UpdateImageAnchor(self)
|
||||
end,
|
||||
|
||||
@@ -99,13 +87,18 @@ local methods = {
|
||||
self.label:SetVertexColor(r, g, b)
|
||||
end,
|
||||
|
||||
["OnWidthSet"] = function(self, width)
|
||||
if self.resizing then return end
|
||||
UpdateImageAnchor(self)
|
||||
end,
|
||||
|
||||
["SetImage"] = function(self, path, ...)
|
||||
local image = self.image
|
||||
image:SetTexture(path)
|
||||
|
||||
if image:GetTexture() then
|
||||
self.imageshown = true
|
||||
local n = select("#", ...)
|
||||
local n = select('#', ...)
|
||||
if n == 4 or n == 8 then
|
||||
image:SetTexCoord(...)
|
||||
else
|
||||
@@ -122,7 +115,7 @@ local methods = {
|
||||
end,
|
||||
|
||||
["SetFontObject"] = function(self, font)
|
||||
self:SetFont((font or GameFontHighlightSmall):GetFont())
|
||||
self.label:SetFontObject(font or GameFontHighlightSmall)
|
||||
end,
|
||||
|
||||
["SetImageSize"] = function(self, width, height)
|
||||
@@ -131,8 +124,7 @@ local methods = {
|
||||
UpdateImageAnchor(self)
|
||||
end,
|
||||
}
|
||||
|
||||
--[[-----------------------------------------------------------------------------
|
||||
--[[-----------------------------------------------------------------------------
|
||||
Constructor
|
||||
-------------------------------------------------------------------------------]]
|
||||
local function Constructor()
|
||||
@@ -140,6 +132,8 @@ local function Constructor()
|
||||
frame:Hide()
|
||||
|
||||
local label = frame:CreateFontString(nil, "BACKGROUND", "GameFontHighlightSmall")
|
||||
label:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
|
||||
label:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
|
||||
label:SetJustifyH("LEFT")
|
||||
label:SetJustifyV("TOP")
|
||||
|
||||
@@ -155,8 +149,10 @@ local function Constructor()
|
||||
for method, func in pairs(methods) do
|
||||
widget[method] = func
|
||||
end
|
||||
frame.obj, label.obj, image.obj = widget, widget, widget
|
||||
|
||||
return AceGUI:RegisterAsWidget(widget)
|
||||
AceGUI:RegisterAsWidget(widget)
|
||||
return widget
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type, Constructor, Version)
|
||||
+18
-63
@@ -1,40 +1,7 @@
|
||||
local Type, Version = "MultiLineEditBox", 23
|
||||
local Type, Version = "MultiLineEditBox", 20
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
local pairs = pairs
|
||||
|
||||
-- WoW APIs
|
||||
local GetCursorInfo, GetSpellName, ClearCursor = GetCursorInfo, GetSpellName, ClearCursor
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
local _G = _G
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: ACCEPT, ChatFontNormal
|
||||
|
||||
--[[-----------------------------------------------------------------------------
|
||||
Support functions
|
||||
-------------------------------------------------------------------------------]]
|
||||
local function Layout(self)
|
||||
self:SetHeight(self.numlines * 14 + (self.disablebutton and 19 or 41) + self.labelHeight)
|
||||
|
||||
if self.labelHeight == 0 then
|
||||
self.scrollBar:SetPoint("TOP", self.frame, "TOP", 0, -23)
|
||||
else
|
||||
self.scrollBar:SetPoint("TOP", self.label, "BOTTOM", 0, -19)
|
||||
end
|
||||
|
||||
if self.disablebutton then
|
||||
self.scrollBar:SetPoint("BOTTOM", self.frame, "BOTTOM", 0, 21)
|
||||
self.scrollBG:SetPoint("BOTTOMLEFT", 0, 4)
|
||||
else
|
||||
self.scrollBar:SetPoint("BOTTOM", self.button, "TOP", 0, 18)
|
||||
self.scrollBG:SetPoint("BOTTOMLEFT", self.button, "TOPLEFT")
|
||||
end
|
||||
end
|
||||
|
||||
--[[-----------------------------------------------------------------------------
|
||||
Scripts
|
||||
-------------------------------------------------------------------------------]]
|
||||
@@ -125,7 +92,7 @@ local function OnTextSet(self)
|
||||
self.obj.button:Disable()
|
||||
end
|
||||
|
||||
local function OnVerticalScroll(self, offset) -- ScrollFrame
|
||||
function OnVerticalScroll(self, offset) -- ScrollFrame
|
||||
local editBox = self.obj.editBox
|
||||
editBox:SetHitRectInsets(0, 0, offset, editBox:GetHeight() - offset - self:GetHeight())
|
||||
end
|
||||
@@ -134,17 +101,22 @@ end
|
||||
Methods
|
||||
-------------------------------------------------------------------------------]]
|
||||
local methods = {
|
||||
["GetText"] = function(self)
|
||||
return self.editBox:GetText()
|
||||
end,
|
||||
|
||||
["OnAcquire"] = function(self)
|
||||
self.editBox:SetText("")
|
||||
self:SetDisabled(false)
|
||||
self:SetWidth(200)
|
||||
self:DisableButton(false)
|
||||
self:SetNumLines()
|
||||
self.entered = nil
|
||||
self:SetMaxLetters(0)
|
||||
end,
|
||||
|
||||
-- ["OnRelease"] = nil,
|
||||
["OnRelease"] = function(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end,
|
||||
|
||||
["SetDisabled"] = function(self, disabled)
|
||||
local editBox = self.editBox
|
||||
@@ -168,43 +140,27 @@ local methods = {
|
||||
self.label:SetText(text)
|
||||
if self.labelHeight ~= 10 then
|
||||
self.labelHeight = 10
|
||||
self.scrollBar:SetPoint("TOP", self.label, "BOTTOM", 0, -19)
|
||||
self:SetHeight(self.frame.height + 10)
|
||||
self.label:Show()
|
||||
end
|
||||
elseif self.labelHeight ~= 0 then
|
||||
self.labelHeight = 0
|
||||
self.label:Hide()
|
||||
self.scrollBar:SetPoint("TOP", self.frame, "TOP", 0, -23)
|
||||
self:SetHeight(self.frame.height - 10)
|
||||
end
|
||||
Layout(self)
|
||||
end,
|
||||
|
||||
["SetNumLines"] = function(self, value)
|
||||
if not value or value < 4 then
|
||||
value = 4
|
||||
end
|
||||
self.numlines = value
|
||||
Layout(self)
|
||||
self:SetHeight(value * 14 + 41 + self.labelHeight)
|
||||
end,
|
||||
|
||||
["SetText"] = function(self, text)
|
||||
self.editBox:SetText(text)
|
||||
end,
|
||||
|
||||
["GetText"] = function(self)
|
||||
return self.editBox:GetText()
|
||||
end,
|
||||
|
||||
["SetMaxLetters"] = function (self, num)
|
||||
self.editBox:SetMaxLetters(num or 0)
|
||||
end,
|
||||
|
||||
["DisableButton"] = function(self, disabled)
|
||||
self.disablebutton = disabled
|
||||
if disabled then
|
||||
self.button:Hide()
|
||||
else
|
||||
self.button:Show()
|
||||
end
|
||||
Layout(self)
|
||||
end
|
||||
}
|
||||
|
||||
@@ -231,7 +187,7 @@ local function Constructor()
|
||||
label:SetHeight(10)
|
||||
|
||||
local button = CreateFrame("Button", ("%s%dButton"):format(Type, widgetNum), frame, "UIPanelButtonTemplate2")
|
||||
button:SetPoint("BOTTOMLEFT", 0, 4)
|
||||
button:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 4)
|
||||
button:SetHeight(22)
|
||||
button:SetWidth(label:GetStringWidth() + 24)
|
||||
button:SetText(ACCEPT)
|
||||
@@ -294,9 +250,7 @@ local function Constructor()
|
||||
frame = frame,
|
||||
label = label,
|
||||
labelHeight = 10,
|
||||
numlines = 4,
|
||||
scrollBar = scrollBar,
|
||||
scrollBG = scrollBG,
|
||||
scrollFrame = scrollFrame,
|
||||
type = Type
|
||||
}
|
||||
@@ -305,7 +259,8 @@ local function Constructor()
|
||||
end
|
||||
button.obj, editBox.obj, scrollFrame.obj = widget, widget, widget
|
||||
|
||||
return AceGUI:RegisterAsWidget(widget)
|
||||
AceGUI:RegisterAsWidget(widget)
|
||||
return widget
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type, Constructor, Version)
|
||||
+241
@@ -0,0 +1,241 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local pairs, assert, type = pairs, assert, type
|
||||
local min, max, floor = math.min, math.max, math.floor
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
|
||||
-------------
|
||||
-- Widgets --
|
||||
-------------
|
||||
--[[
|
||||
Widgets must provide the following functions
|
||||
Acquire() - Called when the object is aquired, should set everything to a default hidden state
|
||||
Release() - Called when the object is Released, should remove any anchors and hide the Widget
|
||||
|
||||
And the following members
|
||||
frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
||||
type - the type of the object, same as the name given to :RegisterWidget()
|
||||
|
||||
Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet
|
||||
It will be cleared automatically when a widget is released
|
||||
Placing values directly into a widget object should be avoided
|
||||
|
||||
If the Widget can act as a container for other Widgets the following
|
||||
content - frame or derivitive that children will be anchored to
|
||||
|
||||
The Widget can supply the following Optional Members
|
||||
|
||||
|
||||
]]
|
||||
|
||||
--------------------------
|
||||
-- Scroll Frame --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "ScrollFrame"
|
||||
local Version = 9
|
||||
|
||||
local function OnAcquire(self)
|
||||
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.status = nil
|
||||
-- do SetScroll after niling status, but before clearing localstatus
|
||||
-- so the scroll value isnt populated back into status, but not kept in localstatus either
|
||||
self:SetScroll(0)
|
||||
for k in pairs(self.localstatus) do
|
||||
self.localstatus[k] = nil
|
||||
end
|
||||
self.scrollframe:SetPoint("BOTTOMRIGHT",self.frame,"BOTTOMRIGHT",0,0)
|
||||
self.scrollbar:Hide()
|
||||
self.scrollBarShown = nil
|
||||
self.content.height, self.content.width = nil, nil
|
||||
end
|
||||
|
||||
local function SetScroll(self, value)
|
||||
local status = self.status or self.localstatus
|
||||
local viewheight = self.scrollframe:GetHeight()
|
||||
local height = self.content:GetHeight()
|
||||
local offset
|
||||
|
||||
if viewheight > height then
|
||||
offset = 0
|
||||
else
|
||||
offset = floor((height - viewheight) / 1000.0 * value)
|
||||
end
|
||||
self.content:ClearAllPoints()
|
||||
self.content:SetPoint("TOPLEFT", self.scrollframe, "TOPLEFT", 0, offset)
|
||||
self.content:SetPoint("TOPRIGHT", self.scrollframe, "TOPRIGHT", 0, offset)
|
||||
status.offset = offset
|
||||
status.scrollvalue = value
|
||||
end
|
||||
|
||||
local function MoveScroll(self, value)
|
||||
local status = self.status or self.localstatus
|
||||
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
|
||||
|
||||
if height > viewheight then
|
||||
self.scrollbar:Hide()
|
||||
else
|
||||
self.scrollbar:Show()
|
||||
local diff = height - viewheight
|
||||
local delta = 1
|
||||
if value < 0 then
|
||||
delta = -1
|
||||
end
|
||||
self.scrollbar:SetValue(min(max(status.scrollvalue + delta*(1000/(diff/45)),0), 1000))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function FixScroll(self)
|
||||
if self.updateLock then return end
|
||||
self.updateLock = true
|
||||
local status = self.status or self.localstatus
|
||||
local height, viewheight = self.scrollframe:GetHeight(), self.content:GetHeight()
|
||||
local offset = status.offset or 0
|
||||
local curvalue = self.scrollbar:GetValue()
|
||||
if viewheight < height then
|
||||
if self.scrollBarShown then
|
||||
self.scrollBarShown = nil
|
||||
self.scrollbar:Hide()
|
||||
self.scrollbar:SetValue(0)
|
||||
self.scrollframe:SetPoint("BOTTOMRIGHT",self.frame,"BOTTOMRIGHT",0,0)
|
||||
self:DoLayout()
|
||||
end
|
||||
else
|
||||
if not self.scrollBarShown then
|
||||
self.scrollBarShown = true
|
||||
self.scrollbar:Show()
|
||||
self.scrollframe:SetPoint("BOTTOMRIGHT", self.frame,"BOTTOMRIGHT",-20,0)
|
||||
self:DoLayout()
|
||||
end
|
||||
local value = (offset / (viewheight - height) * 1000)
|
||||
if value > 1000 then value = 1000 end
|
||||
self.scrollbar:SetValue(value)
|
||||
self:SetScroll(value)
|
||||
if value < 1000 then
|
||||
self.content:ClearAllPoints()
|
||||
self.content:SetPoint("TOPLEFT", self.scrollframe, "TOPLEFT", 0, offset)
|
||||
self.content:SetPoint("TOPRIGHT", self.scrollframe, "TOPRIGHT", 0, offset)
|
||||
status.offset = offset
|
||||
end
|
||||
end
|
||||
self.updateLock = nil
|
||||
end
|
||||
|
||||
local function OnMouseWheel(this, value)
|
||||
this.obj:MoveScroll(value)
|
||||
end
|
||||
|
||||
local function OnScrollValueChanged(this, value)
|
||||
this.obj:SetScroll(value)
|
||||
end
|
||||
|
||||
local function FixScrollOnUpdate(this)
|
||||
this:SetScript("OnUpdate", nil)
|
||||
this.obj:FixScroll()
|
||||
end
|
||||
|
||||
local function OnSizeChanged(this)
|
||||
this:SetScript("OnUpdate", FixScrollOnUpdate)
|
||||
end
|
||||
|
||||
local function LayoutFinished(self, width, height)
|
||||
self.content:SetHeight(height or 0 + 20)
|
||||
self.scrollframe:SetScript("OnUpdate", FixScrollOnUpdate)
|
||||
end
|
||||
|
||||
-- called to set an external table to store status in
|
||||
local function SetStatusTable(self, status)
|
||||
assert(type(status) == "table")
|
||||
self.status = status
|
||||
if not status.scrollvalue then
|
||||
status.scrollvalue = 0
|
||||
end
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
content.width = width
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
content.height = height
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame", nil, UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.MoveScroll = MoveScroll
|
||||
self.FixScroll = FixScroll
|
||||
self.SetScroll = SetScroll
|
||||
self.LayoutFinished = LayoutFinished
|
||||
self.SetStatusTable = SetStatusTable
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
|
||||
self.localstatus = {}
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
--Container Support
|
||||
local scrollframe = CreateFrame("ScrollFrame", nil, frame)
|
||||
scrollframe.obj = self
|
||||
scrollframe:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
|
||||
scrollframe:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
|
||||
scrollframe:EnableMouseWheel(true)
|
||||
scrollframe:SetScript("OnMouseWheel", OnMouseWheel)
|
||||
scrollframe:SetScript("OnSizeChanged", OnSizeChanged)
|
||||
self.scrollframe = scrollframe
|
||||
|
||||
local content = CreateFrame("Frame", nil, scrollframe)
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT", scrollframe, "TOPLEFT", 0, 0)
|
||||
content:SetPoint("TOPRIGHT", scrollframe, "TOPRIGHT", 0, 0)
|
||||
content:SetHeight(400)
|
||||
self.content = content
|
||||
scrollframe:SetScrollChild(content)
|
||||
|
||||
local num = AceGUI:GetNextWidgetNum(Type)
|
||||
local name = ("AceConfigDialogScrollFrame%dScrollBar"):format(num)
|
||||
local scrollbar = CreateFrame("Slider", name, scrollframe, "UIPanelScrollBarTemplate")
|
||||
scrollbar.obj = self
|
||||
scrollbar:SetPoint("TOPLEFT", scrollframe, "TOPRIGHT", 4, -16)
|
||||
scrollbar:SetPoint("BOTTOMLEFT", scrollframe, "BOTTOMRIGHT", 4, 16)
|
||||
scrollbar:SetScript("OnValueChanged", OnScrollValueChanged)
|
||||
scrollbar:SetMinMaxValues(0, 1000)
|
||||
scrollbar:SetValueStep(1)
|
||||
scrollbar:SetValue(0)
|
||||
scrollbar:SetWidth(16)
|
||||
scrollbar:Hide()
|
||||
self.scrollbar = scrollbar
|
||||
|
||||
local scrollbg = scrollbar:CreateTexture(nil, "BACKGROUND")
|
||||
scrollbg:SetAllPoints(scrollbar)
|
||||
scrollbg:SetTexture(0, 0, 0, 0.4)
|
||||
|
||||
self.localstatus.scrollvalue = 0
|
||||
|
||||
--self:FixScroll()
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
--AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-------------
|
||||
-- Widgets --
|
||||
-------------
|
||||
--[[
|
||||
Widgets must provide the following functions
|
||||
Acquire() - Called when the object is aquired, should set everything to a default hidden state
|
||||
Release() - Called when the object is Released, should remove any anchors and hide the Widget
|
||||
|
||||
And the following members
|
||||
frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
||||
type - the type of the object, same as the name given to :RegisterWidget()
|
||||
|
||||
Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet
|
||||
It will be cleared automatically when a widget is released
|
||||
Placing values directly into a widget object should be avoided
|
||||
|
||||
If the Widget can act as a container for other Widgets the following
|
||||
content - frame or derivitive that children will be anchored to
|
||||
|
||||
The Widget can supply the following Optional Members
|
||||
|
||||
|
||||
]]
|
||||
|
||||
--------------------------
|
||||
-- Simple Group --
|
||||
--------------------------
|
||||
--[[
|
||||
This is a simple grouping container, no selection, no borders
|
||||
It will resize automatically to the height of the controls added to it
|
||||
]]
|
||||
|
||||
do
|
||||
local Type = "SimpleGroup"
|
||||
local Version = 5
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetWidth(300)
|
||||
self:SetHeight(100)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
end
|
||||
|
||||
local function LayoutFinished(self, width, height)
|
||||
if self.noAutoHeight then return end
|
||||
self:SetHeight(height or 0)
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
content:SetWidth(width)
|
||||
content.width = width
|
||||
end
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
content:SetHeight(height)
|
||||
content.height = height
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.frame = frame
|
||||
self.LayoutFinished = LayoutFinished
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(100)
|
||||
frame:SetWidth(100)
|
||||
frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,frame)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
content:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+284
@@ -0,0 +1,284 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local min, max, floor = math.min, math.max, math.floor
|
||||
local tonumber = tonumber
|
||||
|
||||
-- WoW APIs
|
||||
local PlaySound = PlaySound
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: GameFontHighlightSmall
|
||||
|
||||
--------------------------
|
||||
-- Slider --
|
||||
--------------------------
|
||||
do
|
||||
local Type = "Slider"
|
||||
local Version = 10
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetWidth(200)
|
||||
self:SetHeight(44)
|
||||
self:SetDisabled(false)
|
||||
self:SetIsPercent(nil)
|
||||
self:SetSliderValues(0,100,1)
|
||||
self:SetValue(0)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.slider:EnableMouseWheel(false)
|
||||
self:SetDisabled(false)
|
||||
end
|
||||
|
||||
local function Control_OnEnter(this)
|
||||
this.obj:Fire("OnEnter")
|
||||
end
|
||||
|
||||
local function Control_OnLeave(this)
|
||||
this.obj:Fire("OnLeave")
|
||||
end
|
||||
|
||||
local function UpdateText(self)
|
||||
local value = self.value or 0
|
||||
if self.ispercent then
|
||||
self.editbox:SetText(("%s%%"):format(floor(value*1000+0.5)/10))
|
||||
else
|
||||
self.editbox:SetText(floor(value*100+0.5)/100)
|
||||
end
|
||||
end
|
||||
|
||||
local function UpdateLabels(self)
|
||||
local min, max = (self.min or 0), (self.max or 100)
|
||||
if self.ispercent then
|
||||
self.lowtext:SetFormattedText("%s%%",(min * 100))
|
||||
self.hightext:SetFormattedText("%s%%",(max * 100))
|
||||
else
|
||||
self.lowtext:SetText(min)
|
||||
self.hightext:SetText(max)
|
||||
end
|
||||
end
|
||||
|
||||
local function Slider_OnValueChanged(this)
|
||||
local self = this.obj
|
||||
if not this.setup then
|
||||
local newvalue
|
||||
newvalue = this:GetValue()
|
||||
if newvalue ~= self.value and not self.disabled then
|
||||
self.value = newvalue
|
||||
self:Fire("OnValueChanged", newvalue)
|
||||
end
|
||||
if self.value then
|
||||
local value = self.value
|
||||
UpdateText(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function Slider_OnMouseUp(this)
|
||||
local self = this.obj
|
||||
self:Fire("OnMouseUp",this:GetValue())
|
||||
end
|
||||
|
||||
local function Slider_OnMouseWheel(this, v)
|
||||
local self = this.obj
|
||||
if not self.disabled then
|
||||
local value = self.value
|
||||
if v > 0 then
|
||||
value = min(value + (self.step or 1),self.max)
|
||||
else
|
||||
value = max(value - (self.step or 1), self.min)
|
||||
end
|
||||
self.slider:SetValue(value)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
if disabled then
|
||||
self.slider:EnableMouse(false)
|
||||
self.label:SetTextColor(.5,.5,.5)
|
||||
self.hightext:SetTextColor(.5,.5,.5)
|
||||
self.lowtext:SetTextColor(.5,.5,.5)
|
||||
--self.valuetext:SetTextColor(.5,.5,.5)
|
||||
self.editbox:SetTextColor(.5,.5,.5)
|
||||
self.editbox:EnableMouse(false)
|
||||
self.editbox:ClearFocus()
|
||||
else
|
||||
self.slider:EnableMouse(true)
|
||||
self.label:SetTextColor(1,.82,0)
|
||||
self.hightext:SetTextColor(1,1,1)
|
||||
self.lowtext:SetTextColor(1,1,1)
|
||||
--self.valuetext:SetTextColor(1,1,1)
|
||||
self.editbox:SetTextColor(1,1,1)
|
||||
self.editbox:EnableMouse(true)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetValue(self, value)
|
||||
self.slider.setup = true
|
||||
self.slider:SetValue(value)
|
||||
self.value = value
|
||||
UpdateText(self)
|
||||
self.slider.setup = nil
|
||||
end
|
||||
|
||||
local function SetLabel(self, text)
|
||||
self.label:SetText(text)
|
||||
end
|
||||
|
||||
local function SetSliderValues(self, min, max, step)
|
||||
local frame = self.slider
|
||||
frame.setup = true
|
||||
self.min = min
|
||||
self.max = max
|
||||
self.step = step
|
||||
frame:SetMinMaxValues(min or 0,max or 100)
|
||||
UpdateLabels(self)
|
||||
frame:SetValueStep(step or 1)
|
||||
if self.value then
|
||||
frame:SetValue(self.value)
|
||||
end
|
||||
frame.setup = nil
|
||||
end
|
||||
|
||||
local function EditBox_OnEscapePressed(this)
|
||||
this:ClearFocus()
|
||||
end
|
||||
|
||||
local function EditBox_OnEnterPressed(this)
|
||||
local self = this.obj
|
||||
local value = this:GetText()
|
||||
if self.ispercent then
|
||||
value = value:gsub('%%','')
|
||||
value = tonumber(value) / 100
|
||||
else
|
||||
value = tonumber(value)
|
||||
end
|
||||
|
||||
if value then
|
||||
PlaySound("igMainMenuOptionCheckBoxOn")
|
||||
self:Fire("OnMouseUp",value)
|
||||
end
|
||||
end
|
||||
|
||||
local function EditBox_OnEnter(this)
|
||||
this:SetBackdropBorderColor(0.5,0.5,0.5,1)
|
||||
end
|
||||
|
||||
local function EditBox_OnLeave(this)
|
||||
this:SetBackdropBorderColor(0.3,0.3,0.3,0.8)
|
||||
end
|
||||
|
||||
local function SetIsPercent(self, value)
|
||||
self.ispercent = value
|
||||
UpdateLabels(self)
|
||||
UpdateText(self)
|
||||
end
|
||||
|
||||
local function FrameOnMouseDown(this)
|
||||
this.obj.slider:EnableMouseWheel(true)
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local SliderBackdrop = {
|
||||
bgFile = "Interface\\Buttons\\UI-SliderBar-Background",
|
||||
edgeFile = "Interface\\Buttons\\UI-SliderBar-Border",
|
||||
tile = true, tileSize = 8, edgeSize = 8,
|
||||
insets = { left = 3, right = 3, top = 6, bottom = 6 }
|
||||
}
|
||||
|
||||
local ManualBackdrop = {
|
||||
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
edgeFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
tile = true, edgeSize = 1, tileSize = 5,
|
||||
}
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
self.SetDisabled = SetDisabled
|
||||
self.SetValue = SetValue
|
||||
self.SetSliderValues = SetSliderValues
|
||||
self.SetLabel = SetLabel
|
||||
self.SetIsPercent = SetIsPercent
|
||||
|
||||
self.alignoffset = 25
|
||||
|
||||
frame:EnableMouse(true)
|
||||
frame:SetScript("OnMouseDown",FrameOnMouseDown)
|
||||
self.slider = CreateFrame("Slider",nil,frame)
|
||||
local slider = self.slider
|
||||
slider:SetScript("OnEnter",Control_OnEnter)
|
||||
slider:SetScript("OnLeave",Control_OnLeave)
|
||||
slider:SetScript("OnMouseUp", Slider_OnMouseUp)
|
||||
slider.obj = self
|
||||
slider:SetOrientation("HORIZONTAL")
|
||||
slider:SetHeight(15)
|
||||
slider:SetHitRectInsets(0,0,-10,0)
|
||||
slider:SetBackdrop(SliderBackdrop)
|
||||
--slider:EnableMouseWheel(true)
|
||||
slider:SetScript("OnMouseWheel", Slider_OnMouseWheel)
|
||||
|
||||
local label = frame:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
label:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
label:SetPoint("TOPRIGHT",frame,"TOPRIGHT",0,0)
|
||||
label:SetJustifyH("CENTER")
|
||||
label:SetHeight(15)
|
||||
self.label = label
|
||||
|
||||
self.lowtext = slider:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall")
|
||||
self.lowtext:SetPoint("TOPLEFT",slider,"BOTTOMLEFT",2,3)
|
||||
|
||||
self.hightext = slider:CreateFontString(nil,"ARTWORK","GameFontHighlightSmall")
|
||||
self.hightext:SetPoint("TOPRIGHT",slider,"BOTTOMRIGHT",-2,3)
|
||||
|
||||
|
||||
local editbox = CreateFrame("EditBox",nil,frame)
|
||||
editbox:SetAutoFocus(false)
|
||||
editbox:SetFontObject(GameFontHighlightSmall)
|
||||
editbox:SetPoint("TOP",slider,"BOTTOM",0,0)
|
||||
editbox:SetHeight(14)
|
||||
editbox:SetWidth(70)
|
||||
editbox:SetJustifyH("CENTER")
|
||||
editbox:EnableMouse(true)
|
||||
editbox:SetScript("OnEscapePressed",EditBox_OnEscapePressed)
|
||||
editbox:SetScript("OnEnterPressed",EditBox_OnEnterPressed)
|
||||
editbox:SetScript("OnEnter",EditBox_OnEnter)
|
||||
editbox:SetScript("OnLeave",EditBox_OnLeave)
|
||||
editbox:SetBackdrop(ManualBackdrop)
|
||||
editbox:SetBackdropColor(0,0,0,0.5)
|
||||
editbox:SetBackdropBorderColor(0.3,0.3,0.30,0.80)
|
||||
self.editbox = editbox
|
||||
editbox.obj = self
|
||||
|
||||
slider:SetThumbTexture("Interface\\Buttons\\UI-SliderBar-Button-Horizontal")
|
||||
|
||||
frame:SetWidth(200)
|
||||
frame:SetHeight(44)
|
||||
slider:SetPoint("TOP",label,"BOTTOM",0,0)
|
||||
slider:SetPoint("LEFT",frame,"LEFT",3,0)
|
||||
slider:SetPoint("RIGHT",frame,"RIGHT",-3,0)
|
||||
|
||||
|
||||
slider:SetValue(self.value or 0)
|
||||
slider:SetScript("OnValueChanged",Slider_OnValueChanged)
|
||||
|
||||
AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+389
@@ -0,0 +1,389 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local pairs, ipairs, assert, type = pairs, ipairs, assert, type
|
||||
|
||||
-- WoW APIs
|
||||
local PlaySound = PlaySound
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
local _G = _G
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: PanelTemplates_TabResize, PanelTemplates_SetDisabledTabState, PanelTemplates_SelectTab, PanelTemplates_DeselectTab
|
||||
|
||||
-------------
|
||||
-- Widgets --
|
||||
-------------
|
||||
--[[
|
||||
Widgets must provide the following functions
|
||||
Acquire() - Called when the object is aquired, should set everything to a default hidden state
|
||||
Release() - Called when the object is Released, should remove any anchors and hide the Widget
|
||||
|
||||
And the following members
|
||||
frame - the frame or derivitive object that will be treated as the widget for size and anchoring purposes
|
||||
type - the type of the object, same as the name given to :RegisterWidget()
|
||||
|
||||
Widgets contain a table called userdata, this is a safe place to store data associated with the wigdet
|
||||
It will be cleared automatically when a widget is released
|
||||
Placing values directly into a widget object should be avoided
|
||||
|
||||
If the Widget can act as a container for other Widgets the following
|
||||
content - frame or derivitive that children will be anchored to
|
||||
|
||||
The Widget can supply the following Optional Members
|
||||
|
||||
|
||||
]]
|
||||
|
||||
--------------------------
|
||||
-- Tab Group --
|
||||
--------------------------
|
||||
|
||||
do
|
||||
local Type = "TabGroup"
|
||||
local Version = 25
|
||||
|
||||
local PaneBackdrop = {
|
||||
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 3, right = 3, top = 5, bottom = 3 }
|
||||
}
|
||||
|
||||
local function OnAcquire(self)
|
||||
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.status = nil
|
||||
for k in pairs(self.localstatus) do
|
||||
self.localstatus[k] = nil
|
||||
end
|
||||
self.tablist = nil
|
||||
for _, tab in pairs(self.tabs) do
|
||||
tab:Hide()
|
||||
end
|
||||
self:SetTitle()
|
||||
end
|
||||
|
||||
local function Tab_SetText(self, text)
|
||||
self:_SetText(text)
|
||||
local width = self.obj.frame.width or self.obj.frame:GetWidth() or 0
|
||||
PanelTemplates_TabResize(self, 0, nil, width)
|
||||
end
|
||||
|
||||
local function UpdateTabLook(self)
|
||||
if self.disabled then
|
||||
PanelTemplates_SetDisabledTabState(self)
|
||||
elseif self.selected then
|
||||
PanelTemplates_SelectTab(self)
|
||||
else
|
||||
PanelTemplates_DeselectTab(self)
|
||||
end
|
||||
end
|
||||
|
||||
local function Tab_SetSelected(self, selected)
|
||||
self.selected = selected
|
||||
UpdateTabLook(self)
|
||||
end
|
||||
|
||||
local function Tab_OnClick(self)
|
||||
if not (self.selected or self.disabled) then
|
||||
PlaySound("igCharacterInfoTab")
|
||||
self.obj:SelectTab(self.value)
|
||||
end
|
||||
end
|
||||
|
||||
local function Tab_SetDisabled(self, disabled)
|
||||
self.disabled = disabled
|
||||
UpdateTabLook(self)
|
||||
end
|
||||
|
||||
local function Tab_OnEnter(this)
|
||||
local self = this.obj
|
||||
self:Fire("OnTabEnter", self.tabs[this.id].value, this)
|
||||
end
|
||||
|
||||
local function Tab_OnLeave(this)
|
||||
local self = this.obj
|
||||
self:Fire("OnTabLeave", self.tabs[this.id].value, this)
|
||||
end
|
||||
|
||||
local function Tab_OnShow(this)
|
||||
_G[this:GetName().."HighlightTexture"]:SetWidth(this:GetTextWidth() + 30)
|
||||
end
|
||||
|
||||
local function CreateTab(self, id)
|
||||
local tabname = "AceGUITabGroup"..self.num.."Tab"..id
|
||||
local tab = CreateFrame("Button",tabname,self.border,"OptionsFrameTabButtonTemplate")
|
||||
tab.obj = self
|
||||
tab.id = id
|
||||
|
||||
tab.text = _G[tabname .. "Text"]
|
||||
tab.text:ClearAllPoints()
|
||||
tab.text:SetPoint("LEFT", tab, "LEFT", 14, -3)
|
||||
tab.text:SetPoint("RIGHT", tab, "RIGHT", -12, -3)
|
||||
|
||||
tab:SetScript("OnClick",Tab_OnClick)
|
||||
tab:SetScript("OnEnter",Tab_OnEnter)
|
||||
tab:SetScript("OnLeave",Tab_OnLeave)
|
||||
tab:SetScript("OnShow", Tab_OnShow)
|
||||
|
||||
tab._SetText = tab.SetText
|
||||
tab.SetText = Tab_SetText
|
||||
tab.SetSelected = Tab_SetSelected
|
||||
tab.SetDisabled = Tab_SetDisabled
|
||||
|
||||
return tab
|
||||
end
|
||||
|
||||
local function SetTitle(self, text)
|
||||
self.titletext:SetText(text or "")
|
||||
if text and text ~= "" then
|
||||
self.alignoffset = 25
|
||||
else
|
||||
self.alignoffset = 18
|
||||
end
|
||||
self:BuildTabs()
|
||||
end
|
||||
|
||||
-- called to set an external table to store status in
|
||||
local function SetStatusTable(self, status)
|
||||
assert(type(status) == "table")
|
||||
self.status = status
|
||||
end
|
||||
|
||||
local function SelectTab(self, value)
|
||||
local status = self.status or self.localstatus
|
||||
|
||||
local found
|
||||
for i, v in ipairs(self.tabs) do
|
||||
if v.value == value then
|
||||
v:SetSelected(true)
|
||||
found = true
|
||||
else
|
||||
v:SetSelected(false)
|
||||
end
|
||||
end
|
||||
status.selected = value
|
||||
if found then
|
||||
self:Fire("OnGroupSelected",value)
|
||||
end
|
||||
end
|
||||
|
||||
local function SetTabs(self, tabs)
|
||||
self.tablist = tabs
|
||||
self:BuildTabs()
|
||||
end
|
||||
|
||||
|
||||
local widths = {}
|
||||
local rowwidths = {}
|
||||
local rowends = {}
|
||||
local function BuildTabs(self)
|
||||
local hastitle = (self.titletext:GetText() and self.titletext:GetText() ~= "")
|
||||
local status = self.status or self.localstatus
|
||||
local tablist = self.tablist
|
||||
local tabs = self.tabs
|
||||
|
||||
if not tablist then return end
|
||||
|
||||
local width = self.frame.width or self.frame:GetWidth() or 0
|
||||
|
||||
for i = #widths, 1, -1 do
|
||||
widths[i] = nil
|
||||
end
|
||||
for i = #rowwidths, 1, -1 do
|
||||
rowwidths[i] = nil
|
||||
end
|
||||
for i = #rowends, 1, -1 do
|
||||
rowends[i] = nil
|
||||
end
|
||||
|
||||
--Place Text into tabs and get thier initial width
|
||||
for i, v in ipairs(tablist) do
|
||||
local tab = tabs[i]
|
||||
if not tab then
|
||||
tab = self:CreateTab(i)
|
||||
tabs[i] = tab
|
||||
end
|
||||
|
||||
tab:Show()
|
||||
tab:SetText(v.text)
|
||||
tab:SetDisabled(v.disabled)
|
||||
tab.value = v.value
|
||||
|
||||
widths[i] = tab:GetWidth() - 6 --tabs are anchored 10 pixels from the right side of the previous one to reduce spacing, but add a fixed 4px padding for the text
|
||||
end
|
||||
|
||||
for i = (#tablist)+1, #tabs, 1 do
|
||||
tabs[i]:Hide()
|
||||
end
|
||||
|
||||
--First pass, find the minimum number of rows needed to hold all tabs and the initial tab layout
|
||||
local numtabs = #tablist
|
||||
local numrows = 1
|
||||
local usedwidth = 0
|
||||
|
||||
for i = 1, #tablist do
|
||||
--If this is not the first tab of a row and there isn't room for it
|
||||
if usedwidth ~= 0 and (width - usedwidth - widths[i]) < 0 then
|
||||
rowwidths[numrows] = usedwidth + 10 --first tab in each row takes up an extra 10px
|
||||
rowends[numrows] = i - 1
|
||||
numrows = numrows + 1
|
||||
usedwidth = 0
|
||||
end
|
||||
usedwidth = usedwidth + widths[i]
|
||||
end
|
||||
rowwidths[numrows] = usedwidth + 10 --first tab in each row takes up an extra 10px
|
||||
rowends[numrows] = #tablist
|
||||
|
||||
--Fix for single tabs being left on the last row, move a tab from the row above if applicable
|
||||
if numrows > 1 then
|
||||
--if the last row has only one tab
|
||||
if rowends[numrows-1] == numtabs-1 then
|
||||
--if there are more than 2 tabs in the 2nd last row
|
||||
if (numrows == 2 and rowends[numrows-1] > 2) or (rowends[numrows] - rowends[numrows-1] > 2) then
|
||||
--move 1 tab from the second last row to the last, if there is enough space
|
||||
if (rowwidths[numrows] + widths[numtabs-1]) <= width then
|
||||
rowends[numrows-1] = rowends[numrows-1] - 1
|
||||
rowwidths[numrows] = rowwidths[numrows] + widths[numtabs-1]
|
||||
rowwidths[numrows-1] = rowwidths[numrows-1] - widths[numtabs-1]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--anchor the rows as defined and resize tabs to fill thier row
|
||||
local starttab = 1
|
||||
for row, endtab in ipairs(rowends) do
|
||||
local first = true
|
||||
for tabno = starttab, endtab do
|
||||
local tab = tabs[tabno]
|
||||
tab:ClearAllPoints()
|
||||
if first then
|
||||
tab:SetPoint("TOPLEFT", self.frame, "TOPLEFT", 0, -(hastitle and 14 or 7)-(row-1)*20 )
|
||||
first = false
|
||||
else
|
||||
tab:SetPoint("LEFT", tabs[tabno-1], "RIGHT", -10, 0)
|
||||
end
|
||||
end
|
||||
|
||||
-- equal padding for each tab to fill the available width,
|
||||
-- if the used space is above 75% already
|
||||
local padding = 0
|
||||
if not (numrows == 1 and rowwidths[1] < width*0.75) then
|
||||
padding = (width - rowwidths[row]) / (endtab - starttab+1)
|
||||
end
|
||||
|
||||
for i = starttab, endtab do
|
||||
PanelTemplates_TabResize(tabs[i], padding + 4, nil, width)
|
||||
end
|
||||
starttab = endtab + 1
|
||||
end
|
||||
|
||||
self.borderoffset = (hastitle and 17 or 10)+((numrows)*20)
|
||||
self.border:SetPoint("TOPLEFT",self.frame,"TOPLEFT",1,-self.borderoffset)
|
||||
end
|
||||
|
||||
local function BuildTabsOnUpdate(this)
|
||||
BuildTabs(this.obj)
|
||||
this:SetScript("OnUpdate", nil)
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
local contentwidth = width - 60
|
||||
if contentwidth < 0 then
|
||||
contentwidth = 0
|
||||
end
|
||||
content:SetWidth(contentwidth)
|
||||
content.width = contentwidth
|
||||
BuildTabs(self)
|
||||
self.frame:SetScript("OnUpdate", BuildTabsOnUpdate)
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
local contentheight = height - (self.borderoffset + 23)
|
||||
if contentheight < 0 then
|
||||
contentheight = 0
|
||||
end
|
||||
content:SetHeight(contentheight)
|
||||
content.height = contentheight
|
||||
end
|
||||
|
||||
local function LayoutFinished(self, width, height)
|
||||
if self.noAutoHeight then return end
|
||||
self:SetHeight((height or 0) + (self.borderoffset + 23))
|
||||
end
|
||||
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
|
||||
self.num = AceGUI:GetNextWidgetNum(Type)
|
||||
|
||||
self.localstatus = {}
|
||||
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
self.SetTitle = SetTitle
|
||||
self.CreateTab = CreateTab
|
||||
self.SelectTab = SelectTab
|
||||
self.BuildTabs = BuildTabs
|
||||
self.SetStatusTable = SetStatusTable
|
||||
self.SetTabs = SetTabs
|
||||
self.LayoutFinished = LayoutFinished
|
||||
self.frame = frame
|
||||
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
|
||||
frame.obj = self
|
||||
|
||||
frame:SetHeight(100)
|
||||
frame:SetWidth(100)
|
||||
frame:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||
|
||||
self.alignoffset = 18
|
||||
|
||||
local titletext = frame:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
titletext:SetPoint("TOPLEFT",frame,"TOPLEFT",14,0)
|
||||
titletext:SetPoint("TOPRIGHT",frame,"TOPRIGHT",-14,0)
|
||||
titletext:SetJustifyH("LEFT")
|
||||
titletext:SetHeight(18)
|
||||
titletext:SetText("")
|
||||
|
||||
self.titletext = titletext
|
||||
|
||||
local border = CreateFrame("Frame",nil,frame)
|
||||
self.border = border
|
||||
self.borderoffset = 27
|
||||
border:SetPoint("TOPLEFT",frame,"TOPLEFT",1,-27)
|
||||
border:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",-1,3)
|
||||
|
||||
border:SetBackdrop(PaneBackdrop)
|
||||
border:SetBackdropColor(0.1,0.1,0.1,0.5)
|
||||
border:SetBackdropBorderColor(0.4,0.4,0.4)
|
||||
|
||||
self.tabs = {}
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,border)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",border,"TOPLEFT",10,-7)
|
||||
content:SetPoint("BOTTOMRIGHT",border,"BOTTOMRIGHT",-10,7)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
+746
@@ -0,0 +1,746 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local next, pairs, ipairs, assert, type = next, pairs, ipairs, assert, type
|
||||
local math_min, math_max, floor = math.min, math.max, floor
|
||||
local select, tremove, unpack = select, table.remove, unpack
|
||||
|
||||
-- WoW APIs
|
||||
local CreateFrame, UIParent = CreateFrame, UIParent
|
||||
|
||||
-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded
|
||||
-- List them here for Mikk's FindGlobals script
|
||||
-- GLOBALS: GameTooltip, FONT_COLOR_CODE_CLOSE
|
||||
|
||||
-- Recycling functions
|
||||
local new, del
|
||||
do
|
||||
local pool = setmetatable({},{__mode='k'})
|
||||
function new()
|
||||
local t = next(pool)
|
||||
if t then
|
||||
pool[t] = nil
|
||||
return t
|
||||
else
|
||||
return {}
|
||||
end
|
||||
end
|
||||
function del(t)
|
||||
for k in pairs(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
pool[t] = true
|
||||
end
|
||||
end
|
||||
|
||||
--------------
|
||||
-- TreeView --
|
||||
--------------
|
||||
|
||||
do
|
||||
local Type = "TreeGroup"
|
||||
local Version = 23
|
||||
|
||||
local DEFAULT_TREE_WIDTH = 175
|
||||
local DEFAULT_TREE_SIZABLE = true
|
||||
|
||||
local PaneBackdrop = {
|
||||
bgFile = "Interface\\ChatFrame\\ChatFrameBackground",
|
||||
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||
tile = true, tileSize = 16, edgeSize = 16,
|
||||
insets = { left = 3, right = 3, top = 5, bottom = 3 }
|
||||
}
|
||||
|
||||
local DraggerBackdrop = {
|
||||
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
||||
edgeFile = nil,
|
||||
tile = true, tileSize = 16, edgeSize = 0,
|
||||
insets = { left = 3, right = 3, top = 7, bottom = 7 }
|
||||
}
|
||||
|
||||
local function OnAcquire(self)
|
||||
self:SetTreeWidth(DEFAULT_TREE_WIDTH,DEFAULT_TREE_SIZABLE)
|
||||
self:EnableButtonTooltips(true)
|
||||
end
|
||||
|
||||
local function OnRelease(self)
|
||||
|
||||
self.frame:ClearAllPoints()
|
||||
self.frame:Hide()
|
||||
self.status = nil
|
||||
for k, v in pairs(self.localstatus) do
|
||||
if k == "groups" then
|
||||
for k2 in pairs(v) do
|
||||
v[k2] = nil
|
||||
end
|
||||
else
|
||||
self.localstatus[k] = nil
|
||||
end
|
||||
end
|
||||
self.localstatus.scrollvalue = 0
|
||||
self.localstatus.treewidth = DEFAULT_TREE_WIDTH
|
||||
self.localstatus.treesizable = DEFAULT_TREE_SIZABLE
|
||||
end
|
||||
|
||||
local function GetButtonParents(line)
|
||||
local parent = line.parent
|
||||
if parent and parent.value then
|
||||
return parent.value, GetButtonParents(parent)
|
||||
end
|
||||
end
|
||||
|
||||
local function GetButtonUniqueValue(line)
|
||||
local parent = line.parent
|
||||
if parent and parent.value then
|
||||
return GetButtonUniqueValue(parent).."\001"..line.value
|
||||
else
|
||||
return line.value
|
||||
end
|
||||
end
|
||||
|
||||
local function ButtonOnClick(this)
|
||||
local self = this.obj
|
||||
self:Fire("OnClick",this.uniquevalue, this.selected)
|
||||
if not this.selected then
|
||||
self:SetSelected(this.uniquevalue)
|
||||
this.selected = true
|
||||
this:LockHighlight()
|
||||
self:RefreshTree()
|
||||
end
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
local function ExpandOnClick(this)
|
||||
local button = this.button
|
||||
local self = button.obj
|
||||
local status = (self.status or self.localstatus).groups
|
||||
status[button.uniquevalue] = not status[button.uniquevalue]
|
||||
self:RefreshTree()
|
||||
end
|
||||
|
||||
local function ButtonOnDoubleClick(button)
|
||||
local self = button.obj
|
||||
local status = self.status or self.localstatus
|
||||
local status = (self.status or self.localstatus).groups
|
||||
status[button.uniquevalue] = not status[button.uniquevalue]
|
||||
self:RefreshTree()
|
||||
end
|
||||
|
||||
local function EnableButtonTooltips(self, enable)
|
||||
self.enabletooltips = enable
|
||||
end
|
||||
|
||||
local function Button_OnEnter(this)
|
||||
local self = this.obj
|
||||
self:Fire("OnButtonEnter", this.uniquevalue, this)
|
||||
|
||||
if self.enabletooltips then
|
||||
GameTooltip:SetOwner(this, "ANCHOR_NONE")
|
||||
GameTooltip:SetPoint("LEFT",this,"RIGHT")
|
||||
GameTooltip:SetText(this.text:GetText() or "", 1, .82, 0, 1)
|
||||
|
||||
GameTooltip:Show()
|
||||
end
|
||||
end
|
||||
|
||||
local function Button_OnLeave(this)
|
||||
local self = this.obj
|
||||
self:Fire("OnButtonLeave", this.uniquevalue, this)
|
||||
|
||||
if self.enabletooltips then
|
||||
GameTooltip:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local buttoncount = 1
|
||||
local function CreateButton(self)
|
||||
local button = CreateFrame("Button",("AceGUI30TreeButton%d"):format(buttoncount),self.treeframe, "OptionsListButtonTemplate")
|
||||
buttoncount = buttoncount + 1
|
||||
button.obj = self
|
||||
|
||||
local icon = button:CreateTexture(nil, "OVERLAY")
|
||||
icon:SetWidth(14)
|
||||
icon:SetHeight(14)
|
||||
button.icon = icon
|
||||
|
||||
button:SetScript("OnClick",ButtonOnClick)
|
||||
button:SetScript("OnDoubleClick", ButtonOnDoubleClick)
|
||||
button:SetScript("OnEnter",Button_OnEnter)
|
||||
button:SetScript("OnLeave",Button_OnLeave)
|
||||
|
||||
button.toggle.button = button
|
||||
button.toggle:SetScript("OnClick",ExpandOnClick)
|
||||
|
||||
return button
|
||||
end
|
||||
|
||||
local function UpdateButton(button, treeline, selected, canExpand, isExpanded)
|
||||
local self = button.obj
|
||||
local toggle = button.toggle
|
||||
local frame = self.frame
|
||||
local text = treeline.text or ""
|
||||
local icon = treeline.icon
|
||||
local iconCoords = treeline.iconCoords
|
||||
local level = treeline.level
|
||||
local value = treeline.value
|
||||
local uniquevalue = treeline.uniquevalue
|
||||
local disabled = treeline.disabled
|
||||
|
||||
button.treeline = treeline
|
||||
button.value = value
|
||||
button.uniquevalue = uniquevalue
|
||||
if selected then
|
||||
button:LockHighlight()
|
||||
button.selected = true
|
||||
else
|
||||
button:UnlockHighlight()
|
||||
button.selected = false
|
||||
end
|
||||
local normalTexture = button:GetNormalTexture()
|
||||
local line = button.line
|
||||
button.level = level
|
||||
if ( level == 1 ) then
|
||||
button:SetNormalFontObject("GameFontNormal")
|
||||
button:SetHighlightFontObject("GameFontHighlight")
|
||||
button.text:SetPoint("LEFT", (icon and 16 or 0) + 8, 2)
|
||||
else
|
||||
button:SetNormalFontObject("GameFontHighlightSmall")
|
||||
button:SetHighlightFontObject("GameFontHighlightSmall")
|
||||
button.text:SetPoint("LEFT", (icon and 16 or 0) + 8 * level, 2)
|
||||
end
|
||||
|
||||
if disabled then
|
||||
button:EnableMouse(false)
|
||||
button.text:SetText("|cff808080"..text..FONT_COLOR_CODE_CLOSE)
|
||||
else
|
||||
button.text:SetText(text)
|
||||
button:EnableMouse(true)
|
||||
end
|
||||
|
||||
if icon then
|
||||
button.icon:SetTexture(icon)
|
||||
button.icon:SetPoint("LEFT", button, "LEFT", 8 * level, (level == 1) and 0 or 1)
|
||||
else
|
||||
button.icon:SetTexture(nil)
|
||||
end
|
||||
|
||||
if iconCoords then
|
||||
button.icon:SetTexCoord(unpack(iconCoords))
|
||||
else
|
||||
button.icon:SetTexCoord(0, 1, 0, 1)
|
||||
end
|
||||
|
||||
if canExpand then
|
||||
if not isExpanded then
|
||||
toggle:SetNormalTexture("Interface\\Buttons\\UI-PlusButton-UP")
|
||||
toggle:SetPushedTexture("Interface\\Buttons\\UI-PlusButton-DOWN")
|
||||
else
|
||||
toggle:SetNormalTexture("Interface\\Buttons\\UI-MinusButton-UP")
|
||||
toggle:SetPushedTexture("Interface\\Buttons\\UI-MinusButton-DOWN")
|
||||
end
|
||||
toggle:Show()
|
||||
else
|
||||
toggle:Hide()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
local function OnScrollValueChanged(this, value)
|
||||
if this.obj.noupdate then return end
|
||||
local self = this.obj
|
||||
local status = self.status or self.localstatus
|
||||
status.scrollvalue = value
|
||||
self:RefreshTree()
|
||||
AceGUI:ClearFocus()
|
||||
end
|
||||
|
||||
-- called to set an external table to store status in
|
||||
local function SetStatusTable(self, status)
|
||||
assert(type(status) == "table")
|
||||
self.status = status
|
||||
if not status.groups then
|
||||
status.groups = {}
|
||||
end
|
||||
if not status.scrollvalue then
|
||||
status.scrollvalue = 0
|
||||
end
|
||||
if not status.treewidth then
|
||||
status.treewidth = DEFAULT_TREE_WIDTH
|
||||
end
|
||||
if not status.treesizable then
|
||||
status.treesizable = DEFAULT_TREE_SIZABLE
|
||||
end
|
||||
self:SetTreeWidth(status.treewidth,status.treesizable)
|
||||
self:RefreshTree()
|
||||
end
|
||||
|
||||
--sets the tree to be displayed
|
||||
--[[
|
||||
example tree
|
||||
|
||||
Alpha
|
||||
Bravo
|
||||
-Charlie
|
||||
-Delta
|
||||
-Echo
|
||||
Foxtrot
|
||||
|
||||
tree = {
|
||||
{
|
||||
value = "A",
|
||||
text = "Alpha"
|
||||
},
|
||||
{
|
||||
value = "B",
|
||||
text = "Bravo",
|
||||
children = {
|
||||
{
|
||||
value = "C",
|
||||
text = "Charlie"
|
||||
},
|
||||
{
|
||||
value = "D",
|
||||
text = "Delta"
|
||||
children = {
|
||||
{
|
||||
value = "E",
|
||||
text = "Echo"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
value = "F",
|
||||
text = "Foxtrot"
|
||||
},
|
||||
}
|
||||
]]
|
||||
local function SetTree(self, tree, filter)
|
||||
self.filter = filter
|
||||
if tree then
|
||||
assert(type(tree) == "table")
|
||||
end
|
||||
self.tree = tree
|
||||
self:RefreshTree()
|
||||
end
|
||||
|
||||
local function ShouldDisplayLevel(tree)
|
||||
local result = false
|
||||
for k, v in ipairs(tree) do
|
||||
if v.children == nil and v.visible ~= false then
|
||||
result = true
|
||||
elseif v.children then
|
||||
result = result or ShouldDisplayLevel(v.children)
|
||||
end
|
||||
if result then return result end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function addLine(self, v, tree, level, parent)
|
||||
local line = new()
|
||||
line.value = v.value
|
||||
line.text = v.text
|
||||
line.icon = v.icon
|
||||
line.iconCoords = v.iconCoords
|
||||
line.disabled = v.disabled
|
||||
line.tree = tree
|
||||
line.level = level
|
||||
line.parent = parent
|
||||
line.visible = v.visible
|
||||
line.uniquevalue = GetButtonUniqueValue(line)
|
||||
if v.children then
|
||||
line.hasChildren = true
|
||||
else
|
||||
line.hasChildren = nil
|
||||
end
|
||||
self.lines[#self.lines+1] = line
|
||||
return line
|
||||
end
|
||||
|
||||
local function BuildLevel(self, tree, level, parent)
|
||||
local groups = (self.status or self.localstatus).groups
|
||||
local hasChildren = self.hasChildren
|
||||
|
||||
for i, v in ipairs(tree) do
|
||||
if v.children then
|
||||
if not self.filter or ShouldDisplayLevel(v.children) then
|
||||
local line = addLine(self, v, tree, level, parent)
|
||||
if groups[line.uniquevalue] then
|
||||
self:BuildLevel(v.children, level+1, line)
|
||||
end
|
||||
end
|
||||
elseif v.visible ~= false or not self.filter then
|
||||
addLine(self, v, tree, level, parent)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
--fire an update after one frame to catch the treeframes height
|
||||
local function FirstFrameUpdate(this)
|
||||
local self = this.obj
|
||||
this:SetScript("OnUpdate",nil)
|
||||
self:RefreshTree()
|
||||
end
|
||||
|
||||
local function ResizeUpdate(this)
|
||||
this.obj:RefreshTree()
|
||||
end
|
||||
|
||||
local function RefreshTree(self)
|
||||
local buttons = self.buttons
|
||||
local lines = self.lines
|
||||
|
||||
for i, v in ipairs(buttons) do
|
||||
v:Hide()
|
||||
end
|
||||
while lines[1] do
|
||||
local t = tremove(lines)
|
||||
for k in pairs(t) do
|
||||
t[k] = nil
|
||||
end
|
||||
del(t)
|
||||
end
|
||||
|
||||
if not self.tree then return end
|
||||
--Build the list of visible entries from the tree and status tables
|
||||
local status = self.status or self.localstatus
|
||||
local groupstatus = status.groups
|
||||
local tree = self.tree
|
||||
|
||||
local treeframe = self.treeframe
|
||||
|
||||
self:BuildLevel(tree, 1)
|
||||
|
||||
local numlines = #lines
|
||||
|
||||
local maxlines = (floor(((self.treeframe:GetHeight()or 0) - 20 ) / 18))
|
||||
|
||||
local first, last
|
||||
|
||||
if numlines <= maxlines then
|
||||
--the whole tree fits in the frame
|
||||
status.scrollvalue = 0
|
||||
self:ShowScroll(false)
|
||||
first, last = 1, numlines
|
||||
else
|
||||
self:ShowScroll(true)
|
||||
--scrolling will be needed
|
||||
self.noupdate = true
|
||||
self.scrollbar:SetMinMaxValues(0, numlines - maxlines)
|
||||
--check if we are scrolled down too far
|
||||
if numlines - status.scrollvalue < maxlines then
|
||||
status.scrollvalue = numlines - maxlines
|
||||
self.scrollbar:SetValue(status.scrollvalue)
|
||||
end
|
||||
self.noupdate = nil
|
||||
first, last = status.scrollvalue+1, status.scrollvalue + maxlines
|
||||
end
|
||||
|
||||
local buttonnum = 1
|
||||
for i = first, last do
|
||||
local line = lines[i]
|
||||
local button = buttons[buttonnum]
|
||||
if not button then
|
||||
button = self:CreateButton()
|
||||
|
||||
buttons[buttonnum] = button
|
||||
button:SetParent(treeframe)
|
||||
button:SetFrameLevel(treeframe:GetFrameLevel()+1)
|
||||
button:ClearAllPoints()
|
||||
if i == 1 then
|
||||
if self.showscroll then
|
||||
button:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
|
||||
button:SetPoint("TOPLEFT", self.treeframe, "TOPLEFT", 0, -10)
|
||||
else
|
||||
button:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
|
||||
button:SetPoint("TOPLEFT", self.treeframe, "TOPLEFT", 0, -10)
|
||||
end
|
||||
else
|
||||
button:SetPoint("TOPRIGHT", buttons[buttonnum-1], "BOTTOMRIGHT",0,0)
|
||||
button:SetPoint("TOPLEFT", buttons[buttonnum-1], "BOTTOMLEFT",0,0)
|
||||
end
|
||||
end
|
||||
|
||||
UpdateButton(button, line, status.selected == line.uniquevalue, line.hasChildren, groupstatus[line.uniquevalue] )
|
||||
button:Show()
|
||||
buttonnum = buttonnum + 1
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function SetSelected(self, value)
|
||||
local status = self.status or self.localstatus
|
||||
if status.selected ~= value then
|
||||
status.selected = value
|
||||
self:Fire("OnGroupSelected", value)
|
||||
end
|
||||
end
|
||||
|
||||
local function BuildUniqueValue(...)
|
||||
local n = select('#', ...)
|
||||
if n == 1 then
|
||||
return ...
|
||||
else
|
||||
return (...).."\001"..BuildUniqueValue(select(2,...))
|
||||
end
|
||||
end
|
||||
|
||||
local function Select(self, uniquevalue, ...)
|
||||
self.filter = false
|
||||
local status = self.status or self.localstatus
|
||||
local groups = status.groups
|
||||
for i = 1, select('#', ...) do
|
||||
groups[BuildUniqueValue(select(i, ...))] = true
|
||||
end
|
||||
status.selected = uniquevalue
|
||||
self:RefreshTree()
|
||||
self:Fire("OnGroupSelected", uniquevalue)
|
||||
end
|
||||
|
||||
local function SelectByPath(self, ...)
|
||||
self:Select(BuildUniqueValue(...), ...)
|
||||
end
|
||||
|
||||
--Selects a tree node by UniqueValue
|
||||
local function SelectByValue(self, uniquevalue)
|
||||
self:Select(uniquevalue, ("\001"):split(uniquevalue))
|
||||
end
|
||||
|
||||
|
||||
local function ShowScroll(self, show)
|
||||
self.showscroll = show
|
||||
if show then
|
||||
self.scrollbar:Show()
|
||||
if self.buttons[1] then
|
||||
self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",-22,-10)
|
||||
end
|
||||
else
|
||||
self.scrollbar:Hide()
|
||||
if self.buttons[1] then
|
||||
self.buttons[1]:SetPoint("TOPRIGHT", self.treeframe,"TOPRIGHT",0,-10)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function OnWidthSet(self, width)
|
||||
local content = self.content
|
||||
local treeframe = self.treeframe
|
||||
local status = self.status or self.localstatus
|
||||
status.fullwidth = width
|
||||
|
||||
local contentwidth = width - status.treewidth - 20
|
||||
if contentwidth < 0 then
|
||||
contentwidth = 0
|
||||
end
|
||||
content:SetWidth(contentwidth)
|
||||
content.width = contentwidth
|
||||
|
||||
local maxtreewidth = math_min(400, width - 50)
|
||||
|
||||
if maxtreewidth > 100 and status.treewidth > maxtreewidth then
|
||||
self:SetTreeWidth(maxtreewidth, status.treesizable)
|
||||
end
|
||||
treeframe:SetMaxResize(maxtreewidth,1600)
|
||||
end
|
||||
|
||||
|
||||
local function OnHeightSet(self, height)
|
||||
local content = self.content
|
||||
local contentheight = height - 20
|
||||
if contentheight < 0 then
|
||||
contentheight = 0
|
||||
end
|
||||
content:SetHeight(contentheight)
|
||||
content.height = contentheight
|
||||
end
|
||||
|
||||
|
||||
local function TreeOnMouseWheel(this, delta)
|
||||
local self = this.obj
|
||||
if self.showscroll then
|
||||
local scrollbar = self.scrollbar
|
||||
local min, max = scrollbar:GetMinMaxValues()
|
||||
local value = scrollbar:GetValue()
|
||||
local newvalue = math_min(max,math_max(min,value - delta))
|
||||
if value ~= newvalue then
|
||||
scrollbar:SetValue(newvalue)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function SetTreeWidth(self, treewidth, resizable)
|
||||
if not resizable then
|
||||
if type(treewidth) == 'number' then
|
||||
resizable = false
|
||||
elseif type(treewidth) == 'boolean' then
|
||||
resizable = treewidth
|
||||
treewidth = DEFAULT_TREE_WIDTH
|
||||
else
|
||||
resizable = false
|
||||
treewidth = DEFAULT_TREE_WIDTH
|
||||
end
|
||||
end
|
||||
self.treeframe:SetWidth(treewidth)
|
||||
self.dragger:EnableMouse(resizable)
|
||||
|
||||
local status = self.status or self.localstatus
|
||||
status.treewidth = treewidth
|
||||
status.treesizable = resizable
|
||||
|
||||
-- recalculate the content width
|
||||
if status.fullwidth then
|
||||
self:OnWidthSet(status.fullwidth)
|
||||
end
|
||||
end
|
||||
|
||||
local function draggerLeave(this)
|
||||
this:SetBackdropColor(1, 1, 1, 0)
|
||||
end
|
||||
|
||||
local function draggerEnter(this)
|
||||
this:SetBackdropColor(1, 1, 1, 0.8)
|
||||
end
|
||||
|
||||
local function draggerDown(this)
|
||||
local treeframe = this:GetParent()
|
||||
treeframe:StartSizing("RIGHT")
|
||||
end
|
||||
|
||||
local function draggerUp(this)
|
||||
local treeframe = this:GetParent()
|
||||
local self = treeframe.obj
|
||||
local frame = treeframe:GetParent()
|
||||
treeframe:StopMovingOrSizing()
|
||||
--treeframe:SetScript("OnUpdate", nil)
|
||||
treeframe:SetUserPlaced(false)
|
||||
--Without this :GetHeight will get stuck on the current height, causing the tree contents to not resize
|
||||
treeframe:SetHeight(0)
|
||||
treeframe:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
treeframe:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",0,0)
|
||||
|
||||
local status = self.status or self.localstatus
|
||||
status.treewidth = treeframe:GetWidth()
|
||||
|
||||
treeframe.obj:Fire("OnTreeResize",treeframe:GetWidth())
|
||||
-- recalculate the content width
|
||||
treeframe.obj:OnWidthSet(status.fullwidth)
|
||||
-- update the layout of the content
|
||||
treeframe.obj:DoLayout()
|
||||
end
|
||||
|
||||
local function LayoutFinished(self, width, height)
|
||||
if self.noAutoHeight then return end
|
||||
self:SetHeight((height or 0) + 20)
|
||||
end
|
||||
|
||||
local createdcount = 0
|
||||
local function Constructor()
|
||||
local frame = CreateFrame("Frame",nil,UIParent)
|
||||
local self = {}
|
||||
self.type = Type
|
||||
self.lines = {}
|
||||
self.levels = {}
|
||||
self.buttons = {}
|
||||
self.hasChildren = {}
|
||||
self.localstatus = {}
|
||||
self.localstatus.groups = {}
|
||||
self.filter = false
|
||||
|
||||
local treeframe = CreateFrame("Frame",nil,frame)
|
||||
treeframe.obj = self
|
||||
treeframe:SetPoint("TOPLEFT",frame,"TOPLEFT",0,0)
|
||||
treeframe:SetPoint("BOTTOMLEFT",frame,"BOTTOMLEFT",0,0)
|
||||
treeframe:SetWidth(DEFAULT_TREE_WIDTH)
|
||||
treeframe:SetScript("OnUpdate",FirstFrameUpdate)
|
||||
treeframe:SetScript("OnSizeChanged",ResizeUpdate)
|
||||
|
||||
treeframe:EnableMouseWheel(true)
|
||||
treeframe:SetScript("OnMouseWheel", TreeOnMouseWheel)
|
||||
treeframe:SetBackdrop(PaneBackdrop)
|
||||
treeframe:SetBackdropColor(0.1,0.1,0.1,0.5)
|
||||
treeframe:SetBackdropBorderColor(0.4,0.4,0.4)
|
||||
|
||||
treeframe:SetResizable(true)
|
||||
treeframe:SetMinResize(100, 1)
|
||||
treeframe:SetMaxResize(400,1600)
|
||||
local dragger = CreateFrame("Frame", nil, treeframe)
|
||||
dragger:SetWidth(8)
|
||||
dragger:SetPoint("TOP", treeframe, "TOPRIGHT")
|
||||
dragger:SetPoint("BOTTOM", treeframe, "BOTTOMRIGHT")
|
||||
dragger:SetBackdrop(DraggerBackdrop)
|
||||
dragger:SetBackdropColor(1, 1, 1, 0)
|
||||
dragger:SetScript("OnMouseDown", draggerDown)
|
||||
dragger:SetScript("OnMouseUp", draggerUp)
|
||||
dragger:SetScript("OnEnter", draggerEnter)
|
||||
dragger:SetScript("OnLeave", draggerLeave)
|
||||
|
||||
self.dragger = dragger
|
||||
self.treeframe = treeframe
|
||||
self.OnRelease = OnRelease
|
||||
self.OnAcquire = OnAcquire
|
||||
|
||||
self.SetTree = SetTree
|
||||
self.SetTreeWidth = SetTreeWidth
|
||||
self.RefreshTree = RefreshTree
|
||||
self.SetStatusTable = SetStatusTable
|
||||
self.BuildLevel = BuildLevel
|
||||
self.CreateButton = CreateButton
|
||||
self.SetSelected = SetSelected
|
||||
self.ShowScroll = ShowScroll
|
||||
self.SetStatusTable = SetStatusTable
|
||||
self.Select = Select
|
||||
self.SelectByValue = SelectByValue
|
||||
self.SelectByPath = SelectByPath
|
||||
self.OnWidthSet = OnWidthSet
|
||||
self.OnHeightSet = OnHeightSet
|
||||
self.EnableButtonTooltips = EnableButtonTooltips
|
||||
--self.Filter = Filter
|
||||
self.LayoutFinished = LayoutFinished
|
||||
|
||||
self.frame = frame
|
||||
frame.obj = self
|
||||
|
||||
createdcount = createdcount + 1
|
||||
local scrollbar = CreateFrame("Slider",("AceConfigDialogTreeGroup%dScrollBar"):format(createdcount),treeframe,"UIPanelScrollBarTemplate")
|
||||
self.scrollbar = scrollbar
|
||||
local scrollbg = scrollbar:CreateTexture(nil,"BACKGROUND")
|
||||
scrollbg:SetAllPoints(scrollbar)
|
||||
scrollbg:SetTexture(0,0,0,0.4)
|
||||
scrollbar.obj = self
|
||||
self.noupdate = true
|
||||
scrollbar:SetPoint("TOPRIGHT",treeframe,"TOPRIGHT",-10,-26)
|
||||
scrollbar:SetPoint("BOTTOMRIGHT",treeframe,"BOTTOMRIGHT",-10,26)
|
||||
scrollbar:SetScript("OnValueChanged", OnScrollValueChanged)
|
||||
scrollbar:SetMinMaxValues(0,0)
|
||||
self.localstatus.scrollvalue = 0
|
||||
scrollbar:SetValueStep(1)
|
||||
scrollbar:SetValue(0)
|
||||
scrollbar:SetWidth(16)
|
||||
self.noupdate = nil
|
||||
|
||||
local border = CreateFrame("Frame",nil,frame)
|
||||
self.border = border
|
||||
border:SetPoint("TOPLEFT",treeframe,"TOPRIGHT", 0,0)
|
||||
border:SetPoint("BOTTOMRIGHT",frame,"BOTTOMRIGHT",0,0)
|
||||
|
||||
border:SetBackdrop(PaneBackdrop)
|
||||
border:SetBackdropColor(0.1,0.1,0.1,0.5)
|
||||
border:SetBackdropBorderColor(0.4,0.4,0.4)
|
||||
|
||||
--Container Support
|
||||
local content = CreateFrame("Frame",nil,border)
|
||||
self.content = content
|
||||
content.obj = self
|
||||
content:SetPoint("TOPLEFT",border,"TOPLEFT",10,-10)
|
||||
content:SetPoint("BOTTOMRIGHT",border,"BOTTOMRIGHT",-10,10)
|
||||
|
||||
AceGUI:RegisterAsContainer(self)
|
||||
--AceGUI:RegisterAsWidget(self)
|
||||
return self
|
||||
end
|
||||
|
||||
AceGUI:RegisterWidgetType(Type,Constructor,Version)
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user