Compare commits

...
Sign in to create a new pull request.

34 commits
master ... qml

Author SHA1 Message Date
649d48c096
[cmake, qml] refactor: cmake reorg, match grid behavior to carousel
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-14 16:00:54 -04:00
1604c102eb
[eden] marquee text on carousel (grid todo)
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-14 14:39:26 -04:00
0793e85b47
proper cmake uris, fix game carousel
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-14 14:39:24 -04:00
06fe470e5c
fix
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-14 14:39:02 -04:00
783afb54e7
Oops
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-14 14:39:02 -04:00
97d648cac6
carousel/list view
Signed-off-by: crueter <swurl@swurl.xyz>
2025-09-14 14:39:02 -04:00
0e13a362f1
Working configuration
Signed-off-by: crueter <swurl@swurl.xyz>
2025-09-14 14:39:02 -04:00
a1db66cf7a
tmp
Signed-off-by: crueter <swurl@swurl.xyz>
2025-09-14 14:39:01 -04:00
2f6b686859
merge
Signed-off-by: crueter <swurl@swurl.xyz>
2025-09-14 14:38:42 -04:00
6d7820cf01
fix windows dir opening
All checks were successful
eden-license / license-header (pull_request) Successful in 33s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 13:31:32 -04:00
a572ee58d3
fix windows
All checks were successful
eden-license / license-header (pull_request) Successful in 33s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 12:28:59 -04:00
ae31504772
fix msvc
All checks were successful
eden-license / license-header (pull_request) Successful in 34s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 12:09:12 -04:00
63961f3741
fix cpm-fetch (again)
All checks were successful
eden-license / license-header (pull_request) Successful in 35s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 11:51:44 -04:00
c70d9140a4
Fix cpm-fetch
All checks were successful
eden-license / license-header (pull_request) Successful in 35s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 11:49:10 -04:00
b326f2e3e0
fix headers
All checks were successful
eden-license / license-header (pull_request) Successful in 32s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 11:47:34 -04:00
8974aed013
QProgressDialog abstractor, more moving
All checks were successful
eden-license / license-header (pull_request) Successful in 34s
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 11:39:10 -04:00
2d94767f96
Fix license headers
Some checks failed
eden-license / license-header (pull_request) Failing after 34s
2025-09-13 10:25:59 -04:00
c65f075638
better handling for sys/vfs/rootobject
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:59 -04:00
4356e80e50
fix
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:59 -04:00
c45d9a71e8
fix win
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:59 -04:00
fd7711aa5d
cleanup
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:59 -04:00
c6a2d2acad
Fix license headers
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:59 -04:00
ca349ad7b0
[qt_common] reorg, move more stuff out of main
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:58 -04:00
75f18095e0
[qt_common] update translations
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:36 -04:00
092e645296
Fix license headers 2025-09-13 10:25:36 -04:00
49670ebb0f
[qt] frontend abstraction and message box early handling
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:36 -04:00
115d0484a6
thank you Qt Creator, very cool
Signed-off-by: crueter <crueter@crueter.xyz>
2025-09-13 10:25:36 -04:00
886b649a0d
Fix license headers 2025-09-13 10:25:36 -04:00
195bd7005e
more common funcs
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:35 -04:00
ae62ee3d27
explicitly check write status for dir
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:19 -04:00
cb5719ec0e
debug: log user/save id
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:19 -04:00
3508208473
Fix license headers
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:19 -04:00
7a0712af1f
move fw install
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:19 -04:00
11db0f0dbf
qt_common init
Signed-off-by: crueter <crueter@eden-emu.dev>
2025-09-13 10:25:18 -04:00
186 changed files with 6349 additions and 1534 deletions

View file

