Compare commits
12 Commits
9e96151a42
...
android-te
| Author | SHA1 | Date | |
|---|---|---|---|
| a3c7d9cf75 | |||
| 4aa23d3670 | |||
| fb3e6dc705 | |||
| 37b38a0537 | |||
| 26ca6d1cf9 | |||
| 44f5a39d0c | |||
| 5531d83994 | |||
| d9a3e8914b | |||
| b3945d9d0e | |||
| 62a6f49eba | |||
| 466e0570d4 | |||
| 11fe8ca54d |
@@ -8,51 +8,65 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
|
|
||||||
find_package(PkgConfig REQUIRED)
|
find_package(PkgConfig REQUIRED)
|
||||||
find_package(Freetype REQUIRED)
|
find_package(Freetype REQUIRED)
|
||||||
find_package(glfw3 CONFIG REQUIRED)
|
|
||||||
find_package(Lua REQUIRED)
|
find_package(Lua REQUIRED)
|
||||||
|
find_package(SDL3 CONFIG REQUIRED)
|
||||||
|
find_package(SDL3_image CONFIG REQUIRED)
|
||||||
|
find_package(SQLiteCpp REQUIRED)
|
||||||
|
find_package(toml11 CONFIG REQUIRED)
|
||||||
|
|
||||||
pkg_check_modules(LUNASVG REQUIRED IMPORTED_TARGET lunasvg)
|
pkg_check_modules(LUNASVG REQUIRED IMPORTED_TARGET lunasvg)
|
||||||
add_library(lunasvg::lunasvg ALIAS PkgConfig::LUNASVG)
|
add_library(lunasvg::lunasvg ALIAS PkgConfig::LUNASVG)
|
||||||
|
|
||||||
#pkg_check_modules(SQLITE3 REQUIRED IMPORTED_TARGET sqlite3)
|
|
||||||
#add_library(sqlite3::sqlite3 ALIAS PkgConfig::SQLITE3)
|
|
||||||
|
|
||||||
find_package(SQLiteCpp REQUIRED)
|
|
||||||
|
|
||||||
find_package(toml11 CONFIG REQUIRED)
|
|
||||||
|
|
||||||
find_package(RmlUi CONFIG REQUIRED)
|
find_package(RmlUi CONFIG REQUIRED)
|
||||||
|
|
||||||
|
add_library(rmlui_backend STATIC
|
||||||
|
vendor/rmlui_backend/RmlUi_Backend_SDL_SDLrenderer.cpp
|
||||||
|
vendor/rmlui_backend/RmlUi_Platform_SDL.cpp
|
||||||
|
vendor/rmlui_backend/RmlUi_Renderer_SDL.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_include_directories(rmlui_backend PUBLIC
|
||||||
|
vendor/rmlui_backend
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(rmlui_backend PUBLIC
|
||||||
|
RMLUI_SDL_VERSION_MAJOR=3
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(rmlui_backend PUBLIC
|
||||||
|
RmlUi::RmlUi
|
||||||
|
SDL3::SDL3
|
||||||
|
SDL3_image::SDL3_image
|
||||||
|
Freetype::Freetype
|
||||||
|
)
|
||||||
|
|
||||||
add_executable(${PROJECT_NAME}
|
add_executable(${PROJECT_NAME}
|
||||||
src/main.cpp
|
src/main.cpp
|
||||||
vendor/rmlui_backend/RmlUi_Backend_GLFW_GL3.cpp
|
src/database.cpp
|
||||||
vendor/rmlui_backend/RmlUi_Platform_GLFW.cpp
|
src/ui.cpp
|
||||||
vendor/rmlui_backend/RmlUi_Renderer_GL3.cpp
|
src/image_utils.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||||
src
|
src
|
||||||
vendor/rmlui_backend
|
${FREETYPE_INCLUDE_DIRS}
|
||||||
${FREETYPE_INCLUDE_DIRS}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(${PROJECT_NAME} PRIVATE
|
target_link_libraries(${PROJECT_NAME} PRIVATE
|
||||||
RmlUi::RmlUi
|
rmlui_backend
|
||||||
RmlUi::Debugger
|
RmlUi::RmlUi
|
||||||
RmlUi::Lua
|
RmlUi::Debugger
|
||||||
${LUA_LIBRARIES}
|
RmlUi::Lua
|
||||||
Freetype::Freetype
|
${LUA_LIBRARIES}
|
||||||
lunasvg::lunasvg
|
Freetype::Freetype
|
||||||
glfw
|
lunasvg::lunasvg
|
||||||
toml11::toml11
|
toml11::toml11
|
||||||
SQLiteCpp
|
SQLiteCpp
|
||||||
)
|
)
|
||||||
|
|
||||||
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
install(TARGETS ${PROJECT_NAME} DESTINATION bin)
|
||||||
install(DIRECTORY assets DESTINATION bin)
|
install(DIRECTORY assets DESTINATION bin)
|
||||||
|
|
||||||
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
target_compile_definitions(${PROJECT_NAME} PRIVATE
|
||||||
ASSETS_DIR="${CMAKE_SOURCE_DIR}/bin/assets"
|
ASSETS_DIR="${CMAKE_SOURCE_DIR}/bin/assets"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
3
android/.gitignore
vendored
Normal file
3
android/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
.gradle
|
||||||
|
build
|
||||||
|
app/.cxx
|
||||||
138
android/app/CMakeLists.txt
Normal file
138
android/app/CMakeLists.txt
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.22.1)
|
||||||
|
project(critterfolio_android LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
set(DEPS_INSTALL ${CMAKE_BINARY_DIR}/deps/install)
|
||||||
|
|
||||||
|
# ExternalProject_Add spawns a completely separate CMake invocation for each
|
||||||
|
# dependency, so none of AGP's Android toolchain settings carry over
|
||||||
|
# automatically - we have to forward them explicitly to every sub-build.
|
||||||
|
set(ANDROID_TOOLCHAIN_ARGS
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
|
||||||
|
-DANDROID_ABI=${ANDROID_ABI}
|
||||||
|
-DANDROID_PLATFORM=${ANDROID_PLATFORM}
|
||||||
|
-DANDROID_STL=c++_shared
|
||||||
|
-DCMAKE_BUILD_TYPE=Release
|
||||||
|
-DCMAKE_INSTALL_PREFIX=${DEPS_INSTALL}
|
||||||
|
-DCMAKE_PREFIX_PATH=${DEPS_INSTALL}
|
||||||
|
-DBUILD_SHARED_LIBS=OFF
|
||||||
|
-DCMAKE_CXX_STANDARD=17
|
||||||
|
-DCMAKE_CXX_STANDARD_REQUIRED=ON
|
||||||
|
-DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH
|
||||||
|
-DCMAKE_FIND_ROOT_PATH_MODE_LIBRARY=BOTH
|
||||||
|
-DCMAKE_FIND_ROOT_PATH_MODE_INCLUDE=BOTH
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(SDL3 REQUIRED CONFIG)
|
||||||
|
find_package(SDL3_image REQUIRED CONFIG)
|
||||||
|
|
||||||
|
ExternalProject_Add(freetype_ext
|
||||||
|
GIT_REPOSITORY https://github.com/freetype/freetype.git
|
||||||
|
GIT_TAG VER-2-13-3
|
||||||
|
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
|
||||||
|
-DFT_DISABLE_ZLIB=TRUE
|
||||||
|
-DFT_DISABLE_BZIP2=TRUE
|
||||||
|
-DFT_DISABLE_PNG=TRUE
|
||||||
|
-DFT_DISABLE_HARFBUZZ=TRUE
|
||||||
|
-DFT_DISABLE_BROTLI=TRUE
|
||||||
|
)
|
||||||
|
|
||||||
|
ExternalProject_Add(lunasvg_ext
|
||||||
|
GIT_REPOSITORY https://github.com/sammycage/lunasvg.git
|
||||||
|
GIT_TAG v3.2.1
|
||||||
|
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
|
||||||
|
DEPENDS freetype_ext
|
||||||
|
)
|
||||||
|
|
||||||
|
# Lua ships no official CMakeLists, and we don't want its standalone lua/luac
|
||||||
|
# CLI targets (those need readline). We compile just the library sources.
|
||||||
|
ExternalProject_Add(lua_ext
|
||||||
|
GIT_REPOSITORY https://github.com/lua/lua.git
|
||||||
|
GIT_TAG v5.4.8
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
BUILD_IN_SOURCE 1
|
||||||
|
BUILD_COMMAND sh -c "${CMAKE_C_COMPILER} --target=${CMAKE_C_COMPILER_TARGET} --sysroot=${CMAKE_SYSROOT} -c -O2 -fPIC -DLUA_USE_POSIX $(ls *.c | grep -v -e '^lua.c$' -e '^luac.c$') && ${CMAKE_AR} rcs liblua.a *.o"
|
||||||
|
INSTALL_COMMAND ${CMAKE_COMMAND} -E make_directory ${DEPS_INSTALL}/lib ${DEPS_INSTALL}/include
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy liblua.a ${DEPS_INSTALL}/lib/
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy lua.h lauxlib.h lualib.h luaconf.h ${DEPS_INSTALL}/include/
|
||||||
|
)
|
||||||
|
|
||||||
|
ExternalProject_Add(sqlitecpp_ext
|
||||||
|
GIT_REPOSITORY https://github.com/SRombauts/SQLiteCpp.git
|
||||||
|
GIT_TAG 3.3.3
|
||||||
|
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
|
||||||
|
-DSQLITECPP_INTERNAL_SQLITE=ON
|
||||||
|
-DSQLITECPP_RUN_CPPLINT=OFF
|
||||||
|
-DSQLITECPP_RUN_CPPCHECK=OFF
|
||||||
|
)
|
||||||
|
|
||||||
|
ExternalProject_Add(toml11_ext
|
||||||
|
GIT_REPOSITORY https://github.com/ToruNiina/toml11.git
|
||||||
|
GIT_TAG v3.8.1
|
||||||
|
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
|
||||||
|
)
|
||||||
|
|
||||||
|
ExternalProject_Add(rmlui_ext
|
||||||
|
GIT_REPOSITORY https://github.com/mikke89/RmlUi.git
|
||||||
|
GIT_TAG master
|
||||||
|
CMAKE_ARGS ${ANDROID_TOOLCHAIN_ARGS}
|
||||||
|
-DFreetype_ROOT=${DEPS_INSTALL}
|
||||||
|
-DRMLUI_LUA_BINDINGS=ON
|
||||||
|
-DRMLUI_LUA_BINDINGS_LIBRARY=lua
|
||||||
|
-DRMLUI_SVG_PLUGIN=ON
|
||||||
|
-DRMLUI_SAMPLES=OFF
|
||||||
|
-DBUILD_TESTING=OFF
|
||||||
|
DEPENDS freetype_ext lunasvg_ext lua_ext
|
||||||
|
)
|
||||||
|
|
||||||
|
set(PROJECT_SRC_DIR ${CMAKE_SOURCE_DIR}/../../src)
|
||||||
|
set(RMLUI_BACKEND_DIR ${CMAKE_SOURCE_DIR}/../../vendor/rmlui_backend)
|
||||||
|
|
||||||
|
add_library(critterfolio SHARED
|
||||||
|
${PROJECT_SRC_DIR}/main.cpp
|
||||||
|
${PROJECT_SRC_DIR}/database.cpp
|
||||||
|
${PROJECT_SRC_DIR}/ui.cpp
|
||||||
|
${PROJECT_SRC_DIR}/image_utils.cpp
|
||||||
|
${RMLUI_BACKEND_DIR}/RmlUi_Backend_SDL_SDLrenderer.cpp
|
||||||
|
${RMLUI_BACKEND_DIR}/RmlUi_Platform_SDL.cpp
|
||||||
|
${RMLUI_BACKEND_DIR}/RmlUi_Renderer_SDL.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_dependencies(critterfolio freetype_ext lunasvg_ext lua_ext sqlitecpp_ext toml11_ext rmlui_ext)
|
||||||
|
|
||||||
|
target_include_directories(critterfolio PRIVATE
|
||||||
|
${PROJECT_SRC_DIR}
|
||||||
|
${RMLUI_BACKEND_DIR}
|
||||||
|
${DEPS_INSTALL}/include
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(critterfolio PRIVATE
|
||||||
|
RMLUI_SDL_VERSION_MAJOR=3
|
||||||
|
RMLUI_STATIC_LIB
|
||||||
|
)
|
||||||
|
|
||||||
|
# Linked by explicit path rather than find_package(), since find_package()
|
||||||
|
# runs during THIS configure pass - before any ExternalProject_Add step
|
||||||
|
# above has actually built or installed anything (those only run later,
|
||||||
|
# during the actual build). This is what the superbuild pattern is for.
|
||||||
|
target_link_directories(critterfolio PRIVATE ${DEPS_INSTALL}/lib)
|
||||||
|
|
||||||
|
target_link_libraries(critterfolio PRIVATE
|
||||||
|
SDL3::SDL3
|
||||||
|
SDL3_image::SDL3_image
|
||||||
|
rmlui
|
||||||
|
rmlui_debugger
|
||||||
|
rmlui_lua
|
||||||
|
lua
|
||||||
|
freetype
|
||||||
|
lunasvg
|
||||||
|
plutovg
|
||||||
|
SQLiteCpp
|
||||||
|
sqlite3
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(critterfolio PRIVATE "-Wl,-u,SDL_main")
|
||||||
67
android/app/build.gradle.kts
Normal file
67
android/app/build.gradle.kts
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.critterfolio.app"
|
||||||
|
compileSdk = 34
|
||||||
|
ndkVersion = "26.3.11579264"
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
applicationId = "com.critterfolio.app"
|
||||||
|
minSdk = 24
|
||||||
|
targetSdk = 34
|
||||||
|
versionCode = 1
|
||||||
|
versionName = "0.1.0"
|
||||||
|
|
||||||
|
ndk {
|
||||||
|
abiFilters += listOf("arm64-v8a")
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments += "-DANDROID_STL=c++_shared"
|
||||||
|
cppFlags += "-std=c++17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildFeatures {
|
||||||
|
prefab = true
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
path = file("CMakeLists.txt")
|
||||||
|
version = "3.22.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
getByName("main") {
|
||||||
|
assets.srcDirs("../../assets")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
isMinifyEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
|
||||||
|
packaging {
|
||||||
|
jniLibs {
|
||||||
|
useLegacyPackaging = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(files("libs/SDL3-3.4.14.aar"))
|
||||||
|
implementation(files("libs/SDL3_image-3.4.4.aar"))
|
||||||
|
}
|
||||||
BIN
android/app/libs/SDL3-3.4.14.aar
Normal file
BIN
android/app/libs/SDL3-3.4.14.aar
Normal file
Binary file not shown.
BIN
android/app/libs/SDL3_image-3.4.4.aar
Normal file
BIN
android/app/libs/SDL3_image-3.4.4.aar
Normal file
Binary file not shown.
25
android/app/src/main/AndroidManifest.xml
Normal file
25
android/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<uses-feature android:glEsVersion="0x00030000" android:required="true" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:label="Critterfolio"
|
||||||
|
android:hasCode="true">
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="com.critterfolio.app.MainActivity"
|
||||||
|
android:label="Critterfolio"
|
||||||
|
android:configChanges="orientation|screenSize|keyboardHidden"
|
||||||
|
android:exported="true"
|
||||||
|
android:screenOrientation="fullSensor">
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.critterfolio.app;
|
||||||
|
|
||||||
|
import org.libsdl.app.SDLActivity;
|
||||||
|
|
||||||
|
public class MainActivity extends SDLActivity {
|
||||||
|
@Override
|
||||||
|
protected String[] getLibraries() {
|
||||||
|
return new String[] {
|
||||||
|
"SDL3",
|
||||||
|
"critterfolio"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
3
android/build.gradle.kts
Normal file
3
android/build.gradle.kts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
plugins {
|
||||||
|
id("com.android.application") version "8.5.2" apply false
|
||||||
|
}
|
||||||
47
android/buildlog.txt
Normal file
47
android/buildlog.txt
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
> Task :app:preBuild UP-TO-DATE
|
||||||
|
> Task :app:preDebugBuild UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugNativeDebugMetadata NO-SOURCE
|
||||||
|
> Task :app:javaPreCompileDebug UP-TO-DATE
|
||||||
|
> Task :app:checkDebugAarMetadata UP-TO-DATE
|
||||||
|
> Task :app:generateDebugResValues UP-TO-DATE
|
||||||
|
> Task :app:mapDebugSourceSetPaths UP-TO-DATE
|
||||||
|
> Task :app:generateDebugResources UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugResources UP-TO-DATE
|
||||||
|
> Task :app:packageDebugResources UP-TO-DATE
|
||||||
|
> Task :app:parseDebugLocalResources UP-TO-DATE
|
||||||
|
> Task :app:createDebugCompatibleScreenManifests UP-TO-DATE
|
||||||
|
> Task :app:extractDeepLinksDebug UP-TO-DATE
|
||||||
|
> Task :app:processDebugMainManifest UP-TO-DATE
|
||||||
|
> Task :app:processDebugManifest UP-TO-DATE
|
||||||
|
> Task :app:processDebugManifestForPackage UP-TO-DATE
|
||||||
|
> Task :app:processDebugResources UP-TO-DATE
|
||||||
|
> Task :app:compileDebugJavaWithJavac UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugShaders UP-TO-DATE
|
||||||
|
> Task :app:compileDebugShaders NO-SOURCE
|
||||||
|
> Task :app:generateDebugAssets UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugAssets UP-TO-DATE
|
||||||
|
> Task :app:compressDebugAssets UP-TO-DATE
|
||||||
|
> Task :app:desugarDebugFileDependencies UP-TO-DATE
|
||||||
|
> Task :app:dexBuilderDebug UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugGlobalSynthetics UP-TO-DATE
|
||||||
|
> Task :app:processDebugJavaRes NO-SOURCE
|
||||||
|
> Task :app:mergeDebugJavaResource UP-TO-DATE
|
||||||
|
> Task :app:checkDebugDuplicateClasses UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugStartupProfile UP-TO-DATE
|
||||||
|
> Task :app:mergeExtDexDebug UP-TO-DATE
|
||||||
|
> Task :app:mergeLibDexDebug UP-TO-DATE
|
||||||
|
> Task :app:mergeProjectDexDebug UP-TO-DATE
|
||||||
|
> Task :app:configureCMakeDebug[arm64-v8a]
|
||||||
|
> Task :app:buildCMakeDebug[arm64-v8a]
|
||||||
|
> Task :app:mergeDebugJniLibFolders UP-TO-DATE
|
||||||
|
> Task :app:validateSigningDebug UP-TO-DATE
|
||||||
|
> Task :app:writeDebugAppMetadata UP-TO-DATE
|
||||||
|
> Task :app:writeDebugSigningConfigVersions UP-TO-DATE
|
||||||
|
> Task :app:mergeDebugNativeLibs
|
||||||
|
> Task :app:stripDebugDebugSymbols
|
||||||
|
> Task :app:packageDebug
|
||||||
|
> Task :app:createDebugApkListingFileRedirect UP-TO-DATE
|
||||||
|
> Task :app:assembleDebug
|
||||||
|
|
||||||
|
BUILD SUCCESSFUL in 9s
|
||||||
|
37 actionable tasks: 5 executed, 32 up-to-date
|
||||||
2
android/gradle.properties
Normal file
2
android/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
org.gradle.jvmargs=-Xmx2048m
|
||||||
|
android.useAndroidX=true
|
||||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
251
android/gradlew
vendored
Executable file
251
android/gradlew
vendored
Executable file
@@ -0,0 +1,251 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright © 2015-2021 the original authors.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# Gradle start up script for POSIX generated by Gradle.
|
||||||
|
#
|
||||||
|
# Important for running:
|
||||||
|
#
|
||||||
|
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||||
|
# noncompliant, but you have some other compliant shell such as ksh or
|
||||||
|
# bash, then to run this script, type that shell name before the whole
|
||||||
|
# command line, like:
|
||||||
|
#
|
||||||
|
# ksh Gradle
|
||||||
|
#
|
||||||
|
# Busybox and similar reduced shells will NOT work, because this script
|
||||||
|
# requires all of these POSIX shell features:
|
||||||
|
# * functions;
|
||||||
|
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||||
|
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||||
|
# * compound commands having a testable exit status, especially «case»;
|
||||||
|
# * various built-in commands including «command», «set», and «ulimit».
|
||||||
|
#
|
||||||
|
# Important for patching:
|
||||||
|
#
|
||||||
|
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||||
|
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||||
|
#
|
||||||
|
# The "traditional" practice of packing multiple parameters into a
|
||||||
|
# space-separated string is a well documented source of bugs and security
|
||||||
|
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||||
|
# options in "$@", and eventually passing that to Java.
|
||||||
|
#
|
||||||
|
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||||
|
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||||
|
# see the in-line comments for details.
|
||||||
|
#
|
||||||
|
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||||
|
# Darwin, MinGW, and NonStop.
|
||||||
|
#
|
||||||
|
# (3) This script is generated from the Groovy template
|
||||||
|
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
|
# within the Gradle project.
|
||||||
|
#
|
||||||
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
app_path=$0
|
||||||
|
|
||||||
|
# Need this for daisy-chained symlinks.
|
||||||
|
while
|
||||||
|
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||||
|
[ -h "$app_path" ]
|
||||||
|
do
|
||||||
|
ls=$( ls -ld "$app_path" )
|
||||||
|
link=${ls#*' -> '}
|
||||||
|
case $link in #(
|
||||||
|
/*) app_path=$link ;; #(
|
||||||
|
*) app_path=$APP_HOME$link ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
# This is normally unused
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
APP_BASE_NAME=${0##*/}
|
||||||
|
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||||
|
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
} >&2
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "$( uname )" in #(
|
||||||
|
CYGWIN* ) cygwin=true ;; #(
|
||||||
|
Darwin* ) darwin=true ;; #(
|
||||||
|
MSYS* | MINGW* ) msys=true ;; #(
|
||||||
|
NONSTOP* ) nonstop=true ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH="\\\"\\\""
|
||||||
|
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||||
|
else
|
||||||
|
JAVACMD=$JAVA_HOME/bin/java
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD=java
|
||||||
|
if ! command -v java >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
|
case $MAX_FD in #(
|
||||||
|
max*)
|
||||||
|
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
|
warn "Could not query maximum file descriptor limit"
|
||||||
|
esac
|
||||||
|
case $MAX_FD in #(
|
||||||
|
'' | soft) :;; #(
|
||||||
|
*)
|
||||||
|
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||||
|
# shellcheck disable=SC2039,SC3045
|
||||||
|
ulimit -n "$MAX_FD" ||
|
||||||
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, stacking in reverse order:
|
||||||
|
# * args from the command line
|
||||||
|
# * the main class name
|
||||||
|
# * -classpath
|
||||||
|
# * -D...appname settings
|
||||||
|
# * --module-path (only if needed)
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||||
|
|
||||||
|
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||||
|
if "$cygwin" || "$msys" ; then
|
||||||
|
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||||
|
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||||
|
|
||||||
|
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||||
|
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
for arg do
|
||||||
|
if
|
||||||
|
case $arg in #(
|
||||||
|
-*) false ;; # don't mess with options #(
|
||||||
|
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||||
|
[ -e "$t" ] ;; #(
|
||||||
|
*) false ;;
|
||||||
|
esac
|
||||||
|
then
|
||||||
|
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||||
|
fi
|
||||||
|
# Roll the args list around exactly as many times as the number of
|
||||||
|
# args, so each arg winds up back in the position where it started, but
|
||||||
|
# possibly modified.
|
||||||
|
#
|
||||||
|
# NB: a `for` loop captures its iteration list before it begins, so
|
||||||
|
# changing the positional parameters here affects neither the number of
|
||||||
|
# iterations, nor the values presented in `arg`.
|
||||||
|
shift # remove old arg
|
||||||
|
set -- "$@" "$arg" # push replacement arg
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
|
# Collect all arguments for the java command:
|
||||||
|
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||||
|
# and any embedded shellness will be escaped.
|
||||||
|
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||||
|
# treated as '${Hostname}' itself on the command line.
|
||||||
|
|
||||||
|
set -- \
|
||||||
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
|
-classpath "$CLASSPATH" \
|
||||||
|
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
|
||||||
|
"$@"
|
||||||
|
|
||||||
|
# Stop when "xargs" is not available.
|
||||||
|
if ! command -v xargs >/dev/null 2>&1
|
||||||
|
then
|
||||||
|
die "xargs is not available"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use "xargs" to parse quoted args.
|
||||||
|
#
|
||||||
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
|
#
|
||||||
|
# In Bash we could simply go:
|
||||||
|
#
|
||||||
|
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||||
|
# set -- "${ARGS[@]}" "$@"
|
||||||
|
#
|
||||||
|
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||||
|
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||||
|
# character that might be a shell metacharacter, then use eval to reverse
|
||||||
|
# that process (while maintaining the separation between arguments), and wrap
|
||||||
|
# the whole thing up as a single "set" statement.
|
||||||
|
#
|
||||||
|
# This will of course break if any of these variables contains a newline or
|
||||||
|
# an unmatched quote.
|
||||||
|
#
|
||||||
|
|
||||||
|
eval "set -- $(
|
||||||
|
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||||
|
xargs -n1 |
|
||||||
|
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||||
|
tr '\n' ' '
|
||||||
|
)" '"$@"'
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
||||||
94
android/gradlew.bat
vendored
Normal file
94
android/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
@rem
|
||||||
|
@rem Copyright 2015 the original author or authors.
|
||||||
|
@rem
|
||||||
|
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@rem you may not use this file except in compliance with the License.
|
||||||
|
@rem You may obtain a copy of the License at
|
||||||
|
@rem
|
||||||
|
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
@rem
|
||||||
|
@rem Unless required by applicable law or agreed to in writing, software
|
||||||
|
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@rem See the License for the specific language governing permissions and
|
||||||
|
@rem limitations under the License.
|
||||||
|
@rem
|
||||||
|
@rem SPDX-License-Identifier: Apache-2.0
|
||||||
|
@rem
|
||||||
|
|
||||||
|
@if "%DEBUG%"=="" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%"=="" set DIRNAME=.
|
||||||
|
@rem This is normally unused
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||||
|
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if %ERRORLEVEL% equ 0 goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto execute
|
||||||
|
|
||||||
|
echo. 1>&2
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||||
|
echo. 1>&2
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||||
|
echo location of your Java installation. 1>&2
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=
|
||||||
|
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
set EXIT_CODE=%ERRORLEVEL%
|
||||||
|
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||||
|
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||||
|
exit /b %EXIT_CODE%
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
||||||
17
android/settings.gradle.kts
Normal file
17
android/settings.gradle.kts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
pluginManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
gradlePluginPortal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencyResolutionManagement {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.name = "critterfolio"
|
||||||
|
include(":app")
|
||||||
BIN
assets/fonts/Hubballi-Regular.ttf
Normal file
BIN
assets/fonts/Hubballi-Regular.ttf
Normal file
Binary file not shown.
93
assets/fonts/OFL.txt
Normal file
93
assets/fonts/OFL.txt
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
Copyright 2016 The Hubballi Project Authors (https://github.com/erinmclaughlin/Hubballi)
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
BIN
assets/images/icon.png
Executable file
BIN
assets/images/icon.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 627 KiB |
55
assets/images/icon.svg
Executable file
55
assets/images/icon.svg
Executable file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 269 KiB |
BIN
assets/images/icon.tga
Normal file
BIN
assets/images/icon.tga
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 625 KiB |
BIN
assets/local.db
BIN
assets/local.db
Binary file not shown.
@@ -2,18 +2,81 @@
|
|||||||
<head>
|
<head>
|
||||||
<title>Critterfolio</title>
|
<title>Critterfolio</title>
|
||||||
<link type="text/rcss" href="styles/main.rcss" />
|
<link type="text/rcss" href="styles/main.rcss" />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body data-model="app_data">
|
<body data-model="app_data">
|
||||||
<h1>{{page_title}}</h1>
|
<div id="header">
|
||||||
|
<div class="header-title-group">
|
||||||
|
<h1>Critterfolio</h1>
|
||||||
|
<!-- <span class="header-tag">Field Registry</span> -->
|
||||||
|
</div>
|
||||||
|
<button id="new-critter-btn" data-event-click="is_add_critter_dialog_open = true"><div>+</div></button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<h2>Critter Roster</h2>
|
|
||||||
|
<!-- Critter Cards / Drawer Rows -->
|
||||||
<div class="critter-list">
|
<div class="critter-list">
|
||||||
<div data-for="entry : critters" class="critter-card">
|
<button data-for="entry : critters" class="critter-card">
|
||||||
<h3>#{{entry.key}} - {{entry.value.name}}</h3>
|
<div class="critter-card-body">
|
||||||
<p><strong>Species:</strong> {{entry.value.species}}</p>
|
<div class="thumb-frame">
|
||||||
<p><strong>Gender:</strong> {{entry.value.gender}}</p>
|
<img class="critter-thumb" src="images/icon.tga" />
|
||||||
<p><strong>Notes:</strong> {{entry.value.notes}}</p>
|
</div>
|
||||||
|
|
||||||
|
<div class="critter-info">
|
||||||
|
<div class="critter-card-header">
|
||||||
|
<h3 class="critter-name">{{entry.value.name}}</h3>
|
||||||
|
<span class="critter-id">#{{entry.key}}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info-pills">
|
||||||
|
<span class="pill"><strong>Sp:</strong> {{entry.value.species}}</span>
|
||||||
|
<span class="pill"><strong>Sex:</strong> {{entry.value.gender}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="btn-delete" data-event-click="delete_critter(entry.key)">X</button>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modals -->
|
||||||
|
<div class="modal-overlay" data-visible="is_add_critter_dialog_open">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h2>Add New Critter</h2>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p class="error" data-visible="error_message != ''">{{error_message}}</p>
|
||||||
|
<div class="form-group"><label>Name:</label><input type="text" data-value="new_critter_form.name" /></div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group half"><label>Species:</label><input type="text" data-value="new_critter_form.species" /></div>
|
||||||
|
<div class="form-group half"><label>Gender (M/F):</label><input type="text" data-value="new_critter_form.gender" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group half"><label>Mother ID (Optional):</label><input type="text" data-value="new_critter_form.mother_id" /></div>
|
||||||
|
<div class="form-group half"><label>Father ID (Optional):</label><input type="text" data-value="new_critter_form.father_id" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group"><label>Notes:</label><textarea data-value="new_critter_form.notes"></textarea></div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button class="btn-cancel" data-event-click="is_add_critter_dialog_open = false">Cancel</button>
|
||||||
|
<button class="btn-submit" data-event-click="submit_add_critter">Save</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-overlay" data-visible="is_confirmation_dialog_open">
|
||||||
|
<div class="modal-content modal-content-small">
|
||||||
|
<div class="modal-header"><h2>Confirmation</h2></div>
|
||||||
|
<div class="modal-body"><p class="dialog-text">{{confirmation_dialog_text}}</p></div>
|
||||||
|
<div class="modal-buttons">
|
||||||
|
<button class="btn-cancel" data-event-click="confirm_dialog_no">Cancel</button>
|
||||||
|
<button class="btn-submit" data-event-click="confirm_dialog_yes">Yes</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,24 +1,30 @@
|
|||||||
/* ==========================================================================
|
|
||||||
RCSS User Agent Defaults (Plain HTML Style)
|
|
||||||
========================================================================== */
|
|
||||||
|
|
||||||
/* Core Application Window Foundation */
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
font-family: "Hubballi";
|
||||||
|
|
||||||
|
--background-color: #2b2d2f;
|
||||||
|
--foreground-color: #f0eae1;
|
||||||
|
--accent-green: #606c38;
|
||||||
|
--accent-bright-green: #7d8d4c;
|
||||||
|
--accent-gold: #dda15e;
|
||||||
|
--accent-clay: #bc6c25;
|
||||||
|
--card-bg: #383b3e;
|
||||||
|
--border-dark: #191a1b;
|
||||||
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
font-family: "rmlui-debugger-font";
|
font-size: 15pt;
|
||||||
font-size: 16px;
|
background-color: var(--background-color);
|
||||||
background-color: #ffffff;
|
color: var(--foreground-color);
|
||||||
color: #000000;
|
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
padding: 0px;
|
padding: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Generic Structural Layout Defaults */
|
/* Generic Structural Layout Defaults */
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
div, section, article, header, footer, main, nav, aside {
|
div, section, article, header, footer, main, nav, aside {
|
||||||
display: block;
|
display: block;
|
||||||
@@ -31,238 +37,346 @@ span, em, strong, a {
|
|||||||
/* Typographic Defaults */
|
/* Typographic Defaults */
|
||||||
p {
|
p {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 1em 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
display: block;
|
display: block;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
margin: 0.83em 0px;
|
margin: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
h1 { font-size: 2em; }
|
h1 { font-size: 2em; }
|
||||||
h2 { font-size: 1.5em; }
|
h2 { font-size: 1.5em; }
|
||||||
h3 { font-size: 1.17em; }
|
h3 { font-size: 1.17em; }
|
||||||
h4 { font-size: 1em; }
|
|
||||||
h5 { font-size: 0.83em; }
|
|
||||||
h6 { font-size: 0.67em; }
|
|
||||||
|
|
||||||
em {
|
em { font-style: italic; }
|
||||||
font-style: italic;
|
strong { font-weight: bold; }
|
||||||
}
|
|
||||||
|
|
||||||
strong {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #0000ee;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #551a8b;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
display: block;
|
|
||||||
margin: 1em 40px;
|
|
||||||
padding-left: 10px;
|
|
||||||
border-left: 2px #cccccc;
|
|
||||||
color: #555555;
|
|
||||||
}
|
|
||||||
|
|
||||||
code, pre {
|
|
||||||
font-family: "rmlui-debugger-font";
|
|
||||||
background-color: #606060;
|
|
||||||
color: #000000;
|
|
||||||
padding: 3px;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
white-space: pre;
|
|
||||||
margin: 1em 0px;
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Lists */
|
|
||||||
ul, ol {
|
|
||||||
display: block;
|
|
||||||
margin: 1em 0px;
|
|
||||||
padding-left: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Tables
|
|
||||||
========================================================================== */
|
|
||||||
table {
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr {
|
|
||||||
display: table-row;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 4px 8px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: #eaeaea;
|
|
||||||
}
|
|
||||||
|
|
||||||
col {
|
|
||||||
display: table-column;
|
|
||||||
}
|
|
||||||
|
|
||||||
colgroup {
|
|
||||||
display: table-column-group;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead, tbody, tfoot {
|
|
||||||
display: table-row-group;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
Form Inputs & Interactive UI Components
|
Form Inputs & Interactive UI Components
|
||||||
========================================================================== */
|
========================================================================== */
|
||||||
button, input, select, textarea {
|
button, input, select, textarea {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-family: "rmlui-debugger-font";
|
font-family: "Hubballi";
|
||||||
font-size: 14px;
|
font-size: 14pt;
|
||||||
margin: 2px;
|
margin: 0px;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Traditional gray bevel button style */
|
button {
|
||||||
button, input[type="submit"], input[type="button"] {
|
padding: 6px 16px;
|
||||||
padding: 4px 12px;
|
background-color: var(--accent-green);
|
||||||
min-width: 40px;
|
color: var(--foreground-color);
|
||||||
background-color: #e1e1e1;
|
border: 3px var(--border-dark);
|
||||||
color: #000000;
|
border-radius: 6px;
|
||||||
border: 1px #767676;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
button:hover, input[type="submit"]:hover, input[type="button"]:hover {
|
button:hover {
|
||||||
background-color: #d0d0d0;
|
background-color: var(--accent-bright-green);
|
||||||
border: 1px #4f4f4f;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button:active, input[type="submit"]:active, input[type="button"]:active {
|
button:active {
|
||||||
background-color: #b5b5b5;
|
background-color: var(--accent-gold);
|
||||||
border: 1px #000000;
|
color: var(--border-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Standard text field styling */
|
input[type="text"], textarea {
|
||||||
input[type="text"], input[type="password"], textarea {
|
padding: 8px 12px;
|
||||||
padding: 4px;
|
background-color: #212325;
|
||||||
background-color: #ffffff;
|
color: var(--foreground-color);
|
||||||
color: #000000;
|
border: 3px var(--border-dark);
|
||||||
border: 1px #767676;
|
border-radius: 6px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"]:focus, input[type="password"]:focus, textarea:focus {
|
input[type="text"]:focus, textarea:focus {
|
||||||
border: 1px #005aff;
|
background-color: #2b2d2f;
|
||||||
|
border-color: var(--accent-bright-green);
|
||||||
}
|
}
|
||||||
|
|
||||||
textarea {
|
textarea {
|
||||||
display: block;
|
display: block;
|
||||||
white-space: pre-wrap;
|
height: 150px;
|
||||||
width: 300px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Select Box & Dropdowns */
|
/* ===================================
|
||||||
select {
|
App Header
|
||||||
padding: 4px;
|
=================================== */
|
||||||
background-color: #ffffff;
|
#header {
|
||||||
color: #000000;
|
display: flex;
|
||||||
border: 1px #767676;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 20px;
|
||||||
|
background-color: var(--accent-green);
|
||||||
|
border: 3px var(--border-dark);
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-title-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header h1 {
|
||||||
|
font-size: 2.5em;
|
||||||
|
color: var(--foreground-color);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-tag {
|
||||||
|
font-size: 12pt;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: var(--border-dark);
|
||||||
|
color: var(--accent-gold);
|
||||||
|
padding: 2px 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-critter-btn {
|
||||||
|
height: 48px;
|
||||||
|
width: 48px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 28pt;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 3px var(--border-dark);
|
||||||
|
background-color: var(--accent-gold);
|
||||||
|
color: var(--border-dark);
|
||||||
|
padding: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#new-critter-btn:hover {
|
||||||
|
background-color: var(--accent-bright-green);
|
||||||
|
color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================================
|
||||||
|
Drawer List Styles
|
||||||
|
=================================== */
|
||||||
|
.critter-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critter-card {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px 14px;
|
||||||
|
background-color: var(--card-bg);
|
||||||
|
color: var(--foreground-color);
|
||||||
|
border: 3px var(--border-dark);
|
||||||
|
border-left: 10px var(--accent-green);
|
||||||
|
border-radius: 6px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ==========================================================================
|
.critter-card:hover {
|
||||||
RmlUi Specific Elements (Tabsets, Progress Bars, etc.)
|
background-color: #45484c;
|
||||||
========================================================================== */
|
border-left-color: var(--accent-bright-green);
|
||||||
tabset {
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tabset tabs {
|
.critter-card-body {
|
||||||
display: block;
|
|
||||||
border-bottom: 1px #cccccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Simple default layout style for built-in tabs */
|
|
||||||
tab {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 6px 12px;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border: 1px #cccccc;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tab:selected {
|
|
||||||
background-color: #ffffff;
|
|
||||||
border-bottom: 1px #ffffff; /* Overlap the bottom border line */
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
panel {
|
|
||||||
display: block;
|
|
||||||
padding: 10px;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Standard HTML5 / RmlUi Progress Bar Fallback */
|
|
||||||
progress {
|
|
||||||
display: inline-block;
|
|
||||||
width: 160px;
|
|
||||||
height: 20px;
|
|
||||||
background-color: #e6e6e6;
|
|
||||||
border: 1px #b0b0b0;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress-value {
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #0078d7; /* Classic Windows Blue fill */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ==========================================================================
|
|
||||||
Utility & Modern Layout Helpers
|
|
||||||
========================================================================== */
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
display: flex;
|
||||||
}
|
|
||||||
|
|
||||||
.flex-row {
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
gap: 14px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-col {
|
.thumb-frame {
|
||||||
|
background-color: var(--accent-green);
|
||||||
|
border: 2px var(--border-dark);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critter-thumb {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critter-info {
|
||||||
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
gap: 2px;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justify-center {
|
.critter-card-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.critter-name {
|
||||||
|
font-size: 18pt;
|
||||||
|
margin: 0px;
|
||||||
|
color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.critter-id {
|
||||||
|
font-size: 11pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--accent-gold);
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-pills {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pill {
|
||||||
|
font-size: 12pt;
|
||||||
|
background-color: #212325;
|
||||||
|
color: var(--foreground-color);
|
||||||
|
border: 1px var(--accent-green);
|
||||||
|
padding: 1px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete {
|
||||||
|
padding: 4px 10px;
|
||||||
|
font-size: 11pt;
|
||||||
|
font-weight: bold;
|
||||||
|
background-color: var(--accent-clay);
|
||||||
|
color: #ffffff;
|
||||||
|
border: 2px var(--border-dark);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-delete:hover {
|
||||||
|
background-color: #a3561a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===================================
|
||||||
|
Modals Layout
|
||||||
|
=================================== */
|
||||||
|
.modal-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: #1f1f1fdd;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.align-center {
|
.modal-content {
|
||||||
align-items: center;
|
background-color: var(--card-bg);
|
||||||
|
color: var(--foreground-color);
|
||||||
|
border: 4px var(--border-dark);
|
||||||
|
border-radius: 10px;
|
||||||
|
width: 600px;
|
||||||
|
padding: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden {
|
.modal-content-small {
|
||||||
display: none;
|
width: 450px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-header {
|
||||||
|
background-color: var(--accent-green);
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 12px 20px;
|
||||||
|
border-bottom: 3px var(--border-dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-header h2 {
|
||||||
|
font-size: 20pt;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-body {
|
||||||
|
padding: 18px 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group.half {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content label {
|
||||||
|
font-size: 14pt;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
padding: 8px;
|
||||||
|
border: 2px var(--border-dark);
|
||||||
|
background-color: var(--accent-clay);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-text {
|
||||||
|
font-size: 18pt;
|
||||||
|
color: var(--foreground-color);
|
||||||
|
padding: 10px 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons {
|
||||||
|
padding: 14px 20px;
|
||||||
|
background-color: #2b2d2f;
|
||||||
|
border-top: 3px var(--border-dark);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 12px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-buttons button {
|
||||||
|
min-width: 120px;
|
||||||
|
height: 42px;
|
||||||
|
font-size: 16pt;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel {
|
||||||
|
background-color: #45484c;
|
||||||
|
color: var(--foreground-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-cancel:hover {
|
||||||
|
background-color: #565a5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit {
|
||||||
|
background-color: var(--accent-green);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-submit:hover {
|
||||||
|
background-color: var(--accent-bright-green);
|
||||||
|
}
|
||||||
53
flake.nix
53
flake.nix
@@ -9,11 +9,18 @@
|
|||||||
outputs = { self, nixpkgs, utils }:
|
outputs = { self, nixpkgs, utils }:
|
||||||
utils.lib.eachDefaultSystem (system:
|
utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = import nixpkgs { inherit system; };
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
config = {
|
||||||
|
allowUnfree = true;
|
||||||
|
android_sdk.accept_license = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
rmlui = pkgs.stdenv.mkDerivation rec {
|
rmlui = pkgs.stdenv.mkDerivation rec {
|
||||||
pname = "rmlui";
|
pname = "rmlui";
|
||||||
version = "master";
|
version = "master";
|
||||||
|
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
owner = "mikke89";
|
owner = "mikke89";
|
||||||
repo = "RmlUi";
|
repo = "RmlUi";
|
||||||
@@ -52,14 +59,9 @@
|
|||||||
];
|
];
|
||||||
|
|
||||||
runtimeDeps = with pkgs; [
|
runtimeDeps = with pkgs; [
|
||||||
raylib
|
sdl3
|
||||||
glfw
|
sdl3-image
|
||||||
libGL
|
libGL
|
||||||
libX11
|
|
||||||
libXcursor
|
|
||||||
libXrandr
|
|
||||||
libXinerama
|
|
||||||
libXi
|
|
||||||
wayland
|
wayland
|
||||||
libxkbcommon
|
libxkbcommon
|
||||||
rmlui
|
rmlui
|
||||||
@@ -70,18 +72,45 @@
|
|||||||
sqlitecpp
|
sqlitecpp
|
||||||
toml11
|
toml11
|
||||||
];
|
];
|
||||||
|
|
||||||
|
ndkVersion = "26.3.11579264";
|
||||||
|
|
||||||
|
androidComposition = pkgs.androidenv.composeAndroidPackages {
|
||||||
|
platformVersions = [ "34" ];
|
||||||
|
buildToolsVersions = [ "34.0.0" ];
|
||||||
|
includeNDK = true;
|
||||||
|
ndkVersions = [ ndkVersion ];
|
||||||
|
cmakeVersions = [ "3.22.1" ];
|
||||||
|
abiVersions = [ "arm64-v8a" ];
|
||||||
|
includeEmulator = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
androidSdkRoot = "${androidComposition.androidsdk}/libexec/android-sdk";
|
||||||
|
androidNdkRoot = "${androidSdkRoot}/ndk/${ndkVersion}";
|
||||||
|
|
||||||
|
androidDeps = [
|
||||||
|
androidComposition.androidsdk
|
||||||
|
pkgs.gradle
|
||||||
|
pkgs.jdk17
|
||||||
|
pkgs.ninja
|
||||||
|
];
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
nativeBuildInputs = buildDeps;
|
nativeBuildInputs = buildDeps ++ androidDeps;
|
||||||
buildInputs = runtimeDeps;
|
buildInputs = runtimeDeps;
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export CP_PATH="${pkgs.raylib}/include:${pkgs.glfw}/include:${rmlui}/include"
|
export CP_PATH="${rmlui}/include"
|
||||||
export LIBRARY_PATH="${pkgs.raylib}/lib:${pkgs.glfw}/lib:${pkgs.libGL}/lib:${rmlui}/lib"
|
export LIBRARY_PATH="${pkgs.libGL}/lib:${rmlui}/lib"
|
||||||
export LD_LIBRARY_PATH="$LIBRARY_PATH:$LD_LIBRARY_PATH"
|
export LD_LIBRARY_PATH="$LIBRARY_PATH:$LD_LIBRARY_PATH"
|
||||||
|
|
||||||
echo "Env ready"
|
export ANDROID_HOME="${androidSdkRoot}"
|
||||||
|
export ANDROID_SDK_ROOT="${androidSdkRoot}"
|
||||||
|
export ANDROID_NDK_HOME="${androidNdkRoot}"
|
||||||
|
export ANDROID_NDK_ROOT="${androidNdkRoot}"
|
||||||
|
|
||||||
|
echo "Env ready (desktop + Android NDK ${ndkVersion}, arm64-v8a)"
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
16
src/asset_io.hpp
Normal file
16
src/asset_io.hpp
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
inline SDL_IOStream *OpenAssetFile(const std::string &virtual_path) {
|
||||||
|
std::string resolved = virtual_path;
|
||||||
|
#if defined(__ANDROID__)
|
||||||
|
if (resolved.rfind("./assets/", 0) == 0) {
|
||||||
|
resolved = resolved.substr(9);
|
||||||
|
} else if (resolved.rfind("assets/", 0) == 0) {
|
||||||
|
resolved = resolved.substr(7);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return SDL_IOFromFile(resolved.c_str(), "rb");
|
||||||
|
}
|
||||||
191
src/database.cpp
Normal file
191
src/database.cpp
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
#include "database.hpp"
|
||||||
|
#include "asset_io.hpp"
|
||||||
|
#include <SQLiteCpp/Database.h>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
std::string get_writable_db_path() {
|
||||||
|
char *pref = SDL_GetPrefPath("Critterfolio", "CritterFolio");
|
||||||
|
std::string path = pref ? pref : "./";
|
||||||
|
if (pref) {
|
||||||
|
SDL_free(pref);
|
||||||
|
}
|
||||||
|
path += "local.db";
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ensure_db_copied() {
|
||||||
|
std::string dest_path = get_writable_db_path();
|
||||||
|
|
||||||
|
SDL_IOStream *check = SDL_IOFromFile(dest_path.c_str(), "rb");
|
||||||
|
if (check) {
|
||||||
|
SDL_CloseIO(check);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SDL_IOStream *src = OpenAssetFile("./assets/local.db");
|
||||||
|
if (!src) {
|
||||||
|
std::cout << "[ERROR] Could not open seed database in assets: " << SDL_GetError() << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sint64 size = SDL_GetIOSize(src);
|
||||||
|
if (size < 0) {
|
||||||
|
std::cout << "[ERROR] Could not determine seed database size.\n";
|
||||||
|
SDL_CloseIO(src);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Uint8> buffer(static_cast<size_t>(size));
|
||||||
|
if (SDL_ReadIO(src, buffer.data(), buffer.size()) != buffer.size()) {
|
||||||
|
std::cout << "[ERROR] Failed reading seed database.\n";
|
||||||
|
SDL_CloseIO(src);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SDL_CloseIO(src);
|
||||||
|
|
||||||
|
SDL_IOStream *dst = SDL_IOFromFile(dest_path.c_str(), "wb");
|
||||||
|
if (!dst) {
|
||||||
|
std::cout << "[ERROR] Could not open destination database for writing: " << SDL_GetError() << "\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ok = SDL_WriteIO(dst, buffer.data(), buffer.size()) == buffer.size();
|
||||||
|
SDL_CloseIO(dst);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
std::cout << "[ERROR] Failed writing copied database.\n";
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool db_get_all_critters(std::map<int, Critter> &critters) {
|
||||||
|
try {
|
||||||
|
SQLite::Database db(get_writable_db_path(), SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
|
||||||
|
SQLite::Statement q(db, "SELECT * FROM critters");
|
||||||
|
|
||||||
|
critters.clear();
|
||||||
|
|
||||||
|
while (q.executeStep()) {
|
||||||
|
int id = q.getColumn(0);
|
||||||
|
|
||||||
|
critters.try_emplace(
|
||||||
|
id,
|
||||||
|
id,
|
||||||
|
q.getColumn(1).getText(),
|
||||||
|
q.getColumn(2).getText(),
|
||||||
|
q.getColumn(3).getInt(),
|
||||||
|
q.getColumn(4).getInt(),
|
||||||
|
q.getColumn(5).getText(),
|
||||||
|
q.getColumn(6).getText()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
std::cout << "[ERROR] DB exception: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool db_add_critter(const Critter &c, int &out_id) {
|
||||||
|
try {
|
||||||
|
SQLite::Database db(get_writable_db_path(), SQLite::OPEN_READWRITE);
|
||||||
|
SQLite::Statement q(db, "INSERT INTO critters (name, gender, mother_id, father_id, notes, species) VALUES (?, ?, ?, ?, ?, ?)");
|
||||||
|
|
||||||
|
q.bind(1, c.name);
|
||||||
|
q.bind(2, c.gender);
|
||||||
|
q.bind(3, c.mother_id);
|
||||||
|
q.bind(4, c.father_id);
|
||||||
|
q.bind(5, c.notes);
|
||||||
|
q.bind(6, c.species);
|
||||||
|
|
||||||
|
q.exec();
|
||||||
|
out_id = static_cast<int>(db.getLastInsertRowid());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
std::cout << "[ERROR] DB add exception: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool db_delete_critter(int id) {
|
||||||
|
try {
|
||||||
|
SQLite::Database db(get_writable_db_path(), SQLite::OPEN_READWRITE);
|
||||||
|
SQLite::Statement q(db, "DELETE FROM critters WHERE id = ?");
|
||||||
|
q.bind(1, id);
|
||||||
|
q.exec();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (const std::exception &e) {
|
||||||
|
std::cout << "[ERROR] DB delete exception: " << e.what() << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void db_print_all_critters(const std::map<int, Critter> &critters) {
|
||||||
|
std::cout << "Current loaded critters:";
|
||||||
|
for (const auto &[key, c] : critters) {
|
||||||
|
std::cout << "\nCritter " << key << "\n";
|
||||||
|
std::cout << "ID: " << c.id << std::endl;
|
||||||
|
std::cout << "Name: " << c.name << std::endl;
|
||||||
|
std::cout << "Gender: " << c.gender << std::endl;
|
||||||
|
std::cout << "Mother ID: " << c.mother_id << std::endl;
|
||||||
|
std::cout << "Father ID: " << c.father_id << std::endl;
|
||||||
|
std::cout << "Notes: " << c.notes << std::endl;
|
||||||
|
std::cout << "Species: " << c.species << std::endl;
|
||||||
|
}
|
||||||
|
std::cout << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool validate_critter(const Critter &c, const std::map<int, Critter> &all_critters, std::string &out_error) {
|
||||||
|
if (c.mother_id != 0 && c.father_id != 0 && c.mother_id == c.father_id) {
|
||||||
|
out_error = "Mother ID and Father ID cannot be the same.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.mother_id != 0) {
|
||||||
|
auto it = all_critters.find(c.mother_id);
|
||||||
|
if (it == all_critters.end()) {
|
||||||
|
out_error = "Mother ID " + std::to_string(c.mother_id) + " does not exist.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Critter &mother = it->second;
|
||||||
|
|
||||||
|
if (mother.gender != "F" && mother.gender != "Female") {
|
||||||
|
out_error = "Mother must have female gender ('F' or 'Female').";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mother.species != c.species) {
|
||||||
|
out_error = "Mother species ('" + mother.species + "') must match critter species ('" + c.species + "').";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c.father_id != 0) {
|
||||||
|
auto it = all_critters.find(c.father_id);
|
||||||
|
if (it == all_critters.end()) {
|
||||||
|
out_error = "Father ID " + std::to_string(c.father_id) + " does not exist.";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const Critter &father = it->second;
|
||||||
|
|
||||||
|
if (father.gender != "M" && father.gender != "Male") {
|
||||||
|
out_error = "Father must have male gender ('M' or 'Male').";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (father.species != c.species) {
|
||||||
|
out_error = "Father species ('" + father.species + "') must match critter species ('" + c.species + "').";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
111
src/database.hpp
111
src/database.hpp
@@ -1,104 +1,13 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
#include "critter.hpp"
|
#include "critter.hpp"
|
||||||
#include <SQLiteCpp/Database.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <map>
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
inline static bool db_get_all_critters(std::map<int, Critter> &critters) {
|
std::string get_writable_db_path();
|
||||||
try {
|
bool ensure_db_copied();
|
||||||
SQLite::Database db("./assets/local.db", SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE);
|
bool db_get_all_critters(std::map<int, Critter> &critters);
|
||||||
SQLite::Statement q(db, "SELECT * FROM critters");
|
bool db_add_critter(const Critter &c, int &out_id);
|
||||||
|
bool db_delete_critter(int id);
|
||||||
critters.clear();
|
void db_print_all_critters(const std::map<int, Critter> &critters);
|
||||||
|
bool validate_critter(const Critter &c, const std::map<int, Critter> &all_critters, std::string &out_error);
|
||||||
while (q.executeStep()) {
|
|
||||||
int id = q.getColumn(0);
|
|
||||||
|
|
||||||
critters.try_emplace(
|
|
||||||
id,
|
|
||||||
id,
|
|
||||||
q.getColumn(1).getText(),
|
|
||||||
q.getColumn(2).getText(),
|
|
||||||
q.getColumn(3).getInt(),
|
|
||||||
q.getColumn(4).getInt(),
|
|
||||||
q.getColumn(5).getText(),
|
|
||||||
q.getColumn(6).getText()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (const std::exception &e) {
|
|
||||||
std::cout << "[ERROR] DB exception: " << e.what() << std::endl;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static void db_print_all_critters(std::map<int, Critter> &critters) {
|
|
||||||
std::cout << "Current loaded critters:";
|
|
||||||
for (auto c : critters) {
|
|
||||||
std::cout << "\nCritter " << c.first << "\n";
|
|
||||||
std::cout << "ID: " << c.second.id << std::endl;
|
|
||||||
std::cout << "Name:" << c.second.name << std::endl;
|
|
||||||
std::cout << "Gender: " << c.second.gender << std::endl;
|
|
||||||
std::cout << "Mother ID: " << c.second.mother_id << std::endl;
|
|
||||||
std::cout << "Father ID: " << c.second.father_id << std::endl;
|
|
||||||
std::cout << "Notes: " << c.second.notes << std::endl;
|
|
||||||
std::cout << "Species: " << c.second.species << std::endl;
|
|
||||||
}
|
|
||||||
std::cout << "\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static bool validate_critter(const Critter &c, std::map<int, Critter> &all_critters, std::string &out_error) {
|
|
||||||
if (c.mother_id != 0 && c.father_id != 0 && c.mother_id == c.father_id) {
|
|
||||||
out_error = "Mother ID and Father ID cannot be the same.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mother Validation
|
|
||||||
if (c.mother_id != 0) {
|
|
||||||
auto it = all_critters.find(c.mother_id);
|
|
||||||
if (it == all_critters.end()) {
|
|
||||||
out_error = "Mother ID " + std::to_string(c.mother_id) + " does not exist.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Critter& mother = it->second;
|
|
||||||
|
|
||||||
if (mother.gender != "F" && mother.gender != "Female") {
|
|
||||||
out_error = "Mother must have female gender ('F' or 'Female').";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mother.species != c.species) {
|
|
||||||
out_error = "Mother species ('" + mother.species + "') must match critter species ('" + c.species + "').";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Father Validation
|
|
||||||
if (c.father_id != 0) {
|
|
||||||
auto it = all_critters.find(c.father_id);
|
|
||||||
if (it == all_critters.end()) {
|
|
||||||
out_error = "Father ID " + std::to_string(c.father_id) + " does not exist.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
const Critter& father = it->second;
|
|
||||||
|
|
||||||
if (father.gender != "M" && father.gender != "Male") {
|
|
||||||
out_error = "Father must have male gender ('M' or 'Male').";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (father.species != c.species) {
|
|
||||||
out_error = "Father species ('" + father.species + "') must match critter species ('" + c.species + "').";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline static bool db_try_add_critter() {
|
|
||||||
|
|
||||||
}
|
|
||||||
38
src/image_utils.cpp
Normal file
38
src/image_utils.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// image_utils.cpp
|
||||||
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
#include "../vendor/stb/stb_image.h"
|
||||||
|
|
||||||
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
||||||
|
#include "../vendor/stb/stb_image_write.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
bool ConvertToThumbnailTGA(const std::string& input_path, const std::string& output_tga_path) {
|
||||||
|
int width, height, channels;
|
||||||
|
|
||||||
|
stbi_uc* data = stbi_load(input_path.c_str(), &width, &height, &channels, 4);
|
||||||
|
if (!data) {
|
||||||
|
std::cout << "[ERROR] Failed to decode image: " << input_path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int result = stbi_write_tga(output_tga_path.c_str(), width, height, 4, data);
|
||||||
|
|
||||||
|
stbi_image_free(data);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
std::cout << "[ERROR] Failed to save TGA thumbnail to: " << output_tga_path << std::endl;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ProcessUserThumbnail(int critter_id, const std::string& user_selected_file_path) {
|
||||||
|
std::string target_tga_path = "./data/thumbnails/" + std::to_string(critter_id) + ".tga";
|
||||||
|
|
||||||
|
if (ConvertToThumbnailTGA(user_selected_file_path, target_tga_path)) {
|
||||||
|
std::cout << "[INFO] Thumbnail successfully generated for Critter #" << critter_id << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
274
src/main.cpp
274
src/main.cpp
@@ -1,162 +1,142 @@
|
|||||||
#include <RmlUi/Config/Config.h>
|
|
||||||
#include <RmlUi/Core/Core.h>
|
|
||||||
#include <RmlUi/Core/DataModelHandle.h>
|
|
||||||
#include <RmlUi/Core/ElementDocument.h>
|
|
||||||
#include <RmlUi/Core/Math.h>
|
|
||||||
#include <RmlUi/Lua/Interpreter.h>
|
|
||||||
#include <RmlUi/Lua/Lua.h>
|
|
||||||
#include <SQLiteCpp/Database.h>
|
|
||||||
#include <iostream>
|
|
||||||
#include <RmlUi/Core.h>
|
|
||||||
#include <RmlUi_Backend.h>
|
|
||||||
#include <RmlUi/Lua/Lua.h>
|
|
||||||
#include <RmlUi/Core/Context.h>
|
|
||||||
#include <lua.h>
|
|
||||||
#include <RmlUi/Debugger/Debugger.h>
|
|
||||||
#include "critter.hpp"
|
#include "critter.hpp"
|
||||||
#include <SQLiteCpp/SQLiteCpp.h>
|
#include "database.hpp"
|
||||||
|
#include "ui.hpp"
|
||||||
|
#include "sdl_file_interface.hpp"
|
||||||
|
#include "../vendor/stb/stb_image.h"
|
||||||
|
#include "../vendor/stb/stb_image_write.h"
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
#include <SDL3/SDL_main.h>
|
||||||
|
#include <RmlUi/Core.h>
|
||||||
|
#include <RmlUi/Core/Core.h>
|
||||||
|
#include <RmlUi/Debugger/Debugger.h>
|
||||||
|
#include <RmlUi/Lua/Lua.h>
|
||||||
|
#include <RmlUi_Backend.h>
|
||||||
|
#include <iostream>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "database.hpp"
|
|
||||||
|
|
||||||
static bool is_initalized = false;
|
static bool is_initalized = false;
|
||||||
static bool is_running = false;
|
static bool is_running = false;
|
||||||
static Rml::Context *ctx;
|
static Rml::Context *ctx = nullptr;
|
||||||
static std::map<int, Critter> critters = {};
|
static std::map<int, Critter> critters;
|
||||||
static std::vector<CritterEntry> critter_list = {};
|
static std::vector<CritterEntry> critter_list;
|
||||||
|
static Rml::ElementDocument *doc;
|
||||||
struct AppData {
|
static AppData app_data;
|
||||||
Rml::String page_title = "Main";
|
static SDLFileInterface file_interface;
|
||||||
} app_data;
|
void shutdown() {
|
||||||
|
Rml::Shutdown();
|
||||||
void SyncMapToList() {
|
Backend::Shutdown();
|
||||||
critter_list.clear();
|
SDL_Quit();
|
||||||
for (const auto &[id, critter] : critters) {
|
|
||||||
critter_list.push_back({id, critter});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
bool initialize() {
|
||||||
void Shutdown() {
|
if (!SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS)) {
|
||||||
Rml::Shutdown();
|
std::cout << "[ERROR] Failed to initialize SDL3: " << SDL_GetError() << "\n";
|
||||||
Backend::Shutdown();
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
bool SetupDataBinding(Rml::Context *context, Rml::DataModelHandle &model_handle) {
|
|
||||||
Rml::DataModelConstructor dmc = ctx->CreateDataModel("app_data");
|
|
||||||
if (!dmc) {
|
|
||||||
std::cout << "[ERROR] could not create 'app_data' data binding.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto handle = dmc.RegisterStruct<Critter>()) {
|
|
||||||
handle.RegisterMember("id", &Critter::id);
|
|
||||||
handle.RegisterMember("name", &Critter::name);
|
|
||||||
handle.RegisterMember("gender", &Critter::gender);
|
|
||||||
handle.RegisterMember("species", &Critter::species);
|
|
||||||
handle.RegisterMember("notes", &Critter::notes);
|
|
||||||
handle.RegisterMember("mother_id", &Critter::mother_id);
|
|
||||||
handle.RegisterMember("father_id", &Critter::father_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto handle = dmc.RegisterStruct<CritterEntry>()) {
|
|
||||||
handle.RegisterMember("key", &CritterEntry::key);
|
|
||||||
handle.RegisterMember("value", &CritterEntry::value);
|
|
||||||
}
|
|
||||||
|
|
||||||
dmc.RegisterArray<std::vector<CritterEntry>>();
|
|
||||||
dmc.Bind("critters", &critter_list);
|
|
||||||
|
|
||||||
dmc.Bind("page_title", &app_data.page_title);
|
|
||||||
|
|
||||||
model_handle = dmc.GetModelHandle();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Initalize() {
|
|
||||||
if (!Backend::Initialize("CritterFolio", 800, 600, true)) {
|
|
||||||
std::cout << "[ERROR] Failed to initalize the backend.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::cout << "[INFO] RmlUI Backend initalized.\n";
|
|
||||||
|
|
||||||
Rml::SetSystemInterface(Backend::GetSystemInterface());
|
|
||||||
Rml::SetRenderInterface(Backend::GetRenderInterface());
|
|
||||||
Rml::Initialise();
|
|
||||||
Rml::Lua::Initialise();
|
|
||||||
|
|
||||||
// TODO: Add more error handling for initalization
|
|
||||||
|
|
||||||
std::cout << "[INFO] Loading local DB...\n";
|
|
||||||
bool db_result = db_get_all_critters(critters);
|
|
||||||
if (!db_result) {
|
|
||||||
std::cout << "[ERROR] Local DB failed to initalize.\n";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::cout << "[INFO] Local DB initalized.\n";
|
|
||||||
|
|
||||||
SyncMapToList();
|
|
||||||
|
|
||||||
// ------- DB TESTING
|
|
||||||
// db_print_all_critters(critters);
|
|
||||||
// ------- END DB TESTING
|
|
||||||
|
|
||||||
ctx = Rml::CreateContext("main", Rml::Vector2i(800, 600));
|
|
||||||
if (!ctx) {
|
|
||||||
std::cout << "[ERROR] Failed to create context.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::cout << "[INFO] Main context initalized.\n";
|
|
||||||
|
|
||||||
Rml::Debugger::Initialise(ctx);
|
|
||||||
|
|
||||||
// lua_State *L = Rml::Lua::Interpreter::GetLuaState();
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Update(Rml::DataModelHandle model_handle) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
int main() {
|
|
||||||
std::cout << "[INFO] Starting critterfolio...\n";
|
|
||||||
|
|
||||||
is_initalized = Initalize();
|
|
||||||
if (!is_initalized) {
|
|
||||||
std::cout << "[ERROR] Failed to initalize, shutting down.";
|
|
||||||
Shutdown();
|
|
||||||
return 1;
|
|
||||||
}
|
}
|
||||||
std::cout << "[INFO] Application initalized, all systems running.\n";
|
if (!Backend::Initialize("CritterFolio", 800, 600, true)) {
|
||||||
|
std::cout << "[ERROR] Failed to initialize backend.\n";
|
||||||
is_running = true;
|
return false;
|
||||||
|
}
|
||||||
Rml::DataModelHandle app_data_handle;
|
std::cout << "[INFO] RmlUI Backend initialized.\n";
|
||||||
|
Rml::SetSystemInterface(Backend::GetSystemInterface());
|
||||||
SetupDataBinding(ctx, app_data_handle);
|
Rml::SetRenderInterface(Backend::GetRenderInterface());
|
||||||
|
Rml::SetFileInterface(&file_interface);
|
||||||
Rml::ElementDocument *doc;
|
Rml::Initialise();
|
||||||
|
Rml::Lua::Initialise();
|
||||||
|
std::cout << "[INFO] Preparing local DB...\n";
|
||||||
|
if (!ensure_db_copied()) {
|
||||||
|
std::cout << "[ERROR] Could not prepare local DB.\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "[INFO] Loading local DB...\n";
|
||||||
|
if (!db_get_all_critters(critters)) {
|
||||||
|
std::cout << "[ERROR] Local DB failed to initialize.\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "[INFO] Local DB initialized.\n";
|
||||||
|
sync_map_to_list(critters, critter_list);
|
||||||
|
ctx = Rml::CreateContext("main", Rml::Vector2i(800, 600));
|
||||||
|
if (!ctx) {
|
||||||
|
std::cout << "[ERROR] Failed to create context.\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "[INFO] Main context initialized.\n";
|
||||||
|
Rml::LoadFontFace("./assets/fonts/Hubballi-Regular.ttf");
|
||||||
|
Rml::Debugger::Initialise(ctx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool load_document() {
|
||||||
doc = ctx->LoadDocument("./assets/main.rml");
|
doc = ctx->LoadDocument("./assets/main.rml");
|
||||||
if (!doc) {
|
if (!doc) {
|
||||||
std::cout << "[ERROR] Could no load assets/main.rml document, shutting down.";
|
std::cout << "[ERROR] Could not load assets/main.rml document, shutting down.\n";
|
||||||
Shutdown();
|
shutdown();
|
||||||
return 1;
|
return false;
|
||||||
|
}
|
||||||
|
std::cout << "[INFO] Document loaded\n";
|
||||||
|
doc->Show();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool reload_doc() {
|
||||||
|
if (doc) {
|
||||||
|
Rml::Factory::ClearStyleSheetCache();
|
||||||
|
Rml::Factory::ClearTemplateCache();
|
||||||
|
doc->Close();
|
||||||
|
doc = nullptr;
|
||||||
|
}
|
||||||
|
if (!load_document()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
bool f5_was_pressed = false;
|
||||||
|
bool f8_was_pressed = false;
|
||||||
|
void handle_input() {
|
||||||
|
const bool *state = SDL_GetKeyboardState(NULL);
|
||||||
|
if (state) {
|
||||||
|
bool f5_state = state[SDL_SCANCODE_F5];
|
||||||
|
if (f5_state && !f5_was_pressed) {
|
||||||
|
f5_was_pressed = true;
|
||||||
|
reload_doc();
|
||||||
|
} else if (!f5_state) {
|
||||||
|
f5_was_pressed = false;
|
||||||
|
}
|
||||||
|
bool f8_state = state[SDL_SCANCODE_F8];
|
||||||
|
if (f8_state && !f8_was_pressed) {
|
||||||
|
f8_was_pressed = true;
|
||||||
|
bool is_visible = Rml::Debugger::IsVisible();
|
||||||
|
Rml::Debugger::SetVisible(!is_visible);
|
||||||
|
} else if (!f8_state) {
|
||||||
|
f8_was_pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int main(int arc, char *argv[]) {
|
||||||
|
std::cout << "[INFO] Starting CritterFolio...\n";
|
||||||
|
is_initalized = initialize();
|
||||||
|
if (!is_initalized) {
|
||||||
|
std::cout << "[ERROR] Failed to initialize, shutting down.\n";
|
||||||
|
shutdown();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
is_running = true;
|
||||||
|
Rml::DataModelHandle app_data_handle;
|
||||||
|
if (!setup_data_binding(ctx, app_data_handle, app_data, critter_list, critters)) {
|
||||||
|
shutdown();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (!load_document()) {
|
||||||
|
shutdown();
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
std::cout << "[INFO] Main page loaded.\n";
|
std::cout << "[INFO] Main page loaded.\n";
|
||||||
|
|
||||||
doc->Show();
|
while (is_running) {
|
||||||
|
is_running = Backend::ProcessEvents(ctx);
|
||||||
while(is_running) {
|
handle_input();
|
||||||
is_running = Backend::ProcessEvents(ctx);
|
ctx->Update();
|
||||||
|
Backend::BeginFrame();
|
||||||
Update(app_data_handle);
|
ctx->Render();
|
||||||
|
Backend::PresentFrame();
|
||||||
ctx->Update();
|
|
||||||
Backend::BeginFrame();
|
|
||||||
ctx->Render();
|
|
||||||
Backend::PresentFrame();
|
|
||||||
}
|
}
|
||||||
|
shutdown();
|
||||||
Shutdown();
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
37
src/sdl_file_interface.hpp
Normal file
37
src/sdl_file_interface.hpp
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "asset_io.hpp"
|
||||||
|
#include <RmlUi/Core/FileInterface.h>
|
||||||
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
class SDLFileInterface : public Rml::FileInterface {
|
||||||
|
public:
|
||||||
|
Rml::FileHandle Open(const Rml::String &path) override {
|
||||||
|
SDL_IOStream *io = OpenAssetFile(path);
|
||||||
|
return reinterpret_cast<Rml::FileHandle>(io);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close(Rml::FileHandle file) override {
|
||||||
|
SDL_CloseIO(reinterpret_cast<SDL_IOStream *>(file));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Read(void *buffer, size_t size, Rml::FileHandle file) override {
|
||||||
|
return SDL_ReadIO(reinterpret_cast<SDL_IOStream *>(file), buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Seek(Rml::FileHandle file, long offset, int origin) override {
|
||||||
|
SDL_IOWhence whence = SDL_IO_SEEK_SET;
|
||||||
|
if (origin == SEEK_CUR) whence = SDL_IO_SEEK_CUR;
|
||||||
|
else if (origin == SEEK_END) whence = SDL_IO_SEEK_END;
|
||||||
|
|
||||||
|
return SDL_SeekIO(reinterpret_cast<SDL_IOStream *>(file), offset, whence) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Tell(Rml::FileHandle file) override {
|
||||||
|
return static_cast<size_t>(SDL_TellIO(reinterpret_cast<SDL_IOStream *>(file)));
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Length(Rml::FileHandle file) override {
|
||||||
|
return static_cast<size_t>(SDL_GetIOSize(reinterpret_cast<SDL_IOStream *>(file)));
|
||||||
|
}
|
||||||
|
};
|
||||||
139
src/ui.cpp
Normal file
139
src/ui.cpp
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
#include "ui.hpp"
|
||||||
|
#include "database.hpp"
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
void sync_map_to_list(const std::map<int, Critter> &critters, std::vector<CritterEntry> &critter_list) {
|
||||||
|
critter_list.clear();
|
||||||
|
for (const auto &[id, critter] : critters) {
|
||||||
|
critter_list.push_back({id, critter});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool setup_data_binding(Rml::Context *context, Rml::DataModelHandle &model_handle, AppData &app_data, std::vector<CritterEntry> &critter_list, std::map<int, Critter> &critters) {
|
||||||
|
Rml::DataModelConstructor dmc = context->CreateDataModel("app_data");
|
||||||
|
if (!dmc) {
|
||||||
|
std::cout << "[ERROR] Could not create 'app_data' data binding.\n";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto handle = dmc.RegisterStruct<Critter>()) {
|
||||||
|
handle.RegisterMember("id", &Critter::id);
|
||||||
|
handle.RegisterMember("name", &Critter::name);
|
||||||
|
handle.RegisterMember("gender", &Critter::gender);
|
||||||
|
handle.RegisterMember("species", &Critter::species);
|
||||||
|
handle.RegisterMember("notes", &Critter::notes);
|
||||||
|
handle.RegisterMember("mother_id", &Critter::mother_id);
|
||||||
|
handle.RegisterMember("father_id", &Critter::father_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto handle = dmc.RegisterStruct<CritterEntry>()) {
|
||||||
|
handle.RegisterMember("key", &CritterEntry::key);
|
||||||
|
handle.RegisterMember("value", &CritterEntry::value);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (auto handle = dmc.RegisterStruct<NewCritterForm>()) {
|
||||||
|
handle.RegisterMember("name", &NewCritterForm::name);
|
||||||
|
handle.RegisterMember("gender", &NewCritterForm::gender);
|
||||||
|
handle.RegisterMember("mother_id", &NewCritterForm::mother_id);
|
||||||
|
handle.RegisterMember("father_id", &NewCritterForm::father_id);
|
||||||
|
handle.RegisterMember("notes", &NewCritterForm::notes);
|
||||||
|
handle.RegisterMember("species", &NewCritterForm::species);
|
||||||
|
}
|
||||||
|
|
||||||
|
dmc.RegisterArray<std::vector<CritterEntry>>();
|
||||||
|
|
||||||
|
dmc.Bind("critters", &critter_list);
|
||||||
|
dmc.Bind("page_title", &app_data.page_title);
|
||||||
|
dmc.Bind("error_message", &app_data.error_message);
|
||||||
|
|
||||||
|
// New Critter dialog
|
||||||
|
dmc.Bind("is_add_critter_dialog_open", &app_data.is_add_critter_dialog_open);
|
||||||
|
dmc.Bind("new_critter_form", &app_data.new_critter_form);
|
||||||
|
|
||||||
|
// Confirmation dialog
|
||||||
|
dmc.Bind("is_confirmation_dialog_open", &app_data.is_confirmation_dialog_open);
|
||||||
|
dmc.Bind("confirmation_dialog_text", &app_data.confirmation_dialog_text);
|
||||||
|
|
||||||
|
// Submit Add Critter
|
||||||
|
dmc.BindEventCallback("submit_add_critter", [&app_data, &critters, &critter_list](Rml::DataModelHandle handle, Rml::Event &, const Rml::VariantList &) {
|
||||||
|
Critter temp;
|
||||||
|
temp.name = app_data.new_critter_form.name;
|
||||||
|
temp.gender = app_data.new_critter_form.gender;
|
||||||
|
temp.mother_id = app_data.new_critter_form.mother_id;
|
||||||
|
temp.father_id = app_data.new_critter_form.father_id;
|
||||||
|
temp.notes = app_data.new_critter_form.notes;
|
||||||
|
temp.species = app_data.new_critter_form.species;
|
||||||
|
|
||||||
|
std::string err;
|
||||||
|
if (!validate_critter(temp, critters, err)) {
|
||||||
|
app_data.error_message = err;
|
||||||
|
handle.DirtyVariable("error_message");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int new_id = 0;
|
||||||
|
if (db_add_critter(temp, new_id)) {
|
||||||
|
db_get_all_critters(critters);
|
||||||
|
sync_map_to_list(critters, critter_list);
|
||||||
|
|
||||||
|
app_data.new_critter_form.Clear();
|
||||||
|
app_data.error_message.clear();
|
||||||
|
app_data.is_add_critter_dialog_open = false;
|
||||||
|
|
||||||
|
handle.DirtyVariable("critters");
|
||||||
|
handle.DirtyVariable("new_critter_form");
|
||||||
|
handle.DirtyVariable("error_message");
|
||||||
|
handle.DirtyVariable("is_add_critter_dialog_open");
|
||||||
|
} else {
|
||||||
|
app_data.error_message = "Failed to write to database.";
|
||||||
|
handle.DirtyVariable("error_message");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request Critter Deletion
|
||||||
|
dmc.BindEventCallback("delete_critter", [&app_data, &critters, &critter_list](Rml::DataModelHandle const_handle, Rml::Event &, const Rml::VariantList &args) {
|
||||||
|
if (args.empty()) return;
|
||||||
|
|
||||||
|
Rml::DataModelHandle handle = const_handle;
|
||||||
|
|
||||||
|
int target_id = args[0].Get<int>();
|
||||||
|
|
||||||
|
app_data.confirmation_dialog_text = "Are you sure you want to delete critter '" + critters[target_id].name + "'? This action cannot be undone.";
|
||||||
|
|
||||||
|
app_data.on_confirm_action = [&critters, &critter_list, target_id, const_handle]() {
|
||||||
|
Rml::DataModelHandle handle = const_handle;
|
||||||
|
if (db_delete_critter(target_id)) {
|
||||||
|
critters.erase(target_id);
|
||||||
|
sync_map_to_list(critters, critter_list);
|
||||||
|
handle.DirtyVariable("critters");
|
||||||
|
std::cout << "[INFO] Deleted critter with ID #" << target_id << std::endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
app_data.is_confirmation_dialog_open = true;
|
||||||
|
handle.DirtyVariable("confirmation_dialog_text");
|
||||||
|
handle.DirtyVariable("is_confirmation_dialog_open");
|
||||||
|
});
|
||||||
|
|
||||||
|
dmc.BindEventCallback("confirm_dialog_yes", [&app_data](Rml::DataModelHandle const_handle, Rml::Event &, const Rml::VariantList &) {
|
||||||
|
Rml::DataModelHandle handle = const_handle;
|
||||||
|
|
||||||
|
if (app_data.on_confirm_action) {
|
||||||
|
app_data.on_confirm_action();
|
||||||
|
app_data.on_confirm_action = nullptr;
|
||||||
|
}
|
||||||
|
app_data.is_confirmation_dialog_open = false;
|
||||||
|
handle.DirtyVariable("is_confirmation_dialog_open");
|
||||||
|
});
|
||||||
|
|
||||||
|
dmc.BindEventCallback("confirm_dialog_no", [&app_data](Rml::DataModelHandle const_handle, Rml::Event &, const Rml::VariantList &) {
|
||||||
|
Rml::DataModelHandle handle = const_handle;
|
||||||
|
|
||||||
|
app_data.on_confirm_action = nullptr;
|
||||||
|
app_data.is_confirmation_dialog_open = false;
|
||||||
|
handle.DirtyVariable("is_confirmation_dialog_open");
|
||||||
|
});
|
||||||
|
|
||||||
|
model_handle = dmc.GetModelHandle();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
42
src/ui.hpp
Normal file
42
src/ui.hpp
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "critter.hpp"
|
||||||
|
#include <RmlUi/Config/Config.h>
|
||||||
|
#include <RmlUi/Core/Context.h>
|
||||||
|
#include <RmlUi/Core/DataModelHandle.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct NewCritterForm {
|
||||||
|
Rml::String name;
|
||||||
|
Rml::String gender = "F";
|
||||||
|
int mother_id = 0;
|
||||||
|
int father_id = 0;
|
||||||
|
Rml::String notes;
|
||||||
|
Rml::String species;
|
||||||
|
|
||||||
|
void Clear() {
|
||||||
|
name.clear();
|
||||||
|
gender = "F";
|
||||||
|
mother_id = 0;
|
||||||
|
father_id = 0;
|
||||||
|
notes.clear();
|
||||||
|
species.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AppData {
|
||||||
|
Rml::String page_title = "Main";
|
||||||
|
bool is_add_critter_dialog_open = false;
|
||||||
|
bool is_confirmation_dialog_open = false;
|
||||||
|
Rml::String confirmation_dialog_text = "Are you sure?";
|
||||||
|
bool confimation_dialog_result = false;
|
||||||
|
Rml::String error_message;
|
||||||
|
NewCritterForm new_critter_form;
|
||||||
|
|
||||||
|
std::function<void()> on_confirm_action = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
void sync_map_to_list(const std::map<int, Critter> &critters, std::vector<CritterEntry> &critter_list);
|
||||||
|
bool setup_data_binding(Rml::Context *context, Rml::DataModelHandle &model_handle, AppData &app_data, std::vector<CritterEntry> &critter_list, std::map<int, Critter> &critters);
|
||||||
7988
vendor/stb/stb_image.h
vendored
Normal file
7988
vendor/stb/stb_image.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
1724
vendor/stb/stb_image_write.h
vendored
Normal file
1724
vendor/stb/stb_image_write.h
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user