@ -5,10 +5,13 @@ HEADER_HASH="$(cat "$PWD/.ci/license/header-hash.txt")"
echo "Getting branch changes" echo "Getting branch changes"
BRANCH=`git rev-parse --abbrev-ref HEAD` # BRANCH=`git rev-parse --abbrev-ref HEAD`
COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"` # COMMITS=`git log ${BRANCH} --not master --pretty=format:"%h"`
RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}" # RANGE="${COMMITS[${#COMMITS[@]}-1]}^..${COMMITS[0]}"
FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r` # FILES=`git diff-tree --no-commit-id --name-only ${RANGE} -r`
BASE=`git merge-base master HEAD`
FILES=`git diff --name-only $BASE`
#FILES=$(git diff --name-only master) #FILES=$(git diff --name-only master)

2
.gitignore vendored
View file

@ -53,3 +53,5 @@ eden-windows-msvc
artifacts artifacts
*.AppImage* *.AppImage*
/install* /install*
/*.patch
/.cache

View file

@ -0,0 +1,47 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
cmake_minimum_required(VERSION 3.16)
function(EdenModule)
set(oneValueArgs
NAME
URI
NATIVE
)
set(multiValueArgs
LIBRARIES
QML_FILES
SOURCES
)
cmake_parse_arguments(MODULE "" "${oneValueArgs}" "${multiValueArgs}"
"${ARGN}")
set(LIB_NAME Eden${MODULE_NAME})
add_library(${LIB_NAME} STATIC)
message(STATUS "URI for ${MODULE_NAME}: ${MODULE_URI}")
qt_add_qml_module(${LIB_NAME}
URI ${MODULE_URI}
NO_PLUGIN
VERSION 0.1
QML_FILES ${MODULE_QML_FILES}
SOURCES ${MODULE_SOURCES}
${MODULE_UNPARSED_ARGUMENTS}
)
add_library(Eden::${MODULE_NAME} ALIAS ${LIB_NAME})
if (DEFINED MODULE_LIBRARIES)
target_link_libraries(${LIB_NAME} PRIVATE ${MODULE_LIBRARIES})
endif()
endfunction()

View file

@ -245,6 +245,6 @@ include(CPMUtil)
Currently, `cpm-fetch.sh` defines the following directories for cpmfiles (max depth of 2, so subdirs are caught as well): Currently, `cpm-fetch.sh` defines the following directories for cpmfiles (max depth of 2, so subdirs are caught as well):
`externals src/yuzu src/dynarmic .` `externals src/qt_common src/dynarmic .`
Whenever you add a new cpmfile, update the script accordingly Whenever you add a new cpmfile, update the script accordingly

View file

@ -18,20 +18,20 @@ set_property(DIRECTORY APPEND PROPERTY
COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>) COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
# Set compilation flags # Set compilation flags
if (MSVC AND NOT CXX_CLANG) if (MSVC)
set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE) set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
# Silence "deprecation" warnings # Silence "deprecation" warnings
add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE _SCL_SECURE_NO_WARNINGS) add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
# Avoid windows.h junk # Avoid windows.h junk
add_compile_definitions(NOMINMAX) add_definitions(-DNOMINMAX)
# Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors. # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
add_compile_definitions(WIN32_LEAN_AND_MEAN) add_definitions(-DWIN32_LEAN_AND_MEAN)
# Ensure that projects are built with Unicode support. # Ensure that projects are built with Unicode support.
add_compile_definitions(UNICODE _UNICODE) add_definitions(-DUNICODE -D_UNICODE)
# /W4 - Level 4 warnings # /W4 - Level 4 warnings
# /MP - Multi-threaded compilation # /MP - Multi-threaded compilation
@ -69,6 +69,10 @@ if (MSVC AND NOT CXX_CLANG)
/external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers /external:anglebrackets # Treats all headers included by #include <header>, where the header file is enclosed in angle brackets (< >), as external headers
/external:W0 # Sets the default warning level to 0 for external headers, effectively disabling warnings for them. /external:W0 # Sets the default warning level to 0 for external headers, effectively disabling warnings for them.
# Warnings
/W4
/WX-
/we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled /we4062 # Enumerator 'identifier' in a switch of enum 'enumeration' is not handled
/we4189 # 'identifier': local variable is initialized but not referenced /we4189 # 'identifier': local variable is initialized but not referenced
/we4265 # 'class': class has virtual functions, but destructor is not virtual /we4265 # 'class': class has virtual functions, but destructor is not virtual
@ -93,14 +97,6 @@ if (MSVC AND NOT CXX_CLANG)
/wd4702 # unreachable code (when used with LTO) /wd4702 # unreachable code (when used with LTO)
) )
if (NOT CXX_CLANG)
add_compile_options(
# Warnings
/W4
/WX-
)
endif()
if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS) if (USE_CCACHE OR YUZU_USE_PRECOMPILED_HEADERS)
# when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format # when caching, we need to use /Z7 to downgrade debug info to use an older but more cacheable format
# Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21 # Precompiled headers are deleted if not using /Z7. See https://github.com/nanoant/CMakePCHCompiler/issues/21
@ -122,16 +118,11 @@ if (MSVC AND NOT CXX_CLANG)
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE) set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
else() else()
if (NOT MSVC)
add_compile_options(
-fwrapv
)
endif()
add_compile_options( add_compile_options(
-fwrapv
-Werror=all -Werror=all
-Werror=extra -Werror=extra
-Werror=missing-declarations
-Werror=shadow -Werror=shadow
-Werror=unused -Werror=unused
@ -141,19 +132,14 @@ else()
-Wno-missing-field-initializers -Wno-missing-field-initializers
) )
if (CXX_CLANG OR CXX_ICC) # Clang or AppleClang if (CMAKE_CXX_COMPILER_ID MATCHES Clang OR CMAKE_CXX_COMPILER_ID MATCHES IntelLLVM) # Clang or AppleClang
if (NOT MSVC)
add_compile_options(
-Werror=shadow-uncaptured-local
-Werror=implicit-fallthrough
-Werror=type-limits
)
endif()
add_compile_options( add_compile_options(
-Wno-braced-scalar-init -Wno-braced-scalar-init
-Wno-unused-private-field -Wno-unused-private-field
-Wno-nullability-completeness -Wno-nullability-completeness
-Werror=shadow-uncaptured-local
-Werror=implicit-fallthrough
-Werror=type-limits
) )
endif() endif()
@ -161,12 +147,12 @@ else()
add_compile_options("-mcx16") add_compile_options("-mcx16")
endif() endif()
if (APPLE AND CXX_CLANG) if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
add_compile_options("-stdlib=libc++") add_compile_options("-stdlib=libc++")
endif() endif()
# GCC bugs # GCC bugs
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CXX_GCC) if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "11" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
# These diagnostics would be great if they worked, but are just completely broken # These diagnostics would be great if they worked, but are just completely broken
# and produce bogus errors on external libraries like fmt. # and produce bogus errors on external libraries like fmt.
add_compile_options( add_compile_options(
@ -182,15 +168,15 @@ else()
# glibc, which may default to 32 bits. glibc allows this to be configured # glibc, which may default to 32 bits. glibc allows this to be configured
# by setting _FILE_OFFSET_BITS. # by setting _FILE_OFFSET_BITS.
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW) if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
add_compile_definitions(_FILE_OFFSET_BITS=64) add_definitions(-D_FILE_OFFSET_BITS=64)
endif() endif()
if (MINGW) if (MINGW)
add_compile_definitions(MINGW_HAS_SECURE_API) add_definitions(-DMINGW_HAS_SECURE_API)
add_compile_options("-msse4.1") add_compile_options("-msse4.1")
if (MINGW_STATIC_BUILD) if (MINGW_STATIC_BUILD)
add_compile_definitions(QT_STATICPLUGIN) add_definitions(-DQT_STATICPLUGIN)
add_compile_options("-static") add_compile_options("-static")
endif() endif()
endif() endif()
@ -234,7 +220,9 @@ if (YUZU_ROOM_STANDALONE)
endif() endif()
if (ENABLE_QT) if (ENABLE_QT)
add_subdirectory(yuzu) add_definitions(-DYUZU_QT_WIDGETS)
add_subdirectory(qt_common)
add_subdirectory(Eden)
endif() endif()
if (ENABLE_WEB_SERVICE) if (ENABLE_WEB_SERVICE)
@ -245,5 +233,3 @@ if (ANDROID)
add_subdirectory(android/app/src/main/jni) add_subdirectory(android/app/src/main/jni)
target_include_directories(yuzu-android PRIVATE android/app/src/main) target_include_directories(yuzu-android PRIVATE android/app/src/main)
endif() endif()
include(GenerateDepHashes)

22
src/Eden/CMakeLists.txt Normal file
View file

@ -0,0 +1,22 @@
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
find_package(Qt6 REQUIRED COMPONENTS Widgets Core Gui Quick QuickControls2)
qt_standard_project_setup(REQUIRES 6.7)
include(EdenModule)
include_directories(AFTER "${CMAKE_CURRENT_SOURCE_DIR}")
add_subdirectory(Interface)
add_subdirectory(Models)
add_subdirectory(Config)
add_subdirectory(Constants)
add_subdirectory(Items)
add_subdirectory(Util)
add_subdirectory(Main)
add_subdirectory(Native)

View file

@ -0,0 +1,62 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
EdenModule(
NAME Config
URI Eden.Config
QML_FILES
GlobalConfigureDialog.qml
Setting.qml
SectionHeader.qml
pages/SettingsList.qml
pages/global/GlobalTab.qml
pages/global/GlobalTabSwipeView.qml
pages/global/GlobalGeneralPage.qml
pages/global/GlobalSystemPage.qml
pages/global/GlobalCpuPage.qml
pages/global/GlobalGraphicsPage.qml
pages/global/GlobalAudioPage.qml
pages/general/UiGeneralPage.qml
pages/general/UiGameListPage.qml
pages/graphics/RendererPage.qml
pages/graphics/RendererAdvancedPage.qml
pages/graphics/RendererExtensionsPage.qml
pages/system/SystemGeneralPage.qml
pages/system/SystemCorePage.qml
pages/system/FileSystemPage.qml
pages/system/AppletsPage.qml
pages/cpu/CpuGeneralPage.qml
pages/audio/AudioGeneralPage.qml
pages/global/GlobalDebugPage.qml
pages/debug/DebugGeneralPage.qml
pages/debug/DebugGraphicsPage.qml
pages/debug/DebugAdvancedPage.qml
pages/debug/DebugCpuPage.qml
fields/ConfigCheckbox.qml
fields/FieldLabel.qml
fields/ConfigComboBox.qml
fields/ConfigIntLine.qml
fields/ConfigTimeEdit.qml
fields/ConfigIntSpin.qml
fields/ConfigHexEdit.qml
fields/ConfigStringEdit.qml
fields/FieldCheckbox.qml
fields/ConfigIntSlider.qml
fields/BaseField.qml
TestSetting.qml
LIBRARIES Eden::Interface
)

View file

@ -0,0 +1,80 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Constants
import Eden.Items
import Eden.Interface
import Eden.Util
AnimatedDialog {
property list<var> configs
preferredWidth: 1280
title: "Configuration"
standardButtons: Dialog.Ok | Dialog.Cancel
Component.onCompleted: configs = Util.searchItem(swipe, "BaseField")
onAccepted: {
configs.forEach(config => {
config.apply()
// console.log(config.setting.label)
})
QtConfig.save()
}
onRejected: {
configs.forEach(config => config.sync())
QtConfig.reload()
}
VerticalTabBar {
id: tabBar
anchors {
top: parent.top
topMargin: 55
left: parent.left
bottom: parent.bottom
}
contentWidth: 100
currentIndex: swipe.currentIndex
Repeater {
model: ["General", "System", "CPU", "Graphics", "Audio", "Debug", "Controls"]
SettingsTabButton {
required property string modelData
label: modelData
onClicked: tabBar.currentIndex = TabBar.index
}
}
}
SwipeView {
id: swipe
currentIndex: tabBar.currentIndex
orientation: Qt.Vertical
anchors {
left: tabBar.right
right: parent.right
top: parent.top
bottom: parent.bottom
leftMargin: 5
}
clip: true
GlobalGeneralPage {}
GlobalSystemPage {}
GlobalCpuPage {}
GlobalGraphicsPage {}
GlobalAudioPage {}
GlobalDebugPage {}
}
}

View file

@ -0,0 +1,8 @@
import QtQuick
import Eden.Constants
Text {
color: Constants.text
font.pixelSize: 16
}

View file

@ -0,0 +1,92 @@
import QtQuick
import Qt.labs.qmlmodels
import Eden.Config
// TODO: make settings independently available (model vs setting?
DelegateChooser {
id: chooser
role: "type"
DelegateChoice {
roleValue: "bool"
ConfigCheckbox {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "enumCombo"
ConfigComboBox {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "intCombo"
ConfigComboBox {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "intLine"
ConfigIntLine {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "intSlider"
ConfigIntSlider {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "time"
ConfigTimeEdit {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "intSpin"
ConfigIntSpin {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "stringLine"
ConfigStringEdit {
setting: model
width: ListView.view.width
}
}
DelegateChoice {
roleValue: "hex"
ConfigHexEdit {
setting: model
width: ListView.view.width
}
}
}

View file

@ -0,0 +1,39 @@
import QtQuick
import QtQuick.Layouts
import Eden.Constants
Column {
topPadding: 5
leftPadding: 10
RowLayout {
uniformCellSizes: true
Text {
Layout.fillWidth: true
text: model.label
color: Constants.text
font.pixelSize: 16
height: 40
}
Text {
Layout.fillWidth: true
text: model.value
color: "lightblue"
font.pixelSize: 14
height: 40
horizontalAlignment: Text.AlignRight
}
}
Text {
text: model.type + " " + typeof model.value + " " + model.other
color: "lightgray"
font.pixelSize: 12
height: 25
}
}

View file

@ -0,0 +1,170 @@
import QtQuick
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
Item {
id: field
property var setting
property var value
property bool showLabel: true
property bool forceCheckbox: false
property alias enable: enable.checked
property Item contentItem
readonly property string typeName: "BaseField"
clip: true
height: content.height + (helpText.height + helpText.anchors.topMargin)
Component.onCompleted: sync()
function apply() {
if (setting.value !== value) {
setting.value = value
}
}
function sync() {
if (value !== setting.value) {
value = setting.value
}
}
RowLayout {
id: content
height: 50
spacing: 0
anchors {
left: parent.left
right: parent.right
}
z: 2
IconButton {
label: "help"
icon.width: 20
icon.height: 20
onClicked: helpText.toggle()
icon.color: setting.tooltip !== "" ? Constants.text : Constants.dialog
z: 2
}
FieldCheckbox {
id: enable
setting: field.setting
z: 2
force: field.forceCheckbox
}
RowLayout {
Layout.fillWidth: true
uniformCellSizes: true
spacing: 0
z: 2
FieldLabel {
z: 2
id: label
setting: field.setting
}
children: showLabel ? [label, contentItem] : [contentItem]
}
}
Rectangle {
color: Constants.dialog
anchors.fill: content
z: 0
}
Text {
id: helpText
anchors {
left: parent.left
leftMargin: 20
right: parent.right
rightMargin: 20
top: content.bottom
topMargin: -height
}
z: -1
text: setting.tooltip
color: Constants.subText
font.pixelSize: 12
wrapMode: Text.WordWrap
visible: false
opacity: 0
function toggle() {
if (visible) {
hideAnim.start()
} else {
showAnim.start()
}
}
ParallelAnimation {
id: showAnim
SmoothedAnimation {
target: helpText
property: "opacity"
from: 0
to: 1
velocity: 3
}
SmoothedAnimation {
target: helpText
property: "anchors.topMargin"
from: -helpText.height
to: 0
duration: 300
velocity: -1
}
onStarted: helpText.visible = true
}
ParallelAnimation {
id: hideAnim
SmoothedAnimation {
target: helpText
property: "opacity"
from: 1
to: 0
velocity: 3
}
SmoothedAnimation {
target: helpText
property: "anchors.topMargin"
from: 0
to: -helpText.height
duration: 300
velocity: -1
}
onFinished: helpText.visible = false
}
}
}

View file

@ -0,0 +1,27 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Constants
BaseField {
forceCheckbox: true
// // TODO: global/custom
// contentItem: CheckBox {
// id: control
// Layout.rightMargin: 10
// Layout.fillWidth: true
// font.pixelSize: 15
// indicator.implicitHeight: 25
// indicator.implicitWidth: 25
// text: setting.label
// checked: setting.value
// onClicked: value = checked
// checkable: true
// }
}

View file

@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import QtQuick.Controls.Material.impl
import Eden.Constants
import Eden.Config
BaseField {
contentItem: ComboBox {
id: control
enabled: enable
Layout.fillWidth: true
Layout.rightMargin: 10
font.pixelSize: 14
model: setting.combo
currentIndex: value
background: MaterialTextContainer {
implicitWidth: 120
implicitHeight: 40
outlineColor: (enabled
&& control.hovered) ? control.Material.primaryTextColor : control.Material.hintTextColor
focusedOutlineColor: control.Material.accentColor
controlHasActiveFocus: control.activeFocus
controlHasText: true
horizontalPadding: control.Material.textFieldHorizontalPadding
}
}
}

View file

@ -0,0 +1,26 @@
import QtQuick
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
BaseField {
contentItem: BetterTextField {
enabled: enable
Layout.fillWidth: true
Layout.rightMargin: 10
validator: RegularExpressionValidator {
regularExpression: /[0-9a-fA-F]{0,8}/
}
font.pixelSize: 15
text: Number(value).toString(16)
suffix: setting.suffix
onTextEdited: value = Number("0x" + text)
}
}

View file

@ -0,0 +1,28 @@
import QtQuick
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
BaseField {
contentItem: BetterTextField {
enabled: enable
Layout.fillWidth: true
Layout.rightMargin: 10
inputMethodHints: Qt.ImhDigitsOnly
validator: IntValidator {
bottom: setting.min
top: setting.max
}
font.pixelSize: 15
text: value
suffix: setting.suffix
onTextEdited: value = parseInt(text)
}
}

View file

@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
// Lots of cancer but idrc
BaseField {
id: field
contentItem: RowLayout {
Layout.fillWidth: true
Slider {
Layout.fillWidth: true
from: setting.min
to: setting.max
stepSize: 1
value: field.value
onMoved: field.value = value
Layout.rightMargin: 10
snapMode: Slider.SnapAlways
}
Text {
font.pixelSize: 14
color: Constants.text
text: field.value + setting.suffix
Layout.rightMargin: 10
}
}
}

View file

@ -0,0 +1,26 @@
import QtQuick
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
BaseField {
id: field
contentItem: BetterSpinBox {
enabled: enable
Layout.fillWidth: true
Layout.rightMargin: 10
from: setting.min
to: setting.max
font.pixelSize: 15
value: field.value
label: setting.suffix
onValueModified: field.value = value
}
}

View file

@ -0,0 +1,22 @@
import QtQuick
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
BaseField {
contentItem: BetterTextField {
enabled: enable
Layout.fillWidth: true
Layout.rightMargin: 10
font.pixelSize: 15
text: value
suffix: setting.suffix
onTextEdited: value = text
}
}

View file

@ -0,0 +1,27 @@
import QtQuick
import QtQuick.Layouts
import Eden.Items
import Eden.Config
import Eden.Constants
BaseField {
// TODO: real impl
contentItem: BetterTextField {
enabled: enable
Layout.fillWidth: true
Layout.rightMargin: 10
inputMethodHints: Qt.ImhDigitsOnly
validator: IntValidator {
bottom: setting.min
top: setting.max
}
font.pixelSize: 15
text: value
suffix: setting.suffix
}
}

View file

@ -0,0 +1,20 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Eden.Constants
CheckBox {
property bool force: false
property var setting
property var other: setting.other === null ? setting : setting.other
indicator.implicitHeight: 25
indicator.implicitWidth: 25
checked: visible ? other.value : true
onClicked: if (visible)
other.value = checked
visible: setting.other !== null || force
}

View file

@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Eden.Constants
Text {
property var setting
text: setting.label
color: Constants.text
font.pixelSize: 14
height: 50
ToolTip.text: setting.tooltip
Layout.fillWidth: true
}

View file

@ -0,0 +1 @@
<RCC/>

View file

@ -0,0 +1,47 @@
import QtQuick
import QtQuick.Layouts
import Eden.Config
import Eden.Constants
import Eden.Interface
ColumnLayout {
required property int category
property bool inset: false
property string header: ""
property list<string> idInclude: []
property list<string> idExclude: []
SectionHeader {
text: header
visible: header != ""
}
ListView {
clip: true
boundsBehavior: Flickable.StopAtBounds
interactive: false
implicitHeight: contentHeight
delegate: Setting {}
Layout.fillHeight: true
Layout.fillWidth: true
Layout.leftMargin: 5
spacing: 8
model: SettingsInterface.category(category, idInclude, idExclude)
Rectangle {
anchors.fill: parent
color: "transparent"
border {
color: inset ? Constants.text : "transparent"
width: 1
}
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.Audio
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.Cpu
}
}
}

View file

@ -0,0 +1,19 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
// TODO: filter
SettingsList {
category: SettingsCategories.Debugging
}
}
}

View file

@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.CpuDebug
}
}
}

View file

@ -0,0 +1,28 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
RowLayout {
Layout.fillWidth: true
// TODO: split
SettingsList {
category: SettingsCategories.Debugging
}
// TODO: wrong category?
SettingsList {
category: SettingsCategories.Miscellaneous
}
}
}
}

View file

@ -0,0 +1,20 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
Layout.fillWidth: true
category: SettingsCategories.DebuggingGraphics
}
}
}

View file

@ -0,0 +1,18 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
// TODO: language, theme
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.UiGameList
}
}
}

View file

@ -0,0 +1,23 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.UiGeneral
// onContentHeightChanged: console.log(height, parent.height)
}
SettingsList {
category: SettingsCategories.Linux
visible: Qt.platform.os === "linux"
}
}
}

View file

@ -0,0 +1,16 @@
import QtQuick
import Eden.Config
GlobalTab {
property alias swipe: swipe
tabs: ["Audio"]
GlobalTabSwipeView {
id: swipe
currentIndex: tabBar.currentIndex
AudioGeneralPage {}
}
}

View file

@ -0,0 +1,15 @@
import QtQuick
import Eden.Config
GlobalTab {
property alias swipe: swipe
tabs: ["CPU"]
GlobalTabSwipeView {
id: swipe
currentIndex: tabBar.currentIndex
CpuGeneralPage {}
}
}

View file

@ -0,0 +1,18 @@
import QtQuick
import Eden.Config
GlobalTab {
property alias swipe: swipe
tabs: ["General", "Graphics", "Advanced", "CPU"]
GlobalTabSwipeView {
id: swipe
currentIndex: tabBar.currentIndex
DebugGeneralPage {}
DebugGraphicsPage {}
DebugAdvancedPage {}
DebugCpuPage {}
}
}

View file

@ -0,0 +1,18 @@
import QtQuick
import Eden.Config
GlobalTab {
property alias swipe: swipe
tabs: ["General", "Hotkeys", "Game List"]
GlobalTabSwipeView {
id: swipe
currentIndex: tabBar.currentIndex
// TODO: platform-specific stuff
UiGeneralPage {}
Item {}
UiGameListPage {}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import Eden.Config
GlobalTab {
property alias swipe: swipe
tabs: ["Graphics", "Advanced", "Extensions"]
GlobalTabSwipeView {
id: swipe
currentIndex: tabBar.currentIndex
RendererPage {}
RendererAdvancedPage {}
RendererExtensionsPage {}
}
}

View file

@ -0,0 +1,19 @@
import QtQuick
import Eden.Config
GlobalTab {
property alias swipe: swipe
tabs: ["System", "Core", "Profiles", "Filesystem", "Applets"]
GlobalTabSwipeView {
id: swipe
currentIndex: tabBar.currentIndex
SystemGeneralPage {}
SystemCorePage {}
Item {}
FileSystemPage {}
AppletsPage {}
}
}

View file

@ -0,0 +1,34 @@
import QtQuick 2.15
import QtQuick.Controls.Material
import Eden.Constants
Item {
required property list<string> tabs
property alias tabBar: tabBar
TabBar {
id: tabBar
currentIndex: swipe.currentIndex
anchors {
top: parent.top
left: parent.left
right: parent.right
}
Repeater {
model: tabs
TabButton {
font.pixelSize: 16
text: modelData
}
}
background: Rectangle {
color: tabBar.Material.backgroundColor
radius: 8
}
}
}

View file

@ -0,0 +1,16 @@
import QtQuick 2.15
import QtQuick.Controls.Material
import Eden.Constants
SwipeView {
anchors {
top: tabBar.bottom
left: parent.left
right: parent.right
bottom: parent.bottom
leftMargin: 20
topMargin: 10
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.RendererAdvanced
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.RendererExtensions
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.Renderer
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.LibraryApplet
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.DataStorage
}
}
}

View file

@ -0,0 +1,17 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.Core
}
}
}

View file

@ -0,0 +1,22 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Interface
import Eden.Config
ScrollView {
id: scroll
ColumnLayout {
width: scroll.width - scroll.effectiveScrollBarWidth
SettingsList {
category: SettingsCategories.Network
}
SettingsList {
category: SettingsCategories.System
idExclude: ["custom_rtc", "custom_rtc_offset", "current_user"]
}
}
}

View file

@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
set_source_files_properties(Constants.qml
PROPERTIES
QT_QML_SINGLETON_TYPE true
)
EdenModule(
NAME Constants
URI Eden.Constants
QML_FILES Constants.qml
)

View file

@ -0,0 +1,24 @@
pragma Singleton
import QtQuick
QtObject {
readonly property int width: 1200
readonly property int height: 1000
property color accent: "#FF4444"
property color accentPressed: "#ff5252"
readonly property color bg: "#111111"
readonly property color dialog: "#222222"
readonly property color dialogButton: "#000000"
readonly property color sub: "#181818"
readonly property color button: "#1E1E1E"
readonly property color buttonHighlighted: "#4A4A4A"
readonly property color text: "#EEEEEE"
readonly property color subText: "#AAAAAA"
property real scalar: 1.0
}

View file

@ -0,0 +1,24 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
find_package(Qt6 REQUIRED COMPONENTS Core)
qt_add_library(EdenInterface STATIC)
qt_add_qml_module(EdenInterface
URI Eden.Interface
NO_PLUGIN
SOURCES
SettingsInterface.h SettingsInterface.cpp
QMLSetting.h QMLSetting.cpp
MetaObjectHelper.h
QMLConfig.h
SOURCES TitleManager.h TitleManager.cpp
)
target_link_libraries(EdenInterface PUBLIC Qt6::Quick)
target_link_libraries(EdenInterface PRIVATE Qt6::Core)
add_library(Eden::Interface ALIAS EdenInterface)

View file

@ -0,0 +1,22 @@
#ifndef METAOBJECTHELPER_H
#define METAOBJECTHELPER_H
#include <QMetaObject>
#include <QObject>
#include <QQmlProperty>
class MetaObjectHelper : public QObject {
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
using QObject::QObject;
Q_INVOKABLE QString typeName(QObject* object, const QString& property) const
{
QQmlProperty qmlProperty(object, property);
QMetaProperty metaProperty = qmlProperty.property();
return metaProperty.typeName();
}
};
#endif // METAOBJECTHELPER_H

View file

@ -0,0 +1,26 @@
#ifndef QMLCONFIG_H
#define QMLCONFIG_H
#include "qt_common/qt_config.h"
#include <QObject>
class QMLConfig : public QObject {
Q_OBJECT
QtConfig *m_config;
public:
QMLConfig()
: m_config{new QtConfig}
{}
Q_INVOKABLE inline void reload() {
m_config->ReloadAllValues();
}
Q_INVOKABLE inline void save() {
m_config->SaveAllValues();
}
};
#endif // QMLCONFIG_H

View file

@ -0,0 +1,263 @@
#include "QMLSetting.h"
#include "common/settings.h"
#include <QVariant>
QMLSetting::QMLSetting(Settings::BasicSetting *setting, QObject *parent, RequestType request)
: QObject(parent)
, m_setting(setting)
{
// TODO: restore/touch
/*
if (!Settings::IsConfiguringGlobal() && managed) {
restore_button = CreateRestoreGlobalButton(setting.UsingGlobal(), this);
touch = [this]() {
LOG_DEBUG(Frontend, "Enabling custom setting for \"{}\"", setting.GetLabel());
restore_button->setEnabled(true);
restore_button->setVisible(true);
};
}
*/
const auto type = setting->TypeId();
request = [&]() {
if (request != RequestType::Default) {
return request;
}
switch (setting->Specialization() & Settings::SpecializationTypeMask) {
case Settings::Specialization::Default:
return RequestType::Default;
case Settings::Specialization::Time:
return RequestType::DateTimeEdit;
case Settings::Specialization::Hex:
return RequestType::HexEdit;
case Settings::Specialization::RuntimeList:
// managed = false;
[[fallthrough]];
case Settings::Specialization::List:
return RequestType::ComboBox;
case Settings::Specialization::Scalar:
return RequestType::Slider;
case Settings::Specialization::Countable:
return RequestType::SpinBox;
case Settings::Specialization::Radio:
return RequestType::RadioGroup;
default:
break;
}
return request;
}();
if (type == typeid(bool)) {
m_type = "bool";
m_metaType = QMetaType::Bool;
} else if (setting->IsEnum()) {
m_metaType = QMetaType::UInt;
if (request == RequestType::RadioGroup) {
m_type = "radio";
// TODO: Add the options and whatnot
// see CreateRadioGroup
} else {
m_type = "enumCombo";
}
} else if (setting->IsIntegral()) {
m_metaType = QMetaType::UInt;
switch (request) {
case RequestType::Slider:
case RequestType::ReverseSlider:
// TODO: Reversal and multiplier
m_type = "intSlider";
break;
case RequestType::Default:
case RequestType::LineEdit:
m_type = "intSpin";
break;
case RequestType::DateTimeEdit:
// TODO: disabled/restrict
m_type = "time";
break;
case RequestType::SpinBox:
// TODO: suffix
m_type = "intSpin";
break;
case RequestType::HexEdit:
m_type = "hex";
break;
case RequestType::ComboBox:
// TODO: Add the options and whatnot
// see CreateComboBox
m_type = "intCombo";
break;
default:
// UNIMPLEMENTED();
break;
}
} else if (setting->IsFloatingPoint()) {
m_metaType = QMetaType::Double;
switch (request) {
case RequestType::Default:
case RequestType::SpinBox:
// TODO: suffix
m_type = "doubleSpin";
break;
case RequestType::Slider:
case RequestType::ReverseSlider:
// TODO: multiplier, suffix, reversal
m_type = "doubleSlider";
break;
default:
// UNIMPLEMENTED assert
// UNIMPLEMENTED();
break;
}
} else if (type == typeid(std::string)) {
m_metaType = QMetaType::QString;
switch (request) {
case RequestType::Default:
case RequestType::LineEdit:
m_type = "stringLine";
break;
case RequestType::ComboBox:
m_type = "stringCombo";
break;
default:
// UNIMPLEMENTED();
break;
}
}
}
QVariant QMLSetting::value() const
{
QVariant var = QVariant::fromValue(QString::fromStdString(m_setting->ToString()));
var.convert(QMetaType(m_metaType));
return var;
}
void QMLSetting::setValue(const QVariant &newValue)
{
QVariant var = newValue;
var.convert(QMetaType(m_metaType));
m_setting->LoadString(var.toString().toStdString());
emit valueChanged();
}
bool QMLSetting::global() const
{
return m_setting->UsingGlobal();
}
void QMLSetting::setGlobal(bool newGlobal)
{
m_setting->SetGlobal(newGlobal);
emit globalChanged();
emit valueChanged();
}
void QMLSetting::restore()
{
std::string toSet = Settings::IsConfiguringGlobal() ? m_setting->DefaultToString() : m_setting->ToStringGlobal();
setValue(QString::fromStdString(toSet));
setGlobal(false);
}
QMLSetting *QMLSetting::other() const
{
return m_other;
}
void QMLSetting::setOther(QMLSetting *newOther)
{
if (m_other == newOther)
return;
m_other = newOther;
emit otherChanged();
}
u32 QMLSetting::max() const
{
return std::strtol(m_setting->MaxVal().c_str(), nullptr, 0);
}
u32 QMLSetting::min() const
{
return std::strtol(m_setting->MinVal().c_str(), nullptr, 0);
}
QString QMLSetting::suffix() const
{
return m_suffix;
}
void QMLSetting::setSuffix(const QString &newSuffix)
{
if (m_suffix == newSuffix)
return;
m_suffix = newSuffix;
emit suffixChanged();
}
QStringList QMLSetting::combo() const
{
return m_combo;
}
void QMLSetting::setCombo(const QStringList &newCombo)
{
if (m_combo == newCombo)
return;
m_combo = newCombo;
emit comboChanged();
}
QString QMLSetting::type() const
{
return m_type;
}
void QMLSetting::setType(const QString &newType)
{
if (m_type == newType)
return;
m_type = newType;
emit typeChanged();
}
u32 QMLSetting::id() const
{
return m_setting->Id();
}
QString QMLSetting::tooltip() const
{
return m_tooltip;
}
void QMLSetting::setTooltip(const QString &newTooltip)
{
if (m_tooltip == newTooltip)
return;
m_tooltip = newTooltip;
emit tooltipChanged();
}
QString QMLSetting::label() const
{
return m_label.isEmpty() ? QString::fromStdString(m_setting->GetLabel()) : m_label;
}
void QMLSetting::setLabel(const QString &newLabel)
{
if (m_label == newLabel)
return;
m_label = newLabel;
emit labelChanged();
}

View file

@ -0,0 +1,104 @@
#ifndef QMLSETTING_H
#define QMLSETTING_H
#include <QObject>
#include "common/settings_common.h"
enum class RequestType {
Default,
ComboBox,
SpinBox,
Slider,
ReverseSlider,
LineEdit,
HexEdit,
DateTimeEdit,
RadioGroup,
MaxEnum,
};
class QMLSetting : public QObject {
Q_OBJECT
public:
explicit QMLSetting(Settings::BasicSetting *setting, QObject *parent = nullptr, RequestType request = RequestType::Default);
QVariant value() const;
void setValue(const QVariant &newValue);
bool global() const;
void setGlobal(bool newGlobal);
QString label() const;
void setLabel(const QString &newLabel);
QString tooltip() const;
void setTooltip(const QString &newTooltip);
u32 id() const;
QString type() const;
void setType(const QString &newType);
QStringList combo() const;
void setCombo(const QStringList &newCombo);
QString suffix() const;
void setSuffix(const QString &newSuffix);
// TODO: float versions
u32 min() const;
u32 max() const;
QMLSetting *other() const;
void setOther(QMLSetting *newOther);
public slots:
void restore();
signals:
void valueChanged();
void globalChanged();
void labelChanged();
void tooltipChanged();
void typeChanged();
void comboChanged();
void suffixChanged();
void otherChanged();
private:
Settings::BasicSetting *m_setting;
QMLSetting *m_other;
QString m_label;
QString m_tooltip;
QString m_type;
QStringList m_combo;
QString m_suffix;
QMetaType::Type m_metaType;
Q_PROPERTY(QVariant value READ value WRITE setValue NOTIFY valueChanged FINAL)
Q_PROPERTY(bool global READ global WRITE setGlobal NOTIFY globalChanged FINAL)
Q_PROPERTY(QString label READ label WRITE setLabel NOTIFY labelChanged FINAL)
Q_PROPERTY(QString tooltip READ tooltip WRITE setTooltip NOTIFY tooltipChanged FINAL)
Q_PROPERTY(u32 id READ id FINAL CONSTANT)
Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged FINAL)
Q_PROPERTY(QStringList combo READ combo WRITE setCombo NOTIFY comboChanged FINAL)
Q_PROPERTY(QString suffix READ suffix WRITE setSuffix NOTIFY suffixChanged FINAL)
Q_PROPERTY(u32 min READ min FINAL CONSTANT)
Q_PROPERTY(u32 max READ max FINAL CONSTANT)
Q_PROPERTY(QMLSetting *other READ other WRITE setOther NOTIFY otherChanged FINAL)
};
Q_DECLARE_METATYPE(QMLSetting *)
#endif // QMLSETTING_H

View file

@ -0,0 +1,124 @@
#include "SettingsInterface.h"
#include "common/settings.h"
#include "common/logging/log.h"
#include "qt_common/uisettings.h"
SettingsInterface::SettingsInterface(QObject* parent)
: QObject{parent}
, translations{ConfigurationShared::InitializeTranslations(parent)}
, combobox_translations{ConfigurationShared::ComboboxEnumeration(parent)}
{
}
SettingsModel *SettingsInterface::category(SettingsCategories::Category category,
QList<QString> idInclude,
QList<QString> idExclude)
{
std::vector<Settings::BasicSetting *> settings = Settings::values.linkage.by_category[static_cast<Settings::Category>(category)];
std::vector<Settings::BasicSetting *> uisettings = UISettings::values.linkage.by_category[static_cast<Settings::Category>(category)];
settings.insert(settings.end(), uisettings.begin(), uisettings.end());
QList<QMLSetting *> settingsList;
for (Settings::BasicSetting *setting : settings) {
// paired settings get ignored
if (setting->Specialization() == Settings::Specialization::Paired) {
LOG_DEBUG(Frontend, "\"{}\" has specialization Paired: ignoring", setting->GetLabel());
continue;
}
if ((idInclude.empty() || idInclude.contains(setting->GetLabel()))
&& (idExclude.empty() || !idExclude.contains(setting->GetLabel()))) {
settingsList.append(this->getSetting(setting));
}
}
SettingsModel *model = new SettingsModel(this);
model->append(settingsList);
return model;
}
int SettingsInterface::id(const QString &key)
{
return setting(key)->id();
}
bool SettingsInterface::global() const
{
return Settings::IsConfiguringGlobal();
}
void SettingsInterface::setGlobal(bool newGlobal)
{
Settings::SetConfiguringGlobal(newGlobal);
}
QMLSetting *SettingsInterface::getSetting(Settings::BasicSetting *setting)
{
if (setting == nullptr) {
return nullptr;
}
if (m_settings.contains(setting->GetLabel())) {
return m_settings.value(setting->GetLabel());
}
const int id = setting->Id();
const auto [label, tooltip] = [&]() {
const auto& setting_label = setting->GetLabel();
if (translations->contains(id)) {
return std::pair{translations->at(id).first, translations->at(id).second};
}
LOG_WARNING(Frontend, "Translation table lacks entry for \"{}\"", setting_label);
return std::pair{QString::fromStdString(setting_label), QString()};
}();
const auto type = setting->EnumIndex();
QStringList items;
const ConfigurationShared::ComboboxTranslations* enumeration{nullptr};
if (combobox_translations->contains(type)) {
enumeration = &combobox_translations->at(type);
for (const auto& [_, name] : *enumeration) {
items << name;
}
}
// TODO: Suffix (fr)
QString suffix = "";
if ((setting->Specialization() & Settings::SpecializationAttributeMask) ==
Settings::Specialization::Percentage) {
suffix = "%";
}
// paired setting (I/A)
QMLSetting *other = this->getSetting(setting->PairedSetting());
QMLSetting *qsetting = new QMLSetting(setting, this);
qsetting->setLabel(label);
qsetting->setTooltip(tooltip);
qsetting->setCombo(items);
qsetting->setSuffix(suffix);
qsetting->setOther(other);
m_settings.insert(setting->GetLabel(), qsetting);
return qsetting;
}
QMLSetting *SettingsInterface::setting(const QString &key)
{
std::string skey = key.toStdString();
if (!m_settings.contains(skey)) {
Settings::BasicSetting *basicSetting = Settings::values.linkage.by_key.contains(skey) ?
Settings::values.linkage.by_key[skey] :
UISettings::values.linkage.by_key[skey];
return getSetting(basicSetting);
} else {
return m_settings.value(skey);
}
}

View file

@ -0,0 +1,83 @@
#ifndef SETTINGSINTERFACE_H
#define SETTINGSINTERFACE_H
#include <QObject>
#include <QQmlEngine>
#include "QMLSetting.h"
#include "qt_common/shared_translation.h"
#include "Models/SettingsModel.h"
namespace SettingsCategories {
Q_NAMESPACE
enum class Category {
Android = static_cast<u32>(Settings::Category::Android),
Audio = static_cast<u32>(Settings::Category::Audio),
Core = static_cast<u32>(Settings::Category::Core),
Cpu = static_cast<u32>(Settings::Category::Cpu),
CpuDebug = static_cast<u32>(Settings::Category::CpuDebug),
CpuUnsafe = static_cast<u32>(Settings::Category::CpuUnsafe),
Overlay = static_cast<u32>(Settings::Category::Overlay),
Renderer = static_cast<u32>(Settings::Category::Renderer),
RendererAdvanced = static_cast<u32>(Settings::Category::RendererAdvanced),
RendererExtensions = static_cast<u32>(Settings::Category::RendererExtensions),
RendererDebug = static_cast<u32>(Settings::Category::RendererDebug),
System = static_cast<u32>(Settings::Category::System),
SystemAudio = static_cast<u32>(Settings::Category::SystemAudio),
DataStorage = static_cast<u32>(Settings::Category::DataStorage),
Debugging = static_cast<u32>(Settings::Category::Debugging),
DebuggingGraphics = static_cast<u32>(Settings::Category::DebuggingGraphics),
GpuDriver = static_cast<u32>(Settings::Category::GpuDriver),
Miscellaneous = static_cast<u32>(Settings::Category::Miscellaneous),
Network = static_cast<u32>(Settings::Category::Network),
WebService = static_cast<u32>(Settings::Category::WebService),
AddOns = static_cast<u32>(Settings::Category::AddOns),
Controls = static_cast<u32>(Settings::Category::Controls),
Ui = static_cast<u32>(Settings::Category::Ui),
UiAudio = static_cast<u32>(Settings::Category::UiAudio),
UiGeneral = static_cast<u32>(Settings::Category::UiGeneral),
UiLayout = static_cast<u32>(Settings::Category::UiLayout),
UiGameList = static_cast<u32>(Settings::Category::UiGameList),
Screenshots = static_cast<u32>(Settings::Category::Screenshots),
Shortcuts = static_cast<u32>(Settings::Category::Shortcuts),
Multiplayer = static_cast<u32>(Settings::Category::Multiplayer),
Services = static_cast<u32>(Settings::Category::Services),
Paths = static_cast<u32>(Settings::Category::Paths),
Linux = static_cast<u32>(Settings::Category::Linux),
LibraryApplet = static_cast<u32>(Settings::Category::LibraryApplet),
MaxEnum = static_cast<u32>(Settings::Category::MaxEnum),
};
Q_ENUM_NS(Category)
}
class SettingsInterface : public QObject {
Q_OBJECT
QML_ELEMENT
public:
explicit SettingsInterface(QObject* parent = nullptr);
QMLSetting *getSetting(Settings::BasicSetting *setting);
Q_INVOKABLE QMLSetting *setting(const QString &key);
Q_INVOKABLE SettingsModel *category(SettingsCategories::Category category,
QList<QString> idInclude = {},
QList<QString> idExclude = {});
Q_INVOKABLE int id(const QString &key);
bool global() const;
void setGlobal(bool newGlobal);
signals:
void globalChanged();
private:
QMap<std::string, QMLSetting *> m_settings;
std::unique_ptr<ConfigurationShared::TranslationMap> translations;
std::unique_ptr<ConfigurationShared::ComboboxTranslationMap> combobox_translations;
Q_PROPERTY(bool global READ global WRITE setGlobal NOTIFY globalChanged FINAL)
};
#endif // SETTINGSINTERFACE_H

View file

@ -0,0 +1,40 @@
#include "TitleManager.h"
#include "common/scm_rev.h"
#include <fmt/format.h>
TitleManager::TitleManager(QObject *parent) {}
const QString TitleManager::title() const
{
static const std::string description = std::string(Common::g_build_version);
static const std::string build_id = std::string(Common::g_build_id);
static const std::string compiler = std::string(Common::g_compiler_id);
std::string yuzu_title;
if (Common::g_is_dev_build) {
yuzu_title = fmt::format("Eden Nightly | {}-{} | {}", description, build_id, compiler);
} else {
yuzu_title = fmt::format("Eden | {} | {}", description, compiler);
}
const auto override_title = fmt::format(fmt::runtime(
std::string(Common::g_title_bar_format_idle)),
build_id);
const auto window_title = override_title.empty() ? yuzu_title : override_title;
// TODO(crueter): Running
return QString::fromStdString(window_title);
// if (title_name.empty()) {
// return QString::fromStdString(window_title);
// } else {
// const auto run_title = [window_title, title_name, title_version, gpu_vendor]() {
// if (title_version.empty()) {
// return fmt::format("{} | {} | {}", window_title, title_name, gpu_vendor);
// }
// return fmt::format("{} | {} | {} | {}", window_title, title_name, title_version,
// gpu_vendor);
// }();
// setWindowTitle(QString::fromStdString(run_title));
// }
}

View file

@ -0,0 +1,18 @@
#ifndef TITLEMANAGER_H
#define TITLEMANAGER_H
#include <QObject>
class TitleManager : public QObject
{
Q_OBJECT
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
public:
explicit TitleManager(QObject *parent = nullptr);
const QString title() const;
signals:
void titleChanged();
};
#endif // TITLEMANAGER_H

View file

@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Controls.Material
import Eden.Constants
Dialog {
id: dia
property int preferredWidth: Overlay.overlay.width / 2
property int preferredHeight: Overlay.overlay.height / 1.25
width: Math.min(preferredWidth, Overlay.overlay.width)
height: Math.min(preferredHeight, Overlay.overlay.height)
property int radius: 12
property bool colorful: false
anchors.centerIn: Overlay.overlay
enter: Transition {
NumberAnimation {
property: "opacity"
duration: 200
from: 0.0
to: 1.0
}
}
exit: Transition {
NumberAnimation {
property: "opacity"
duration: 200
from: 1.0
to: 0.0
}
}
header: Rectangle {
topLeftRadius: dia.radius
topRightRadius: dia.radius
color: colorful ? Qt.alpha(Constants.accent, 0.5) : Constants.dialog
height: 50
Text {
anchors.fill: parent
font.pixelSize: Math.round(25)
text: title
color: Constants.text
font.bold: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
background: Rectangle {
radius: dia.radius
color: Constants.dialog
}
footer: DialogButtonBox {
id: control
background: Item {}
delegate: Button {
id: btn
}
}
Overlay.modal: Item {}
Overlay.modeless: Item {}
}

View file

@ -0,0 +1,59 @@
import QtQuick
import QtQuick.Controls
import Eden.Constants
Menu {
background: Rectangle {
implicitWidth: 200
implicitHeight: 40
color: Constants.button
radius: 10
}
function fixAmpersands(originalText) {
var regex = /&(\w)/g
return originalText.replace(regex, "<u>$1</u>")
}
delegate: MenuItem {
id: control
font.pixelSize: 14
background: Rectangle {
color: control.down || control.hovered
|| control.highlighted ? Constants.buttonHighlighted : Constants.button
}
contentItem: Item {
Text {
anchors {
left: parent.left
leftMargin: 5 + (control.checkable ? control.indicator.width : 0)
verticalCenter: parent.verticalCenter
}
text: fixAmpersands(control.text)
color: Constants.text
font: control.font
}
Text {
anchors {
right: parent.right
rightMargin: 5
verticalCenter: parent.verticalCenter
}
Component.onCompleted: if (control.action != null
&& typeof control.action.shortcut !== 'undefined')
text = control.action.shortcut
color: Constants.text
font: control.font
}
}
}
}

View file

@ -0,0 +1,33 @@
import QtQuick
import QtQuick.Controls
import Eden.Constants
MenuBar {
background: Rectangle {
implicitHeight: 30
color: Constants.button
}
function fixAmpersands(originalText) {
var regex = /&(\w)/g
return originalText.replace(regex, "<u>$1</u>")
}
delegate: MenuBarItem {
id: control
font.pixelSize: 16
background: Rectangle {
color: control.down || control.hovered
|| control.highlighted ? Constants.buttonHighlighted : Constants.button
}
contentItem: Text {
text: fixAmpersands(control.text)
color: Constants.text
font: control.font
}
}
}

View file

@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
EdenModule(
NAME Items
URI Eden.Items
QML_FILES
StatusBarButton.qml
BetterMenu.qml
AnimatedDialog.qml
BetterMenuBar.qml
SettingsTabButton.qml
IconButton.qml
VerticalTabBar.qml
fields/BetterSpinBox.qml
fields/BetterTextField.qml
fields/FieldFooter.qml
)

View file

@ -0,0 +1,23 @@
import QtQuick
import QtQuick.Controls.Material
import Eden.Constants
Button {
required property string label
bottomInset: 0
topInset: 0
leftPadding: 5
rightPadding: 5
width: icon.width
height: icon.height
icon.source: "qrc:/icons/" + label.toLowerCase() + ".svg"
icon.width: 45
icon.height: 45
icon.color: Constants.text
background: Item {}
}

View file

@ -0,0 +1,40 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Constants
TabButton {
required property string label
id: button
implicitHeight: 100
width: 95
contentItem: ColumnLayout {
IconButton {
label: button.label
Layout.maximumHeight: 60
Layout.maximumWidth: 65
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
onClicked: button.clicked()
}
Text {
font.pixelSize: 16
text: label
Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter
color: Constants.text
}
}
// background: Rectangle {
// color: button.Material.backgroundColor
// }
}

View file

@ -0,0 +1,36 @@
import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Eden.Constants
MouseArea {
id: button
required property string text
property color textColor: Constants.text
implicitHeight: 20
implicitWidth: txt.width
hoverEnabled: true
onHoveredChanged: rect.color = containsMouse ? Constants.buttonHighlighted : "transparent"
Rectangle {
id: rect
anchors.fill: parent
color: "transparent"
Text {
id: txt
font.pixelSize: 12
leftPadding: 4
rightPadding: 4
color: button.textColor
text: button.text
}
}
}

View file

@ -0,0 +1,46 @@
import QtQuick
import QtQuick.Controls.Material
import Eden.Constants
TabBar {
clip: true
id: control
contentItem: ListView {
model: control.contentModel
currentIndex: control.currentIndex
spacing: control.spacing
orientation: ListView.Vertical
boundsBehavior: Flickable.StopAtBounds
flickableDirection: Flickable.AutoFlickIfNeeded
snapMode: ListView.SnapToItem
highlightMoveDuration: 300
highlightRangeMode: ListView.ApplyRange
preferredHighlightBegin: 40
preferredHighlightEnd: height - 40
highlight: Item {
z: 2
Rectangle {
radius: 5
anchors {
right: parent.right
verticalCenter: parent.verticalCenter
}
height: parent.height / 2
width: 5
color: Constants.accent
}
}
}
background: Rectangle {
color: control.Material.backgroundColor
radius: 8
}
}

View file

@ -0,0 +1,68 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Controls.impl
import Eden.Constants
SpinBox {
id: control
property string label: ""
from: -0x7FFFFFFF
to: 0x7FFFFFFF
contentItem: BetterTextField {
text: parent.textFromValue(parent.value, parent.locale)
placeholderText: parent.label
width: parent.width
font: parent.font
horizontalAlignment: Qt.AlignHCenter
verticalAlignment: Qt.AlignVCenter
inputMethodHints: Qt.ImhFormattedNumbersOnly
onEditingFinished: {
control.value = parseFloat(text.replace(/,/g, ""))
valueModified()
}
}
up.indicator: IconLabel {
icon {
source: "qrc:/icons/forward.svg"
}
x: control.mirrored ? 0 : control.width - width
implicitWidth: 40
implicitHeight: 40
height: parent.height
width: height / 2
}
down.indicator: IconLabel {
icon {
source: "qrc:/icons/back.svg"
}
x: control.mirrored ? control.width - width : 0
implicitWidth: 40
implicitHeight: 40
height: parent.height
width: height / 2
}
background: Item {}
FieldFooter {
anchors {
bottom: contentItem.bottom
}
}
}

View file

@ -0,0 +1,38 @@
import QtQuick
import QtQuick.Controls.Material
import Eden.Constants
import Eden.Items
TextField {
property string suffix: ""
placeholderTextColor: enabled && activeFocus ? Constants.accent : Qt.darker(
Constants.text, 1.3)
color: enabled ? Constants.text : Qt.darker(Constants.text, 1.5)
background: Rectangle {
color: "transparent"
}
FieldFooter {}
horizontalAlignment: "AlignHCenter"
Text {
id: txt
text: suffix
font.pixelSize: 14
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 5
}
color: "gray"
}
}

View file

@ -0,0 +1,20 @@
import QtQuick
import QtQuick.Controls.Material
import Eden.Constants
Rectangle {
height: 2
color: enabled ? Constants.text : Qt.darker(Constants.text, 1.5)
width: parent.width
anchors {
bottom: parent.bottom
left: parent.left
}
Behavior on color {
ColorAnimation {
duration: 250
}
}
}

View file

@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
EdenModule(
NAME Main
URI Eden.Main
QML_FILES
Main.qml
StatusBar.qml
GameList.qml
GameGridCard.qml
GameGrid.qml
MarqueeText.qml
GameCarouselCard.qml
GameCarousel.qml
LIBRARIES Eden::Interface
)

View file

@ -0,0 +1,87 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
import QtCore
import Eden.Constants
import Eden.Interface
ListView {
id: carousel
focus: true
focusPolicy: Qt.StrongFocus
model: EdenGameList
orientation: ListView.Horizontal
clip: false
flickDeceleration: 1500
snapMode: ListView.SnapToItem
spacing: 20
keyNavigationWraps: true
function increment() {
incrementCurrentIndex()
if (currentIndex === count)
currentIndex = 0
}
function decrement() {
decrementCurrentIndex()
if (currentIndex === -1)
currentIndex = count - 1
}
// TODO(crueter): handle move/displace/add (requires thread worker on game list and a bunch of other shit)
Rectangle {
id: hg
clip: false
z: 3
property var item: carousel.currentItem
anchors {
centerIn: parent
}
height: item === null ? 0 : item.height + 10
width: item === null ? 0 : item.width + 10
color: "transparent"
border {
color: "deepskyblue"
width: 4
}
radius: 8
MarqueeText {
id: container
anchors.bottom: hg.top
anchors.left: hg.left
anchors.right: hg.right
canMarquee: true
text: hg.item === null ? "" : toTitleCase(hg.item.title)
font.pixelSize: 22
font.family: "Monospace"
color: "lightblue"
background: Constants.bg
}
}
highlightRangeMode: ListView.StrictlyEnforceRange
preferredHighlightBegin: currentItem === null ? 0 : x + width / 2 - currentItem.width / 2
preferredHighlightEnd: currentItem === null ? 0 : x + width / 2 + currentItem.width / 2
highlightMoveDuration: 300
delegate: GameCarouselCard {
id: game
width: 300
height: 300
}
}

View file

@ -0,0 +1,39 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
import QtCore
import Eden.Constants
Item {
property string title: model.name.replace(/-/g, " ")
id: wrapper
width: 300
height: 300
Rectangle {
anchors.fill: parent
color: "transparent"
border {
width: 4
color: PathView.isCurrentItem ? "deepskyblue" : "transparent"
}
Image {
id: image
fillMode: Image.PreserveAspectFit
source: "file://" + model.path
clip: true
anchors {
fill: parent
margins: 10
}
}
}
}

View file

@ -0,0 +1,43 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
import QtCore
import Eden.Constants
import Eden.Interface
GridView {
property var setting
id: grid
property int cellSize: Math.floor(width / setting.value)
highlightFollowsCurrentItem: true
clip: true
cellWidth: cellSize
cellHeight: cellSize + 20
model: EdenGameList
delegate: GameGridCard {
id: game
width: grid.cellSize - 20
height: grid.cellHeight - 20
}
highlight: Rectangle {
color: "transparent"
z: 5
radius: 16
border {
color: "deepskyblue"
width: 4
}
}
focus: true
focusPolicy: "StrongFocus"
}

View file

@ -0,0 +1,79 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
import QtCore
import Eden.Constants
Rectangle {
id: wrapper
color: Constants.dialog
radius: 16
Image {
id: image
fillMode: Image.PreserveAspectFit
source: "file://" + model.path
clip: true
anchors {
top: parent.top
bottom: nameText.top
left: parent.left
right: parent.right
margins: 10
}
height: parent.height
MouseArea {
id: mouseArea
hoverEnabled: true
z: 3
x: (parent.width - parent.paintedWidth) / 2
y: (parent.height - parent.paintedHeight) / 2
width: parent.paintedWidth
height: parent.paintedHeight
onContainsMouseChanged: {
if (containsMouse) {
wrapper.GridView.view.currentIndex = index
wrapper.GridView.view.focus = true
}
}
}
}
MarqueeText {
id: nameText
clip: true
anchors {
bottom: parent.bottom
bottomMargin: 5
left: parent.left
right: parent.right
leftMargin: 5
rightMargin: 5
}
text: toTitleCase(model.name.replace(/-/g, " "))
font.pixelSize: 18
font.family: "Monospace"
color: "lightblue"
background: Constants.dialog
canMarquee: wrapper.GridView.isCurrentItem
}
}

127
src/Eden/Main/GameList.qml Normal file
View file

@ -0,0 +1,127 @@
import QtQuick
import QtQuick.Controls
import Qt.labs.platform
import QtCore
import Eden.Constants
import Eden.Interface
// import Eden.Native.Gamepad
Rectangle {
id: root
property var setting: SettingsInterface.setting("grid_columns")
property int gx: 0
property int gy: 0
readonly property int deadzone: 8000
readonly property int repeatTimeMs: 125
color: Constants.bg
// TODO: use the original yuzu backend for dis
// Gamepad {
// id: gamepad
// // onUpPressed: grid.moveCurrentIndexUp()
// // onDownPressed: grid.moveCurrentIndexDown()
// // onLeftPressed: grid.moveCurrentIndexLeft()
// // onRightPressed: grid.moveCurrentIndexRight()
// onLeftPressed: carousel.decrement()
// onRightPressed: carousel.increment()
// onAPressed: console.log("A pressed")
// onLeftStickMoved: (x, y) => {
// gx = x
// gy = y
// }
// }
// Timer {
// interval: repeatTimeMs
// running: true
// repeat: true
// onTriggered: {
// if (gx > deadzone) {
// gamepad.rightPressed()
// } else if (gx < -deadzone) {
// gamepad.leftPressed()
// }
// if (gy > deadzone) {
// gamepad.downPressed()
// } else if (gy < -deadzone) {
// gamepad.upPressed()
// }
// }
// }
// Timer {
// interval: 16
// running: true
// repeat: true
// onTriggered: gamepad.pollEvents()
// }
FolderDialog {
id: openDir
folder: StandardPaths.writableLocation(StandardPaths.HomeLocation)
onAccepted: {
button.visible = false
view.anchors.bottom = root.bottom
EdenGameList.addDir(folder)
}
}
Item {
id: view
anchors {
bottom: button.top
left: parent.left
right: parent.right
top: parent.top
margins: 8
}
GameGrid {
setting: root.setting
id: grid
anchors.fill: parent
}
// GameCarousel {
// id: carousel
// height: 300
// anchors {
// right: view.right
// left: view.left
// verticalCenter: view.verticalCenter
// }
// }
}
Button {
id: button
font.pixelSize: 25
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
margins: 8
}
text: "Add Directory"
onClicked: openDir.open()
background: Rectangle {
color: button.pressed ? Constants.accentPressed : Constants.accent
radius: 5
}
}
}

207
src/Eden/Main/Main.qml Normal file
View file

@ -0,0 +1,207 @@
import QtQuick
import QtQuick.Controls.Material
import Eden.Config
import Eden.Items
import Eden.Constants
ApplicationWindow {
width: Constants.width
height: Constants.height
visible: true
title: TitleManager.title
Material.theme: Material.Dark
Material.accent: Material.Red
Material.roundedScale: Material.NotRounded
GameList {
anchors {
top: parent.top
left: parent.left
right: parent.right
bottom: status.top
}
}
/** Dialogs */
GlobalConfigureDialog {
id: globalConfig
}
menuBar: BetterMenuBar {
BetterMenu {
title: qsTr("&File")
contentWidth: 225
Action {
text: qsTr("&Install files to NAND...")
}
MenuSeparator {}
Action {
text: qsTr("L&oad File...")
shortcut: "Ctrl+O"
}
Action {
text: qsTr("Load &Folder...")
}
MenuSeparator {}
BetterMenu {
title: "&Recent Files"
}
MenuSeparator {}
Action {
text: qsTr("Load/Remove &Amiibo...")
shortcut: "F2"
}
MenuSeparator {}
Action {
text: qsTr("Open &eden Directory")
}
MenuSeparator {}
Action {
text: qsTr("E&xit")
shortcut: "Ctrl+Q"
}
}
BetterMenu {
title: qsTr("&Emulation")
contentWidth: 240
Action {
text: qsTr("&Pause")
shortcut: "F4"
}
Action {
text: qsTr("&Stop")
shortcut: "F5"
}
Action {
text: qsTr("&Restart")
shortcut: "F6"
}
MenuSeparator {}
Action {
text: qsTr("Con&figure...")
shortcut: "Ctrl+,"
onTriggered: globalConfig.open()
}
Action {
text: qsTr("Configure &Current Game...")
shortcut: "Ctrl+."
}
}
BetterMenu {
title: qsTr("&View")
contentWidth: 260
Action {
text: qsTr("F&ullscreen")
shortcut: "F11"
checkable: true
}
Action {
text: qsTr("Single &Window Mode")
checkable: true
}
Action {
text: qsTr("Display D&ock Widget Headers")
checkable: true
}
Action {
text: qsTr("Show &Filter Bar")
shortcut: "Ctrl+F"
checkable: true
}
Action {
text: qsTr("Show &Status Bar")
shortcut: "Ctrl+S"
checkable: true
}
MenuSeparator {}
}
BetterMenu {
title: qsTr("&Tools")
contentWidth: 225
Action {
text: qsTr("Install &Decryption Keys")
}
Action {
text: qsTr("Install &Firmware")
}
Action {
text: qsTr("&Verify Installed Contents")
}
MenuSeparator {}
BetterMenu {
title: qsTr("&Amiibo")
}
Action {
text: qsTr("Open A&lbum")
}
Action {
text: qsTr("Open &Mii Editor")
}
Action {
text: qsTr("Open Co&ntroller Menu")
}
Action {
text: qsTr("Open &Home Menu")
}
MenuSeparator {}
Action {
text: qsTr("&Capture Screenshot")
shortcut: "Ctrl+P"
}
BetterMenu {
title: "&TAS"
}
}
}
StatusBar {
id: status
height: 30
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
}
}

View file

@ -0,0 +1,129 @@
import QtQuick
import QtQuick.Controls
import Eden.Constants
Item {
id: container
anchors {}
height: txt.contentHeight
clip: true
// TODO(crueter): util?
function toTitleCase(str) {
return str.replace(/\w\S*/g, text => text.charAt(0).toUpperCase(
) + text.substring(1).toLowerCase())
}
property string text
property string spacing: " "
property string combined: text + spacing
property string display: animate ? combined.substring(
step) + combined.substring(
0, step) : text
property int step: 0
property bool animate: canMarquee && txt.contentWidth > parent.width
property bool canMarquee: false
property font font
property color color
property color background
onCanMarqueeChanged: checkMarquee()
function checkMarquee() {
if (canMarquee && txt.contentWidth > width) {
step = 0
delay.start()
} else {
delay.stop()
marquee.stop()
}
}
Timer {
id: marquee
interval: 150
running: false
repeat: true
onTriggered: {
parent.step = (parent.step + 1) % parent.combined.length
if (parent.step === 0) {
stop()
delay.start()
}
}
}
Timer {
id: delay
interval: 1500
repeat: false
onTriggered: {
marquee.start()
}
}
// fake container to gauge contentWidth
Text {
id: txt
visible: false
text: parent.text
font: container.font
onContentWidthChanged: container.checkMarquee()
}
Text {
anchors {
fill: parent
leftMargin: 10
rightMargin: 10
}
color: container.color
font: container.font
text: parent.display
horizontalAlignment: Text.AlignLeft
}
Rectangle {
anchors.fill: parent
z: 2
gradient: Gradient {
orientation: Gradient.Horizontal
GradientStop {
position: 0.0
color: marquee.running ? container.background : "transparent"
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
GradientStop {
position: 0.1
color: "transparent"
}
GradientStop {
position: 0.9
color: "transparent"
}
GradientStop {
position: 1.0
color: marquee.running ? container.background : "transparent"
Behavior on color {
ColorAnimation {
duration: 200
}
}
}
}
}
}

160
src/Eden/Main/StatusBar.qml Normal file
View file

@ -0,0 +1,160 @@
import QtQuick
import QtQuick.Controls.Material
import QtQuick.Layouts
import Eden.Constants
import Eden.Items
ToolBar {
id: toolbar
property string graphicsBackend: "vulkan"
property string gpuAccuracy: "high"
property string consoleMode: "docked"
property int adapting: 5
property int antialiasing: 0
property int volume: 100
property string firmwareVersion: "16.0.3"
implicitHeight: 30
background: Rectangle {
color: Constants.bg
}
// TODO: reduce duplicate code
RowLayout {
id: row
anchors {
left: parent.left
top: parent.top
bottom: parent.bottom
leftMargin: 10
}
StatusBarButton {
property alias value: toolbar.graphicsBackend
text: value.toUpperCase()
textColor: value === "vulkan" ? "orange" : "lightblue"
onClicked: {
if (value === "vulkan") {
value = "opengl"
} else {
value = "vulkan"
}
}
}
StatusBarButton {
property alias value: toolbar.gpuAccuracy
text: value.toUpperCase()
textColor: value === "high" ? "orange" : "lightgreen"
onClicked: {
if (value === "high") {
value = "normal"
} else {
value = "high"
}
}
}
StatusBarButton {
property alias value: toolbar.consoleMode
text: value.toUpperCase()
textColor: Constants.text
onClicked: {
if (value === "docked") {
value = "handheld"
} else {
value = "docked"
}
}
}
StatusBarButton {
property list<string> choices: ["nearest", "bilinear", "bicubic", "gaussian", "scaleforce", "fsr"]
property alias index: toolbar.adapting
property string value: choices[index]
text: value.toUpperCase()
onClicked: {
let newIndex = index + 1
if (newIndex >= choices.length) {
newIndex = 0
}
index = newIndex
}
}
StatusBarButton {
property list<string> choices: ["no aa", "fxaa", "msaa"]
property alias index: toolbar.antialiasing
property string value: choices[index]
text: value.toUpperCase()
onClicked: {
let newIndex = index + 1
if (newIndex >= choices.length) {
newIndex = 0
}
index = newIndex
}
}
StatusBarButton {
id: volumeButton
property alias value: toolbar.volume
text: "VOLUME: " + value + "%"
onClicked: {
volumeSlider.open()
}
onWheel: wheel => {
value += wheel.angleDelta.y / 120
}
}
}
Popup {
id: volumeSlider
width: 200
height: 50
x: volumeButton.x
y: volumeButton.y - height
focus: true
Slider {
value: volumeButton.value
onMoved: volumeButton.value = value
from: 0
to: 200
anchors.fill: parent
}
}
}

View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
qt_add_library(EdenModels STATIC
GameListModel.h GameListModel.cpp
SettingsModel.h SettingsModel.cpp
)
target_link_libraries(EdenModels
PRIVATE
Qt6::Gui
)
add_library(Eden::Models ALIAS EdenModels)

View file

@ -0,0 +1,67 @@
#include "GameListModel.h"
#include <QDirIterator>
const QStringList GameListModel::ValidSuffixes{"jpg", "png", "webp", "jpeg"};
GameListModel::GameListModel(QObject *parent) {
QHash<int, QByteArray> rez = QStandardItemModel::roleNames();
rez.insert(GLMRoleTypes::NAME, "name");
rez.insert(GLMRoleTypes::PATH, "path");
rez.insert(GLMRoleTypes::FILESIZE, "size");
QStandardItemModel::setItemRoleNames(rez);
}
QVariant GameListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role == GLMRoleTypes::NAME) {
return itemFromIndex(index)->text();
}
return QStandardItemModel::data(index, role);
}
void GameListModel::addDir(const QString &toAdd)
{
QString name = toAdd;
#ifdef Q_OS_WINDOWS
name.replace("file:///", "");
#else
name.replace("file://", "");
#endif
m_dirs << name;
reload();
}
void GameListModel::removeDir(const QString &toRemove)
{
m_dirs.removeAll(toRemove);
reload();
}
void GameListModel::reload()
{
clear();
for (const QString &dir : std::as_const(m_dirs)) {
qDebug() << dir;
for (const auto &entry : QDirListing(dir, QDirListing::IteratorFlag::FilesOnly)) {
if (ValidSuffixes.contains(entry.completeSuffix().toLower())) {
QString path = entry.absoluteFilePath();
QString name = entry.baseName();
qreal size = entry.size();
QString sizeString = QLocale::system().formattedDataSize(size);
QStandardItem *game = new QStandardItem(name);
game->setData(path, GLMRoleTypes::PATH);
game->setData(sizeString, GLMRoleTypes::FILESIZE);
invisibleRootItem()->appendRow(game);
}
}
}
}

View file

@ -0,0 +1,39 @@
#ifndef GAMELISTMODEL_H
#define GAMELISTMODEL_H
#include <QObject>
#include <QStandardItemModel>
typedef struct Game {
QString absPath;
QString name;
QString fileSize;
} Game;
class GameListModel : public QStandardItemModel
{
Q_OBJECT
public:
enum GLMRoleTypes {
NAME = Qt::UserRole + 1,
PATH,
FILESIZE
};
GameListModel(QObject *parent = nullptr);
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
Q_INVOKABLE void addDir(const QString &toAdd);
Q_INVOKABLE void removeDir(const QString &toRemove);
static const QStringList ValidSuffixes;
private:
QStringList m_dirs;
QList<Game> m_data;
void reload();
};
#endif // GAMELISTMODEL_H

View file

@ -0,0 +1,90 @@
#include "SettingsModel.h"
SettingsModel::SettingsModel(QObject* parent) : QAbstractListModel(parent) {}
int SettingsModel::rowCount(const QModelIndex& parent) const {
return m_data.count();
}
QVariant SettingsModel::data(const QModelIndex& index, int role) const {
if (!index.isValid())
return QVariant();
QMLSetting *s = m_data[index.row()];
switch (role) {
case LABEL:
return s->label();
case TOOLTIP:
return s->tooltip();
case VALUE:
return s->value();
case ID:
return s->id();
case TYPE:
return s->type();
case COMBO:
return s->combo();
case SUFFIX:
return s->suffix();
case MIN:
return s->min();
case MAX:
return s->max();
case OTHER:
return QVariant::fromValue(s->other());
default:
break;
}
return QVariant();
}
bool SettingsModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (data(index, role) != value) {
QMLSetting *s = m_data[index.row()];
switch (role) {
case VALUE:
s->setValue(value);
break;
}
emit dataChanged(index, index, {role});
return true;
}
return false;
}
void SettingsModel::append(QMLSetting *setting)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
m_data << setting;
endInsertRows();
}
void SettingsModel::append(QList<QMLSetting *> settings)
{
for (QMLSetting *setting : settings) {
append(setting);
}
}
QHash<int, QByteArray> SettingsModel::roleNames() const
{
QHash<int,QByteArray> rez;
rez[LABEL] = "label";
rez[TOOLTIP] = "tooltip";
rez[VALUE] = "value";
rez[ID] = "id";
rez[TYPE] = "type";
rez[COMBO] = "combo";
rez[SUFFIX] = "suffix";
rez[MIN] = "min";
rez[MAX] = "max";
rez[OTHER] = "other";
return rez;
}

View file

@ -0,0 +1,42 @@
#ifndef SETTINGSMODEL_H
#define SETTINGSMODEL_H
#include <QAbstractListModel>
#include "Interface/QMLSetting.h"
class SettingsModel : public QAbstractListModel {
Q_OBJECT
public:
enum SMTypes {
LABEL = Qt::UserRole + 1,
TOOLTIP,
ID,
VALUE,
TYPE,
COMBO, // combobox translations
SUFFIX,
MIN,
MAX,
OTHER
};
explicit SettingsModel(QObject* parent = nullptr);
// Basic functionality:
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
void append(QMLSetting *setting);
void append(QList<QMLSetting *> settings);
protected:
QHash<int, QByteArray> roleNames() const override;
private:
QList<QMLSetting *> m_data;
};
#endif // SETTINGSMODEL_H

View file

@ -0,0 +1,65 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
qt_add_executable(eden
main.cpp
icons.qrc
)
set(MODULES
Eden::Util
Eden::Items
Eden::Config
Eden::Interface
Eden::Constants
Eden::Main
Eden::Models
)
# if (ENABLE_SDL2)
# add_subdirectory(Gamepad)
# set(MODULES ${MODULES} Eden::Gamepad)
# endif()
target_link_libraries(eden
PRIVATE
Qt6::Core
Qt6::Widgets
Qt6::Gui
Qt6::Quick
Qt6::QuickControls2
${MODULES}
)
target_link_libraries(eden PRIVATE common core input_common frontend_common qt_common network video_core)
target_link_libraries(eden PRIVATE Boost::headers glad fmt)
target_link_libraries(eden PRIVATE ${PLATFORM_LIBRARIES} Threads::Threads)
target_link_libraries(eden PRIVATE Vulkan::Headers)
target_compile_definitions(eden PRIVATE
# Use QStringBuilder for string concatenation to reduce
# the overall number of temporary strings created.
-DQT_USE_QSTRINGBUILDER
# Disable implicit type narrowing in signal/slot connect() calls.
-DQT_NO_NARROWING_CONVERSIONS_IN_CONNECT
# Disable unsafe overloads of QProcess' start() function.
-DQT_NO_PROCESS_COMBINED_ARGUMENT_START
# Disable implicit QString->QUrl conversions to enforce use of proper resolving functions.
-DQT_NO_URL_CAST_FROM_STRING
)
set_target_properties(eden PROPERTIES OUTPUT_NAME "eden")
include(GNUInstallDirs)
install(TARGETS eden
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

View file

@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
EdenModule(
NAME Gamepad
URI Eden.Native.Gamepad
SOURCES gamepad.h gamepad.cpp
)

View file

@ -0,0 +1,79 @@
#include "gamepad.h"
// TODO(crueter): This is just temporary
Gamepad::Gamepad(QObject *parent)
: QObject(parent)
{
SDL_Init(SDL_INIT_GAMECONTROLLER);
}
Gamepad::~Gamepad()
{
if (controller)
SDL_GameControllerClose(controller);
SDL_Quit();
}
void Gamepad::openController(int deviceIndex)
{
if (controller) {
closeController();
}
controller = SDL_GameControllerOpen(deviceIndex);
}
void Gamepad::closeController()
{
if (controller) {
SDL_GameControllerClose(controller);
controller = nullptr;
}
}
void Gamepad::pollEvents()
{
SDL_Event event;
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_CONTROLLERDEVICEADDED:
openController(event.cdevice.which);
break;
case SDL_CONTROLLERDEVICEREMOVED:
if (controller
&& event.cdevice.which
== SDL_JoystickInstanceID(SDL_GameControllerGetJoystick(controller))) {
closeController();
}
break;
case SDL_CONTROLLERBUTTONDOWN:
switch (event.cbutton.button) {
case SDL_CONTROLLER_BUTTON_DPAD_UP:
emit upPressed();
break;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN:
emit downPressed();
break;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT:
emit leftPressed();
break;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT:
emit rightPressed();
break;
case SDL_CONTROLLER_BUTTON_A:
emit aPressed();
break;
}
break;
case SDL_CONTROLLERAXISMOTION:
if (event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTX
|| event.caxis.axis == SDL_CONTROLLER_AXIS_LEFTY) {
int x = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTX);
int y = SDL_GameControllerGetAxis(controller, SDL_CONTROLLER_AXIS_LEFTY);
emit leftStickMoved(x, y);
}
break;
}
}
}

View file

@ -0,0 +1,31 @@
#pragma once
#include <QElapsedTimer>
#include <QObject>
#include <QQmlEngine>
#include <SDL2/SDL.h>
class Gamepad : public QObject {
Q_OBJECT
QML_ELEMENT
public:
explicit Gamepad(QObject *parent = nullptr);
~Gamepad();
Q_INVOKABLE void pollEvents();
signals:
void upPressed();
void downPressed();
void leftPressed();
void rightPressed();
void aPressed();
void leftStickMoved(int x, int y);
private:
SDL_GameController *controller = nullptr;
void closeController();
void openController(int deviceIndex);
};

15
src/Eden/Native/icons.qrc Normal file
View file

@ -0,0 +1,15 @@
<RCC>
<qresource prefix="/">
<file>icons/audio.svg</file>
<file>icons/controls.svg</file>
<file>icons/cpu.svg</file>
<file>icons/general.svg</file>
<file>icons/graphics.svg</file>
<file>icons/system.svg</file>
<file>icons/debug.svg</file>
<file>icons/forward.svg</file>
<file>icons/back.svg</file>
<file>icons/help.svg</file>
<file alias="icons/eden.svg">../../../dist/dev.eden_emu.eden.svg</file>
</qresource>
</RCC>

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M560-131v-82q90-26 145-100t55-168q0-94-55-168T560-749v-82q124 28 202 125.5T840-481q0 127-78 224.5T560-131ZM120-360v-240h160l200-200v640L280-360H120Zm440 40v-322q47 22 73.5 66t26.5 96q0 51-26.5 94.5T560-320ZM400-606l-86 86H200v80h114l86 86v-252ZM300-480Z"/></svg>

After

Width:  |  Height:  |  Size: 378 B

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="20"
viewBox="0 -960 227.99999 400"
width="11.4"
fill="#ffffff"
version="1.1"
id="svg1"
sodipodi:docname="back.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="24.291667"
inkscape:cx="24.061749"
inkscape:cy="17.413379"
inkscape:window-width="2560"
inkscape:window-height="1368"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M 200,-560 228,-588.5 56.5,-760 228,-931.5 200,-960 0,-760 Z"
id="path1"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.5" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M189-160q-60 0-102.5-43T42-307q0-9 1-18t3-18l84-336q14-54 57-87.5t98-33.5h390q55 0 98 33.5t57 87.5l84 336q2 9 3.5 18.5T919-306q0 61-43.5 103.5T771-160q-42 0-78-22t-54-60l-28-58q-5-10-15-15t-21-5H385q-11 0-21 5t-15 15l-28 58q-18 38-54 60t-78 22Zm3-80q19 0 34.5-10t23.5-27l28-57q15-31 44-48.5t63-17.5h190q34 0 63 18t45 48l28 57q8 17 23.5 27t34.5 10q28 0 48-18.5t21-46.5q0 1-2-19l-84-335q-7-27-28-44t-49-17H285q-28 0-49.5 17T208-659l-84 335q-2 6-2 18 0 28 20.5 47t49.5 19Zm348-280q17 0 28.5-11.5T580-560q0-17-11.5-28.5T540-600q-17 0-28.5 11.5T500-560q0 17 11.5 28.5T540-520Zm80-80q17 0 28.5-11.5T660-640q0-17-11.5-28.5T620-680q-17 0-28.5 11.5T580-640q0 17 11.5 28.5T620-600Zm0 160q17 0 28.5-11.5T660-480q0-17-11.5-28.5T620-520q-17 0-28.5 11.5T580-480q0 17 11.5 28.5T620-440Zm80-80q17 0 28.5-11.5T740-560q0-17-11.5-28.5T700-600q-17 0-28.5 11.5T660-560q0 17 11.5 28.5T700-520Zm-360 60q13 0 21.5-8.5T370-490v-40h40q13 0 21.5-8.5T440-560q0-13-8.5-21.5T410-590h-40v-40q0-13-8.5-21.5T340-660q-13 0-21.5 8.5T310-630v40h-40q-13 0-21.5 8.5T240-560q0 13 8.5 21.5T270-530h40v40q0 13 8.5 21.5T340-460Zm140-20Z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M360-360v-240h240v240H360Zm80-80h80v-80h-80v80Zm-80 320v-80h-80q-33 0-56.5-23.5T200-280v-80h-80v-80h80v-80h-80v-80h80v-80q0-33 23.5-56.5T280-760h80v-80h80v80h80v-80h80v80h80q33 0 56.5 23.5T760-680v80h80v80h-80v80h80v80h-80v80q0 33-23.5 56.5T680-200h-80v80h-80v-80h-80v80h-80Zm320-160v-400H280v400h400ZM480-480Z"/></svg>

After

Width:  |  Height:  |  Size: 435 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M480-200q66 0 113-47t47-113v-160q0-66-47-113t-113-47q-66 0-113 47t-47 113v160q0 66 47 113t113 47Zm-80-120h160v-80H400v80Zm0-160h160v-80H400v80Zm80 40Zm0 320q-65 0-120.5-32T272-240H160v-80h84q-3-20-3.5-40t-.5-40h-80v-80h80q0-20 .5-40t3.5-40h-84v-80h112q14-23 31.5-43t40.5-35l-64-66 56-56 86 86q28-9 57-9t57 9l88-86 56 56-66 66q23 15 41.5 34.5T688-640h112v80h-84q3 20 3.5 40t.5 40h80v80h-80q0 20-.5 40t-3.5 40h84v80H688q-32 56-87.5 88T480-120Z"/></svg>

After

Width:  |  Height:  |  Size: 566 B

View file

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
height="20"
viewBox="0 -960 227.99999 400"
width="11.4"
fill="#ffffff"
version="1.1"
id="svg1"
sodipodi:docname="forward.svg"
inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<defs
id="defs1" />
<sodipodi:namedview
id="namedview1"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="24.291667"
inkscape:cx="24.020583"
inkscape:cy="24"
inkscape:window-width="2560"
inkscape:window-height="1368"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg1" />
<path
d="M 28,-560 0,-588.5 171.5,-760 0,-931.5 28,-960 228,-760 Z"
id="path1"
style="fill:#ffffff;fill-opacity:1;stroke-width:0.5" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="m370-80-16-128q-13-5-24.5-12T307-235l-119 50L78-375l103-78q-1-7-1-13.5v-27q0-6.5 1-13.5L78-585l110-190 119 50q11-8 23-15t24-12l16-128h220l16 128q13 5 24.5 12t22.5 15l119-50 110 190-103 78q1 7 1 13.5v27q0 6.5-2 13.5l103 78-110 190-118-50q-11 8-23 15t-24 12L590-80H370Zm70-80h79l14-106q31-8 57.5-23.5T639-327l99 41 39-68-86-65q5-14 7-29.5t2-31.5q0-16-2-31.5t-7-29.5l86-65-39-68-99 42q-22-23-48.5-38.5T533-694l-13-106h-79l-14 106q-31 8-57.5 23.5T321-633l-99-41-39 68 86 64q-5 15-7 30t-2 32q0 16 2 31t7 30l-86 65 39 68 99-42q22 23 48.5 38.5T427-266l13 106Zm42-180q58 0 99-41t41-99q0-58-41-99t-99-41q-59 0-99.5 41T342-480q0 58 40.5 99t99.5 41Zm-2-140Z"/></svg>

After

Width:  |  Height:  |  Size: 771 B

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg
fill="#000000"
height="800px"
width="800px"
version="1.1"
id="Layer_1"
viewBox="0 0 512 512"
xml:space="preserve"
sodipodi:docname="graphics.svg"
inkscape:version="1.4.1 (93de688d07, 2025-03-30)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"><defs
id="defs5" /><sodipodi:namedview
id="namedview5"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
inkscape:zoom="0.52947631"
inkscape:cx="404.17295"
inkscape:cy="224.75038"
inkscape:window-width="956"
inkscape:window-height="1150"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0"
inkscape:current-layer="Layer_1" />
<g
id="g5"
style="fill:#000000;fill-opacity:1">
<g
id="g4"
style="fill:#000000;fill-opacity:1">
<g
id="g3"
style="fill:#000000;fill-opacity:1">
<path
d="M490.667,96H85.333V74.667c0-11.782-9.551-21.333-21.333-21.333H21.333C9.551,53.333,0,62.885,0,74.667 C0,86.449,9.551,96,21.333,96h21.333v21.333v256V416c0,11.782,9.551,21.333,21.333,21.333c11.782,0,21.333-9.551,21.333-21.333 v-21.333H128v42.667c0,11.782,9.551,21.333,21.333,21.333c11.782,0,21.333-9.551,21.333-21.333v-42.667h42.667v42.667 c0,11.782,9.551,21.333,21.333,21.333c11.782,0,21.333-9.551,21.333-21.333v-42.667h42.667v42.667 c0,11.782,9.551,21.333,21.333,21.333s21.333-9.551,21.333-21.333v-42.667h149.333c11.782,0,21.333-9.551,21.333-21.333v-256 C512,105.551,502.449,96,490.667,96z M469.333,352h-384V138.667h384V352z"
id="path1"
style="fill:#000000;fill-opacity:1" />
<path
d="M362.667,330.667c47.131,0,85.333-38.202,85.333-85.333S409.798,160,362.667,160s-85.333,38.202-85.333,85.333 S315.535,330.667,362.667,330.667z M362.667,202.667c23.567,0,42.667,19.099,42.667,42.667S386.234,288,362.667,288 S320,268.901,320,245.333S339.099,202.667,362.667,202.667z"
id="path2"
style="fill:#000000;fill-opacity:1" />
<path
d="M192,330.667h42.667c11.782,0,21.333-9.551,21.333-21.333v-128c0-11.782-9.551-21.333-21.333-21.333H192 c-47.131,0-85.333,38.202-85.333,85.333S144.869,330.667,192,330.667z M192,202.667h21.333V288H192 c-23.567,0-42.667-19.099-42.667-42.667S168.433,202.667,192,202.667z"
id="path3"
style="fill:#000000;fill-opacity:1" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#FFFFFF"><path d="M478-240q21 0 35.5-14.5T528-290q0-21-14.5-35.5T478-340q-21 0-35.5 14.5T428-290q0 21 14.5 35.5T478-240Zm-36-154h74q0-33 7.5-52t42.5-52q26-26 41-49.5t15-56.5q0-56-41-86t-97-30q-57 0-92.5 30T342-618l66 26q5-18 22.5-39t53.5-21q32 0 48 17.5t16 38.5q0 20-12 37.5T506-526q-44 39-54 59t-10 73Zm38 314q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/></svg>

After

Width:  |  Height:  |  Size: 695 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M560-200q17 0 28.5-11.5T600-240q0-17-11.5-28.5T560-280q-17 0-28.5 11.5T520-240q0 17 11.5 28.5T560-200Zm120 0q17 0 28.5-11.5T720-240q0-17-11.5-28.5T680-280q-17 0-28.5 11.5T640-240q0 17 11.5 28.5T680-200ZM120-440v-360q0-33 23.5-56.5T200-880h560q33 0 56.5 23.5T840-800v360h-80v-360H200v360h-80Zm80 80v200h560v-200H200Zm0 280q-33 0-56.5-23.5T120-160v-280h720v280q0 33-23.5 56.5T760-80H200Zm0-360h560-560Zm0 80h560-560Z"/></svg>

After

Width:  |  Height:  |  Size: 539 B

69
src/Eden/Native/main.cpp Normal file
View file

@ -0,0 +1,69 @@
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "Interface/QMLConfig.h"
#include "Interface/SettingsInterface.h"
#include "Interface/TitleManager.h"
#include "Models/GameListModel.h"
#include "core/core.h"
#include <QQuickStyle>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QQuickStyle::setStyle(QObject::tr("Material"));
QCoreApplication::setOrganizationName(QStringLiteral("eden-emu"));
QCoreApplication::setApplicationName(QStringLiteral("eden"));
QApplication::setDesktopFileName(QStringLiteral("org.eden-emu.eden"));
QGuiApplication::setWindowIcon(QIcon(":/icons/eden.svg"));
/// Settings, etc
Settings::SetConfiguringGlobal(true);
QMLConfig *config = new QMLConfig;
// // TODO: Save all values on launch and per game etc
// app.connect(&app, &QCoreApplication::aboutToQuit, &app, [config]() {
// config->save();
// });
/// Expose Enums
// Core
std::unique_ptr<Core::System> system = std::make_unique<Core::System>();
/// CONTEXT
QQmlApplicationEngine engine;
auto ctx = engine.rootContext();
ctx->setContextProperty(QStringLiteral("QtConfig"), QVariant::fromValue(config));
// Enums
qmlRegisterUncreatableMetaObject(SettingsCategories::staticMetaObject, "Eden.Interface", 1, 0, "SettingsCategories", QString());
// Directory List
GameListModel *gameListModel = new GameListModel(&app);
ctx->setContextProperty(QStringLiteral("EdenGameList"), gameListModel);
// Settings Interface
SettingsInterface *interface = new SettingsInterface(&engine);
ctx->setContextProperty(QStringLiteral("SettingsInterface"), interface);
// Title Manager
TitleManager *title = new TitleManager(&engine);
ctx->setContextProperty(QStringLiteral("TitleManager"), title);
/// LOAD
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("Eden.Main", "Main");
return app.exec();
}

View file

@ -0,0 +1,17 @@
# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: Copyright 2025 crueter
# SPDX-License-Identifier: GPL-3.0-or-later
set_source_files_properties(Util.qml
PROPERTIES
QT_QML_SINGLETON_TYPE true
)
EdenModule(
NAME Util
URI Eden.Util
QML_FILES
Util.qml
)

Some files were not shown because too many files have changed in this diff Show more